• Nem Talált Eredményt

Az osztályokról bővebben

III. fejezet - Objektum-orientált programozás C++ nyelven

2. Osztályok és objektumok

2.2. Az osztályokról bővebben

};

A this mutató deklarációja normál tagfüggvények esetén Osztálytípus*constthis, illetve constOsztálytípus*constthis a konstans tagfüggvényekben.

2.2. Az osztályokról bővebben

Az előző alfejezetben eljutottunk a struktúráktól az osztályokig. Megismerkedtünk azokkal a megoldásokkal, amelyek jól használható osztályok kialakítását teszik lehetővé. Bátran hozzáfoghatunk a feladatok osztályok segítségével történő megoldásához.

Jelen fejezetben kicsit továbblépünk, és áttekintjük az osztályokkal kapcsolatos - kevésbé általános - tudnivalókat. A fejezet tartalmát akkor javasoljuk feldolgozni, ha az Olvasó már jártassággal rendelkezik az osztályok készítésében.

2.2.1. Statikus osztálytagok

C++ nyelven az osztályok adattagjai előtt megadhatjuk a static kulcsszót, jelezve azt, hogy ezeket a tagokat (a tagfüggvényekhez hasonlóan) megosztva használják az osztály objektumai. Az egyetlen példányban létrejövő statikus adattag közvetlenül az osztályhoz tartozik, így az akkor is elérhető, ha egyetlen objektuma sem létezik az osztálynak.

A statikus adattag inicializálását az osztályon kívül kell elvégezni (függetlenül az adattag elérhetőségétől).

Kivételt képeznek a static constegész és felsorolás típusú adattagok, melyek kezdőértéke az osztályon belül is megadható.

Ha a statikus adattag nyilvános elérésű, akkor a programban bárhonnan felhasználhatjuk az osztály neve és a hatókör (::) operátor magadásával. Ellenkező esetben csak az osztály példányai érik el ezeket a tagokat.

Az alábbi példában a statikus tagok használatának bemutatásán túlmenően, a konstansok osztályban való elhelyezésének megoldásait (static const és enum) is szemléltetjük. Az általunk definiált matematikai osztály (Math) lehetővé teszi, hogy a Sin() és a Cos() tagfüggvényeket radián vagy fok mértékegységű adatokkal hívjuk:

#include <iostream> void KiirPI() { cout.precision(18); cout<<Pi<<endl;}

};

// A statikus adattagok létrehozása és inicializálása const double Math::Pi = M_PI;

double Math::dFok2Radian = Math::Pi/180;

Math::Egyseg Math::eMode = Math::radian;

A példában látható módon, az osztályon belül egy felsorolást is elhelyezhetünk. Az így elkészített Egyseg típusnévre és a felsorolt (fok, radian) konstansokra az osztálynévvel minősített név segítségével hivatkozhatunk.

Ezek a nevek osztály hatókörrel rendelkeznek, függetlenül az enum kulcsszó után megadott típusnévtől (Math::Egyseg): Math::radian, Math::fok.

A statikus adattagok kezelésére általában statikus tagfüggvényeket használunk (Math::Sin(), Math::Cos(), Math::Mertekegyseg()). A statikus tagfüggvényekből azonban a normál adattagokhoz nem férhetünk hozzá, mivel a paramétereik között nem szerepel a this mutató. A nem statikus tagfüggvényekből az osztály statikus tagjait korlátozás nélkül elérhetjük.

A Math osztály lehetséges alkalmazását az alábbiakban láthatjuk:

int main() {

double y = Math::Sin(Math::Pi/6); // radiánban számol Math::Mertekegyseg(Math::fok); // fokokban számol y = Math::Sin(30);

Math::Mertekegyseg(Math::radian); // radiánban számol y = Math::Sin(Math::Pi/6);

Math m; // oszálypéldány

m.Mertekegyseg(Math::fok); // vagy m.Mertekegyseg(m.fok);

y = m.Sin(30);

m.Mertekegyseg(m.radian); // vagy m.Mertekegyseg(Math::radian);

y = m.Sin(Math::Pi/6);

m.KiirPI();

}

2.2.2. Az osztályok kialakításának lehetőségei

A C++ nyelv szabályai többféle osztálykialakítási megoldást is lehetővé tesznek. Az alábbi példákban szigorúan elkülönítjük az egyes eseteket, azonban a programozási gyakorlatban ezeket vegyesen használjuk.

2.2.2.1. Implicit inline tagfüggvények alkalmazása

Az első esetben az osztály leírásában szerepeltetjük a tagfüggvények teljes definícióját. A fordító az ilyen tagfüggvényeket automatikusan inline függvénynek tekinti. A megoldás nagy előnye, hogy a teljes osztályt egyetlen fejállományban tárolhatjuk, és az osztály tagjait könnyen áttekinthetjük. Általában kisebb méretű osztályok esetén alkalmazható hatékonyan ez a megoldás.

