Programozzunk objektumokkal

Az objektumorientált programozást úgy lehet felvázolni, mint egy utat a gyors és hatékony programozáshoz. A cikkben a C++-szon keresztül fogjuk végigvinni az objektumorientáltság alapjait. A következő témákról fogok beszélni:

Az objektumorientáltság alapeleme az osztály. Az osztályok létrehozásakor új adattípust hozunk létre, melyeket ugyanúgy használhatunk, mint más beépített adattípusokat (mint pl. az int, a char, stb.). Emellett az osztálydefinícióban megadunk váltózókat, és a feladathoz kapcsolódó függvénykódot is. Az osztály nem más, mint tagváltozók és a  tagfüggvények gyűjteménye.

Osztályok, és objektumok létrehozása

Mint láthatjuk az osztályok definiálását a ’class’ kulcsszóval vezetjük be, majd a ’{} operátorok között adjuk meg az osztályt felépítő kódot, a tagváltozókat (adattagokat) és a tagfüggvények, melyek az osztályhoz tartoznak. Az osztály definiálásakor nem inicializálhatjuk az adattagokat (tehát nem adhatunk értéket sem nekik). Az osztályt ’;’operátorral zárjuk le.

class  Szam //az osztály deklarálása

{

      int Egy;    //a tagváltozó(k) megadása

 

      int GetSzam ()    //a tagfüggvény(ek) megadása

      {

            return Egy;

      }

};

Az osztály deklarálásakor a fordító nem foglal le az osztály számára memóriát, ehhez példányosítanunk kell, azaz hozzunk létre belőle egy objektumot. Figyeljünk oda, hogy az osztály deklarációjának mindig meg kell előznie a példányosítást.

Szam ElsoSzam; //egy objekum létrehozása

A létrehozott osztályobjektum már memóriát foglal, és a beépített változókhoz hasonlóan addig létezik, amíg a definíció érvényes.

Egységbezárás

Az osztályoknak van egy olyan tulajdonsága, hogy megadhatjuk a hozzáférés „jogosultságát” az osztályban foglalt tagokhoz. Ez azt jelenti, hogy az osztálytagok elé egy hozzáférés-módosítót kell illesztenünk. Alapértelmezés szerint ez privát (private/saját). Ahhoz, hogy kívülről is hozzáférjünk (pl. globális függvényekkel) az osztály tagjaihoz, ezt módosítanunk kell. A hozzáférés-módosító minden utána következő tagra vonatkozik a definíción belül, amíg nincs újabb hozzáférés-módosító megadva.

class  Szam

{

public:

      int Egy;

      int GetSzam ()

      {

            return Egy;

      }

};

public – a hozzáférés-módosítóval teljesen nyílttá tehetjük az őt követő tagokat, tehát a nemtag függvények is hozzáférnek.

protected – a védett hozzáférés-módosítót az öröklődés folyamán fogjuk tárgyalni

private – a „saját” tagokat csak az osztály tagfüggvényei érhetik el.

Az objektum adattagjaihoz a példányon keresztül férünk hozzá a ’.’ operátor használatával. Egy osztályból akárhány példányt létrehozhatunk, ha azok neve különböző.

Szam.Egy = 1; //hozzáférés és értékadás

Szam.GetSzam();

Az egységbezárás elvének megfelelően az osztály belső adatstruktúráit a felhasználónak nem szabad látnia, ez lehetővé teszi számunkra a tagváltozókba kerülő értékek ellenőrzését. Optimális esetben az osztály adattagjaihoz csak az osztály tagfüggvényein keresztül férhetünk hozzá. Ennek megfelelően módosítjuk a kódot is. Ezáltal ellenőrizhetjük az adattagokba kerülő értékeket, megelőzve a programozási hibákat.

class  Szam

{

private: //hozzáférésmódosító

      int Egy;

public: //hozzáférésmódosító

      int GetSzam ()

      {

            return Egy;

      }

      void SetSzam(int E)

      {

            Egy = E;

      }

};

Konstruktorok és destruktorok

A tagfüggvényeknek van két különleges típusa: a konstruktor és a destruktor.

A konstruktorok: az osztályokat inicializáló tagfüggvények. Tehát olyan különleges függvény, ami minden esetben meghívódik, mikor az osztályból egy példányt létrehozunk. Minden C++ osztálynak van konstruktora, ha mi nem készítünk saját konstruktort az osztályhoz, akkor a fordító készít egy alapértelmezett konstruktort, és az adattagoknak alapértelmezett értéket ad meg (a mi alapértelmezett konstruktorainknak nincsenek paraméterei, és általában az adattagokat mi is az alapértelmezett értékekkel töltjük fel.).

