Document, comentariu, eseu, bacalaureat, liceu si facultate
Top documenteAdmitereTesteUtileContact
      
    


 


Ultimele referate adaugate

Adauga referat - poti sa ne ajuti cu un referat?

Politica de confidentialitate



Ultimele referate descarcare de pe site
  CREDITUL IPOTECAR PENTRU INVESTITII IMOBILIARE (economie)
  Comertul cu amanuntul (economie)
  IDENTIFICAREA CRIMINALISTICA (drept)
  Mecanismul motor, Biela, organe mobile proiect (diverse)
  O scrisoare pierduta (romana)
  O scrisoare pierduta (romana)
  Ion DRUTA (romana)
  COMPORTAMENT PROSOCIAL-COMPORTAMENT ANTISOCIAL (psihologie)
  COMPORTAMENT PROSOCIAL-COMPORTAMENT ANTISOCIAL (psihologie)
  Starea civila (geografie)
 

Ultimele referate cautate in site
   domnisoara hus
   legume
    istoria unui galban
   metanol
   recapitulare
   profitul
   caract
   comentariu liric
   radiolocatia
   praslea cel voinic si merele da aur
 
despre:
 
Clase derivate. Mosteniri C++
Colt dreapta
Vizite: ? Nota: ? Ce reprezinta? Intrebari si raspunsuri
 

d7z3zi
Clasele derivate dau posibilitatea de a se defini intr-un mod simplu, eficient si flexibil clase noi prin adaugarea unor functionalitati claselor deja existente, fara sa fie necesara reprogramarea sau recompilarea acestora. Existenta claselor derivate permite exprimarea relatiilor ierarhice intre conceptele pe care clasele le reprezinta si asigura o interfata comuna pentru mai multe clase diferite.
De exemplu, entitatile cerc, triunghi, dreptunghi, sunt corelate intre ele prin aceea ca toate sunt forme geometrice, deci ele au in comun conceptul de forma geometrica. Pentru a reprezenta un cerc, un triunghi sau un dreptunghi, intr-un program, trebuie ca aceste clase, care reprezinta fiecare forma geometrica in parte, sa aiba in comun clasa care reprezinta in general o forma geometrica. O clasa care asigura proprietati comune mai multor altor clase se defineste ca o clasa de baza. Clasele derivate mostenesc de la o clasa de baza toate caracteristicile acesteia, la care adauga alte caracteristici noi, specifice ei.

5.1 Clase derivate

Se considera un program care descrie organizarea personalului unei institutii fara folosirea claselor derivate. O clasa numita Angajat detine date si functii referitoare la un angajat al institutiei:

class AngajatA char * nume; float salariu; public:
Angajat();
Angajat(char *n, int s, float sal);
Angajat(Angajat& r); void display();
S;
Angajat::display()A cout << nume << “ ” << salariu << endl;
S

Diferite categorii de angajati necesita date suplimentare fata de cele definite in clasa Angajat, corespunzatoare postului pe care il detin. De exemplu, un sef de sectie (administator) este un angajat (deci sunt necesare toate datele care descriu aceasta calitate) dar mai sunt necesare si alte informatii, de exemplu precizare sectiei pe care o conduce. De aceea, clasa Administator trebuie sa includa un obiect de tipul Angajat , la care se adauga alte date:




class AdministratorA
Angajat ang; int sectie; public: void display();
S;

Posibilitatea de a include intr-o clasa date descrise intr-o alta clasa are in limbajele obiect-orientate un suport mai eficient, mai flexibil si mai simplu de utilizat decat simpla includere a unui obiect din tipul dorit: derivarea claselor.
Un administrator este un angajat, de aceea clasa Administrator se poate construi prin derivare din clasa Angajat astfel:

class Administrator : public AngajatA int sectie; public: void display();
S

Aceasta operatie care se poate reprezenta schematic prin indicarea unei derivari cu o sageata (arc directionat) de la clasa a derivata la clasa de baza.

clasa de baza Angajat

clasa derivata Administrator

In general, derivarea unei clase se specifica in felul urmator:

class nume_derivata : specificator_acces nume_baza A
// corpul clasei
S;

Specificatorul de acces poate fi unul din cuvintele-cheie: public, private, protected. Cand o clasa este derivata dintr-o clasa de baza, clasa derivata mosteneste toti membrii clasei de baza (cu exceptia unora: constructori, destructor si functia operator de asignare).
Tipul de acces din interiorul clasei derivate la membrii clasei de baza este dat de specificatorul de acces. Daca nu este indicat, specificatorul de acces este implicit private
Cand specificatorul de acces este public, toti membrii de tip public ai clasei de baza devin membri de tip public ai clasei derivate; toti membrii protected ai clasei de baza devin membri protected ai clasei derivate. Membrii private ai clasei de baza raman private in clasa de baza si nu sunt accesibili membrilor clasei derivate. Aceasta restrictie de acces ar putea pare surprinzatoare, dar, daca s-ar permite accesul dintr-o clasa derivata la membrii private ai clasei de baza, notiunile de incapsulare si ascundere a datelor nu ar mai avea nici o semnificatie. Intr-adevar, prin simpla derivare a unei clase, ar putea fi accesati toti membrii clasei respective.
Metoda cea mai adecvata de acces la membrii private clasei de baza din clasa derivata este prin utilizarea functiilor membre publice ai clasei de baza.
De exemplu, nu se poate implementa functia display() din clasa Administrator prin accesarea membrilor private ai clasei Angajat:

void Administrator::display()A cout << nume << “ ”<< salariu << endl; // error cout << sectie << endl;
S

In schimb, se poate folosi functia membra publica display() a clasei Angajat:

void Administrator::display()A
Angajat::display(); cout << sectie << endl;
S

Redefinirea functiei display() in clasa derivata ascunde functia cu acelasi nume din clasa de baza, de aceea este necesara calificarea functiei cu numele clasei de baza folosind operatorul de rezolutie: Angajat::display().
Trebuie facuta precizarea ca functia display() din clasa derivata nu supraincarca ci redefineste (redefines) functia cu acelasi nume din clasa de baza.
Un membru al unei clasei derivate care are acelasi tip si nume cu un membru al clasei de baza il redefineste pe cel din clasa de baza.
Din clasa de baza (in functii membre ale acesteia si pentru obiecte din clasa de baza) este accesat membrul din clasa de baza.
Din clasa derivata (in functii membre ale acesteia sau pentru obiecte din clasa derivata) este accesat membrul redefinit in clasa derivata. Se spune ca membrul din clasa de baza este ascuns (hidden) de membrul redefinit in clasa derivata. Un membru ascuns din clasa de baza poate fi totusi accesat daca se foloseste operatorul de rezolutie (::) pentru clasa de baza. De exemplu:

class baza A public: int a, b;
S;

class derivata A public: int b, c; // b este redefinit
S; void fb() A derivata d; d.a = 1; // a din baza d.baza::b = 2; // b din baza d.b = 3; // b din derivata d.c = 4; // c din derivata
S

Este posibila mostenirea din mai multe clase de baza. De exemplu:

class X : public Y, public Z, ….public W A
// corpul clasei
S;

Evident, specificatorii de acces pot sa difere (pot fi oricare din public, private, protected). Mostenirea multipla va fi reluata intr-o subsectiune urmatoare.

O clasa de baza se numeste baza directa, daca ea este mentionata in lista de clase de baza. O clasa de baza se numeste baza indirecta daca nu este baza directa dar este clasa de baza pentru una din clasele mentionate in lista claselor de baza. Intr-o clasa derivata se mostenesc atat membrii bazelor directe cat si membrii bazelor indirecte si, de asemenea, se pastreaza mecanismul de redefinire si ascundere a membrilor redefiniti. De exemplu:

