|
pe destructorul implict.Constructorul si destructorul implicit au functii
|
səhifə | 4/18 | tarix | 12.09.2018 | ölçüsü | 1,36 Mb. | | #81721 |
| pe destructorul implict.Constructorul si destructorul implicit au functii
nule si sunt creati pentru a respecta standardul compilatorului.
-19-
Daca realizati clase si obiecte valoroase,pe care doriti sa le oferiti
si altor programatori,verificati cu atentie memoria,inainte,dupa crearea
obiectului,cat si dupa eliberarea sa.Un obiect corect programat trebuie
sa nu lase "nici o urma" in memorie.Este bine sa realizati un mic pro-
gram in care sa afisati permanent continutul stream-urilor cin,cout si
cerr.Apoi apelati unitatea realizata,cosntruiti obiectele si observati
aspectul stream-urilor utilizate.Daca obiectul creaza si alte stream-uri,
verificati cu atentie fiecare stream.
Exista o literatura intreaga referitoare la constructori si destruc-
tori.Regula de aur este urmatoarea: "solutia cea mai buna este intotdea-
una cea mai simpla".
Pentru ca o anumita functie,sau chiar o intreaga clasa sa poata avea
acces la membri privati ai unei alte clase,se poate utiliza cuvantul cheie
"friend".Functiile declarate cu cuvantul cheie "friend" vor fi tratate ca
si cand ar fi fost declarate "extern".Functiile si clasele cu vizibilitate
globala pot fi declarate ca "friend" inainte de a fi definite,dar cele
care sunt membri ai altor clase nu pot fi declarate "friend" inainte de a
se declara prototipul clasei de baza (cea in care va fi membru).
EXEMPLU (vezi si cplus11.cpp )
#include
#include
class obiect1
{ friend class arie;
private: int raza;
public: obiect1(){ raza=10; };
}
class arie
{ public: int b;
arie(){ b=5; };
modifica(obiect1 x);
};
arie::modifica(obiect1 x)
{ b=x.raza; return b; };
int main()
{
obiect1 data1;
arie data2;
data2.b=data2.modifica(data1);
cout << "membrul apelat are valoarea: \n";
cout << data2.b;
getch();
}
Se observa ca variabila b a primit valoarea variabilei raza din obiectul
pentru care a fost declarata clasa "friend" chiar daca raza a fost decla-
rata cu specificatorul "private".
(Exemplu: cout << data1.b determina o eroare de compilare ).
Daca o clasa este "friend",toate functiile sale vor fi "friend".
Nu are rost sa utilizati acest mecanism decat in situatiile in care
este extrem de util,sau imperios necesar.De exemplu: daca realizati o
unitate de obiecte si doriti sa va rezervati accesul la una dintre varia-
bilele private a clasei respective (pentru depanare rapida).
-20-
Se observa ca clasa "friend" nu apeleaza direct membri privati ai clasei
prietene ci utilizeaza o functie,care are ca parametru un obiect din
tipul de data al clasei pentru care a fost declarata "friend".Acest me-
canism pare destul de complicat,dar poate fi extrem de util in anumite
situatii.Astfel,clasele declarate "extern",preluate din biblioteci DLL si
cele preluate din unitati sunt definite in alte module si nu pot fi mo-
dificate pentru a simplifica operatiile de depanare(intr-o clasa declarata
local se schimba pur si simplu specificatorul din "private" in "public" si
apoi se deruleaza programul secvential pana cand se identifica eroarea).
Pentru a putea avea acces la membri privati ai unei clase declarata extern
puteti include in definita acesteia si o clasa sau o functie declarata
"friend",care va fi utilizata exclusiv pentru depanare (De exemplu se
poate declara o clasa "friend class Debug").Daca utilizati aceasta clasa
"friend" pentru toate clasele declarate intr-o biblioteca DLL,va fi mult
mai usor sa depanati o aplicatie,fara sa fie necesar sa modificati si
fila care contine biblioteca DLL (uneori,in procesul de depanare doriti
sa urmariti succesiv valoarea unei anumite variabile private,in urma unor
operatii succesive la care participa in cursul aplicatiei).
Membrii unei clase pot fi si proceduri.In limbajul C++ o procedura este
egala cu o functie care nu returneaza nimic.Prin urmare se declara si se
utilizeaza ca si cand ar fi o functie de tip "void".
EXEMPLU: ( vezi si cplus12.cpp )
#include
#include
class obiect1
{ public : int a; float b,c;
obiect1() { a=3; b=3.141592; c=0; };
void fun1() { c=a*b; };
~obiect1() { a=0; b=0; c=0; };
};
int main()
{
obiect1 numar;
numar.fun1();
cout << "membrul apelat are valoarea: \n";
cout << numar.c;
numar.~obiect1();
getch();
}
In rest,o procedura se comporta exact la fel ca si o procedura din Pascal.
Se observa ca procedura se apeleaza direct,fara sa fie necesara o ecuatie.
Puteti utiliza cu succes o procedura,ori de cate ori doriti sa efectuati
un numar de operatii,fara sa fie necesara returnarea unei valori imediate.
Un alt gen de functie care poate fi membru al unei clase este functia
"template".Template este un mecanism utilizat pentru a genera functii si
clase care pot accepta ca parametri mai multe tipuri de date.Se spune
despre aceste functii si clase ca sunt "tipuri parametrizate" sau ca
opereaza cu parametrii de tip "generic".Prin utilizarea acestui mecanism
se pot declara functii care opereaza cu parametrii din doua sau mai multe
tipuri de data,alternativ.Aceeasi functie va putea fi utilizata si pentru
date de tip int si pentru date de tip float,double sau char.
-21-
EXEMPLU: (vezi si cplus13.cpp)
#include
#include
template
type1 min(type1 a,type2 b)
{ type1 r,b_converted;
r=a;
b_converted = (type1) b;
if (b_converted < a) r=b_converted;
return r;
};
int main()
{
int c;
double d;
c=33;
d=7.41;
cout << "numarul mai mic este: " << min(c,d);
cout << "\n numarul mai mic este: " << min(d,c);
cout << "\n numarul mai mic este: " << min(c,'A');
cout << "\n numarul mai mic este: " << min(d,'X');
getch();
}
Se observa ca functia face conversia dintre tipul1 si tipul2 de data.Ca
rezultat,cele doua tipuri de data pot fi inversate intre ele,deoarece
functia va face automat conversia astfel incat sa poata executa operatia.
Mai mult decat atat,functia accepta orice alt tip de data care poate fi
convertit la valoarea necesara pentru comparatie.
Se poate spune despre aceste functii ca isi schimba reciproc parametrii.
Atunci cand o functie "template" este apelata pentru prima data cu un
anumit tip de data,compilatorul creaza o instanta,adica o versiune a
functiei specializata pentru tipul respectiv de data.Aceasta instanta
poate fi apoi apelata ori de cate ori este necesar pentru tipul respectiv
de data.Instanta respectiva va fi utilizata chiar daca se vor utiliza
date din module diferite (dar din acelasi tip de data cu cel pentru care
s-a realizat instanta functiei).Acest mecanism se poate utiliza pentru
orice tipuri de date care accepta conversia reciproca.
Mai mult decat atat,functia "template" poate fi specializata astfel
incat sa prezinte un comportament "special" doar pentru un anumit tip de
parametru si sa prezinte un comportament "normal" pentru restul parametri-
lor.
In mod similar,se pot realiza clase "templates" sau familii de clase
care opereaza asupra unui anumit tip de data.
EXEMPLU: template class TempClass
{ public: TempClass (void);
~TempClass(void);
int MemberSet(T a,int b);
private: T Tarray[i];
int arraysize;
}
In exemplul de mai sus,clasa template TempClass utilizeaza doi parametri:
-22-
un parametru din tipul de data T,si un parametru din tipul int.Paramterul
T poate accepta orice tipuri de data,inclusiv structuri si clase iar
pentru parametrul de tip int se va putea utiliza o constanta de tip int.
De exemplu,se va putea utiliza i pentru a specifica dimensiunea unei
arii de date,care va fi utilizata pentru parametrul T.
Un alt tip de membru al unei clase poate fi o alta clasa.In acest caz
se spune ca este o clasa "intricata" ("nested class") in alta clasa.Clasa
care este membru al unei alte clase,are vizibilitate locala,doar in
interiorul clasei in care a fost declarata.Declararea unei astfel de
clase nu introduce in clasa gazda decat un tip de data,fara materializare.
La declararea unui obiect din clasa gazda nu se creaza automat si un
obiect din clasa intricata.Daca este necesar,obiectul din tipul clasei
intricate trebuie declarat individual.
EXEMPLU: (vezi si cplus14.cpp)
#include
#include
class obiect1
{ public: int a; float b,c;
obiect1(){ a=9; b=13.2345; c=0; };
float fun1() { c=a*b; return c; };
~obiect1() { a=0; b=0; c=0; }
class obiect2
{ public: int d; };
};
int main()
{
obiect1 numar;
obiect1::obiect2 data1;
data1.d=7;
cout << "membrul apelat are valoarea: \n";
cout << data1.d;
numar.~obiect1();
getch();
}
Se observa ca clasa obiect2 este inclusa in clasa obiect1.Pentru a putea
opera cu membrul d din clasa obiect2 este necesar sa se declare un obiect
din clasa obiect2,utilizand operatorul de scope (::) prin care se specifi-
ca faptul ca obiect2 este membru al clasei obiect1.
In mod obisnuit,clasele incluse in alte clase se utilizeaza doar pentru
operatii in cadrul clasei "gazda" si nu au vizibilitate in afara acestei
clase.Exemplul a fost prezentat pentru a intelege mecanismul de apelare
a membrilor,atunci cand este necesar (de exemplu in timpul unei operatii
de depanare a unei aplicatii).
Restul tipurilor de date care pot fi utilizate ca membri (uniuni,struc-
turi,enumarari,campuri de biti,etc.) nu prezinta nici un fel de particu-
laritati si se utiulizeaza corespunzator cu tipul respectiv de data.
Pentru accesul rapid la o clasa,sau la membrul unei clase se pot uti-
liza pointerii.Un pointer specific pentru membrii claselor este pointerul
"this",care pointeaza obiectul care contine functia apelata (pointeaza o
instanta a clasei respective,adica un obiect).Pointerul "this" returneaza
adresa obiectului care contine functia apelata.
-23-
CLASE DERIVATE
Asadar,o clasa este un tip de data,iar un obiect este o instanta a unei
clase,adica o variabila din acel tip.O clasa poate fi "sablon" nu numai
pentru un obiect din clasa respectiva,dar si pentru declararea si defi-
nirea unei alte clase.Procesul prin care o clasa mosteneste caractere de
la o alta clasa poarta numele de derivare.Clasa utilizata ca sablon poarta
numele de clasa de baza,iar clasa obtinuta se numeste clasa derivata.
Pentru a specifica faptul ca o clasa noua este derivata dintr-o alta
clasa se utilizeaza o expresie de genul:
Numele clasei derivate : specificator Numele clasei de baza {definitia};
Daca mostenirea va fi virtuala se poate adauga si specificatorul "virtual"
EXEMPLE: class A: virtual public class B { .... expresii };
class C: private virtual class D { ... expresii };
class E: protected class F {};
Clasa mostenitoare va avea acces la toti membrii publici si protected ai
clasei ancestoare.Specificatorul protected,se utilizeaza in interiorul
unei clase,tocmai pentru a limita accesul la datele respective numai
pentru membrii clasei si pentru clasele derivate.Deci,protected este
un specificator intermediar intre public si private: permite accesul si
din exteriorul clasei,dar numai pentru clasele drivate (mostenitoare).
Membrii privati ai clasei ancestrale nu pot fi accesati din clasa derivata
(nu au vizibilitate in afara clasei).
EXEMPLU: (vezi si cplus15.cpp)
#include
#include
class obiect1
{ public: int a; float b,c;
obiect1(){ a=5; b=3.73; c=2.72; d=12;};
~obiect1(){ a=0; b=0; c=0; d=0; };
protected: int d;
}
class obiect2: public obiect1
{ public: int x;
obiect2() { x=d; };
};
int main()
{
obiect2 numar;
cout << "membrul apelat are valoarea: \n";
cout << numar.x;
getch();
}
Se observa ca obiect2,care este derivat din obiect1,utilizeaza in cons-
tructor variabila d,mostenita de la obiect1(desi d este un membru de tip
"protected" si nu are vizibilitate in afara clasei).Obiect2 mosteneste si
toti ceilalti membri din obiect1.Puteti incerca cu : cout << numar.a,
sau cout << numar.b etc.
Acest mecanism sta la baza familiilor de clase,in care exista un cap
de serie si un numar oarecare de clase derivate succesiv,in care se
adauga progresiv functii sau membrii de date cu actiuni specifice.
-24-
In aceste familii de clase (cea mai cunoscuta este MFC=Microsoft Founda-
tion Class Library),exista o structura ierarhica de tip arbore genealogic
in care o clasa de baza este derivata de una...sau mai multe ori,pentru a
obtine grupuri de clase descendente specializate.
EXEMPLU: (vezi si cplus16.cpp)
#include
#include
class obiect1
{ public: int a; float b,c;
obiect1(){ a=5; b=3.73; c=2.72; d=12; };
~obiect1(){ a=0;b=0;c=0; };
protected: int d;
};
class obiect2: protected obiect1
{ public: int x;
obiect2() { x=d; };
};
class obiect3: private obiect2
{ public: int y;
obiect3() { y=x+d+a; }
};
int main()
{
obiect2 numar;
obiect3 numar2;
cout << "membrul apelat are valoarea: \n";
cout << numar.x;
cout << "\n valoarea lui y din obiect3 este: \n";
cout << numar2.y;
getch();
}
Se observa ca obiect2 este derivat din obiect1,iar obiect3 este derivat
din obiect2.Ca rezultat,obiect3 mosteneste toti membrii obiectelor ances-
toare,la care se adauga si membrul y specific numai pentru obiect3.
Fiind clasa derivata,poate avea acces la membri claselor "sablon" si
utilizeaza aceasta proprietate in constructorul sau,care formeaza y cu
ajutorul membrilor mosteniti.Dar,absolut toti membrii mosteniti vor avea
in obiect3 caracterul "private" deoarece,pentru procesul de mostenire s-a
utilizat specificatorul "protected" in primul caz si respectiv "private"
in cel de al doilea caz.Regula pentru specificatorul de mostenire este
urmatoarea: 1.daca specificatorul este "public",atunci membrii public si
protected din clasa ancestoare raman tot public si protected si in clasa
derivata 2.daca specificatorul este "protected" atunci membrii de tip
public si protected vor fi mosteniti cu specificatorul "protected",iar
3. daca specificatorul utilizat in ecuatie este "private" atunci toti
membri mosteniti vor avea specificatorul "private".
In exemplul de mai sus,la cea de a doua mostenire s-a utilizat speci-
ficatorul "private",astfel incat toti membrii mosteniti vor fi privati
(nu au vizibilitate in afara clasei).De exemplu: cout << numar2.x retur-
neaza o eroare de compliare (chiar daca x a fost definit ca public in
obiect2 si poate fi apelat prin cout << numar.x ).
-25-
Intelegerea mecanismului de mostenire este extrem de importanta nu
doar pentru clasele definite de catre utilizator,ci si pentru a putea
opera cu bibliotecile mari de clase,cum este de exemplu OWL (Object
Windows Library).C++ poate realiza orice interfata de tip Windows cu
ajutorul obiectelor definite in biblioteca OWL,dar pentru a apela toate
functiile,datele sau metodele acestor obiecte,trebuie sa puteti urmari
genealogic filiatia fiecarui obiect.
Clasele definite nu sunt in mod obligatoriu statice.Pentru a asigura
o cat mai mare diversitate si libertate de exprimare,clasele de obiecte
accepta si functii virtuale,adica functii care sunt doar declarate,dar
urmeaza sa fie definite doar in momentul utilizarii.
EXEMPLU: (vezi si cplus17.cpp)
#include
#include
class obiect1
{ public: int a; float b,c;
obiect1(){ a=5; b=3.73; c=2.72 ; d=12;};
~obiect1()( a=0;b=0;c=0; };
virtual void arie();
protected: int d;
}
class obiect2: public obiect1
{ public: int x;
obiect2() { x=d; };
}
void obiect1::arie()
{ b=a*c; };
int main()
{
obiect2 numar;
numar.arie();
cout << "membrul apelat are valoarea: \n";
cout << numar.b;
getch();
}
Se observa ca definitia pentru procedura "arie()" este in afara clasei
obiect1,iar clasa obiect2 mosteneste procedura gata definita.In exemplu,
definitia pentru clasa obiect1 este in acelasi modul cu definitia pentru
procedura arie(),dar este posibil ca o clasa sa fie importata dintr-o
biblioteca iar definitia functiilor virtuale sa se faca diferit,in functie
de modulul in care va fi aplicata functia.Prin acest mecansim,de la o
singura clasa de baza se pot dezvolta obiecte diferite,in functie de
necesitatile de moment.Ca rezultat,instantele clasei respective (adica
obiectele generate) pot fi diferite.Pe langa acest mecanism,clasele de-
rivate pot adauga date si functii noi,astfel incat orice obiect derivat
sa poata deveni mai specializat decat cel obtinut din clasa de baza.
O clasa care contine cel putin o functie virtuala este denumita clasa
polimorfica,deoarece poate genera obiecte diferite in functie de modul
in care va fi definita functia virtuala.Clasele pot contine mai multe
functii virtuale,caz in care vor genera un polimorfism si mai accentuat.
Pointerul spre clasa de baza are acelasi tip ca si cel spre clasa drivata.
-26-
Un tip special de functie virtuala este functia virtuala pura.O astfel
de functie se declara ca avand valoarea zero (fx()=0).O clasa care contine
cel putin si o functie virtuala pura,poarta numele de clasa abstracta.
Clasele abstracte sunt concepute special pentru a fi cap de serie,
intr-o familie de clase,adica pentru a fi mostenite.O clasa abstracta
poate fi clasa de baza pentru clasele derivate,dar nu poate fi utilizata
pentru a declara obiecte (nu se pot declara instante ale clasei abstracte)
si nici nu poate fi utilizata ca argument al unor functii.Se pot declara
pointeri spre clasa abstracta.
Pentru ca o clasa derivata dintr-o clasa abstracta,sa poata fi utili-
zata pentru a declara obiecte,este obligatoriu ca functia virtuala pura
Dostları ilə paylaş: |
|
|