Szam ( )     //alapértelmezett konstruktor

{

      Egy = 1;

}

A konstruktorban az osztály inicializálásához szükséges utasításokat adhatjuk ki, feladatokat végezhetjük el, mint például az értékadás műveletet. A konstruktor neve megegyezik az osztályéval, és sosincs visszatérési értéke (még void sem). Paramétere, viszont akármennyi lehet. Ha az osztályhoz készítettünk konstruktort, akkor a példányosításkor a konstruktor paramétereit is meg kell adjuk. Egy osztályhoz több konstruktort is készíthetünk (túlterhelt konstruktoroknak hívjuk őket), viszont fontos, hogy a paramétereik különbözzenek.

Szam (int E)      //konstruktor

{

      SetSzam(E);

}

Hogy a fordító ezek közül melyiket hívja meg, az attól függ, hogy a példányosításkor melyik konstruktor paraméterlistájával dolgoztunk.

Szam MasodikSzam(1); //példányosítás konstruktorral

A destruktor az osztályok másik különleges függvénye. A destruktor akkor kerül meghívásra, amikor az objektumot megszüntetjük. Ide kerülnek az  objektum megszüntetésével kapcsolatos elvégzendő feladatok. Neve szintén megegyezik az osztály nevével, viszont a destruktort a ’~’ operátorral vezetjük be. A destruktoroknak ugyanúgy nincs visszatérési értékük, mint a konstruktoroknak, viszont paraméterei sincsenek.

~Szam ( )     //destruktor

{

}

Tagobjektumok, objektumtömbök

Megtehetjük, hogy egy osztály definiálásakor egy meglévő osztályt veszünk fel adattagként (tagobjektum, vagy beágyazott osztály).  Ebben az esetben a „tartalmazó” osztály konstruktorában inicializálhatjuk a tagobjektumot. Ha nem tesszük, akkor a tagobjektum alapértelmezett konstruktora kerül meghívásra.

class Beagyazott

{

      Beagyazott (int B1)

      {...}

};

 

class Tartalmazo

{

      Tartalmazo(int T1):Beagyazott (B1)

      {...}

};

Az objektumtömböknél a tömb létrehozásakor minden egyes példánynál a fordító meghívja az alapértelmezett konstruktort.

Szam TombSzam[10]; //objekumtömb létrehozása

Konstruktorok és destruktorok hívási sorrendje

Globális objektumoknál a program elindulásakor hívódik meg a konstruktor (a main előtt!!!), a destruktor pedig a program végén.

Lokális objektumoknál (pl. egy függvényen belül) akkor hívódik meg a konstruktor, mikor a vezérlés eléri az objektum definícióját, a destruktor, pedig amikor a vezérlés elhagyja a blokkot, amiben a definíció szerepelt.

Lokálisan definiált statikus objektumoknál (a ’static’ kulcsszóval bevezetett objektumokból egyetlenegy példány jön létre az egész program futása alatt) a konstruktor akkor hívódik, mikor a vezérlés eléri a definíciót, a destruktor pedig a program végén.

Dinamikusan létrehozott objektumoknál (’new’ operátor) az objektum létrehozásakor hívódik a konstruktor, a destruktor, pedig mikor az objektumot megsemmisítjük (’delete’ operátor).

Inline függvények

Az osztálytag függvények definícióját az osztályon kívülre is helyezhetjük, ebben az esetben a függvénydefiníciót meg kell előznie az osztály nevének a ’::’ hatókörfeloldó operátorral együtt.

<visszatérési érték> <osztálynév>::<függvénynév> (<paraméterlista>)

Az osztályon belül definiált függvényeket nevezzük inline függvényeknek. Ezeket a függvényeket híváskor a fordító minden esetben a teljes függvénykóddal helyettesíti, ezáltal az alkalmazás gyorsabb lesz, viszont a függvénykód is terjedelmesebb. Javaslatként azokat a függvényeket definiáljuk az osztályon belül, melyek rövidek, vagy melyeknél szükség van a gyorsaságra. Egy osztályon kívül definiált függvényt a fordító inline-ként kezel, ha a visszatérési érték után kitesszük az ’inline’ kulcsszót.

<visszatérési érték> inline <osztálynév>::<függvénynév> (<paraméterlista>)

Fontos: az osztályhoz tartozó függvényeket mindenféleképpen deklarálnunk kell az osztályon belül (a visszatérési érték, a függvénynév és a paraméterlista megadásával).

Halász Gábor