Objektumorientált programozás C++-ban – 2. rész

Osztályok származtatása

Most belekezdünk az objektumorientáltság egyik leglényegesebb tulajdonságába. Előzőekben megbeszéltük, hogy mi az az osztály, és hogyan hozhatjuk létre. Most arról fogunk beszélni, hogyan használhatjuk fel a már megírt osztályainkat többször, az aktuális feladatnak megfelelően. Példaként nézzünk meg egy családot: ugyebár egy átlagos családban vannak szülők, és gyermekek. Mondjuk apa és fia. Először definiáljunk egy „apa” osztályt.

#include <iostream.h>

class Apa

{

protected:

      int Eletkor;

      int Magasság;

public:

      Apa() //Apa konstruktora

      {

      SetApa(E, M);

}

 

void SetApa (int E, int M)   //Apa értékeinek beállítása

{

      Eletkor = E;

      Magassag = M;

}

 

void GetApa (int *E, int *M) //Apa értékeinek lekérdezése

{

      *E = Eletkor;

      *M = Magassag;

}

 

void Kiir (void)  //Apa értékeinek kiiratása

{

      cout << Eletkor << endl << Magassag << endl;

}

};

Az apának van egy fia.

class Fiu : public Apa

{

};

Aki hasonlít az apjára, mert örököl tőle tulajdonságokat: a Fiu az Apa minden adattagját örökli, ezáltal minden adattag rendelkezésünkre áll, amit az ősosztályban definiáltunk (ősosztálynak hívjuk azt az osztályt, amiből származtattunk – most az Apa az ős). A public hozzáférés-módosító használatával az ősosztály minden nyilvános adattagja nyilvános marad a Fiu osztályunkban (és minden protected tag védett marad – erről mindjárt beszélünk bővebben is). Ha private módosítót használtunk volna, akkor minden nyilvános és védett tag privát lenne. A származtatott osztály (Fiu) rendelkezhet új tulajdonságokkal. Tegyük most ezt.

 

#include <string.h>

#include <iostream.h>

 

class Fiu : public Apa

{

protected:

      char *Szemszin;   //az új tulajdonság

 

public:

      Fiu (int E, int M) : Apa(E, M)     //Fiu konstruktor

      {

            Szemszin = new char ('\0');

      }

 

      ~ Fiu       //Fiu destruktor

      {

            delete [] Szemszin;

      }

 

      void SetSzemszin (char *Sz)  //a Szemszin beállítása

      {

            delete [] Szemszin;     //a lefoglalt memória felszabadítása

            Szemszin = new char [strlen (Sz)+1]; //új memória lefoglalása

            strcpy (Szemszin, Sz);  //a paraméter sztring másolása

      }

 

      void Kiir (void)  //adatok kiíratása

      {

Apa::Kiir();      //meghívjuk az ősosztály Kiir fgv-ét

            cout << Szemszin << endl; //kiiratjuk az új adattagot is

      }

};

Az új adattípus amivel a Fiu osztályunk rendelkezik egy sztring tárolására alkalmas változó. Ennek megfelelően adtunk új függvényeket is a Fiu-hoz. A SetSzemszin függvénnyel beállíthatjuk a Szemszin értékét, és az új Kiir függvénnyel kiíratjuk a képernyőre az új adattípust is. Így most a Fiu-nak most két különböző Kiir függvénye van (egy öröklött, és egy saját). Ha egy Fiu objektumhoz hívjuk meg ezt a függvényt, akkor mindig a Fiu-ban definiált Kiir fog lefutni, mivel ez felüldefiniálja az ősosztály függvényét.

A konstruktor amit írtunk meghívja az ősosztályhoz tartozó konstruktort, és átadja az ősosztályban definiált adattagok értékeit (:Apa (E, M)), emellett a blokkban inicializálja a sztringet. A konstruktoron keresztül elvégezhetjük az ősosztály adattagjainak értékadását is (mint ahogy azt jelen esetben meg is tettük). Az előző konstruktorunkban értékeket csatoltunk az Apa osztály adattagjaihoz a SetApa függvény meghívásával. Ezt most annyival bővítettük ki, hogy a Fiu sztringváltozóját inicializáljuk.

Konstruktorunkban elvégezhetjük akár az új adattagunk értékadását is, most viszont csak memóriát foglaltunk le a számára. A konstruktorban használt taginicializáció általános formája a következő:

<osztálynév> (<paraméterlista>) : <ősosztály> (<paraméterlista>)

Ha nem akarjuk inicializálni az adattagokat, meghívhatjuk az alapértelmezett konstruktort:

Fiu()

{

      Szemszin = new char ('\0');

}

formában is, így az ősosztálytól kapott adattagok értéke nulla lesz (az ősosztály alapértelmezett konstruktorát hívja meg a fordító).

Az öröklött tagok elérése

Találkozunk a protected hozzáférés-módosítóval. A protected-ként (védettként) jelölt adattagok külső függvényből nem láthatók, viszont az osztályból származtatott osztályok számára elérhető lesz. Ha az Apa osztály tartalmazna privát tagokat is, akkor azokat a származtatott Fiu osztályból is csak az Apa nyilvános tagfüggvényein keresztül érhetnénk el.