class A A public: void f();S; class B : public A A S; class C : public B Apublic: void f(); S; void f() A
C::f(); // apel f() din clasa C
A::f(); // apel f() din clasa A
B::f(); // apel f() din clasa A,
// deoarece nu este redefinita in B
S

Prin mostenire multipla si indirecta se pot crea ierarhii de clase care se reprezinta prin grafuri aciclice directionate.

5.1.1 Constructori si destructori in clasele derivate

Constructorii si destructorii sunt functii membre care nu se mostenesc. La crearea unei instante a unei clase derivate (obiect) se apeleaza implicit mai intai constructorii claselor de baza si apoi constructorul clasei derivate. Ordinea in care sunt apelati constructorii claselor de baza este cea din lista claselor de baza din declaratia clasei derivate. Constructorii nu se pot redefini pentru ca ei, in mod obligatoriu, au nume diferite (numele clasei respective).
La distrugerea unui obiect al unei clase derivate se apeleaza implicit destructorii in ordine inversa: mai intai destructorul clasei derivate, apoi constructorii claselor de baza, in ordinea inversa celei din lista din declaratie.
La instantierea unui obiect al unei clase derivate, dintre argumentele care se transmit constructorului acesteia o parte sunt utilizate pentru initializarea datelor membre ale clasei derivate, iar alta parte sunt transmise constructorilor claselor de baza. Argumentele necesare pentru initializarea claselor de baza sunt plasate in definitia (nu in declaratia) constructorului clasei derivate.
Un exemplu simplu, in care constructorul clasei derivate D transfera constructorului clasei de baza B un numar de k argumente arata astfel:

class BA
//…………………. public:
B(tip1 arg1,…,tipk argk);
S; class D:public B A
//…………………. public:
D(tip1 arg1, …,tipk argk,…,tipn argn);
S;
D::D(tip1 arg1, …,tipk argk, ….…,tipn argn)
:B((tip1 arg1,……,tipk argk);
A
// initialzare date membre clasa derivata
S

Daca nici clasa de baza nici clasa derivata nu au definiti constructori, compilatorul genereaza cate un constructor implicit pentru fieccare din clase si-i apeleaza in ordinea baza, apoi derivata.
O clasa derivata trebuie sa aiba prevazut cel putin un constructor in cazul in care clasa de baza are un constructor care nu este implicit.
In exemplul urmator se completeaza cele doua clase Angajat si Administrator cu constructorii si destructorii necesari.

? Exemplul 5.1

class Angajat A char *nume; float salariu; public:
Angajat(const char *n, float sal);
Angajat(Angajat& r);
IAngajat() A cout << "Destructor Baza" << endl; delete nume;
S void display() const A cout << "Nume: " << nume
<< " Salariu: " << salariu << endl ;
S
S;
Angajat::Angajat(const char *n, float sal) A cout << "Constructor baza" << endl; int size = strlen(n); nume = new charasize+1i; strcpy(nume, n); salariu = sal;
S
Angajat(Angajat &r)A cout << "Constructor copiere baza\n"; int size = strlen(r.nume); nume = new charasize+1i; strcpy(nume, r.nume); salariu = r.salariu;
S class Administrator : public Angajat A int sectie; public:
Administrator(const char *n, float sal, int sec);
Administrator(Administrator& r);
IAdministrator() A cout << endl << "Destructor derivata"<< endl;
S void display() const;
S;
Administrator::Administrator(const char *n, float sal, int sec)
:Angajat(n, sal)
A sectie = sec; cout << "Constructor derivata\n";

S void Administrator::display() const A cout << "Sef sectie: " << sectie << endl;
Angajat::display();
S
Administrator::Administrator(Administrator& r)
:Angajat(r)
A cout << "Constructor copiere derivata\n"; sectie = r.sectie;
S void fa1()A
Angajat a1("Ionescu", 2000.0f); a1.display(); // executa Angajat::display()
Administrator m1("Popescu", 3000.0f, 1); m1.display(); // executa Administrator::display()
S

In functia fa1() sunt create doua obiecte, obiectul a1 din clasa Angajat si obiectul m1 din clasa Administrator. Doua din argumentele de apel ale constructorului obiectului m1 sunt transferate constructorului clasei de baza (argumentele const char* n si float sal).
La executia functiei fa1() sunt afisate mesajele:

Constructor baza
Nume: Ionescu Salariu: 2000
Constructor baza
Constructor derivata
Sef sectie: 1
Nume: Popescu Salariu: 3000

Destructor derivata
Destructor baza
Destructor baza

Aceste mesaje evidentiaza ordinea de apel a constructorilor si destructorilor clasei derivate, precum si selectia functiei display() redefinita in clasa derivata.

Pentru cele doua clase din Exemplul 5.1 au fost definiti si constructorii de copiere corespunzatori. Se poate remarca ca argumentul constructorului de copiere al bazei este o referinta la un obiect derivat:

Administrator::Administrator(Administrator& r)
:Angajat(r) A….S

Acest lucru este posibil deoarece conversia de la o referinta la clasa derivata Administrator poate fi convertita fara ambiguitate in referinta la clasa de baza Angajat. Fie functia:

void fa2()A
Administratoe m1(“Popescu, 3000.0f, 1);
Administrator m2(m1); m2.display();
S

Obiectul m2 este creat printr-un constructor de copiere: este apelat mai intai constructorul de copiere al clasei de baza, apoi constructorul de copiere al clasei derivate. Mesajele afisate la executia functiei fa2() sunt urmatoarele:

Constructor baza
Constructor derivata
Constructor copiere baza
Constructor copiere derivata
Sef sectie: 1
Nume: Popescu Salariu: 3000
Destructor derivata
Destructor baza
Destructor derivata
Desctructor baza

Aceasta este situatia de executie corecta, deoarece au fost definiti corect constructorii de copiere in clasa de baza si in clasa derivata.
Situatie de executie eronata apare daca nu se defineste constructorul de copiere in clasa de baza, deoarece in aceasta clasa exista date alocate dinamic. Daca se defineste corect constructorul clasei de baza Angajat, dar nu se defineste constructor de copiere in clasa derivata Administrator, executia este totusi corecta, deoarece constructorul de copiere implicit creat de compilator pentru clasa derivata Administrator apeleaza constructorul de copiere definit al clasei de baza Angajat care previne copierea membru cu membru.
Pe exemplul de mai sus se pot verifica toate aceste situatii diferite care pot sa apara in legatura cu definirea constructorilor de copiere.

In ceea ce privesc destructorii, se poate observa si din exemplele date ca nu se mostenesc destructorii claselor de baza, dar la distrugerea unui obiect derivat este apelat mai intai destructorul clasei derivate si apoi destructorul clasei de baza. Acest lucru este valabil atat in cazul in care au fost definiti destructori in baza si derivata, cat si pentru destructorii impliciti generati de compilator. Dat fiind ca destructorii pot fi declarati functii virtuale, cateva aspecte relativ la comportarea destructorilor virtuali vor fi prezentate in subsectiunea 5.4

5.2 Controlul accesului la membrii clasei de baza

Accesul la membrii clasei de baza mosteniti in clasa derivata este controlat de specificatorul de acces (public, protected, private) din declaratia clasei derivate.
O regula generala este ca, indiferent de specificatorul de acces declarat la derivare, datele de tip private in clasa de baza nu pot fi accesate dintr-o clasa derivata. O alta regula generala este ca, prin derivare, nu se modifica tipul datelor in clasa de baza.
Un membru protected intr-o clasa se comporta ca un membru private, adica poate fi accesat numai de membrii acelei clase si de functiile de tip friend ale clasei. Diferenta intre tipul private si tipul protected apare in mecanismul de derivare: un membru protected al unei clase mostenita ca public intr-o clasa derivata devine tot protected in clasa derivata, adica poate fi accesat numai de functiile membre si friend ale clasei derivate si poate fi transmis mai departe, la o noua derivare, ca tip protected.

5.2.1 Mostenirea de tip public a clasei de baza

Daca specificatorul de acces din declaratia unei clase derivate este public, atunci:
• Datele de tip public ale clasei de baza sunt mostenite ca date de tip public in clasa derivata si deci pot fi accesate din orice punct al domeniului de definitie al clasei derivate.
• Datele de tip protected in clasa de baza sunt mostenite protected in clasa derivata, deci pot fi accesate numai de functii membre si friend ale clasei derivate.

? Exemplul 5.2

In acest exemplu sunt prezentate si comentate cateva din situatiile de acces la membrii clasei de baza din clasa derivata atunci cand specificatorul de acces este public.

#include <iostream.h> class baza A int a; protected: int b; public: int c; void SetA(int x)Aa = x; cout << "SetA din baza\n";S void SetB(int y)Ab = y; cout << "SetB din baza\n";S void SetC(int z)Ac = z; cout << "SetC din baza\n";S
S; class derivata:public baza A int d; public: void SetA(int x) A a = x; // eroare, a este private

S void SetB(int y) A b = y; cout << "SetB din derivata\n";
S void SetC(int z) A c = z; cout << "SetC din derivata\n";
S
S; void fb()A derivata D;
D.a = 1; // eroare, a este private in baza
D.SetA(2); // corect se apeleaza baza::SetA()
D.b = 3; // eroare, b este protected
D.baza::SetB(5); // corect, baza::SetB este public
D.SetB(4); // corect, derivata::SetB este public
D.c = 6; // corect, c este public
D.baza::SetC(7); // corect, baza::SetC este public
D.SetC(8); // corect, derivata::SetC este public
S

Daca se comenteaza liniile de program care provoaca erori si se executa functia fb(), se obtin urmatoarele mesaje la consola:

SetA din baza
SetB din baza
SetB din derivata
SetC din baza
SetC din derivata

5.2.2 Mostenirea de tip protected a clasei de baza

Daca specificatorul de acces din declaratia clasei derivate este protected, atunci toti membrii de tip public si protected din clasa de baza devin membri protected in clasa derivata. Bineinteles, membrii de tip private in clasa de baza nu pot fi accesati din clasa derivata.
Se reiau clasele din exemplul precedent cu mostenire protected:

class derivata:protected baza A
// acelasi corp al clasei
S;

Atunci, in functia fb() sunt identificate ca erori toate apelurile de functii ale clasei de baza pentru un obiect derivat, precum si accesul la variavila c din baza:

void fb()A derivata D;
D.a = 1; // eroare, a este private in baza
D.SetA(2); // eroare, baza::SetA()este protected
D.b = 3; // eroare, b este protected
D.baza::SetB(5); // eroare, baza::SetB este prot
D.SetB(4); // corect, derivata::SetB este public
D.c = 6; // eroare, c este protected
D.baza::SetC(7); // eroare, baza::SetC este prot
D.SetC(8); // corect, derivata::SetC este public
S
Astfel ca, daca se comenteaza toate liniile din functia fb() care produc erori, la executia acesteia se afiseaza urmatoarele rezultate:

SetB din derivata
SetC din derivata

Reiese foarte pregnant, ca in mostenirea protected a unei clase de baza, nu mai pot fi accesati din afara clasei derivate nici unul dintre membrii clasei de baza.

5.2.3 Mostenirea de tip private a clasei de baza

Daca specificatorul de acces din declaratia clasei derivate este private, atunci toti membrii de tip public si protected din clasa de baza devin membri de tip private in clasa derivata si pot fi accesati numai din functii membre si friend ale clasei derivate. Din nou trebuie amintit ca membrii de tip private in clasa de baza nu pot fi accesati din clasa derivata.
Din punct de vedere al clasei derivate, mostenirea de tip private este echivalenta cu mostenirea de tip protected. Si, intr-adevar, daca modificam clasa derivata din Exemplul 5.2 astfel:

class derivata : private baza A
// acelasi corp al clasei
S;

mesajele de erori de compilare si de executie ale functiei fb() sunt aceleasi ca si in mostenirea protected.
Ceea ce diferentiaza mostenirea de tip private fata de mostenirea de tip protected este modul cum vor fi trasmisi mai departe, intr-o noua derivare, membrii clasei de baza. Toti membrii clasei de baza fiind mosteniti de tip private, o noua clasa derivata (care deci mosteneste indirect clasa de baza) nu va mai putea accesa nici unul din membrii clasei de baza.

5.2.4 Modificarea individuala a accesului la membrii clasei de baza

Se pot modifica drepturile de acces la membrii mosteniti din clasa de baza prin declararea individuala a tipuli de acces. Aceste drepturi nu pot depasi, insa, caracterul private a membrilor clasei de baza. De exemplu, se reiau astfel cele doua clase din Exemplul 5.2:

#include <iostream.h> class baza A int a; protected: int b; public: int c;
//……………….
S; class derivata:private baza A int d; public: baza::a; //eroare, a nu poate fi declarat public baza::b; // corect baza::c; // corect
//………………………
S; void fb()A derivata D;
D.a = 1; // eroare, a este private
D.b = 5; // corect, b este public
D.c = 6; // corect, c este public
D.SetB(5); // corect, derivata::SetB este public
D.SetC(8); // corect, derivata::SetC este public
S

Variabilele b si c din clasa baza au fost transformati in membri publici ai clasei derivata prin declaratiile: baza::b; baza::c; in zona de declaratii de tip public a clasei derivata.

5.3 Mostenirea multipla

Asa cum s-a aratat in subsectiunea 5.1, o clasa poate avea mai multe clase de baza directe, daca acestea sunt specificate in declaratia clasei. In exemplul urmator este prezentata clasa derivata care mosteneste doua clase de baza, baza1 si baza2.

? Exemplul 5.3

#include <iostream.h> class baza1A protected: int x; public: baza1(int i) A cout << "Constructor baza1\n"; x = i;
S
Ibaza1()A cout <<"Destructor baza1\n";
S int getx()Areturn x;S
S; class baza2A protected: int y; public: baza2(int j)A cout << "Constructor baza2\n"; y = j;
S int gety()Areturn y;S
Ibaza2()A cout <<"Destructor baza2\n";
S S; class derivata:public baza1, public baza2
A int d; public: derivata (int i, int j);
Iderivata()A cout << "Destructor derivata\n";
S
S; derivata::derivata(int i, int j): baza1(i), baza2(j)
A cout << "Constructor derivata\n";
S void fbm()A derivata D(3,4); cout << D.getx() << " "<< D.gety() << endl;
S

In functia fbm() este creat un obiect D de clasa derivata. Constructorul clasei derivata transfera cate un argument constructorilor fiecarei clase de baza, care initializeaza datele membre x si y. La executia functiei fbm() se afiseaza urmatoarele mesaje, care indica ordinea in care sunt apelati constructorii si destructorii clasei derivate si a claselor de baza.

Constructor baza 1
Constructor baza 2
Constructor derivata
3 4
Destructor derivata
Destructor baza2
Destructor baza 1

5.3.1 Clase de baza virtuale

Intr-o mostenire multipla este posibil ca o clasa sa fie mostenita indirect de mai multe ori, prin intermediul unor clase care mostenesc, fiecare in parte, clasa de baza. De exemplu:

class L A public: int x;S; class A : public L A /* */S; class B : public L A /* */S; class D : public A, public B A /* */S;

Acesta mostenire se poate reprezenta printr-un graf aciclic directionat (directed acyclic graph -; DAG) care indica relatiile dintre subobiectele unui obiect din clasa D. Din graful de reprezentare a mostenirilor, se poate observa faptul ca L este replicata in clasa D.

Un obiect din clasa D va contine membrii clasei L de doua ori, o data prin clasa A (A::L) si o data prin clasa B (B::L). Se pot reprezenta partile distincte ale unui astfel de obiect:

In aceasta situatie, acesul la un membru al clasei L (de exemplu variabila x), al unui obiect de tip D este ambiguu si deci interzis (este semnalat ca eroare la compilare):

D ob; ob.x = 2; // eroare D::x este ambiguu; poate fi in
// baza L a clasei A sau in baza L a clasei B

Se pot elimina amiguitatile si deci erorile de compilare prin calificarea variabilei cu domeniul clasei careia ii apartine:

ob.A::x = 2; // corect, x din A ob.B::x = 3; // corect, x din B

O alta solutie pentru eliminarea ambiguitatilor in mostenirile multiple este de impune crearea unei singure copii din clasa de baza in clasa derivata. Pentru aceasta este necesar ca acea clasa care ar putea produce copii multiple prin mostenire indirecta (clasa L, in exemplul de mai sus) sa fie declarata clasa de baza de tip virtual in clasele care o introduc in clasa cu mostenire multipa. De exemplu:

class L A public: int x; S; class A : virtual public L A /* */ S; class B : virtual public L A /* */ S; class D : public A, public B A /* */S;

O clasa de baza virtuala este mostenita o singura data si creaza o singura copie in clasa derivata. Graful de reprezentare a mostenirilor din aceste declaratii ilustreaza acest comportament:

O clasa poate avea atat clase de baza virtuale cat si nevirtuale, chiar de acelasi tip. De exemplu, in declaratiile:

class L A public: int x; S; class A : virtual public L A /* */ S; class B : virtual public L A /* */ S; class C : public L A /* */ S; class D : public A, public B, public C A /* */ S;

clasa D mosteneste indirect clasa L: de doua ori ca o clasa de baza virtuala prin mostenirea din clasele A si B si inca o data direct, prin mostenirea clasei C. Reprezentarea grafica a unei astfel de mosteniri este urmatoarea:

Un obiect din clasa D va contine doua copii a clase L: o singura copie prin mostenirea virtual prin intermediul claselor A si B si o alta copie prin mostenirea clasei C. Ambiguitatile care pot sa apara in astfel de situatii se rezolva prin calificarea membrilor cu numele clasei (domeniului) din care fac parte.
5.4 Functii virtuale si polimorfism

O functie virtuala este o functie care este declarata de tip virtual in clasa de baza si redefinita intr-o clasa derivata. Redefinirea unei functii virtuale intr-o clasa derivata domina (override) definitia functiei in clasa de baza. Functia declarata virtual in clasa de baza actioneaza ca o descriere generica prin care se defineste interfata comuna, iar functiile redefinite in clasele derivate precizeaza actiunile specifice fiecarei clase derivate.
Mecanismul de virtualitate asigura selectia (dominarea) functiei redefinite in clasa derivata numai la apelul functiei pentru un obiect cunoscut printr-un pointer. In apelul ca functie membra a unui obiect dat cu numele lui, functiile virtuale se comporta normal, ca functii redefinite.
Deoarece mecanismul de virtualitate se manifesta numai in cazul apelului functiilor prin intermediul pointerilor se vor preciza mai intai aspectele privind conversiile de pointeri intre clasele de baza si clasele derivate.

5.4.1 Conversia pointerilor intre clase de baza si derivate

Conversia unui pointer la o clasa derivata in pointer la o clasa de baza a acesteia este implicita, daca derivarea este de tip public si nu exista ambiguitati. Rezultatul conversiei este un pointer la subobiectul din clasa de baza al obiectului din clasa derivata. Acest lucru este posibil deoarece un obiect dintr-o clasa derivata contine cate un pointer ascuns la fiecare din subobiectele componente (care sunt din obiecte din clasele de baza).
Conversia inversa, a unui pointer la o clasa de baza in pointer la derivata nu este admisa implicit. O astfel de conversie se poate forta explicit prin operatorul de conversie cast. Rezultatul unei astfel de conversii este insa nedeterminat, de cele mai multe ori provocand erori de executie. Se vor explicita mai clar aceste aspecte prin urmatorul exemplu.

? Exemplul 5.4

#include <iostream.h> class B A /* */ S; class D:public B A /* */ S; void main()A
D d;
B* pb = &d; // corect, conversie implicita

B ob;
D* pd = &ob; // eroare la compilare, nu se
// poate converti implicit de la
// class B* la class D1*
D* pd =(D*)&ob; // se compileaza corect, dar
// rezultatul este nedeterminat
S

S-a definit o clasa de baza B si o clasa derivata D. Conversia pointerului de tip D* (la un obiect din clasa derivata) in pointer de tip B* (la subobiectul component din clasa de baza) este legala si implicita. Conversia inversa nu este admisa implicit, iar fortarea prin operatorul de conversie cast este admisa de compilator dar produce rezultate nedeterminate la executie. ?

Se mai poate adauga faptul ca si referintele pot fi convertite in mod asemanator: o referinta la o clasa poate fi convertita implicit intr-o referinta la o clasa de baza a acesteia daca derivarea este de tip public si nu exista ambiguitati. Rezultatul conversiei este o referinta la subobiectul component al obiectului de tip derivat.
Referintele nu pot fi utilizate in mecanismul de virtualitate deoarece ele trebuie sa fie intializate la conversie.

5.4.2 Functii virtuale

Atunci cand o functie normala (care nu este virtuala) este definita intr-o clasa de baza si redefinita in clasele derivate, la apelul acesteia ca functie membra a unui obiect pentru care se cunoaste un pointer, se selecteaza functia dupa tipul pointerului, indiferent de tipul obiectului al carui pointer se foloseste (obiect din clasa de baza sau obiect din clasa derivata).
Daca o functie este definita ca functie virtuala in clasa de baza si redefinita in clasele derivate, la apelul acesteia ca functie membra a unui obiect pentru care se cunoaste un pointer, se selecteaza functia dupa tipul obiectului, nu al pointerului. Sunt posibile mai multe situatii:

• Daca obiectul este din clasa de baza nu se poate folosi un pointer la o clasa derivata (Exemplul 5.4).
• Daca obiectul este de tip clasa derivata si pointerul este pointer la clasa derivata, se selecteaza functia redefinita in clasa derivata respectiva.
• Daca obiectul este de tip clasa derivata, iar pointerul folosit este un pointer la o clasa de baza a acesteia, se selecteaza functia redefinita in clasa derivata corespunzatoare tipului obiectului.

Acesta este mecanismul de virtualitate si el permite implementarea polimorfismului in clasele derivate. O functie redefinita intr-o clasa derivata domina functia virtuala corespunzatoare din clasa de baza si o inlocuieste chiar daca tipul pointerului cu care este accesata este pointer la clasa de baza.
Pentru precizarea acestui comportament al functiilor se analizeaza mai multe situatii in exemplul urmator.

? Exemplul 5.5

Se considera o clasa de baza B si doua clase derivate D1 si D2. In clasa de baza sunt definite doua functii: functia normala f()si functia virtuala g(). In fiecare din clasele derivate se redefinesc cele doua functii f() si g(). In functia main() se creaza trei obiecte: un obiect din clasa de baza B indicat prin pointerul B* pb si doua obiecte din clasele derivate D1 si D2. Fiecare dintre obiectele derivate poate fi indicat printr-un pointer la clasa derivata respectiva (D1* pd1, respectiv D2* pd2), precum si printr-un pointer la baza corespunzator (B* pb1 = pd1, respectiv B* pb1 = pd1).

Mesajele afisate la consola la apelul functiilor f() si g()pentru obiecte indicate prin pointeri de tipuri diferite evidentiaza diferenta de comportare a unei functii virtuale fata de o functie normala.

#include <iostream.h> class B A public: void f() A cout << "f() din B\n"; S virtual void g()A cout << "g() din B\n"; S
S; class D1:public B A public: void f() A cout << "f() din D1\n"; S void g() A cout << "g() din D1\n"; S
S; class D2:public B A public: void f() A cout << "f() din D2\n"; S void g() A cout << "g() din D2\n"; S
S; void fv1 A
B* pb = new B;
D1* pd1 = new D1;
D2* pd2 = new D2;
B* pb1 = pd1;
B* pb2 = pd2;

// f() este functie normala
// g() este functie virtuala

// Obiect B, pointer B* pb->f(); // f() din B pb->g(); // g() din B

// Obiecte D1, D2 , pointeri D1*, D2* pd1->f(); // f() din D1 pd2->f(); // f() din D2 pd1->g(); // g() din D1 pd2->g(); // g() din D2

// Obiecte D1, D2 , pointeri B*, B* pb1->f(); // f() din B pb2->f(); // f() din B pb1->g(); // g() din D1 pb2->g(); // g() din D2

delete pb; delete pd1; delete pd2;
S

Mesajele care se afiseaza la consola la executia functiei fv1() sunt cele inscrise ca si comentarii in program.
In primele situatii, cand pointerul este pointer la tipul obiectului, nu se manifesta nici o deosebire intre comportarea unei functii virtuale fata de comportarea unei functii normale: se selecteaza functia corespunzatoare tipului pointerului si obiectului.
Diferenta de comportare se manifesta in ultima situatie, atunci cand este apelata o functie pentru un obiect de tip clasa derivata printr-un pointer la o clasa de baza a acestuia. Pentru functia normala f() se selecteaza varianta depinzand de tipul pointerului. Pentru functia virtuala g() se selecteaza varianta in functie de tipul obiectului, chiar daca este accesat prin pointer de tip baza.
?

Polimorfismul, adica apelul unei functii dintr-o clasa derivata, este posibil numai prin utilizarea pointerilor la obiecte. Obietele insele determina univoc varianta functiei apelate, deci nu se pot selecta alte functii decat cele ale obiectului de tipul respectiv. De exemplu, pentru aceleasi clase definite ca mai sus, se considera functia fv2():

void fv2()A
B obB;
D1 obD1;
D2 obD2; obB.f(); // f() din B obB.g(); // g() din B obD1.f(); // f() din D1 obD1.g(); // g() din D1 obD2.f(); // f() din D2 obD2.g(); // g() din D2
S

Comentariile arata care dintre functiile redefinite este utilizata, si anume functia care corespunde tipilui obiectului, indiferent daca este virtuala sau nu.

In acest moment se pot preciza mai detaliat asemanarile si diferentele dintre functiile supraincarcate (overloaded), functiile redefinite (redefined) si functiile virtuale. In toate aceste situatii numele functiilor este acelasi.
In cazul functiilor supraincarcate lista de argumente trebuie sa difere astfel incat selectia unei variante a functiei sa poata fi facuta fara ambiguitate dupa tipul argumentelor de apel.
Functiile redefinite sau virtuale trebuie sa aiba aceleasi argumente de apel (acelasi protiotip).
Dintre mai multe functii (o functie definita de tip virtual in clasa de baza si redefinita in clasele derivate), selectia variantei functiei se face dupa tipul obiectului pentru care este invocata.
Dintre mai multe functii (o functie definita fara specificatorul virtual in clasa de baza si redefinita in clasele derivate), selectia variantei functiei se face dupa tipul pointerului cu care este invocata.

Nu pot fi declarate functii virtuale: functii ne-membre, constructori, functii de tip static, functii friend.

5.4.3 Mostenirea functiilor virtuale

Atributul virtual se mosteneste pe tot parcursul derivarii. Cu alte cuvinte, daca o functie este declarata de tip virtual in clasa de baza, functia redefinita intr-o clasa derivata este functie de tip virtual in mod implicit (fara sa fie nevoie de declaratie explicita virtual). Acest lucru inseamna ca, daca aceasta clasa derivata serveste pentru definirea unei noi derivari, functia va fi mostenita de asemenea de tip virtual.
Un exemplu in acest sens poate fi obtinut usor din clasele definite in Exemplul 5.5 (clasa B si clasa D1) la care se mai adauga inca o clasa derivata astfel:

class DD1:public D1A public: void f() A cout << "f() din DD1\n";S void g() A cout << "g() din DD1\n";S
S;

Mesajele care se obtin la executia functiei urmatoare sunt prezentate ca si comentarii:

void fdd()A
DD1* pdd1 = new DD1();
B* pb = pdd1;
D1* pd1 = pdd1; pb->f(); // f() din B pd1->f(); // f() din D1 pdd1->f(); // f() din DD1

pb->g(); // g() din DD1 pd1->g(); // g() din DD1 pdd1->g(); // g() din DD1
S

S-a creat un obiect de tip clasa derivata DD1 in memoria heap folosind operatorul de alocare dinamica new. Acest obiect poate fi indicat prin trei pointeri: pointerul la clasa derivata DD1 (DD1* pdd1), returnat de operatorul new, un pointer la clasa de baza B (B* pb) si un pointer la clasa de baza D1 (D1* pd1) . Ultimii doi pointeri au fost obtinut prin conversie implicita de la primul pointer.
Daca se apeleaza functiile f() si g() pentru obiectul creat, cu fiecare dintre cei trei pointeri, se poate observa ca functia g() se comporta intotdeauna ca functie virtuala, chiar daca nu a fost folosit specificatorul virtual la redefinire in clasele derivate D1 si DD1.

Pot sa apara si alte situatii in mostenirea functiilor virtuale. De exemplu, daca o functie virtuala nu este redefinita intr-o clasa derivata, atunci se foloseste functia mostenita din clasa de baza si pentru obiecte de tip clasa derivata. Pentru precizarea acestei situatii se reiau clasele B, D1 si D2 din Exemplul 5.5, modificate astfel:

#include <iostream.h> class B A public: virtual void g()A cout << "g() din B\n"; S
S; class D1:public B A public: void g() A cout << "g() din D1\n"; S
S; class D2:public B A S;

void main() A
D1* pd1 = new D1();
D2* pd2 = new D2(); pd1->g(); // g() din D1 pd2->g(); // g() din B
S

Functia virtuala g()a fost redefinita in clasa D1 si apoi a fost apelata pentru un obiect de clasa D1. In clasa D2 functia virtuala g() nu a fost redefinita, si de aceea la invocarea pentru un obiect din clasa D2 este folosita functia g() din clasa de baza.

5.4.4 Destructori virtuali

Destructorii pot fi declarati de tip virtual, si aceasta declaratie este necesara in situatia in care se dezaloca un obiect de tip clasa derivata folosind pointer la o clasa de baza a acesteia. De exemplu:

#include <iostream.h> class BA public:
B()A cout << "Constructor B\n";S
IB()Acout << "Destructor B\n";S
S; class D:public B A public:
D()A cout << "Constructor D\n";S
ID()Acout << "Destructor D\n";S
S; void main()A
B* p = new D(); // creaza un obiect D delete p; // sterge un obiect B
S

Eroarea care apare la executia acestui program este aceea ca a fost creat un obiect de clasa D si a fost sters numai un subobiect al acestuia, subobiectul din clasa de baza B. Mesajele afisate evidentiaza acest lucru:

Constructor B
Constructor D
Destructor B

Acest lucru face ca memoria libera (heap) sa ramana ocupata in mod inutil (cu partea de date a clasei D), desi se intentiona eliminarea completa din memorie a obiectului creat. Declararea destructorului din clasa de baza de tip virtual rezolva aceasta problema:

class BA public:
B()A cout << "Constructor B\n";S virtual IB()Acout << "Destructor B virtual\n";S
S;

Fara sa fie modificata clasa D sau functia main()de mai sus, la executia acesteia dupa declararea destructorului clasei B ca virtual, se obtin mesajele:

Constructor B
Constructor D
Destructor D
Destructor B virtual

care indica faptul ca, la executia instructiunii delete p a fost apelat desctructorul clasei derivate D, dupa tipul obiectului (obiectul este de clasa D) nu al pointerului folosit (p este de tip B*). Destructorul D::ID() elibereaza partea din clasa derivata a obiectului, dupa care (in mod implicit, fara sa fie nevoie ca programatorul sa specifice acest lucru), este apelat destructorul clasei de baza B care elibereaza subobiectul de baza din obiectul dat.

5.4.5 Clase abstracte

De cele mai multe ori functia declarata de tip virtual in clasa de baza nu defineste o actiune semnificativa si este neaparat necesar ca ea sa fie redefinita in fiecare din clasele derivate. Pentru ca programatorul sa fie obligat sa redefineasca o functie virtuala in toate clasele derivate in care este folosita aceasta functie, se declara functia respectiva virtuala pura. O functie virtuala pura este o functie care nu are definitie in clasa de baza, iar declaratia ei arata in felul urmator: virtual tip_returnat nume_functie(lista_arg) = 0;
O clasa care contine cel putin o functie virtuala pura se numeste clasa abstracta. Deoarece o clasa abstracta contine una sau mai multe functii pentru care nu exista definitii (functii virtuale pure), nu pot fi create instante (obiecte) din acea clasa, dar pot fi creati pointeri si referinte la astfel de clase abstracte. O clasa abstracta este folosita in general ca o clasa fundamentala, din care se construiesc alte clase prin derivare.
Orice clasa derivata dintr-o clasa abstracta este, la randul ei clasa abstracta (si deci nu se pot crea instante ale acesteia) daca nu redefinesc toate functiile virtuale pure mostenite.
Daca o clasa redefineste toate functiile virtuale pure ale claselor ei de baza, devine clasa normala (ne-abstracta) si pot fi create instante (obiecte) ale acesteia.
Exemplul urmator (5.6) evidentiaza caracteristicile si restrictiile legate de clasele abstracte si functiile virtuale pure.

? Exemplul 5.6

#include <iostream.h> class X A // clasa abstracta public: virtual void fp()=0;// functie virtuala pura
S; class Y : public X A S; // clasa abstracta class Z : public YA // clasa normala (ne-abstracta) public: void fp()Acout<< "f() din Z\n";S // redefinire fp()
S;

void main ()A
X obx; // eroare: nu se poate instantia
// clasa abstracta X
Y oby; // eroare: nu se poate instantia
// clasa abstracta Y
Z obz; // corect, clasa Z nu este abstracta
X* pxz = &obz; // corect, se pot defini pointeri
// la clasa abstracta
Y* pyz = &obz; // corect, la fel ca mai sus pxz->fp(); // corect, fp() din Z pyz->fp(); // corect, fp() din Z obz.fp(); // corect, fp() din Z
S

Din clasa abstracta X a fost derivata clasa, de asemenea abstracta, Y. Numai clasa Z redefineste functia virtuala pura fp(), deci ea poate fi instantiata.
Comentariile scrise sunt suficiente pentru a fi inteleasa comportarea in continuare a acestor clase. ?

Un exemplu mai complet care evidentiaza utilitatea functiilor virtuale in general si a functiilor virtuale pure in special este Exemplu 5.7 de mai jos.

? Exemplul 5.7

#include <iostream.h> class ConvertA protected: double x; // valoare intrare double y; // valoare iesire public:
Convert(double i)Ax = i;S double getx()Areturn x;S double gety()Areturn y;S virtual void conv() = 0;
S;
// clasa FC de conversie grade Farenheit in grade Celsius class FC: public ConvertA public:
FC(double i):Convert(i)AS void conv()Ay = (x-32)/1.8;S
S;
// clase IC de conversie inch in centimetri class IC: public ConvertA public:
IC(double i):Convert(i)AS void conv()Ay = 2.54*x;S
S; void main ()A
Convert* p = 0; // pointer la baza cout<<"Introduceti valoarea si tipul conversiei: "; double v; char ch; cin >> v >> ch; switch (ch)A case 'i': //conversie inch --> cm (clasa IC) p = new IC(v); break; case 'f': //conv. Farenheit --> Celsius (clasa FC) p = new FC(v); break;
S if (p)A p->conv(); cout <<p->getx()<< "---> "<<p->gety()<< endl; delete p;
S
S

Acest exemplu este un mic program de conversie a unor date dintr-o valoare de intrare intr-o valoare de iesire; de exemplu, din grade Farenheit in grade Celsius, din inch in centimetri, etc.
Clasa de baza absrtracta Convert este folosita pentru crearea prin derivare a cate unei clase specifice fiecarui tip de conversie de date dorit. Aceasta clasa defineste datele comune, necesare oricarui tip de conversie preconizat, de la o valoare de intrare x la o valoare de iesire y. Functia de conversie conv() nu se poate defini in clasa de baza, ea fiind specifica fiecarui tip de conversie in parte; de aceea functia conv() se declara functie virtuala pura si trebuie sa fie redefinita in fiecare clasa derivata.
In functia main() se executa o conversie a unei valori introduse de la consola, folosind un tip de conversie (o clasa derivata) care se selecteaza pe baza unui caracter introdus la consola.
Acesta este un exemplu in care este destul de pregnanta necesitatea functiilor virtuale: deoarece nu se cunoaste in momentul compilarii tipul de conversie care se va efectua, se foloseste un pointer la clasa de baza pentru orice operatie (crearea unui obiect de conversie nou, apelul functiei conv(), afisarea rezultatelor, distrugerea obiectului la terminarea programului). Singura diferentiere care permite selectia corecta a functiilor, este tipul obiectului creat, care depinde de tipul conversiei cerute la consola.
Se propune ca exercitiu implementarea acestui program de conversie fara folosirea functiilor virtuale.

5.4.6 Polimorfismul

Dupa cum s-a mentionat, una din caracteristicile importante ale programarii orientate pe obiecte este aceea ca permite definirea unei interfete comune pentru mai multe metode specifice diferitelor functionalitati. Aceasta comportare, care asigura simplificarea si organizarea sistemelor de progrme complexe, este cunosuta sub numele de polimorfism. Polimorfismul introdus prin mecanismul de virtualitate este polimorfism la nivel de executie, care permite legarea tarzie (late binding) intre evenimentele din program, in contrast cu legarea timpurie (early binding), proprie apelurilor functiilor normale (nevirtuale).

Intercorelarea (legarea) timpurie se refera la evenimentele care se desfasoarai in timpul compilarii si anume apeluri de functii pentru care sunt cunoscute adresele de apel: functii normale, functii supraincarcate, operatori supraincarcati, functii membre neviruale, functii friend. Apelurile rezolvate in timpul compilarii beneficiaza de o eficienta ridicata.
Termenul de legare tarzie se refera la evenimente din timpul executiei. In astfel de apeluri, adresa functiei care urmeaza sa fie apelata nu este cunoscuta decat in momentul executiei programului. Functiile virtuale sunt evenimente cu legare tarzie: accesul la astfel de functii se face prin pointer la clasa de baza, iar apelarea efectiva a functiei este determinata in timpul executiei de tipul obiectului indicat, pentru care este cunoscut pointerul la clasa sa de baza.
Avantajul principal al legarii tarzii (permisa de polimorfismul asigurat de functiile virtuale) il constitue simplitatea si flexibilitatea programelor rezultate. Bineinteles, exista si dezavantaje, cel mai important fiind timpul suplimentar necesar selectiei functiei apelate efectiv din timpul executiei, ceea ce conduce la un timp de executie mai mare al programelor. Cu alte cuvinte, folosirea functiilor virtuale trebuie rezervata numai pentru situatii in care sunt in mod real necesare, atunci cand beneficiul adus de polimorfism depaseste costul suplimentar de executie.

5.5 Implementarea colectiilor de date prin clase derivate

Asa cum s-a prezentat in sectiunea 3, o colectie de date se caracterizeaza prin modelul de date pe care il reprezinta, care da forma colectiei (de exemplu, lista, arbore, multime) si prin tipul datelor (obiecte ale colectiei), care pot fi de tip predefinit sau definit de utilizator. Definirea individuala a claselor de reprezentare a unei colectii de o anumita forma pentru fiecare tip de obiecte conduce la un numar imens de reprezentari, toate avand aceeasi organizare si functionalitate si diferind doar prin tipul de date la care se refera. S-a putut remarca acest lucru din definirea repetata a claselor pentru reprezentarea tablourilor de diferite tipuri (tablouri de numere intregi, tablouri de siruri de caractere, etc) sau a listelor inlantuite. Implementarea mai generala a unor clase de colectii prin folosirea pointerilor generici ca informatii in tablouri, liste sau arbori implica numeroase restrictii in definirea si utilizarea acestora si in general sunt considerate nesigure ca tip, datorita conversiilor explicite (cast) a tipurilor de pointeri, si deci necontrolate de catre compilator.
O modalitate de a implementa clase de colectii “sigure ca tip” (type-safe) pentru diferite tipuri de date definite de utilizator (clase) este de defini o clasa de baza din care se deriveaza toate clasele pentru care urmeaza sa fie definite colectii de obiecte. Forma colectiei (lista, vector, etc.) se defineste pentru clasa de baza, iar pentru o colectie de aceeasi forma pentru un tip specific de date se poate folosi direct colectia pentru clasa de baza sau se poate deriva o clasa de colectie proprie prin redefinirea unui numar redus de functii. Se poate intelege mai usor acest mecanism pe un exemplu de implementare a tablourilor de obiecte de tipuri definite de utilizator.

5.1.2 Implementarea unui tablou de obiecte

Pentru imnplementarea unui tablou de obiecte de diferite tipuri definite de utilizator se defineste clasa Object ca o clasa de baza pentru toate tipurile derivate:

class ObjectA public:
Object() AS;
IObject() AS;
S;

Clasa ObArray, derivata din clasa Object, defineste un vector de pointeri de tip Object* . Nu este necesar sa fie limitata dimensiunea vectorului deoarece se asigura cresterea dimensiunii acestuia atunci cand este necesar.

class ObArray : public Object A
Object **p; // vector de pointeri int size; // numar de elemente int grows; // increment de crestere dimens. int dimens; // dimens. vector public:
ObArray() A size = 0; grows = 4; dimens = grows; p=(Object**)new BYTEagrows*sizeof(Object*)i;
S void RemoveAll(); int GetSize() A return size; S int Add(Object* x); int InsertAt(int i, Object *x); int RemoveAt(int i);
Object* GetAt(int i) A if(i>=0 && i<size) return paii; else return 0;
S
S;

Constructorul clasei ObArray construieste un vector de pointeri de dimensiune (size) zero.
Elemente noi (pointeri de tip Object*) se pot adauga la sfarsitul vectorului, folosind functia Add(), sau se pot insera intr-o pozitie dorita (functia InsertAt()), si de fiecare data dimensiunea vectorului creste daca este necesar:

int ObArray::Add(Object *x)A if(size<dimens) pasize++i=x; else A
Object **p1 = p; dimens = size + grows; p=(Object**)new BYTEadimens*sizeof(Object*)i; for(int i=0;i<size;i++) paii = p1aii; delete ai (BYTE *)p1; pasize++i = x;
S return size-1;
S int ObArray::InsertAt(int i, Object *x)A if(i<0 || i>size) return 0; if(i==size) return Add(x); if(size<dimens) A for(int j=size;j>i;j--) paji=paj-1i; paii=x;
S else A
Object **p1 = p; dimens = size + grows; p=(Object**)new BYTEadimens*sizeof(Object*)i; for(int j=0;j<i;j++) paji = p1aji; paii=x; for(j=i;j<size;j++) paj+1i=p1aji; delete p1;
S size++; return 1;
S

Functiile membre RemoveAt() si RemoveALL() elimina un pointer dintr-o pozitie data, fara sa stearga obiectul indicat de acesta, sau elimina intreg vectorul de pointeri, transformandu-l intr-un vector de dimensiune nula:

int ObArray::RemoveAt(int i) A if(i<0 || i>=size) return 0; for(int j=i;j<size-1;j++) paji=paj+1i; size--; return 1;
S void RemoveAll()A delete ai (BYTE *)p; p = 0; size = 0;
S

Celelate functii ale clasei sunt usor de inteles.

Pentru o colectie de obiecte de o clasa data, se foloseste clasa Object ca si clasa de baza a acesteia. De exemplu, pentru reprezentarea unui vector de puncte in plan, clasa Point (definita in sectiunea 2) se modifica astfel incat sa aiba ca si clasa de baza clasa Object:

class Point : public Object A double x,y; public:
Point(double xx, double yy) Ax=xx; y=yy;S
Point(double v)A x = v; y = v;S
IPoint() AS; friend ostream &operator<<(ostream &stream,
Point &p) A return stream<<"X= "<<p.x<<" "<<"Y= "<<p.y;
S
//………………
S;

Pentru definirea unui vector de puncte, o prima modalitate este aceea de a crea o clasa noua, clasa PointArray derivata din clasa colectiei de baza, ObArray, in felul urmator:

class PointArray : public ObArray A public:
PointArray() AS;
IPointArray();
Point *GetAt(int i)A return (Point*)ObArray::GetAt(i); S
S;
PointArray::IPointArray()A int size = GetSize(); for(int i=0;i<size;i++) A
Point *p = GetAt(i); delete p;
S
RemoveAll();
S
In clasa PointArray este necesar sa fie redefinite un numar redus de functii, majoritatea fiind folosite cele mostenite de la clasa de baza ObArray. In primul rand trebuie sa fie definit destructorul (care nu se mosteneste) si apoi toate functiile care returneaza un pointer de tipul specific (Point*), deoarece conversia de la tipul de baza la tipul derivat nu este admisa implicit ((Point*) ObArray::GetAt(i); ).
In schimb, dat fiind ca exista o conversie implicita de la pointer de tip clasa derivata (Point*) la pointer la clasa de baza (Object*), nu sunt necesare redefiniri ale functiilor de forma:

int PointArray::Add(Point *x)A return ObArray::Add((Object*)x);S

deoarece, daca nu este redefinita functia Add() in clasa derivata PointArray, atunci oricum se foloseste functia Add() din clasa de baza ObArray, iar conversia unui pointer de tipul Point* in pointer la clasa de baza (Object*) este, de asemenea implicita. Exemplu de utilizare a clasei

void fpa1()A
PointArray array; for(int i=0;i<5;i++) A
Point *p = new Point(i); array.Add(p);
S array.RemoveAt(2); array.InsertAt(3,new Point(9.0));

int size = array.GetSize(); for(i=0;i<size;i++) A
Point *p = array.GetAt(i); cout << *p <<endl;
S
S

La executia acestei functii se afiseaza urmatoarele mesaje:

X = 0 Y= 0
X = 1 Y = 1
X = 2 Y = 2
X = 9 Y = 9
X = 4 Y = 4

O a doua modalitate de a defini o colectie de forma vector pentru tipul de date Point, este aceea de a folosi direct clasa colectiei de baza, ObArray, cu specificarea de fiecare data a conersiei de tip necesare. De exemplu, daca se rescrie functia fpa1(), in care se declara : ObArray array; se obtine o eroare de compilare datorita faptului ca nu poate fi convertit implicit pointerul de tip Object* returnat de functia Add() a clasei ObArray in pointer de tipul Point*. Solutia este simpla, de a defini o conversie explicita astfel:

void fpa1()A
ObArray array; for(int i=0;i<5;i++) A
Point *p = new Point(i); array.Add(p);
S array.RemoveAt(2); array.InsertAt(3,new Point(9.0));

int size = array.GetSize(); for(i=0;i<size;i++) A
Point p = (Point*)array.GetAt(i); cout << *p <<endl;
S
S

La executia acestei functii se obtin aceleasi rezultate ca si la executia functiei precedente.
Astfel de implementari ale claselor de colectii sigure se gasesc in bibliotecile C++. De exemplu in biblioteca Microsoft Foundation Class, care este bibiloteca de clase a compilatorului Microsoft Visual C++ sunt prevazute mai multe clase de colectii sigure ca tip. Clasa CObArray reprezinta un vector de pointeri la obiecte din clasa CObject (clasa Cobject este folosita ca o clasa de baza pentru majoritatea claselor MFC). Clasa CObList reprezinta o lista dublu inlantuita de pointeri neunici la obiecte de clasa CObject. Alte variante de clase de colectii din MFC se pot gasi in manualul de referinta.
5.6 Implementarea colectiilor de date prin clase container

Implementarile colectiilor de date prezentate in aceasta sectiune, ca si in sectiunea 3, folosesc clase concrete, in sensul ca toate operatiile acestora sunt definite si ele pot fi folosite pentru instantieri. Clasele concrete (tipurile concrete -; concrete data types) permit definirea fiecarui concept care nu are suport predefinit in limbaj, dar nu exprima partile comune ale mai multor implementari ale unui anumit concept (model de date). De exemplu, o multime poate fi implementata printr-o lista sau printr-un vector, dar clasele concrete care reprezinta aceste structuri nu pot exprima conceptul de multime comun celor doua modalitati de implementare.
O metoda de a crea programe in care se pot exprima partea comuna (conceptul) mai multor implementari este de a introduce o clasa de baza abstracta care reprezinta interfata unei multimi de implementari a unui concept comun. O astfel de clasa abstracta folosita pentru definirea colectiilor de date se numeste clasa container.
Se considera o multime de obiecte de un tip oarecare T, pentru care se defineste o interfata printr-un iterator, folosind clasa abstracta Set:

class Set A public: virtual void insert(T*) = 0; virtual void remove(T*) = 0; virtual int lookup(T*) = 0;
// iterator virtual T* first()= 0; virtual T* next() = 0; virtual ISet() AS;
S;

Absenta constructorului si prezenta destructorului virtual este tipica in clasele abstracte. Toate functiile membre (cu exceptia destructorului) sunt functii virtuale pure. Fie clasele TList, respectiv TArray, clasele care definesc o lista, respectiv un vector de obiecte de tip T. Folosind aceste clase se pot defini mai multe implementari ale unei multimi. De exemplu:

class TArraySet : public Set, private TArray A int index; public: void insert(T*); void remove(T*); int lookup(T*);
T* first() Aindex = 0; return next();S
T* next();

TArraySet(int size):
TArray(size), index(0) AS
S;
Clasa TArraySet foloseste tipul concret de implementare al multimii ca vector (clasa concreta TArray) ca o clasa de baza cu tip de acces private si redefineste functiile virtuale ale clasei Set, asigurandu-se astfel o interfata identica cu a oricarei alte implementari care foloseste clasa Set ca si clasa de baza.
In mod asemanator se poate defini o clasa TListSet, avand ca si clase de baza clasa abstracta Set si clasa concreta TList, care implementeaza o lista inlantuita. Clasa TListSet defineste o multime reprezentata ca lista inlantuita si, bineinteles, redefineste toate functiile virtuale ale clase Set.
Cele doua clase TListSet si TArraySet implementeaza fiecare modelul de date (conceptul) multime reprezentat ca lista, respectiv ca vector, dar avand interfata identica, asigurata prin mostenirea clasei abstracte Set, care exprima, prin interfata pe care o pune la dispozitie, acest concept de multime.
Mai mult, instante ale claselor TListSet si TArraySet (deci multimi, reprezentate insa diferit, ca lista sau ca vector) pot fi prelucrate prin aceleasi functii care folosesc pointeri la clasa de baza Set:

void fproc(Set* s) A for (T* p = s.first(); p = s.next())A
// prelucrare
S
S void fabstr()A
TArraySet set1(1000); // vector de 1000 de elemente
TListSet set2; fproc(&set1); fproc(&set2);
S

Asadar, clasele container folosite pentru implementarea colectiilor de date sunt clase abstracte care asigura definirea unui concept intr-un mod care permite coexistenta intr-un program a mai multor implementari ale acestuia.
Clasele abstracte se pot utiliza in acelasi mod pentru definirea oricarui concept, nu numai pentru concepte privitoare la colectiile de date, asa cum a fost prezentat in aceasta subsectiune.


Colt dreapta
Creeaza cont
Comentarii:

Nu ai gasit ce cautai? Crezi ca ceva ne lipseste? Lasa-ti comentariul si incercam sa te ajutam.
Esti satisfacut de calitarea acestui document, eseu, cometariu? Apreciem aprecierile voastre.

Nume (obligatoriu):

Email (obligatoriu, nu va fi publicat):

Site URL (optional):


Comentariile tale: (NO HTML)


Noteaza documentul:
In prezent fisierul este notat cu: ? (media unui numar de ? de note primite).

2345678910

 
Copyright© 2005 - 2024 | Trimite document | Harta site | Adauga in favorite
Colt dreapta