A programozási módszertan fejlődésével a procedurális szemléletet elkezdte leváltani az objektum orientált paradigma. Ennek előnye, hogy közelebb áll az emberi gondolkozáshoz: az entitásokat
hierarchiákba lehet szervezni, és az enkapszuláció révén egy entitás minden tulajdonsága (adatok és műveletek) logikailag egy helyen van. Ezen kívül az adatrejtés elvével bizonyos tulajdonságokat
elrejthetünk a külvilág elől.
TartalomjegyzékEgy osztály egy minta valamilyen objektum konstrukciójához. Definiálja az objektum tulajdonságait (tagváltozók) és amit csinálni tud (metódusok). Konvencionálisan az osztályokat egy header fájlban definiáljuk, a metódusok implementációit pedig egy hasonló nevű .cpp fájlban valósítjuk meg. Nézzük például hogyan nézne ki egy alma:
Hasonlóan a függvényes fejezetben látottakhoz, osztályok esetében is a header fájl a konvencionális ellenőrzéssel kezdődik. Az almának van színe és mérete; a public kulcsszó azt jelenti, hogy ez kívülről látható (és publikus változó esetében változtatható) tulajdonság. Az osztályt a következőképpen példányosítjuk: CODE
Alma a1;
Alma a2;
a1.szin = 1; // 1 <=> piros (érett)
a1.meret = 10;
a2.szin = 0; // 0 <=> zöld (éretlen)
a2.meret = 7; // és az éretlen alma kisebb is
Ebben a kódban két Alma típusú objektumot hoztam létre; a fordító az osztálydefinícióból tudja, hogy mennyi memóriát kell foglalni. A kívülről látható tulajdonságokra és metódusokra a . (pont) operátorral lehet hivatkozni.
Elég unalmas dolog minden tagváltozót egyesével inicializálni, sőt ha esetleg elfelejtenéd akkor kapsz egy elfajult almát. Ezenkívül az almák nagyjából ugyanakkorák, tehát
elég lenne csak azt megadni, hogy érett-e vagy nem. Minden osztálynak van két speciális metódusa, a konstruktor és a destruktor. Mindkettő neve megegyezik az osztályéval,
de a destruktor előtt van egy ~ jel. Ha nem adtuk meg, akkor a fordító generál egy default konstruktort és destruktort (például a fenti kódban). Visszatérő értékük nem lehet.
Ha az osztályon kívül implementálsz egy metódust, akkor ki kell tenni az osztály nevét és a scope operátort. Fontos: header fájlban ne implementáld a metódusokat, hacsaknem az osztálydeklarációban, mert linkelési hibát kapsz (ugyanazért, mint a függvényes fejezetben). Mivel megadtam konstruktort (és van paramétere) egy almát úgy deklarálhatok csak, hogy megadom a színét: CODE
//Alma a0; // fordítási hiba
Alma a1(1);
Alma a2(0);
std::cout << "Az elso alma merete: " << a1.meret << "\n";
std::cout << "A masodik alma merete: " << a2.meret << "\n";
// output: 10 7
Nagyon fontos, hogy a destruktort soha ne hívd meg közvetlenül, mert nem tudod hogyan van implementálva! A konstruktor és a destruktor csak két kitüntetett metódus, nem ezek foglalják és szabadítják fel a memóriát! Még fontosabb, hogy konstruktorok nem hívhatják egymást! Van egy kis baj az almával: a színét és a méretét bárki meg tudja változtatni valami érvénytelen értékre (például kék), és különben is ezek az alma privát tulajdonságai amit látunk ugyan, de nem kéne tudnunk beleszólni. A megoldás az, hogy a színt és méretet private -ként deklaráljuk, de az olvasáshoz biztosítunk metódusokat (ezeket más nyelvekben property-nek nevezik).
Privátnak deklaráltam a változókat, így nem lehet hivatkozni rájuk kívülről. Az értéküket viszont el lehet kérni a publikus metódusok segítségével. Ezenkívül lekezeltem azt az esetet is, amikor valaki a konstruktorral akarna kék almát csinálni. CODE
Alma a1(1);
Alma a2(4); // kék alma (itt most zöld lesz)
//a1.meret = 20; // fordítási hiba: 'meret' privát
std::cout << "Az elso alma merete: " << a1.GetMeret() << "\n";
std::cout << "A masodik alma merete: " << a2.GetMeret() << "\n";
// output: 10 7
Természetesen metódusokat is lehet priváttá tenni (akár a konstruktort is, majd látni fogjuk, hogy mikor jó az). Létezik egy harmadik láthatósági szint is, a protected, ez majd öröklődésnél lesz fontos, de most ugyanúgy viselkedne mint a private.
A módszer teljesen hasonló mint az eddigi dinamikus memóriakezeléses példák. Egy almát a következőképpen hozunk létre dinamikusan:
CODE
Alma* a = new Alma(1);
std::cout << "Az alma merete: " << (*a1).GetMeret() << "\n";
std::cout << "Az alma merete: " << a1->GetMeret() << "\n";
delete a;
// output: 10 10
Pointerek esetében a tagokra hivatkozhatsz a -> (nyíl) operátorral is, ez kellemesebb mint a (*). A létrehozás és megsemmisítés itt is a new és delete operátorokkal történik. C++ -ban a legtöbb operátort (de az aritmetikai operátorokat mindenképpen) felül lehet definiálni. Például írjuk meg a komplex számok osztályát az összeadás és szorzás műveletével:
Megjelent egy új dolog: az incializáló lista, itt a tagváltozók konstruktorait lehet meghívni. A sorrend nem mindegy, érdekes hibákat lehet kapni ha összevissza hivogatod a konstruktorokat; érdemes az ilyen változókat az osztály elejére tenni egy kupacba. Az operátorok teljesen szokványos metódusok, kivéve hogy a nevük az operator kulcsszóból és egy jelből áll. Csak létező operátort lehet felüldefiniálni, tehát például × operátort nem lehet csinálni. Előnyük, hogy meghívhatóak infix módon is, azaz két változó közé írva az operátor jelét: CODE
Komplex a(2, 3);
Komplex b(-5, 8);
Komplex c(a + b), d;
std::cout << "a + b == (" << c._re << ", " << c._im << ")\n";
d = a * b; // ugyanaz mint a.operator *(b)
std::cout << "a * b == (" << d._re << ", " << d._im << ")\n";
Felül lehet defininálni az operator new és operator delete operátorokat is, sőt konverziós operátorokat is, például (típus)változó. Ezekkel most nem foglalkozok. Ami érdekesebb, hogy az operátort nem feltétlenül kell az osztályon belül deklarálni, lehet úgy is mint egy sima függvényt. Például írjuk meg a kivonás műveletét: CODE
Komplex operator -(const Komplex& c1, const Komplex& c2)
{
return Komplex(c1._re - c2._re, c1._im - c2._im);
}
// main()-be:
d = a - b; // ugyanaz mint ::operator -(a, b)
std::cout << "a - b == (" << d._re << ", " << d._im << ")\n";
Ha pointeresen hozod létre az objektumot, akkor ki kell írni a teljes metódusnevet (a->operator +(b)) vagy a (*a) + b módszert használni. Egy változót vagy metódust osztályszintűvé lehet tenni a static kulcsszóval. Ilyenkor is az osztályban kell deklarálni, de ekkor a definíció nem itt fog történni, hanem a .cpp fájlban.
A metódusok esete teljesen hasonló, de a .cpp fájlban már nem kell kiírni a static kulcsszót. Használatkor az osztály nevével kell minősiteni a statikus változót. CODE
d = a + Komplex::i;
Statikus osztálynak hívják az olyan osztályt, aminek csak statikus változói és metódusai vannak. C++ -ban nyelvi szinten nincs ilyen, de az osztály konstruktorát priváttá téve lehet szimulálni. Innentől tehát ha statikus osztályról beszélek, akkor a konstruktorát privátnak tekintem. Az ilyen osztályt nem lehet példányosítani és a tagváltozói a program végéig léteznek (mint a rendes statikus változók esetében). Fontos: statikus metódusban nem statikus tagváltozóhoz és metódushoz nem lehet hozzáférni!
Mért nem lehet statikus metódusból elérni nem statikus tagokat? Hát például mert ebben az esetben nincs is objektum. Egy kicsit mélyedjünk bele a metódusok lelki világába. Amikor leírom ezt:
CODE
class Valami
{
public:
int n;
void foo(int i);
};
void Valami::foo(int i)
{
n = i;
}
// main()-be:
Valami v;
v.foo(5);
akkor a fordító valami ilyesmit generál belőle: CODE
void Valami_foo_int(Valami* this, int i)
{
this->n = i;
}
// main()-ben:
Valami v;
Valami_foo_int(&v, 5);
Ez a this pointer láthatatlanul mindig átadódik a metódusnak, így amikor egy tagváltozóra hivatkozok, valójában a this->tagváltozó -ra történik hivatkozás; kivéve a statikus metódusokat, ott ugyanis nincs this pointer (hiszen nincs objektum). Magát a this pointert lehet is használni, például névelfedéskor (egy tagváltozót elfed a metódus lokális változója).
C-ben a strukturák a rekord adatszerkezetet valósítják meg, tehát csak tagváltozóik lehetnek, metódusaik nem (láthatóság meg abszolút nem, minden publikus).
C++ -ban a struct-ot lehet ugyanúgy használni mint a class-t, de van néhány különbség. Osztályoknál ha nem írod ki láthatóságot, akkor az alapértelmezett
a private. Strukturáknál a public. Hasonlóan osztályoknál a default öröklődés szintén private, strukturáknál public (következő fejezet).
Előfordulhat olyan eset, amikor két osztály kereszthivatkozik egymásra. Ez olyan probléma, mint hogy a tojás volt-e előbb vagy a tyúk, tehát megoldhatatlannak tűnik. Szerencsére osztályokat lehet
előre deklarálni, ezzel jelezve a fordítónak, hogy az az azonosító egy osztályt jelöl, de csak később lesz definiálva. Megjegyzendő, hogy ez csak akkor működik, ha az említett hivatkozás referencia vagy pointer (hiszen annak
ismert a mérete, 4 bájt).
CODE
class A;
class B
{
//A a; // így nem jó
A* a; // így jó
};
class A
{
B b;
};
Hasonlóan lehet használni a forward deklarációt az #include hivatkozások csökkentésére. A C++ standard library néhány osztályának forward deklarációja megtalálható az <iosfwd> headerben.
Néhány alapvető dolgot mondtam el osztályokkal kapcsolatban. A következő fejezetben egy nagyon fontos dologról lesz szó, az öröklődésröl és a hozzá kapcsolódó dinamikus kötésről.
Irodalomjegyzék http://en.cppreference.com/w/cpp http://www.cplusplus.com/ http://www.parashift.com/c++-faq-lite/ (ezt különösen ajánlott elolvasni) http://aszt.inf.elte.hu/~gsd/halado_cpp/ (ezt is) |