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ó).
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.
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;
};
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.
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 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ó.