Példaként tekintsük a síkbeli pontok kezelését segítő Pont osztályt!

class Pont { private:

int x,y;

public:

Pont(int a = 0, int b = 0) { x = a; y = b; } int GetX() const { return x; }

int GetY() const { return y; } void SetX(int a) { x = a; } void SetY(int a) { y = a; }

void Mozgat(int a, int b) { x = a; y = b; } void Mozgat(const Pont& p) { x = p.x; y = p.y; } void Kiir() const { cout<<"("<<x<<","<<y<<")\n"; } };

2.2.2.2. Osztálystruktúra a C++/CLI alkalmazásokban

A fentiekhez hasonló megoldást követnek a Visual C++ a .NET projektekben, valamint a Java és a C# nyelvek.

Szembeötlő különbség, hogy az osztálytagok elérhetőségek szerinti csoportosítása helyett, minden tag hozzáférését külön megadjuk.

class Pont {

private: int x,y;

public: Pont(int a = 0, int b = 0) { x = a; y = b; } public: int GetX() const { return x; }

public: int GetY() const { return y; } public: void SetX(int a) { x = a; } public: void SetY(int a) { y = a; }

public: void Mozgat(int a, int b) { x = a; y = b; } public: void Mozgat(const Pont& p) { x = p.x; y = p.y; } public: void Kiir() const { cout<<"("<<x<<","<<y<<")\n"; } };

2.2.2.3. A tagfüggvények tárolása külön modulban

Nagyobb méretű osztályok kezelését segíti, ha a tagfüggvényeket külön C++ modulban tároljuk. Ekkor az osztály deklarációjában az adattagok mellett a tagfüggvények prototípusát szerepeltetjük. Az osztály leírását tartalmazó fejállomány (.H), és a tagfüggvények definícióját tároló modul (.CPP) neve általában megegyezik, és utal az osztályra.

A Pont osztály leírása a Pont.h fejállományban az alábbiak szerint módosul:

#ifndef __PONT_H__

A tagfüggvények nevét az osztály nevével kell minősíteni (::) a Pont.cpp állományban:

#include <iostream>

void Pont::Mozgat(const Pont& p) { x = p.x; y = p.y;

}

void Pont::Kiir() const {

cout<<"("<<x<<","<<y<<")\n";

}

Természetesen explicit módon is alkalmazhatjuk az inline előírást a tagfüggvényekre, azonban ekkor az inline tagfüggvények definícióját a C++ modulból a fejállományba kell áthelyeznünk:

#ifndef __PONT_H__

void Mozgat(int a, int b);

Vannak esetek, amikor a C++ nyelv adatrejtés szabályai megakadályozzák, hogy hatékony programkódot készítsünk. A friend (barát) mechanizmus azonban lehetővé teszi, hogy egy osztály private és protected tagjait az osztályon kívüli függvényekből is elérjük.

A friend deklarációt az osztály leírásán belül, tetszőleges elérésű részben elhelyezhetjük. A „barát” lehet egy külső függvény, egy másik osztály adott tagfüggvénye, de akár egy egész osztály is (vagyis annak minden tagfüggvénye). Ennek megfelelően a friend deklarációban a függvények prototípusát, illetve az osztály nevét szerepeltetjük a class szóval bevezetve.

Felhívjuk a figyelmet arra, hogy barátosztály esetén a „baráti viszony” nem kölcsönös, vagyis csak a friend deklarációban szereplő osztály tagfüggvényei kapnak korlátlan elérést a leírást tartalmazó osztály tagjaihoz.

Az alábbi példában szereplő COsztaly minden tagját korlátozás nélkül eléri a külső Osszegez() függvény, a BOsztalySzamlal() nyilvános tagfüggvénye valamint az AOsztaly minden tagfüggvénye:

class AOsztaly;

friend int BOsztaly::Szamlal(int x);

friend class AOsztaly;

// ...

További példaként tekintsük a síkbeli pontok leírásához használható egyszerűsített Pont osztályunkat! Mivel a pontok távolságát számító művelet eredménye nem kapcsolható egyik ponthoz sem, így a távolság

void SetY(int a) { y = a; }

void Mozgat(int a, int b) { x = a; y = b; } void Mozgat(const Pont& p) { x = p.x; y = p.y; } void Kiir() const { cout<<"("<<x<<","<<y<<")\n"; } };

