O proprietate important a motenirii este faptul c pointerii ctre o clas de baz pot primi adresa unui obiect de tip clas derivat. S ilustrm aceast proprietate ntr-un exemplu. Avem clasa de baz Carte cu cmpul titlu i funcia afisare(). Din ea este derivat clasa Culegere, care este o carte ce conine mai multe lucrri. Culegerea are 2 cmpuri n plus nrLucrari i lucrari numrul i titlurile lucrrilor. i mai are o alt funcie afisare() care afieaz toate datele din culegere.
class Carte { protected: char *titlu; public: Carte(char *titlu); ~Carte(); void afisare(); };
class Culegere : public Carte { protected: int nrLucrari; char **lucrari; public: Culegere(char *titlu, int nrLucrari, char **lucrari); ~Culegere(); void afisare(); };
#endif
Carti.cpp #include<iostream> #include<string.h> #include"Carti.h" using namespace std;
Carte::Carte(char *titlu){ this->titlu = new char[strlen(titlu)+1]; strcpy(this->titlu, titlu); }
CartiMain.cpp #include<iostream> #include<conio.h> #include"Carti.h" using namespace std;
int main() { Carte *carte = new Carte("Moara cu Noroc"); char *poeme[] = {"Luceafarul", "Memento Mori"}; Culegere *culeg = new Culegere("Poeme Eminescu", 2, poeme); Carte *cculeg = culeg;
Observm atribuirea din funcia main(): Carte *cculeg = culeg;
Atribuirea unui pointer de tip clas derivat Culegere* ctre un pointer de tip clas de baz Carte* este perfect valid. Prin intermediul pointerului la clasa de baz Carte putem accesa orice membru din Carte, indiferent dac obiectul referit este de tip Carte sau Culegere. De exemplu putem s accesm funcia afisare(). i ntr-adevar, observm c n rezultatul apelului cculeg->afisare();
este executat funcia Carte::afisare(). Ca s putem accesa funcia Culegere::afisare(), n acest exemplu, suntem nevoii s utilizm pointerul la clasa derivat: culeg->afisare();
Funciile Carte::afisare() i Culegere::afisare() nu au nici o legtur ntre ele n acest program. 2. Metode virtuale. Polimorfismul.
Metod (funcie) virtual este o metod definit n clasa de baz, i care poate fi redefinit n clasele derivate. Se declar cu ajutorul cuvntului cheie virtual. Atunci cnd apelm o metod virtual prin intermediul unui pointer la obiect, se va apela ntotdeauna metoda din tipul obiectului, indiferent de tipul pointerului. Se spune c metodele virtuale pot fi redefinite (overloaded) n clasele derivate. (A se face diferena dintre redefinire i suprancrcare, dou faciliti diferite.) S modificm exemplul de mai sus, i s declarm funcia afisare() din clasa Carte cu cuvntul cheie virtual n fa:
Carti.h
class Carte { protected: char *titlu; public: Carte(char *titlu); ~Carte(); virtual void afisare(); };
Restul programului rmne neschimbat. Executm programul. Rezultatul devine:
carte->afisare(): Moara cu Noroc cculeg->afisare(): Poeme Eminescu. Lucrari: Luceafarul Memento Mori culeg->afisare(): Poeme Eminescu. Lucrari: Luceafarul Memento Mori
De data aceasta funciile Carte::afisare() i Culegere::afisare() sunt ambele virtuale, i sunt legate. A 2-a funcie o redefinete pe prima. ntruct pointerii culeg i cculeg refer acelai obiect, att pentru expresia cculeg->afisare();
ct i pentru culeg->afisare();
se va apela funcia Culegere::afisare() funcia din tipul obiectului. Chiar dac cei 2 pointeri sunt de tipuri diferite. O funcie definit virtual n clasa de baz, va rmne virtual n tot arborele ierarhic a acelei clase, indiferent cte nivele are derivarea. Funcia redefinit trebuie s aib aceiai parametri i acelai tip de return ca i funcia original din clasa de baz. Datorit funciei afisare() virtuale, putem rescrie funcia main() astfel nct s folosim doar pointeri de tip Carte*:
CartiMain.cpp #include<iostream> #include<conio.h> #include"Carti.h" using namespace std;
int main() { char *poeme[] = {"Luceafarul", "Memento Mori"}; Carte *carte = new Carte("Moara cu Noroc"); Carte *cculeg = new Culegere("Poeme Eminescu", 2, poeme);
delete carte; delete cculeg; _getch(); return 0; } Ieire: carte->afisare(): Moara cu Noroc cculeg->afisare(): Poeme Eminescu. Lucrari: Luceafarul Memento Mori
Putem generaliza programul i mai mult i s utilizm un vector de pointeri de tip Carte*, al crui elemente s fie pointeri la diferite tipuri de obiecte. S modificm funcia main() n felul urmtor:
CartiMain.cpp #include<iostream> #include<conio.h> #include"Carti.h" using namespace std;
int main() { char *poeme[] = {"Luceafarul", "Memento Mori"}; Carte *carti[3]; carti[0] = new Carte("Moara cu Noroc"); carti[1] = new Culegere("Poeme Eminescu", 2, poeme); carti[2] = new Carte("Amintiri din copilarie");
for(int i=0; i<3; i++) { cout << i << ". "; carti[i]->afisare(); delete carti[i]; } _getch(); return 0; } Ieire: 0. Moara cu Noroc 1. Poeme Eminescu. Lucrari: Luceafarul Memento Mori 2. Amintiri din copilarie
Observm c singurul loc din program care tie tipul obiectelor sunt liniile de cod care realizeaz instanierea:
carti[0] = new Carte("Moara cu Noroc"); carti[1] = new Culegere("Poeme Eminescu", 2, poeme); carti[2] = new Carte("Amintiri din copilarie");
n restul programului, o singur instruciune tie s afieze fiecare carte n mod corect, corespunztor tipului su: carti[i]->afisare();
Polimorfismul reprezint capacitatea de a apela metode diferite prin intermediul unei singure expresii de genul pb->f()
, unde pb este pointer la o clas de baz, iar f() este o funcie virtual redefinit n clasele derivate. Funcia apelat este decis n timpul execuiei programului, n funcie de tipul obiectului referit de pb. Un astfel de apel de funcie, pentru care nu se cunoate funcia concret apelat n momentul compilrii programului, se numeste apel polimorfic. Polomorfismul reprezint cel de-al 3-lea principiu al POO, dupa ncapsulare (abstractizare) i Motenire. Principiul polimorfismului ridic POO la adevrata sa valoare, ndeosebi n proiectele mari. Putem s scriem o bibliotec de clase n care s manipulm obiectele prin intermediul poiterilor la clasa de baz, fr s cunoatem tipul concret al obiectelor. Iar n alte proiecte, s folosim biblioteca n combinaie cu mai multe clase derivate. 1. Destructor virtual
Ultimul program dat ca exemplu afieaz rezultatul ateptat, dar conine un defect. Pe linia: delete carti[i];
operatorul delete va apela destructorul. Dar pentru c pointerul este de tip Carte*, destructorul apelat va fi doar Carte::~Carte(). Iar cmpul - pointer lucrari, din obiectele de tip Culegere va rmne dezalocat. Ca s corectm acest defect, vom declara i destructorul clasei Carte virtual:
class Carte { protected: char *titlu; public: Carte(char *titlu); virtual ~Carte(); virtual void afisare(); };
De data aceasta, la apelarea operatorului delete pentru pointer la clasa de baz, se va apela tot lanul de destructori de la tipul obiectului pna la cea mai de baz clas, indiferent de tipul pointerului. Pentru a avea garania c obiectele sunt ntotdeauna dezalocate corect, exist urmtoarea regul:
n orice ierarhie de clase C++, clasa cea mai de baz trebuie s conin un destructor virtual. Chiar dac n clasa de baz nu avem ce dezaloca, vom defini un destructor virtual vid (fr instruciuni). 2. Clase abstracte
Exist cazuri cnd o funcie virtual nu are sens s fie implementat ntr-o clas de baz, dar are sens n clasele derivate. De exemplu putem avea clasa de baz Figura cu funcia virtual arie() (convenim c orice figur n plan are o arie), i clasele derivate Dreptunghi i Cerc. Cunoatem formula ariei pentru dreptunghi i cerc, dar nu putem defini aria pentru o figur n general. n acest caz, vom declara funcia din clasa de baz virtual pur.
Funcie virtual pur este o funcie virtual care nu are corp. Se declar adugnd simbolurile "=0;" la sfritul declaraiei funciei. De exemplu, declaraia funciei virtuale pure arie() va arta n felul urmtor: class Figura { public: virtual float arie()=0;
}; Clas abstract este o clas care conine cel puin o funcie virtual pur. Clasele abstracte au restricia c nu pot fi instaniate, adic nu putem crea obiecte pe baza lor. Ele sunt create special pentru a fi derivate. ns putem s declarm pointeri pe clase abstracte, care vor referi obiecte de tip clase derivate concrete. Clasele derivate n schimb trebuie s implementeze funciile virtuale pure motenite, pentru a deveni clase concrete. Codul de mai jos conine declaraia i implementarea claselor Figura, Dreptunghi i Cerc. Clasele derivate implementeaz metodele virtuale pure motenite de la clasa de baz.
int main() { Figura *dr = new Dreptunghi(1,2,4,4); Figura *cerc = new Cerc(1,1,3); //urmatoarea linie ar genera eroare: //Figura *fig = new Figura(); dr->afisare(); cerc->afisare(); delete dr; delete cerc; _getch(); return 0; }
Dac am ncerca s crem un obiect de tip Figura n funcia main(), am obine o eroare de compilare, deoarece clasa Figura este abstract i nu poate fi instaniat. n continuare vom folosi aceste 3 clase ntr-un exemplu care demonstreaz mai elocvent avantajele polimorfismului. Vom aduga la program o funcie global care va determina figura cu arie maxim dintr-un vector de pointeri la figuri:
figuri.h
Figura *figCuArieMax(Figura **figuri, int n);
#endif
figuri.cpp //... Figura *figCuArieMax(Figura **figuri, int n) { float max = 0; Figura *figMax = NULL; for(int i=0; i<n; i++) { float arie = figuri[i]->arie(); if (arie > max) { max = arie; figMax = figuri[i]; } } return figMax; }
n funcia main() sunt instaniate cteva figuri. Apoi este determinat i afiat figura cu arie maxim.
figuriMain.cpp #include<iostream> #include<conio.h> #include"Figuri.h" using namespace std;
int main() { const int n = 3; Figura *figuri[n]; figuri[0] = new Dreptunghi(0,0,2,5); figuri[1] = new Dreptunghi(0,0,2,2); figuri[2] = new Cerc(0,0,3); Figura *figMax = figCuArieMax(figuri, n);
cout << " Dintre figurile:"<<endl; for(int i=0; i<3; i++) { cout << i << ". "; figuri[i]->afisare(); } cout << endl <<" aria maxima o are:"<<endl; figMax->afisare(); for(int i=0; i<n; i++) { delete figuri[i]; } _getch(); return 0; } Ieire Dintre figurile: 0. Dreptunghi cu coordonatele (0,0)-(2,5), si aria 10 1. Dreptunghi cu coordonatele (0,0)-(2,2), si aria 4 2. Cerc cu coordonatele (0,0), raza 3 si aria 28.26
aria maxima o are: Cerc cu coordonatele (0,0), raza 3 si aria 28.26
Se observ c funcia figCuArieMax() poate s determine figura cu arie maxim chiar i dintr- o list care conine att dreptunghiuri ct i cercuri. Acest lucru a fost posibil datorit apelului polimorfic a funciei arie() n funcia figCuArieMax(): float arie = figuri[i]->arie();
Putem chiar s extindem programul i s adugam noi tipuri de figuri, iar funcia figCuArieMax() va ti automat s determine aria maxim i pentru ele. 3. Exerciii
Continuai dezvoltarea ultimului exemplu din acest laborator. Clasa Figura i derivatele sale. Efectuai urmtoarele nbuntiri: 1. Adugai clasa Triunghi derivat din Figura. Triunghiul va fi definit de cele 3 vrfuri, fiecare avand coordonatele x i y. (Ci parametri va avea constructorul clasei Triunghi?) Aria triunghiului se poate calcula dup formula lui Heron: s = sqrt(p*(p-a)*(p-b)*(p-c)), unde p este semiperimetrul: p = (a + b + c ) / 2. 2. Adugai la clasa Figura funcia virtual pur perimetru(). Afiai figura cu perimetrul maxim. 3. Scriei o funcie care sorteaz un vector de pointeri la figuri cresctor dup arie. Afiai figurile sortate.