Clase derivate. Moştenire.
-
Clase derivate.
-
Reutilizarea codului folosind membri obiecte ai claselor.
-
Constructori şi destructori în clasele derivate.
-
Controlul accesului la membrii clasei de bază.
-
Ierarhii de clase.
-
Clase virtuale.
-
Funcţii virtuale.
-
Destructori virtuali.
-
Clase abstracte.
-
Polimorfism.
-
Probleme.
1. Clase derivate.
Prin moştenire, atributele unei clase de bază sunt transmise unor clase derivate. Derivarea permite definirea unor clase noi, prin adăugarea unor funcţionalităţi noi unor clase deja existente, fără reprogramare sau recompilare.
Clasa derivată moşteneşte caracteristicile unei clase de bază (sau mai multor clase de bază, în cadrul moştenirii multiple), la care adaugă caracteristici noi, specifice.
Clasa derivată moşteneşte datele membri şi funcţiile membri din clasa de bază, exceptând constructorii, destructorii şi operatorul de atribuire.
Exemplu de derivare:
trapez paralelogram dreptunghi pătrat
romb
Reutilizarea codului se poate realiza în două moduri:
-
prin compunere – incluzând obiecte în cadrul altor obiecte
-
prin moştenire – creind obiecte noi din cele existente
Sintaxa specificării unei clase derivate este:
class nume_clasă_derivată : specif_acces nume_clasă_bază{
// corp clasă
};
În cazul moştenirii multiple este posibilă moştenirea din mai multe clase de bază:
class derivată : acces1 bază1, ..., accesn bazăn {
// corp clasă;
};
O bază directă este menţionată în lista claselor de bază ale clasei derivate.
Prin moştenire multiplă şi indirectă se crează ierarhii de clase, care sunt grafuri orientate aciclice (în cazul moştenirii simple avem un arbore orientat).
Exemplu de moştenire multiplă:
triunghi dreptunghic dreptunghic-isoscel
isoscel echilateral
2. Reutilizarea codului folosind membri obiecte ai claselor.
Să considerăm 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 conţine membrii deci ordinea de apel a constructorilor va fi: String şi apoi Persoana.
Argumentele se transmit constructorilor claselor membri printr-o listă de iniţializare a membrilor plasată imediat după antetul constructorului. Membrii iniţializaţi sunt separaţi între ei prin virgule şi precedaţi de :
Persoana:: Persoana(const String& n, int v)
: nume(n), varsta(v) {}; // nu mai trebuie facut nimic
Lista de iniţializare a membrilor este singurul mecanism prin care pot fi iniţializaţi membrii care nu au constructori impliciţi, membrii constanţi şi membrii referinţe, deoarece în momentul începerii execuţiei constructorului, aceştia trebuie să fie deja iniţializaţi.
Obiectul membru al clasei trebuie să apară în lista de iniţializare, 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 apelaţi în ordinea în care sunt declaraţi în clasă, nu în cea în care apar în lista de iniţializare. Destructorii sunt apelaţi în ordine inversă constructorilor.
Dacă constructorii membrilor nu au parametri, ei pot să nu apară în lista de iniţializare a membrilor.
-
Constructori şi destructori în clasele derivate.
Constructorii şi destructorii sunt funcţii membri care nu se moştenesc, întrucât aceştia posedă numele clasei.
La creerea şi iniţializarea unui obiect într-o clasă derivată se apelează implicit, mai întâi constructorii claselor de bază (în ordinea în care apar în declaraţia clasei derivate) şi apoi constructorul clasei derivate.
La distrugerea unui obiect al clasei derivate, mai întâi este apelat destructorul clasei derivate, şi apoi destructorii claselor de bază, în ordinea inversă celei în care apar în declaraţia clasei derivate.
-
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, moştenită ca public se comportă ca unul private, adică poate fi accesat numai de membrii clasei şi de funcţiile friend din clasa de bază. În plus, este accesibil şi funcţiilor membri şi funcţiilor 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 moştenire cu acces de tip protected a clasei de bază, membrii public şi protected din clasa de bază devin protected în clasa derivată.
La o moştenire 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 accesaţi.
-
Ierarhii de clase.
O clasă derivată poate fi la rândul 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 moştenirii 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ă considerăm o funcţie membru void afisare() const; în clasa de bază, pe care o vom redefini în clasele derivate, având aceeaşi semnătură:
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) { };
-
Clase virtuale.
Într-o moştenire multiplă, o clasă poate fi moştenită indirect de mai multe ori. De exemplu:
class CBInd {public: int x;};
class CBDir1 : public CBInd { . . .};
class CBDir2 : public CBInd { . . .};
class Der: public CBDir1, public CBDir2 { . . .};
CBInd CBInd Un obiect din clasa Der conţine membrii clasei CBInd
de două ori:
- prin clasa CBDir1 (CBDir1::CBInd)
CBDir1 CBDir2 - prin clasa CBDir2 (CBDir2::CBInd)
Accesul la un membru din clasa CBInd este ambiguu,
Der deci interzis.
Ambiguitatea poate fi rezolvată folosind operatorul de rezoluţie:
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 moştenire multiplă indirectă) se declară clasa de bază de tip virtual.
class CBInd {public: int x;}; CBInd
class CBDir1 : virtual public CBInd{...};
class CBDir2 : virtual public CBInd{...}; CBDir1 CBDir2
class Der: public CBDir1, public CBDir2{...};
Der
-
Funcţii virtuale.
O funcţie membru, definită în clasa de bază poate fi redefinită în clasa derivată cu aceeaşi semnătură. Cele două funcţii: cea din clasa de bază şi cea din clasa derivată, deşi au aceeaşi semnătură au funcţionalităţi diferite.
Dacă funcţia din clasa derivată are o semnătură diferită, atunci ea va fi supraîncărcată în clasa derivată, fiind moştenită şi funcţia 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ă, cât ş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 forţare a conversiei, prin cast, deşi 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
Considerăm o funcţie membru f(), definită în B şi redefinită în D.
class B {
public:
void f();
};
class D : public B {
public:
void f();
};
Putem accesa funcţiile 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; // încercăm 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 funcţia membru din clasa B sau D este dictat de tipul pointerului (cu un pointer din clasa B putem accesa numai funcţii din clasa B, chiar dacă pointerul indică spre un obiect din clasa derivată).
O funcţie virtuală este declarată cu specificatorul virtual în clasa de bază şi este redefinită în clasa derivată.
Revenind asupra exemplului precedent, constatăm că putem selecta o funcţie 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; // încercăm 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 funcţia membru , declarată virtuală în clasa de bază este dictat de tipul obiectului legat de pointerul prin care se apelează funcţia (cu un pointer din clasa B putem accesa funcţia din clasa D, dacă pointerul indică spre un obiect din clasa derivată).
Nu pot fi declarate ca funcţii virtuale: constructorii, funcţiile membri statici, funcţiile friend şi funcţiile nemembri.
Atributul virtual se moşteneşte pe parcursul derivării: dacă o funcţie este declarată virtuală în clasa de bază, funcţia redefinită în clasa derivată îşă păstrează atributul virtual în mod implicit.
-
Destructori virtuali.
Considerăm situaţia î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 selecţia funcţiei este dictată de tipul pointerului, se va şterge numai o parte din obiectul creat (cea moştenită din clasa de bază) şi va exista memorie ocupată în mod inutil cu partea de date din clasa D.
Dacă se declarăm destructorul virtual, selecţia funcţiei este determinată de obiectul indicat de pointer, aşadar se va şterge un obiect din clasa D.
-
Clase abstracte.
De obicei, funcţiile declarate virtuale în clasa de bază nu au funcţionalităţi deosebite în clasa de bază. Ele sunt prevăzute în vederea redefinirii în clasele derivate. Astfel pot exista funcţii, care să nu aibă nici o definiţie în clasa de bază numite funcţii virtuale pure, pe care utilizatorul să fie obligat să le redefinească în clasele derivate înainte de a le folosi.
O funcţie virtuală pură are semnătura:
virtual tip nume(lista_parametric)=0;
O clasă abstractă conţine cel puţin o funcţie virtuală pură. O clasă abstractă nu poate fi instanţiată, adică nu se pot crea obiecte din acea clasă, deoarece funcţiile virtuale pure nu sunt definite, dar se pot crea pointeri şi referinţe la clase abstracte.
O clasă abstractă este folosită drept suport pentru construirea altor clase prin derivare.
O clasă derivată devine la rândul ei clasă abstractă, dacă unele din funcţiile virtuale pure rămân neredefinite. Dacă se redefinesc toate funcţiile virtuale pure, clasa derivată devine normală, şi poate instanţia obiecte.
Exemplu de folosire a unei clase abstracte:
class Conversie{
protected:
double in; //intrarea
double out; //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;
}
}
-
Polimorfism.
Polimorfismul reprezintă posibilitatea de a apela o aceeaşi funcţie cu argumente – obiecte din clase diferite.
Polimorfismul de moştenire reprezintă apelarea unei funcţii din clasa derivată folosind un pointer din clasa de bază. Legarea dinamică determină funcţia apelată pe baza obiectului la care se referă pointerul din clasa de bază în momentul în care se face apelul.
Fiecare obiect care conţine o funcţie virtuală posedă un tabel de adrese de funcţii virtuale declarate în clasă. La apelul unei funcţii virtuale, printr-un pointer sistemul la execuţie :
-
determină adresa tabelului de funcţii virtuale al clasei obiectului
-
determină adresa funcţiei corespunzătoare
-
apelează funcţia de la această adresă
Polimorfismul de moştenire, introdus prin virtualitate este un polimorfism la nivel de execuţie, obţinut prin legarea întârziată (legare dinamică) a adresei funcţiei apelate. Programele rezultate sunt simple şi flexibile, dar au timpi de execuţie mai mari.
Legarea timpurie (la compilare) se referă la apelare de funcţii cu adrese de apel cunoscute: funcţii membre nevirtuale, funcţii friend, funcţii şi operatori supraîncărcaţi, etc. Apelurile rezolvate la compilare au o eficienţă ridicată la execuţie.
Considerăm derivarea Animal Felina Pisica şi clasele:
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;
};
};
#include
#include
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()`
}
-
Probleme.
1.Considerăm clasa Forma cu derivarea Forma Dreptunghi
Cerc
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;
};
Definiţi funcţiile din cele 3 clase.
2. Considerăm derivarea Dreptunghi Paralelipiped. Implementaţi constructori pentru clasele respective şi funcţiile Arie() şi Volum(), pentru Dreptunghi se ia volumul 0.
3. Se consideră ierarhia de clase:
trapez paralelogram dreptunghi pătrat
romb
Toate figurile au două laturi paralele cu axa Ox.
Datele membri sunt constituite din coordonatele celor 4 vârfuri. Funcţiile membri conţin în afara constructorilor, funcţii pentru calculul ariei şi perimetrului.
O dată membru - valid , are valoarea 1 dacă figura respectivă este specificată corect. Constructorii verifică paralelismul laturilor.
Definiţi şi implementaţi ierarhia de clase.
4. Modificaţi programul de mai sus, considerând că pătrat are moştenire multiplă,de la dreptunghi şi romb.
5. Considerăm derivarea:
Functionar
date:
nume
cnp
operatii:
Functionar()
Afisare()
Permanent Temporar
date: date:
salariu plataorara
operatii: nrorelucrate
Permanent() operatii:
Afisare() Temporar()
Afisare()
Funcţia Afisare() din clasa de bază tipăreşte nume şi cnp, iar în clasele derivate se tipăreşte în plus salariul. Definiţi şi implementaţi aceste clase.
6. Definiţi şi implementaţi ierarhia de clase:
triunghi dreptunghic dreptunghic-isoscel
isoscel echilateral
Dostları ilə paylaş: |