double Tavolsag(const Pont & p1, const Pont & p2) { return sqrt(pow(p1.x-p2.x,2.0)+pow(p1.y-p2.y,2.0));

2.2.4. Mi szerepelhet még az osztályokban?

Az osztályokkal történő eddigi ismerkedésünk során adatokat és függvényeket helyeztünk el az osztályokban. A statikus tagok esetén ezt a sort a statikus konstansokkal és az enum típussal bővítettük.

Az alábbiakban először megnézzük, miként helyezhetünk el az osztályunkban konstanst és hivatkozást, ezt követően pedig röviden áttekintjük az ún. egymásba ágyazott osztályok használatával kapcsolatos szabályokat.

2.2.4.1. Objektumok konstans adattagjai

Vannak esetek, amikor az objektumpéldányokhoz valamilyen egyedi konstans értéket szeretnénk kapcsolni, például egy nevet, egy azonosítószámot. Erre van lehetőség, ha az adattagot const előtaggal látjuk el, és felvesszük a konstruktorok taginicializáló listájára.

A következő példában felhasználó objektumokat készítünk, és a felhasználók nyilvános nevét konstansként használjuk:

class Felhasznalo { string jelszo;

public:

const string nev;

Felhasznalo(string user, string psw="") : nev(user) { jelszo=psw;

}

void SetJelszo(string newpsw) { jelszo = newpsw;}

};

int main() {

Felhasznalo nata("Lafenita");

Felhasznalo kertesz("Liza");

nata.SetJelszo("Atinefal1223"); működik, amennyiben az objektumok konstans tagokat tartalmaznak.

2.2.4.2. Hivatkozás típusú adattagok

Mivel hivatkozást csak már létező változóhoz készíthetünk, az objektum konstruálása során a konstansokhoz hasonló módon kell eljárnunk. Az alábbi példában referencia segítségével kapcsoljuk a vezérlő objektumhoz a jeladó objektumot:

class Jelado { private:

int adat;

Vezerlo(Jelado& szenzor) : jelado(szenzor) {}

void AdatotFogad() { cout<<jelado.Olvas(); } };

int main() {

Jelado sebesseg(0x17);

Vezerlo ABS(sebesseg);

ABS.AdatotFogad();

}

2.2.4.3. Adattag objektumok

Gyakran előfordul, hogy egy osztályban egy másik osztály objektumpéldányát helyezzük el adattagként. Fontos szabály, hogy az ilyen osztály objektumainak létrehozásakor a belső objektumok inicializálásáról is gondoskodni kell, amit a megfelelő konstruktorhívás taginicializáló listára való helyezésével érhetünk el.

A konstruktorhívástól eltekinthetünk, ha a tagobjektum osztálya rendelkezik paraméter nélküli (default) konstruktorral, ami automatikus is meghívódik.

void AdatotFogad() { cout<<jelado.Olvas(); } };

C++-ban egy függvényre mutató pointer még akkor sem veheti fel valamely tagfüggvény címét, ha különben a típusuk és a paraméterlistájuk teljesen megegyezik. Ennek oka, hogy a (nem statikus) tagfüggvények az osztály példányain fejtik ki hatásukat. Ugyanez igaz az adattagokhoz rendelt mutatókra is. (További fontos eltérés a hagyományos mutatókhoz képest, hogy a virtuális tagfüggvények mutatón keresztüli hívása esetén is érvényesül a későbbiekben tárgyalt polimorfizmus.)

A mutatók helyes definiálásához az osztály nevét és a hatókör operátort is alkalmaznunk kell:

class Osztaly; előrevetett osztálydeklaráció,

int Osztaly::*p; p mutató egy int típusú adattagra,

void (Osztaly::*pfv)(int); pfv egy olyan tagfüggvényre mutathat, amelyet int argumentummal hívunk, és nem ad vissza értéket.

A következő példában bemutatjuk az osztálytagokra mutató pointerek használatát, melynek során mindkét osztálytagot mutatók segítségével érjük el. Az ilyen mutatók alkalmazása esetén a tagokra a szokásos operátorok helyett a .* (pont csillag), illetve a ->* (nyíl csillag) operátorokkal hivatkozhatunk. Az adattagok és tagfüggvények címének lekérdezéséhez pedig egyaránt a címe (&) operátort kell használnunk.

#include <iostream> int Osztaly::*intptr = &Osztaly::a;

// mutató az Osztaly void típusú, int paraméterű // tagfüggvényére

void (Osztaly::* fvptr)(int) = &Osztaly::f;

// az objektupéldányok létrehozása Osztaly objektum;

// az f() tagfüggvény hívása pointer felhasználásával (objektum.*fvptr)(20);

A typedef alkalmazásával egyszerűbbé tehetjük a pointeres kifejezésekkel való munkát:

typedef int Osztaly::*mutato_int;

Az eddigiek során az osztályhoz tartozó műveleteket tagfüggvények formájában valósítottuk meg. A műveletek elvégzése pedig tagfüggvények hívását jelentette. Sokkal olvashatóbb programokhoz juthatunk, ha a függvényhívások helyett valamilyen hasonló tartalmú műveleti jelet alkalmazhatunk.

A C++ nyelv biztosítja annak a lehetőségét, hogy valamely, programozó által definiált függvényt szabványos operátorhoz kapcsoljunk, kibővítve ezzel az operátor működését. Ez a függvény automatikusan meghívódik, amikor az operátort egy meghatározott szövegkörnyezetben használjuk.