Sunteți pe pagina 1din 11

Clase derivate. Motenire.

1. Clase derivate. 2. Reutilizarea codului folosind membri obiecte ai claselor. 3. Constructori i destructori n clasele derivate. 4. Controlul accesului la membrii clasei de baz. 5. Ierarhii de clase. 6. Clase virtuale. 7. Funcii virtuale. 8. Destructori virtuali. 9. Clase abstracte. 10. Polimorfism. 11. Probleme. 1. Clase derivate. Prin motenire, atributele unei clase de baz sunt transmise unor clase derivate. Derivarea permite definirea unor clase noi, prin adugarea unor funcionaliti noi unor clase deja existente, fr reprogramare sau recompilare. Clasa derivat motenete caracteristicile unei clase de baz (sau mai multor clase de baz, n cadrul motenirii multiple), la care adaug caracteristici noi, specifice. Clasa derivat motenete datele membri i funciile membri din clasa de baz, exceptnd constructorii, destructorii i operatorul de atribuire. Exemplu de derivare: trapez paralelogram dreptunghi romb ptrat

Reutilizarea codului se poate realiza n dou moduri: prin compunere incluznd obiecte n cadrul altor obiecte prin motenire creind obiecte noi din cele existente Sintaxa specificrii unei clase derivate este:

class nume_clas_derivat : specif_acces nume_clas_baz{ // corp clas }; n cazul motenirii multiple este posibil motenirea din mai multe clase de baz: class derivat : acces1 baz1, ..., accesn bazn { // corp clas; }; O baz direct este menionat n lista claselor de baz ale clasei derivate. Prin motenire multipl i indirect se creaz ierarhii de clase, care sunt grafuri orientate aciclice (n cazul motenirii simple avem un arbore orientat). Exemplu de motenire multipl: triunghi dreptunghic isoscel 1 dreptunghic-isoscel echilateral

2. Reutilizarea codului folosind membri obiecte ai claselor. S considerm clasele String i Persoana definite dup cum urmeaz: class String { int lg; char* sir; public: String(char* ); String(const String& ); ~String(); }; class Persoana { int lg_nume; char* nume; int varsta; public: Persoana(char* n, int v); Persoana(const Persoana& p); ~Persoana(); }; Numele persoanei poate fi definit printr-un obiect de tip String, care poate apare ca membru al clasei Persoana: class Persoana { String nume; int varsta; public: Persoana(const String& n, int v); Persoana(const Persoana& p); ~Persoana(); }; Fiecare obiect din clasa Persoana apeleaz doi constructori: Persoana i String. Constructorii din clasele membri se apeleaz naintea constructorului clasei ce conine membrii deci ordinea de apel a constructorilor va fi: String i apoi Persoana. Argumentele se transmit constructorilor claselor membri printr-o list de iniializare a membrilor plasat imediat dup antetul constructorului. Membrii iniializai sunt separai ntre ei prin virgule i precedai de : Persoana:: Persoana(const String& n, int v) : nume(n), varsta(v) {}; // nu mai trebuie facut nimic Lista de iniializare a membrilor este singurul mecanism prin care pot fi iniializai membrii care nu au constructori implicii, membrii constani i membrii referine, deoarece n momentul nceperii execuiei constructorului, acetia trebuie s fie deja iniializai. Obiectul membru al clasei trebuie s apar n lista de iniializare, dac constructorul are list de parametri.

class Club{ String nume; Tabel membri; Data creere; public: Club(const String& n, Data d); }; Club::Club(const String& n, Data d) :nume(n), membri(),creere(d) //lista de initializare { }; Constructorii membrilor sunt apelai n ordinea n care sunt declarai n clas, nu n cea n care apar n lista de iniializare. Destructorii sunt apelai n ordine invers constructorilor. Dac constructorii membrilor nu au parametri, ei pot s nu apar n lista de iniializare a membrilor. 3. Constructori i destructori n clasele derivate. Constructorii i destructorii sunt funcii membri care nu se motenesc, ntruct acetia posed numele clasei. La creerea i iniializarea unui obiect ntr-o clas derivat se apeleaz implicit, mai nti constructorii claselor de baz (n ordinea n care apar n declaraia clasei derivate) i apoi constructorul clasei derivate. La distrugerea unui obiect al clasei derivate, mai nti este apelat destructorul clasei derivate, i apoi destructorii claselor de baz, n ordinea invers celei n care apar n declaraia clasei derivate. 4. Controlul accesului la membrii clasei de baz. Specificatorul de acces public asigur c: membrii public ai clasei de baz devin membri public ai clasei derivate membrii protected ai clasei de baz devin membri protected ai clasei derivate membrii private ai clasei de baz nu sunt accesibili n clasa derivat

Un membru protected al unei clase, motenit ca public se comport ca unul private, adic poate fi accesat numai de membrii clasei i de funciile friend din clasa de baz. n plus, este accesibil i funciilor membri i funciilor friend din clasa derivat, unde este tot protected. La o nou derivare va fi transmis tot ca protected. (Membrii private din clasa de baz sunt inaccesibili n clasa derivat). Un membru protected poate fi privit ca public n clasele derivate i ca private n celelalte clase. La o motenire cu acces de tip protected a clasei de baz, membrii public i protected din clasa de baz devin protected n clasa derivat. La o motenire cu acces de tip private a clasei de baz, membrii public i protected din clasa de baz devin private n clasa derivat, i la o nou derivare nu vor mai putea fi accesai.

12. Ierarhii de clase. O clas derivat poate fi la rndul ei clas de baz. De exemplu: class Angajat{ . . .}; class Sef : public Angajat { . . .}; class Director : public Sef { . . .}; O asemenea ierarhie se reprezint de obicei printr-un arbore, dar n cazul motenirii multiple apare un graf orientat aciclic. Pentru a fi folosit ca baz, o clas trebuie s fie definit; simpla declarare a clasei este insuficient. S considerm o funcie membru void afisare() const; n clasa de baz, pe care o vom redefini n clasele derivate, avnd aceeai semntur: class Angajat{ String nume; double salariu; public: Angajat(const String& p, double s); void afisare() const; }; void Angajat::afisare()const { cout << nume << salariu: << salariu << endl; }; Angajat::Angajat(const String& n, double s) : nume(n), salariu(s) { }; class Sef: public Angajat{ int sectie; public: Sef(const String& n, double s, int sec); void afisare() const; }; void Sef::afisare()const { Angajat::afisare(); cout << Sef Sectie: << sectie << endl; }; Sef::Sef(const String& n, double s, int sec) : Angajat(n,s), sectie(sec) { }; 6. Clase virtuale. ntr-o motenire multipl, o clas poate fi motenit indirect de mai multe ori. De exemplu: class class class class CBInd {public: int x;}; CBDir1 : public CBInd { . . .}; CBDir2 : public CBInd { . . .}; Der: public CBDir1, public CBDir2 { . . .};

CBInd CBDir1 Der

CBInd CBDir2

Un obiect din clasa Der conine membrii clasei CBInd de dou ori: - prin clasa CBDir1 (CBDir1::CBInd) - prin clasa CBDir2 (CBDir2::CBInd) Accesul la un membru din clasa CBInd este ambiguu, deci interzis.

Ambiguitatea poate fi rezolvat folosind operatorul de rezoluie: Der d; d.CBDir1::x=10; // x mostenit prin CBDir1 d.CBDir2::x=25; // x mostenit prin CBDir2 Pentru a crea o singur copie a clasei de baz n clasa derivat (prin motenire multipl indirect) se declar clasa de baz de tip virtual. class class class class CBInd {public: int x;}; CBInd CBDir1 : virtual public CBInd{...}; CBDir2 : virtual public CBInd{...}; CBDir1 CBDir2 Der: public CBDir1, public CBDir2{...}; Der 7. Funcii virtuale. O funcie membru, definit n clasa de baz poate fi redefinit n clasa derivat cu aceeai semntur. Cele dou funcii: cea din clasa de baz i cea din clasa derivat, dei au aceeai semntur au funcionaliti diferite. Dac funcia din clasa derivat are o semntur diferit, atunci ea va fi suprancrcat n clasa derivat, fiind motenit i funcia din clasa de baz. Accesul la membrii clasei de baz i a clasei derivate se poate face prin pointeri la obiecte din clasa de baz i din clasa derivat. Folosind pointeri din clasa de baz.putem accesa obiecte din acea clas, ct i dintr-o clas derivat, deoarece conversia unui pointer din clasa derivat ntr-un pointer din clasa de baz se face implicit. Conversia invers (din clasa de baz n clasa derivat) nu este implicit. O ncercare de conversie conduce la eroare, iar o forare a conversiei, prin cast, dei este corect sintactic, conduce la rezultate imprevizibile. class B {. . .}; class D : public B {. . .}; void main(){ D d; B* pb = &d; // corect B b; D* pd = &b; // gresit pd = (D*)&b; // corect sintactic, dar nesigur Considerm o funcie membru f(), definit n B i redefinit n D. class B { public: void f();

}; class D : public B { public: void f(); }; Putem accesa funciile f() din clasa de baz B i din clasa derivat D prin intermediul unor obiecte din clasele de baz i derivat, sau a unor pointeri la clasa de baz i la clasa derivat. void main(){ B b; D d; b.f(); //legare statica se acceseaza f() din B d.f(); //legare statica se acceseaza f() din D d.B::f(); //legare statica se acceseaza f() din B B *pb = new B; D *pd = new D; B* pb1 = pd; // ncercm acces la clasa D cu pb1 pb->f(); // se acceseaza f() din B pd->f(); // se acceseaza f() din D pb1->f(); // se acceseaza f() din B !! } Prin urmare, accesul la funcia membru din clasa B sau D este dictat de tipul pointerului (cu un pointer din clasa B putem accesa numai funcii din clasa B, chiar dac pointerul indic spre un obiect din clasa derivat). O funcie virtual este declarat cu specificatorul virtual n clasa de baz i este redefinit n clasa derivat. Revenind asupra exemplului precedent, constatm c putem selecta o funcie virtual redefinit n clasa derivat, folosind un pointer din clasa de baz legat la un obiect din clasa derivat: class B { public: virtual void g(); };

// functie virtuala

class D : public B { public: void g(); // redefinita }; void main(){ B* pb = new B; D* pd = new D; B* pb1 = pd; // ncercm acces la clasa D cu pb1 pb->f(); // se acceseaza f() din B pd->f(); // se acceseaza f() din D pb1->f(); // se acceseaza f() din D !! }

Concluzia este aceea c accesul la funcia membru , declarat virtual n clasa de baz este dictat de tipul obiectului legat de pointerul prin care se apeleaz funcia (cu un pointer din clasa B putem accesa funcia din clasa D, dac pointerul indic spre un obiect din clasa derivat). Nu pot fi declarate ca funcii virtuale: constructorii, funciile membri statici, funciile friend i funciile nemembri. Atributul virtual se motenete pe parcursul derivrii: dac o funcie este declarat virtual n clasa de baz, funcia redefinit n clasa derivat pstreaz atributul virtual n mod implicit. 8. Destructori virtuali. Considerm situaia n care se elibereaz un obiect din clasa derivat, folosind un pointer din clasa de baz. class B{ public: B(); ~B(); //pentru functionare corecta se pune virtual ~B() }; class D{ public: D(); ~D(); }; void main(){ B* pb=new(D); // se creaz un obiect n D delete pb; // se sterge un obiect in B } Deoarece selecia funciei este dictat de tipul pointerului, se va terge numai o parte din obiectul creat (cea motenit din clasa de baz) i va exista memorie ocupat n mod inutil cu partea de date din clasa D. Dac se declarm destructorul virtual, selecia funciei este determinat de obiectul indicat de pointer, aadar se va terge un obiect din clasa D. 9. Clase abstracte. De obicei, funciile declarate virtuale n clasa de baz nu au funcionaliti deosebite n clasa de baz. Ele sunt prevzute n vederea redefinirii n clasele derivate. Astfel pot exista funcii, care s nu aib nici o definiie n clasa de baz numite funcii virtuale pure, pe care utilizatorul s fie obligat s le redefineasc n clasele derivate nainte de a le folosi. O funcie virtual pur are semntura: virtual tip nume(lista_parametric)=0; O clas abstract conine cel puin o funcie virtual pur. O clas abstract nu poate fi instaniat, adic nu se pot crea obiecte din acea clas, deoarece funciile virtuale pure nu sunt definite, dar se pot crea pointeri i referine la clase abstracte. O clas abstract este folosit drept suport pentru construirea altor clase prin derivare. 7

O clas derivat devine la rndul ei clas abstract, dac unele din funciile virtuale pure rmn neredefinite. Dac se redefinesc toate funciile virtuale pure, clasa derivat devine normal, i poate instania obiecte. Exemplu de folosire a unei clase abstracte: class Conversie{ protected: double in; double out; //intrarea //iesirea

public: Conversie(double i){in=i;}; //ctor double getin() const{ return in;}; //accesor double getout()const{ return out;}; virtual void conv()=0; //fctie virtuala pura }; class FahrCels : public Conversie{ public: FahrCels(double i) : Conversie(i){}; void conv(){ out = (in 32) / 1.8;}; }; class InchCm : public Conversie{ public: InchCm(double i) : Conversie(i){}; void conv(){ out = 2.54*in;}; }; void main(){ Conversie* pb; double val; char tip; cin >> val >> tip; switch(tip){ case i: pb=new InchCm(val); break; case f: pb=new FahrCels(val); break; }; if(pb){ pb->conv(); cout << pb->getin() << << pb->getout() << endl; delete pb; } } 10. Polimorfism. Polimorfismul reprezint posibilitatea de a apela o aceeai funcie cu argumente obiecte din clase diferite. Polimorfismul de motenire reprezint apelarea unei funcii din clasa derivat folosind un pointer din clasa de baz. Legarea dinamic determin funcia apelat pe baza obiectului la care se refer pointerul din clasa de baz n momentul n care se face apelul.

Fiecare obiect care conine o funcie virtual posed un tabel de adrese de funcii virtuale declarate n clas. La apelul unei funcii virtuale, printr-un pointer sistemul la execuie : determin adresa tabelului de funcii virtuale al clasei obiectului determin adresa funciei corespunztoare apeleaz funcia de la aceast adres Polimorfismul de motenire, introdus prin virtualitate este un polimorfism la nivel de execuie, obinut prin legarea ntrziat (legare dinamic) a adresei funciei apelate. Programele rezultate sunt simple i flexibile, dar au timpi de execuie mai mari. Legarea timpurie (la compilare) se refer la apelare de funcii cu adrese de apel cunoscute: funcii membre nevirtuale, funcii friend, funcii i operatori suprancrcai, etc. Apelurile rezolvate la compilare au o eficien ridicat la execuie. Considerm derivarea Animal class Animal { char numeA[20]; public: Animal(char na[]){ strcpy(numeA, na); }; virtual void Identif(){ cout << Animalul: << numeA << endl; }; }; class Felina : public Animal { char numeF[20]; public: Felina(char nf[], char na[]) : Animal(na) { strcpy(numeF, nf); }; void Identif(){ Animal::Identif(); cout << Specia: << numeF << endl; }; }; class Pisica : public Felina { char numeP[20]; public: Pisica(char np[],char nf[],char na[]) : Felina(nf,na){ Strcpy(numeP, np); }; void Identif(){ Felina::Identif(); Cout << subspecia: << numeP << endl; }; }; Felina Pisica i clasele:

#include <iostream.h> #include <string.h> void main(){ Animal A(reptile), *pA; Felina F(tigru,mamifer); Pisica P(birmaneza, domestica, carnivora); P.Identif(); //legare statica, apel Animal() pa = &A; pa->Identif(); //legare dinamica, apel Animal() pa = &F; pa->Identif(); //legare dinamica, apel Felina() pa = &P; pa->Identif(); //legare dinamica, apel Pisica()` } 11. Probleme. 1.Considerm clasa Forma cu derivarea Forma class Forma{ protected: double x,y; public: Forma(double h=0, double v=0); virtual double Arie()const=0; virtual double Perimetru()const=0; }; class Dreptunghi : public Forma{ public: Dreptunghi(double h=0, double v=0); virtual double Arie()const; virtual double Perimetru()const; }; class Cerc : public Forma{ protected: double raza; public: Cerc(double h=0, double v=0, double r=0); virtual double Arie()const; virtual double Perimetru()const; }; Definii funciile din cele 3 clase. 2. Considerm derivarea Dreptunghi Paralelipiped. Implementai constructori pentru clasele respective i funciile Arie() i Volum(), pentru Dreptunghi se ia volumul 0. Dreptunghi Cerc

10

3. Se consider ierarhia de clase: trapez paralelogram

dreptunghi ptrat romb Toate figurile au dou laturi paralele cu axa Ox. Datele membri sunt constituite din coordonatele celor 4 vrfuri. Funciile membri conin n afara constructorilor, funcii pentru calculul ariei i perimetrului. O dat membru - valid , are valoarea 1 dac figura respectiv este specificat corect. Constructorii verific paralelismul laturilor. Definii i implementai ierarhia de clase. 4. Modificai programul de mai sus, considernd c ptrat are motenire multipl,de la dreptunghi i romb. 5. Considerm derivarea: Functionar date: nume cnp operatii: Functionar() Afisare() Permanent date: salariu operatii: Permanent() Afisare() Temporar date: plataorara nrorelucrate operatii: Temporar() Afisare()

Funcia Afisare() din clasa de baz tiprete nume i cnp, iar n clasele derivate se tiprete n plus salariul. Definii i implementai aceste clase. 6. Definii i implementai ierarhia de clase: triunghi dreptunghic isoscel

dreptunghic-isoscel echilateral

11

Evaluare