A Fiu osztályunk megörökölte az Apa osztálytól az Eletkor és Magassag adattagokat. Mivel ezeket védettként (protected) jelöltük, így a hozzáférés módosító hatására a Fiu függvényeiben közvetlenül elérhetjük az Apa védett tagjait, viszont külső függvényből továbbra sem férünk hozzá. Ha az Apa adattagjait privátként jelöltük volna, akkor az adattagok eléréséhez mindenféleképpen meg kellett volna hívni a SetApa, illetve a GetApa függvényeket. Most ezeket közvetlenül is elérhetjük a Fiu-ból.

Friend-ek

Ha egy osztály, vagy egy globális függvény számára elérhetővé szeretnénk tenni a privát, illetve a védett tagokat, akkor a friend kulcsszót használhatjuk a feladatra. A megoldáshoz a barátként használni kívánt osztályt, vagy függvényt friend-ként kell definiáljuk az osztályon belül.

class Apa

{ (...)     //mindenféle kód

      friend GetEletkor(int *E);

};

Ebben az esetben egy globális GetEletkor függvény elérheti az Apa privát és védett adattagjait. Osztályt is definiálhatunk barátként, ekkor a barát osztály bármelyik függvénye eléri a privát és a védett tagokat.

class Apa

{(...)      //mindenféle kód

      friend class ApaBaratja;

};

Osztályhierarchia

Származtatott osztály is lehet őse másik osztálynak.

class Unoka : public Fiu

{

};

Ebben az esetben az Unoka rendelkezik az Apa, és a Fiu összes adattagjával és tagfüggvényével. Az Fiu-t az Unoka közvetlen ősének nevezzük, míg az Apa osztály az Unoka közvetett őse.

Megtehetjük azt is, hogy több osztályból származtatunk le, így az új osztály értelemszerűen mindkét ősének a tulajdonságaival rendelkezni fog. Ezt nevezzük többszörös öröklődésnek.

Virtuális függvények, polimorfizmus

Ha minden osztályban definiálunk egy függvényt azonos névvel, akkor alapértelmezés szerint a fordító mindig az aktuális példányhoz tartozó tagfüggvényt hívja meg.

Fiu Jozsi(50, 170);    //létrehozunk egy példányt

Jozsi.SetSzemszin("Kek");    //beállítjuk a Fiu Szemszin tagját

Jozsi.Kiir();     //kiíratjuk a Fiu adattagjait

Az objektumorientált programnyelvekben gyakran használt az a megoldás, hogy egy ősosztályra mutató pointerben az ősosztály egy példányának címe helyett egy származtatott osztály példányának címét tároljuk (ezt fordítva is meg lehet adni, viszont mivel a származtatott osztály rendelkezhet olyan adattaggal, ami nincs definiálva az ősosztályban, ezért ez a megoldás nem biztonságos).

Apa *Kalman;

Fiu Jozsi;

Kalman =  &Jozsi;

Ebben az esetben gond merülhet fel, ha a mutató segítségével a Kiir függvényt hívjuk meg. A fordító ugyanis nem tudja előre, hogy a futás közben a pointer éppen melyik osztályra fog mutatni, ezért mindig az ősosztály (Apa) Kiir függvényét hívja meg. Erre a problémára jelent megoldást a virtuális függvények használata. Ha a virtual kulcsszót használjuk az ősosztály Kiir függvényében, akkor a fordító mindig a pointer aktuális címének megfelelő függvényt hívja meg.

class Apa

{(...)

      virtual void Kiir(void)

  (...)

};

---------------------

Apa *Kalman;      //elkészítünk egy pointert az Apa osztályra

Fiu Jozsi(50, 160);    //létrehozunk egy Fiu objektumot

Kalman =  &Jozsi;      //a pointernek odaadjuk a Fiu objektum címét

Jozsi.SetSzemszin("Kek");    //beállítjuk a Szemszin adattagot

Jozsi.Kiir(); //megkapjuk a képernyőn a Fiu mindhárom adattagját

A virtual kulcsszót csak az ősosztály függvénye elé kell kitegyük, minden további származtatott osztályban automatikusan virtuálissá válik a függvény. A kód átláthatósága kedvéért azonban ki szoktuk tenni a származtatott osztályban is. A virtuális függvényeken keresztül elérhetjük az objektum-orientált programozás másik fontos tulajdonságát a polimorfizmust, vagy többalakúságot. Ez azt jelenti, hogy egy utasítással több különböző műveletet is végre tudunk hajtani. Hogy ezen műveletek közül melyik jön létre mindig attól függ, hogy a pointerünk melyik objektumra mutat (jelen esetben a Kiir függvénnyel ki tudjuk íratni az Apa két adattagját, vagy a Fiu mindhárom adattagját). A virtuális függvények használatával a programkódot nem kell minden esetben módosítanunk, ha új származtatott osztályt használunk, és a programkódunk rövidebb is lesz (nem kell minden esetben konkrétan meghatározzuk a fordító számára, hogy a pointer éppen melyik objektumra mutat).

Az öröklődés előnyei

Az öröklődés segítségével lehetőségünk van felhasználni egy megírt osztály adatstruktúrájának felhasználására a klasszikus copy/paste metódus nélkül. Programkódunk átláthatóbb lesz, mert nem lesznek benne kódkettőzések. Használhatjuk a virtuális függvényeket, mely szintén megkönnyíti és gyorsabbá teszi a programozást, mert mindig az aktuális feladatnak megfelelő kódot fogja használni a fordító.

Halász Gábor - gabor.halasz@mailbox.hu