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 si obiecte in C++
Colt dreapta
Vizite: ? Nota: ? Ce reprezinta? Intrebari si raspunsuri
 
Un tip de date intr-un limbaj de programare este o reprezentare a unui concept. De exemplu, tipul float din C++, impreuna cu operatiile definite asupra acestuia (+, -, *, etc.) reprezinta o versiune a conceptului matematic de numere reale. Pentru alte concepte, care nu au o reprezentare directa prin tipurile predefinite ale limbajului, se pot defini noi tipuri de date care sa specifice aceste concepte. Un program care defineste tipuri de date strans corelate cu conceptele continute in aplicatie este mai concis, mai usor de inteles si de modificat. j8s5sm
O clasa este un tip de date definit de utilizator. O declarare a unei clase defineste un tip nou care reuneste date si functii. Acest tip nou poate fi folosit pentru a declara obiecte de acest tip, deci un obiect este un exemplar (o instanta) a unei clase.
Forma generala de declaratie a unei clase care nu mosteneste nici o alta clasa este urmatoarea:

class nume_clasa A date si functii membre private specificator_de_acces date si functii membre specificator_de_acces date si functii membre

……………………………………………. specificator_de_acces date si functii membre
S lista_obiecte;

Cuvantul-cheie class introduce declaratia clasei (tipului de date) cu numele nume_clasa. Daca este urmata de corpul clasei (cuprins intre acolade), aceasta declaratie este totodata o definitie. Daca declaratia contine numai cuvantul-cheie class si numele clasei, atunci aceasta este doar o declaratie de nume de clasa.
Corpul clasei contine definitii de date membre ale clasei si definitii sau declaratii (prototipuri) de functii membre ale clasei, despartite printr-unul sau mai multi specificatori de acces. Un specificator_acces poate fi unul din cuvintele-cheie din C++: public private protected
Specificatorii private si protected asigura o protectie de acces la datele sau functiile membre ale clasei respective, iar specificatorul public permite accesul la acestea si din afara clasei. Efectul unui specificator de acces dureaza pana la urmatorul specificator de acces. Implicit, daca nu se declara nici un specificator de acces, datele sau functiile membre sunt de tip private. De aceea, toate datele sau functiile declarate de la inceputul blocului clasei pana la primul specificator de acces sunt de tip private. Intr-o declaratie de clasa se poate schimba specificatorul de acces ori de cate ori se doreste: unele declaratii sunt trecute public, dupa care se poate reveni la declaratii private, etc. Diferenta intre tipurile de acces private si protected consta in modul in care sunt mostenite drepturile de acces in clase derivate si va fi detaliat in sectiunea care se ocupa cu clasele derivate (sectiunea 5).
Declararea unor obiecte de tipul definit de clasa prin lista_obiecte este optionala.




2.1 Date si functii membre ale clasei

Datele declarate intr-o clasa se numesc date membre si, de obicei, ele sunt protejate (private sau protected), dar exista si situatii in care sunt declarate public. Nu se pot declara auto, extern sau register datele membre ale unei clase.
Functiile definite intr-o clasa se numesc functii membre (sau metode ale clasei) si de obicei ele sunt de tip public, dar pot fi si protejate.
In exemplul urmator se considera definitia unui tip de date pentru reprezentarea numerelor complexe, clasa Complex.

? Exemplul 2.1

#include <iostream.h> class ComplexA double re; double im; public: void init()A re = 0; im = 0;
S void set(double x, double y)A re = x; im = y;
S void display()A cout << re <<” “ << im << endl;
S
S; void main()A
Complex c1; c1.init(); c1.display(); // afiseaza 0 0 c1.set(7.2, 9.3); c1.display(); // afiseaza 7.2 9.3
S

Clasa Complex contine doua date membre private, re si im de tip double si trei functii membre public, init(), set() si display().
In functia main() a programului de mai sus s-a declarat un obiect cu numele c1 de tipul (clasa) Complex. Pentru un obiect dintr-o clasa data se poate apela orice functie membra a clasei, folosind operatorul punct de acces la un membru (al unei structuri, uniuni sau clase); deci se poate scrie: c1.init(), acest lucru insemnand apelul functiei init() pentru obiectul c1 din clasa Complex.
?

2.1.1 Domeniul clasei. Operatorul de rezolutie

O clasa defineste un tip de date al carui nume este numele clasei, precum si un domeniu al clasei. In acelasi timp, o clasa care nu este o clasa locala sau o clasa interna altei clase (acestea sunt descrise in subsectiunea 2.8), are un domeniu de definitie (este cunoscuta in acest domeniu) care incepe de la prima pozitie dupa incheierea corpului clasei si se intinde pana la sfarsitul fisierului in care este introdusa definitia ei si al fisierelor care il includ pe acesta.
Datele si functiile membre ale clasei care nu sunt declarate public au, in mod implicit, ca domeniu de definitie, domeniul clasei respective, adica sunt cunoscute si pot fi folosite numai din functiile membre ale clasei.
Datele si functiile membre publice ale clasei au ca domeniu de definitie intreg domeniul de definitie al clasei, deci pot fi folosite in acest domeniu.
Functiile unei clase pot fi definite in interiorul clasei, asa cum sunt functiile init()set()si display() ale clasei Complex, sau pot fi declarate in interiorul clasei (prin declararea prototipului) si definite in exteriorul ei.
Pentru definirea unei functii in afara clasei (dar, bineinteles in domeniul ei de definitie) numele functiei trebuie sa fie calificat (insotit) de numele clasei respective prin operatorul de rezolutie (::). Sintaxa de definire a unei functii in exteriorul clasei este urmatoarea:

tip_returnat nume_clasa::nume_functie(lista_argumente)A
// corpul functiei
S

In domeniul de definitie al unei clase se pot crea obiecte (instante) ale clasei. Fiecare obiect contine cate o copie individuala a fiecarei variabile a clasei respective (daca nu este de tip static; acest caz va fi descris intr-o subsectiune urmatoare) si pentru fiecare obiect se poate apela orice functie membra publica a clasei.
Accesul la datele membre publice sau apelul functiilor membre publice ale unui obiect se poate face folosind un operator de selectie membru: operatorul punct (.) daca se cunoaste obiectul, sau operatorul -> daca se cunoaste pointerul la obiect.
Datele si functiile membre protejate ale clasei (private sau protected) au ca domeniu de definitie domeniul clasei respective si de aceea nu pot fi accesate decat prin functiile membre ale clasei.
Mai trebuie remarcat inca un aspect referitor la domeniul de definitie al claselor.
Domeniul in care pot fi definite obiecte de tipul unei clasei este domeniul in care este definita clasa. Inainte de definitia clasei nu se pot defini sau declara obiecte din acea clasa. Acest lucru inseamna, implicit, ca nici in corpul unei clase nu se pot declara obiecte de tipul aceleiasi clase (deoarece nu s-a completat definitia clasei).
Numele unei clase se poate declara (sau redeclara) folosind constructia: class nume_clasa;
Intr-un domeniu in care a fost declarat numele unei clase se pot declara pointeri la clasa respectiva. De asemenea, in corpul unei clasei se pot declara pointeri sau referinte la aceeasi clasa.
In exemplul urmator sunt prezentate comentat mai multe situatii referitoare la declaratii si definitii de clase, obiecte, date si functii membre ale acestora. Erorile sunt specificate chiar cu mesajul produs de compilator.

? Exemplul 2.2

#include <iostream.h>
…………………………………. void K::set(int x)A // error:'K':is not a class
// or namespace name a = x;
S class K; // declaratie nume clasa K
K* pob1; // corect, pointer la K
K pb2; // error: 'pb2' uses undefined class 'K' class KA int a; // a este membru privat al clasei
K k; //error:'k':uses'K'which is being defined
K* pobk; // corect, pointer la K public: int b; // b este membru public al clasei
K()AS
K(K& r); // corect, contine o referinta la K int get();// declaratie (prototip) functie membra void set(int x);
S;
// corect: definitii functii membre in
// domeniul de definitie al clasei int K::get()A return a;
S void K::set(int x)A a = x;
S
K pob3; // corect, clasa K este definita void fk()A
K ob4;
K ob5;
K* pob5 = &ob5; ob4.b = 5; // corect, b este membru public pob5->b = 6;

ob4.a = 2; // error:'a' : cannot access
// private member in class 'K' ob4.set(2); // corect, functie publica pob5->get();
S
?

2.1.2 Pointerul this. Functii membre de tip const

Orice functie membra apelata pentru un obiect al unei clase primeste un argument ascuns, pointerul la obiectul pentru care a fost invocata functia, numit pointerul this. Intr-o clasa X pointerul constant this este declarat implicit astfel:
X* const this;
Deoarece this este cuvant cheie, el nu poate fi declarat explicit. De asemenea, fiind declarat implicit pointer constant, el nu poate fi modificat, dar poate fi folosit explicit.
In exemplul de mai sus, functiei init() i se transmite implicit pointerul la obiectul c1, cu numele this. Prin intermediul acestui pointer functia acceseaza datele membre ale obiectului c1 (instanta a clasei Complex). Asignarile din functia init() Are = 0; im = 0;S sunt asignari ale datelor membre ale obiectului c1 accesate prin intermediul pointerului la acesta (cu numele this), primit ca argument implicit la apelul functiei. Acest lucru s-ar putea scrie mai explicit astfel:

void init()A this->re = x; this->im = y;
S

Dar, odata acest mecanism stabilit si cunoscut, nu mai este nevoie sa fie scris de fiecare data, deci nu se va intalni niciodata o astfel de utilizare a pointerului this. In schimb, pointerul this este folosit in functii membre care manevreaza pointeri.
In general, la apelul unei functii in care un argument este de tip pointer la constanta sau referinta la constanta, se interzice modificarea obiectului indicat sau referit astfel. Dar, pentru functiile membre ale claselor, folosirea unui pointer sau referinta la constanta necesita conditii suplimentare fata de folosirea acestora in functii nemembre. De exemplu:

#include <iostream.h> class UA int u; public: int get() Areturn u;S void set(int x) Au = x;S
S; void fu1(const U* pu, int i)A pu->set(i); pu->get();
S void fu2(const U& r, int i)A r.set(i); r.get();
S void main()A
U ob; fu1(&ob,2); fu2(ob,3);
S

La compilarea acestui program se obtine de patru ori urmatorul mesaj de eroare de compilare: 'get' : cannot convert 'this' pointer from 'const class U *' to 'class U *const'. Specificatorul const pentru argumentul formal de tip pointer (respectiv referinta) la clasa U ale celor doua functii fu1() si fu2(), transforma tipul pointerului this transmis implicit acestor functii membre nestatice in “pointer constant la constanta”, adica el are forma: const U* const this si se interzice accesul functiilor membre (in acest caz, functiile get() si set()) la obiectul indicat.
Dar, intentia cu care se utilizeaza argumente de tip pointer (sau referinta) la constanta in apelul functiilor este de a interzice modificarea obiectului, nu de a interzice accesul complet la acesta. Suportul oferit de limbajul C++ pentru rezolvarea acestei probleme este de a declara de tip const acele functii care au dreptul de acces la un obiect indicat prin pointer sau referinta la constanta.
Pentru clasa U, se poate declara de tip const functia get() si atunci obiectul poate fi accesat numai pentru citire atunci cand este transmis ca argument pointer (sau referinta) la constanta. Programul modificat arata astfel:

#include <iostream.h> class UA int u; public: int get() const Areturn u;S void set(int x) Au = x;S
S; void fu1(const U* ps, U* pd)A int i = ps->get(); // corect, get()constA….S
// poate accesa ob. const U* ps ps->set(i); // eroare, set() A….S nu poate
// accesa obiectul const U* ps pd->set(i); // corect, set() A…S poate
// accesa obiectul U* pd i = pd->get(); // corect get()constA….S
// poate accesa obiectul U* pd
S void main()A
U ob1, ob2; fu1(&ob1,&ob2);
S

Se poate observa ca functia membra get()const A...S poate accesa atat un obiect dat prin pointer la constanta (ps), cat si un obiect dat prin pointer normal (pd), in timp ce functia membra set() A…S nu poate accesa decat obiecte date prin pointer normal (in sensul ca nu este pointer la constanta).

2.1.3 Functii membre inline

Im programarea folosind clase, se obisnuieste sa fie definite si apelate multe functii mici (cu numar redus de instructiuni), si acest lucru poate produce un cost ridicat de executie, datorita operatiilor necesare pentru rezervarea spatiului in stiva necesar functiei, apoi pentru transferul argumentelor si returnarea unei valori. De multe ori este posibil ca aceste operatii implicate in apelul unei functii sa depaseasca timpul de executie util al functiei. Acesta problema se rezolva prin intermediul functiilor inline.
In general, o functie declarata inline se schimba la compilare cu corpul ei, si se spune ca apelul functiei se realizeaza prin expandare. In felul acesta se elimina operatiile suplimentare de apel si revenire din functie. Dezavantajul functiilor inline este acela ca produc cresterea dimensiunlor programului compilat, de aceea se recomanda a fi utilizate pentru functii de dimensiuni mici (maximum 3-4 instructiuni). In plus, mai exista si unele restructii privind functiile inline: ele nu pot fi declarate functii externe, deci nu pot fi utilizate decat in modulul de program in care au fost definite si nu sunt admise instructini ciclice (while, for, do-while). Atributul inline poate fi neglijat de compilator daca functia nu poate fi tratata astfel.
O functie membra a unei clase definita (nu doar declarata) in interiorul clasei este implicit functie inline. Acest lucru inseamna ca, de exemplu, functia init() din clasa Complex este implicit inline. O functie membra definita in afara clasei este implicit o functie normala (nu este inline). Dar si o astfel de functie poate fi declarata explicit inline . De exemplu, functia set() din clasa K:

inline void K::set(int x)A a = x;
S

Aceasta posibilitate de definire a functiilor inline in implementarea claselor face ca numeroase apeluri de functii sa nu produca un cost suplimentar, asa cum, in mod eronat, se considera uneori. In toate instructiunile programului din Exemplul 2.1 descris mai sus, apelurile de functii se executa prin expandare si deci nu produc cost suplimentar.

2.1.4 Pointeri la date si functii membre

Este posibil accesul la un membru al unei clase printr-un pointer care memoreaza adresa acelui membru. Un pointer la un membru poate fi obtinut prin aplicarea operatorului adresa & numelui acestui membru calificat cu numele clasei; de exemplu &X::m, pentru membrul m al clasei X. Se vor preciza modurile de definire si utilizare a pointerilor la membrii claselor in exemplul urmator.

? Exemplul 2.3

Se defineste o clasa W se acceseaza prin pointeri datele si functiile membre ale clasei W astfel:

#include <iostream.h> class WA int a; public: int b; void seta(int x)Aa = x;S void setb(int x)Ab = x;S int geta()Areturn a;S int getb()Areturn b;S
S; void main ()A
W ob1, ob2; ob1.setb(5); int W::*pdm; // pdm este un pointer la o data
// membra de tip int a clasei W
W* pw = &ob2; pw->setb(6); pdm = &W::a; // eroare, a este privat pdm = &W::b; // pdm indica data b a clasei W cout << ob1.*pdm << “ ” ; cout << pw->*pdm << endl; // afiseaza 5 6

void (W::*pfm)(int);// pfm este pointer la o
// functie membra a clasei W cu
// argument int si return void pfm = &W::seta; // pfm indica functia seta
(ob1.*pfm)(7); // ob1.a = 7
(pw->*pfm)(10); // ob2.a = 10 pfm = &W::setb; // pfm indica functia setb
(ob1.*pfm)(8); // ob1.b = 8
(pw->*pfm)(11); // ob2.b = 11 cout << ob1.geta() << " "; cout << ob2.getb() << endl; // afiseaza 7 8 cout << ob1.geta() << " "; cout << ob2.getb() << endl; // afiseaza 10 11
S

Prin declaratia int W::*pdm; pointerul pdm este definit ca un tip de “pointer la o data de tip intreg a clasei W”. Un pointer definit ca tip printr-o astfel de declaratie, poate indica (poate primi adresa) oricare data membra de tip intreg a clasei W care nu este protejata. Din acest moment, pointerul poate fi folosit prin operatorul de selectie membru .* pentru un obiect din clasa respectiva (ob1.*pdm), sau prin operatorul de selectie ->* pentru un pointer la un obiect din clasa respectiva (pw->*pdm).

Prin declaratia void (W::*pfm)(int); se defineste pointerul pdf ca un tip de “pointer la o functie membra a clasei W care are un argument de apel de tip int si returneaza un void”. Dupa aceasta definitie, pointerul pfm poate primi adresa oricarei functii care indeplineste conditia data (este o functie membra a clasei W, are un argument de apel de tip int si returneaza un void). De exemplu, poate primi adresa functiei seta() prin asignarea: pfm = &W::seta; Din acest moment, functia seta() poate fi apelata prin pointerul sau pfm folosind operatorul de selectie .* pentru un obiect din clasa W ((ob1.*pfm)(7); ) sau operatorul de selectie ->* pentru un pointer la un obiect din clasa W ((pw->*pfm)(10);).

2.1.5 Incapsularea datelor

Aparent, accesul la datele sau functiile membre ale unei clase s-ar putea rezolva simplu prin declararea de tip public a datelor care se doresc a fi accesate din orice punct al domeniului de definitie al clasei. Intr-adevar, urmatoarea implementare este posibila:

class ComplexA public: double re; double im;

//…………
S; void fc1()A
Complex c1; c1.re = 5.6; // nu apare eroare de compilare c1.im = 7.9;
S

Dar o astfel de implementare nu respecta principiul incapsularii datelor si este recomandat sa fie evitata.
Problema ce inseamna o clasa bine definita are mai multe aspecte. Din punct de vedere al dreptului de acces la membrii clasei, o clasa bine definita permite incapsularea (sau ascunderea informatiilor), prin care un obiect poate ascunde celor care-l folosesc “secretele” sale, adica modul de implementare, prin interzicerea accesului la datele si functiile private sau protected.
In general, un obiect (instanta a unei clase) are o stare, data de totalitatea variabilelor sale, si o comportare, reprezentata de functiile pe care le poate executa. Starea unui obiect variaza in cursul existentei acestuia si depinde de desfasurarea in timp a tuturor functiilor pe care le-a executat.
Incapsularea prevede o bariera explicita in calea accesului la starea unui obiect. Conform acestui principiu al incapsularii, asupra unui obiect se poate actiona numai prin functiile pe care acesta le pune la dispozitie in interfata si care sunt de tip public.
Incapsularea este procesul de separare a elementelor unui tip de date abstract (clasa) in doua parti: structura, data de implementarea acestuia, si comportarea sa, accesata prin interfata. Implementarea consta din definirea datelor si a functiilor membru, iar interfata consta din declaratiile functiilor membru de tip public.
Ascunderea informatiilor este conceputa in C++ pentru prevenirea accidentelor nu a fraudelor. Nici un limbaj de programare nu poate interzice unei persoane sa “vada” implementarea unei clase, dar poate interzice unei functii din program sa citeasca date la care nu are dreptul de acces. (Un sisteme de operare poate, totusi, sa interzica accesul de citire la unele fisiere, deci asccunderea sa fie reala, chiar pentru persoane, nu numai pentru functii din program.)

Revenind la modul in care se pot accesa datele membre ale unei clase, se poate remarca ca, in general, respectand principiul incapsularii, datele membre sunt declarate private sau protected si nu pot fi accesate direct (pentru citire sau scriere) din functii nemembru ale clasei care nu sunt de tip friend (sau nu apartin unei clase friend a clasei respective). Pentru citirea sau modificarea unora dintre datele membre protejate in clasa respectiva se pot prevedea functii membre de tip public, care pot fi apelate din orice punct al domeniului de definitie al clasei si fac parte din interfata clasei.
De exemplu, pentru clasa Complex, o implementare care respecta principiul incapsularii, dar, in acelasi timp permite accesul la datele private ale clasei poate arata astfel:

#include <iostream.h> class ComplexA double re; double im; public: void init()A re = 0; im = 0;
S void set(double x, double y)A re = x; im = y;
S void setre(double x)Are = x;S void setim(double y) Aim = y;S double getre()Areturn re;S double getim()Areturn im;S void display();
S; inline void Complex::display()A cout << re << “ ” << im << endl;
S void main()A
Complex c1; c1.set(7.2, 9.3); c1.display(); // afiseaza 7.2 9.3 c1.setre(1.3); c1.setim(2.8); c1.display(); // afiseaza 1.3 2.8
S

Datele membre ale clasei (re si im) sunt date de tip private, iar accesul la acestea este posibil prin intermediul functiilor membre publice set(), setre(), setim(), etc.
Intr-o astfel de implementare, in care majoritatea functiilor sunt inline (este posibil in acest caz simplu, cu functii de dimensiuni mici) nu apar decat apeluri de functii prin expandate, deci implementarea este atat eleganta cat si eficienta.

2.1.6 Date si functii membre de tip static

O data membra a unei clase poate fi declarata static in declaratia clasei. Va exista o singura copie a unei date de tip static, care nu apartine nici unuia dintre obiectele (instantele) clasei, dar este partajata de toate acestea. Declaratia unei date de tip static intr-o clasa este doar o declaratie, nu o definitie si este necesara definitia acesteia in alta parte in program (in afara clasei). Aceasta se face redeclarand variabila de tip static folosind operatorul de rezolutie pentru clasa careia ii apartine, si fara specificatorul static.
O variabila membru de tip static a unei clase exista inainte de a fi creat un obiect din clasa respectiva si este initializata cu 0. Cea mai frecventa utilizare a datelor membre statice este de a asigura accesul la o resursa comuna mai multor obiecte, deci pot inlocui variabilele globale.

? Exemplul 2.4

Se considera o clasa S care contine o variabila normala v si o variabila statica s. Data de tip static este declarata in clasa S si definita in afara acesteia, folosind operatorul de rezolutie. Se poate urmari evolutia diferita a celor doua variabile v si s prin crearea a doua obiecte x si y de tip S, si prin aplelul functiilor incs() si incv() pentru obiectul x astfel:

class SA int v; static int s; // declaratia var. statice s public:
S() A v = 0;S int gets() Areturn s;S int getv() Areturn v;S void incs() As++;S void incv() Av++;S
S; int S::s; // definitia var. statice s a clasei S

void main ()A cout << “Inainte de incrementare\n”; cout <<“x.s: “<<x.gets()<<“y.s: “<<y.gets()<< endl; cout <<“x.v: “<<x.getv()<<“y.v: “<<y.getv()<< endl; x.incs(); x.incv(); cout << “Dupa incrementare\n”; cout <<“x.s: “<<x.gets()<<“y.s: “<<y.gets()<< endl; cout <<“x.v: “<<x.getv()<<“y.v: “<<y.getv()<< endl;
S

La executia acestui program se afiseaza continutul variabilelor s si v pentru obiectele x si y. Mesajele afisate sunt urmatoarele:

Inainte de incrementare x.s: 0 y.s: 0 x.v: 0 y.v: 0
Dupa incrementare x.s: 1 y.s: 1 x.v: 1 y.v: 0

Diferenta intre comportarea unei date membre de tip static fata de o data normala este evidenta: dupa incrementarea variabilei s pentru obiectul x, ambele obiecte x si y vad aceeasi valoare a variabilei statice s.
?

Functiile membre ale unei clase pot fi, de asemenea, declarate de tip static. La fel ca si datele membre statice, o functie membra de tip static se declara in interiorul clasei si se defineste in afara acesteia, folosind operatorul de rezolutie pentru clasa respectiva. O functie membra statica are vizibilitatea limitata la fisierul in care a fost definita si este independenta de instantele (obiectele) clasei. Fiind independenta de obiectele clasei, o functie statica nu primeste pointerul this, chiar daca apelul se face pentru un obiect al clasei respective.
Fie, de exemplu clasa Task care contine o data membra statica si o functie membra statica:

class TaskA static Task *chain; // declaratie data statica public: static void schedule(int);// decl. functie statica
S;
Task *Task::chain=0; // definirea datei statice void Task::schedule(int p)A // definire func. statica
Task::chain = 0;
S void fs2()A
Task T1;
T1.schedule(4);
Task::schedule(2);
S

Apelul unei functii statice se poate face fie ca functie membra a unui obiect din clasa respectiva, asa cum apare in primul apel din functia fs2(). In aceasta situatie se foloseste doar tipul obiectului T1 pentru apel, nu obiectul in sine, si nici pointerul acestuia nu este transmis implicit (ca un pointer this) functiei schedule(). Compilatorul chiar da un mesaj de atentionare (warning): 'T1' : unreferenced local variable.
Alta restrictie referitoare la functiile membre statice este aceea ca ele nu pot avea acces decat la datele statice ale clasei si la datele si functiile globale ale programului.
Se poate remarca faptul ca specificatorul static are in C++, ca si in C, doua semnificatii: aceea de vizibilitate restrictionata la nivelul fisierului in care sunt definite si aceea de alocare statica, adica obiectele exista si-si mentin valorile lor de-a lungul executiei intregului program.

2.2 Clase, structuri si uniuni

In C++ structurile au o functionaliate foarte apropiata de cea a claselor: ele definesc tipuri de date noi, permit gruparea de date si functii, permit mostenirea. De fapt, singura diferenta intre clase si structuri in C++ este aceea ca, implicit, toti membrii unei structuri sunt de tip public.
Se poate verifica usor acest lucru, chiar pe exemplul din aceasta sectiune. Daca se inlocuieste cuvantul cheie class cu cuvantul cheie struct si se introduce specificatorul de acces private care sa modifice tipul implicit de acces la date, se obtine acelasi program, cu aceeasi functionare:

#include <iostream.h> struct ComplexA private: double re; double im; public: void init()A re = 0; im = 0;
S void set(double x, double y)A re = x; im = y;
S void Setre(double x)Are = x;S void Setim(double y) Aim = y;S double getre()Areturn re;S double getim()Areturn im;S void display();
S; inline void Complex::display()A cout << re << “ ” << im << endl;
S void main()A
Complex c1; c1.set(7.2, 9.3); c1.display(); // afiseaza 7.2 9.3
S

Se poate observa ca, spre deosebire de C, in C++ obiectele de tip structura pot fi declarate folosind doar numele structurii, fara sa mai fie nevoie sa fie precedat de cuvantul-cheie struct.
Aceasta dubla posibilitate de definire a unor tipuri noi de date (prin clase si prin structuri) provine din modul in care a evoluat limbajul C++, pornind de la C. Structurile au fost pastrate in C++ in primul rand pentru translatarea directa a programelor existente, din C in C++. Daca structurile tot trebuie sa existe in C++, atunci adaugarea trasaturilor suplimentare care sunt proprii claselor (functii membru, derivare, mostenire, etc) este o problema simplu de rezolvat la nivelul proiectarii compilatoarelor, iar structurile C++ au devenit astfel mai puternice.
In sfarsit, existenta in momentul de fata a doua cuvinte-cheie pentru definirea tipurilor noi de date, permite evoluarea libera a conceptului class, in timp ce conceptul struct poate fi pastrat in continuare pentru asigurarea compatibilitatii cu programele C deja existente.
Chiar daca se poate folosi o structura acolo unde se doreste definirea unui tip de date abstract (clasa), o practica corecta de scriere a programelor este considerata aceea in care clasele sunt utilizate pentru definirea tipurilor de date noi, iar structurile sunt utilizate atunci cand se doreste o structura de tip C.

Ca si structurile, uniunile (union) in C++ definesc tipuri noi si pot contine atat date cat si functii membru, care sunt implicit publice. In acelasi timp, o uniune C++ pastreaza toate capacitatile din C, printre care cea mai importanta este aceea ca toate datele impart aceleasi locatii de memorie.
Exista mai multe restrictii in utilizarea uniunilor in C++. In primul rand uniunile nu pot fi folosite in mecanismul de derivare a tipurilor de date, nici ca tipuri de baza, nici ca tipuri derivate si deci, nu pot avea functii membru de tip virtual (acestea sunt legate de derivare si vor fi studiate in sectiunea 5). Desi o uniune poate avea constructori, nu sunt admise date membre care au un constructor. De asemenea, nu pot fi membri variabile de tip static.

2.3 Constructori si destructori

Utilizarea unor functii membre ale unei clase, asa cum este functia set() din clasa Complex, pentru initializarea obiectelor este ineleganta si permite strecurarea unor erori de programare. Deoarece nu exista nici o constrangere din partea limbajului ca un obiect sa fie initializat (de exemplu, nu apare nici o eroare de compilare daca nu este apelata functia set() pentru un obiect din clasa Complex), programatorul poate sa uite sa apeleze functia de initializare sau sa o apeleze de mai multe ori. In cazul simplu al clasei prezentate ca exemplu pana acum, acest lucru poate produce doar erori care se evidentiaza usor. In schimb, pentru alte clase, erorile de initializare pot fi dezastruoase sau mai greu de identificat.
Din aceasta cauza, limbajul C++ prevede o modalitate eleganta si unitara pentru initializarea obiectelor de tipuri definite de utilizator, prin intermediul unor functii speciale numite functii constructor (sau, mai scurt, constructori).

2.3.1 Constructori

In exemplul urmator se defineste o clasa care descrie o stiva de numere intregi, clasa IntStack. Detalii asupra acestui tip de date se pot citi mai jos, in subsectiunea 2.7.

? Exemplul 2.5

#include <iostream.h>
#define MAX_SIZE 1000 class IntStack A int vectaMAX_SIZEi; int tos; public:
IntStack()Atos = 0;S void push (int v); int pop();
S; void IntStack::push(int v)A if (tos < MAX_SIZE) vectatos++i = v;
S int IntStack::pop()A if (tos > 0) return vecta--tosi; else return -;1;
S void fs1()A
IntStack stack; stack.push(4); stack.push(9); int x = stack.pop(); int y = stack.pop(); int z = stack.pop(); cout << x << “ ” << y << “ ” << z << endl;
//afiseaza 9 4 -1 stack.push(1); stack.push(2); x = stack.pop(); y = stack.pop(); z = stack.pop(); cout << x << “ ” << y << “ ” << z << endl;
// afiseaza 2 1 -1
S
?

In clasa IntStack este definit un vector de numere intregi de o dimensiune maxima definita in program, vectaMAX_SIZEi, in care se intoduc si se extrag numere in ordinea ultimul intrat-primul extras (last in-first out). Doua functii membre ale clasei, push() si pop() realizeaza introducerea, respectiv extragerea, unui numar intreg din obiectul de tip IntStack pentru care sunt apelate. In functia push() se previne introducerea unui nou numar daca stiva este plina. In functia pop() se returneaza o valoare corecta numai daca stiva nu este goala; daca nu exista nici un numar in stiva, nu se citeste nimic din memorie si se returneaza -; 1. Aceasta tratare a situatiei de eroare nu este suficienta, deoarece valoarea -; 1 poate fi returnata si ca data corecta. O tratare completa a situatiilor de eroare la executia functiilor membre ale unei clase este prezentata in sectiunea 8.

Variabila tos indica prima pozitie libera din stiva si ea trebuie neaparat sa fie initializata la 0 (inceputul stivei) inainte ca stiva sa poata fi folosita, altfel pot apare erori de executie impredictibile (scriere la adrese de memorie necontrolate). Initializarea s-ar putea face printr-o functia membra care sa fie apelata explicit, dar o modalitate mai buna este aceea de a folosi o functie membra speciala pentru initializare, denumita functie constructor.
Un constructor este o functie cu acelasi nume cu numele clasei, care nu returneaza nici o valoare si care initializaza datele membre ale clasei.
De exemplu, in clasa de mai sus constructorul:
IntStack()Atos=0;S initializeaza la 0 variabila tos.
Pentru aceeasi clasa pot fi definite mai multe functii constructor, ca functii supraincarcate, care pot fi selectate de catre compilator in functie de numarul si tipul argumentelor de apel, la fel ca in orice supraincarcare de functii.
Un constructor implicit pentru o clasa X este un constructor care poate fi apelat fara nici un argument. Deci un constructor implicit este fie un constructor care are lista de argumente vida, fie un constructor cu unul sau mai multe argumente, toate fiind prevazute cu valori implicite. De exemplu, X::X(int=0) este un constructor implicit, deoarece el poate fi apelat fara nici un argument, avand definita o valoare implicita a argumentului.
In general, constructorii se declara de tip public, pentru a putea fi apelati din orice punct al domeniului de definitie al clasei respective. La crearea unui obiect dintr-o clasa oarecare este apelat implicit acel constructor al clasei care prezinta cea mai buna potrivire a argumentelor. Daca nu este prevazuta nici o functie constructor, compilatorul genereaza un constructor implicit de tip public, ori de cate ori este necesar.
In Exemplul 2.6 se definesc pentru clasa Complex mai multe functii constructor: constructor implicit, cu un argument si cu doua argumente.

? Exemplul 2.6

#include <iostream.h> class ComplexA double re; double im; public:
Complex()A cout << "Constructor implicit\n";
S
Complex(double v)A
Cout << "Constructor cu 1 arg\n"); re = v; im = v;
S
Complex(double x, double y)A
Cout << "Constructor cu 2 arg\n"; re = x; im = y;
S
//…………..
S; void fc2 ()A
Complex c1;
Complex c2(5);
Complex c3(4,6);
S

La executia functiei fc2(), sunt afisate urmatoarele mesaje:

Constructor implicit
Constructor cu 1 arg
Constructor cu 2 arg

In fiecare dintre aceste situatii a fost creat un obiect de tip Complex, ca obiect local functiei fc2() (c1, c2, c3) si de fiecare data a fost apelat constructorul care are acelasi numar si tip de argumente cu cele de apel.
?

Un constructor este apelat ori de cate ori este creat un obiect dintr-o clasa care are un constructor (definit sau generat de compilator). Un obiect poate fi creat intr-unul din urmatoarele moduri:

• ca variabila globala,
• ca variabila locala,
• prin utilizarea explicita a operatorului new,
• ca obiect temporar,
• prin apelul explicit al unui constructor.

In fiecare situatie, constructorul are rolul de a crea structura de baza a obiectului (sa construiasca obiectele care reprezinta date membre nestatice, sa construiasca tabelele care se refera la derivare si mostenire, daca este cazul) si, in final, sa execute codul specificat in corpul constructorului. Un constructor generat de compilator pentru o clasa X are forma generala X::X()AS, adica nu prevede executia unui cod, ci doar asigura constructia obiectelor membre si ale tabelelor de derivare, daca este cazul.
In toate situatiile afara de ultima, constructorul este apelat in mod implicit, atunci cand se creaza obiectul. Apelul explicit al unui constructor, desi admis de limbaj, este putin utilizat.

Pe langa initializarea datelor membre, in constructori se executa, atunci cand este necesar, operatiile de alocare dinamica a unor date.
De exemplu, implementarea clasei IntStack prezentata in Exemplul 2.5 poate produce un mare consum de memorie, in mod nejustificat: orice obiect de tipul IntStack este creat cu un vector de date de dimensiunea maxima MAX_SIZE definita ca o constanta in program, chiar daca un numar mare de obiecte ar necesita dimensiuni mult mai reduse. Alocarea dinamica a spatiului strict necesar pentru vectorul de numere intregi se poate efectua la crearea obiectului, prin functiile constructor. Exemplul urmator prezinta aceasta posibilitate.

? Exemplul 2.7

Se reia implementarea tipului de date stiva de numere intregi cu alocarea dinamica a vectorului in memoria libera, folosind clasa DStack:

class DStackA int *pvect; int size; int tos; public:
DStack()A pvect = NULL; size = 0; tos = 0;
S
DStack(int s)A pvect = new intasi; size = s; tos = 0;
S void push(int x); int pop();
S; void DStack::push(int x)A if (tos < size) pvectatos++i = x;
S int DStack::pop()A if (tos>0) return pvecta--tosi; else return -1;
S void fd1()A
DStack stack1(100); stack1.push(4); stack1.push(9); int x = stack1.pop(); int y = stack1.pop(); int z = stack1.pop(); cout << x << “ ” << y << “ ” << z << endl;
//afiseaza 9 4 -1
DStack stack2(200); stack2.push(1); stack2.push(2); x = stack2.pop(); y = stack2.pop(); z = stack2.pop(); cout << x << “ ” << y << “ ” << z << endl;
// afiseaza 2 1 -1
S

La declararea unui obiect de clasa DStack, se transmite ca argument dimensiunea dorita a stivei, iar constructorul aloca spatiul necesar in memoria heap. In rest, implementarea clasei DStack este asemanatoare clasei IntStack, prezentata mai sus.
?

2.3.2 Destructori

Multe din clasele definite intr-un program necesita o operatie inversa celei efectuate de constructor, pentru stergerea completa a obiectelor atunci cand sunt distruse (eliminate din memorie). O astfel de operatie este efectuata de o functie membra a clasei, numita functie destructor. Numele destructorului unei clasei X este IX() si este o functie care nu primeste nici un argument si nu returneaza nici o valoare.
In implementarea din Exemplul 2.7 a stivei de numere intregi, la iesirea din functia fd1(), obiectele stack1 si stack2 sunt eliminate din memorie, dar vectorii corespunzatori lor, alocati dinamic in memoria heap, trebuie sa fie si ei stersi, pentru a nu ocupa in mod inutil memoria libera. Aceasta operatie se poate executa in functia destructor astfel: class DStackA
//……. public:
IDStack()A if (pvect)A delete pvect; pvect = NULL;
S
//………..
S;

Destructorii sunt apelati implicit in mai multe situatii:

• atunci cand un obiect local sau temporar iese din domeniul de definitie;
• la sfarsitul programului, pentru obiectele globale;
• la apelul operatorului delete, pentru obiectele alocate dinamic.

Apelul explicit al unui destructor este rar utilizat. Daca o clasa nu are un destructor, compilatorul genereaza un destructor implicit.

2.3.3 Constructori de copiere

Functia principala a unui constructor este aceea de a initializa datele membre ale obiectului creat, folosind pentru aceasta operatie valorile primite ca argumente. Exemple de astfel de initializari se gasesc in toti constructorii definiti pana in prezent.
O alta forma de initializare care se poate face la crearea unui obiect este prin copierea datelor unui alt obiect de acelasi tip. Aceasta operatie este posibila prin intermediul unui constructor mai special al clasei, numit constructor de copiere. Forma generala a constructorului de copiere al unei clase X este:

X::X(X& r)A
// initializare obiect folosind referinta r
S

Constructorul primeste ca argument o referinta r la un obiect din clasa X si initializaza obiectul nou creat folosind datele continute in obiectul referinta r. Pentru crearea unui obiect printr-un constructor de copiere, argumentul transmis trebuie sa fie o referinta la un obiect din aceeasi clasa.
De exemplu, pentru obiecte de tip Complex:

void fc3()A
Complex c1(4,5); // Constructor initializare
Complex c2(c1); // Constructor copiere bitwise
Complex c3 = c2; // Constructor copiere bitwise c3.display(); // afiseaza 4 5
S

La crearea primului obiect (c1) este este apelat constructorul cu doua argumente al clasei Complex. Cel de-al doilea obiect (c2) este creat prin apelul constructorului de copiere al clasei Complex, avind ca argument referinta la obiectul c1. Este posibila si declaratia (definitia) de forma Complex c3 = c2; a unui obiect prin care se apeleaza, de asemenea, constructorul de copiere.
Constructorul de copiere poate fi definit de programator; daca nu este definit un constructor de copiere al clasei, compilatorul genereaza un constructor de copiere care copiaza datele membru cu membru din obiectul referinta in obiectul nou creat. Aceasta modalitate de copiere mai este denumita copie la nivel de biti sau copie bit cu bit (bitwise copy).
Clasa Complex poate fi completata cu un constructor de copiere astfel:

class Complex A
//………….. public:
Complex(Complex &r);
S;
Complex::Complex(Complex &r)A re = r.re; im = r.im;
S

Se pune intrebarea urmatoare: de ce mai este nevoie sa fie definit un constructor de copiere daca el este oricum generat de compilator atunci cand este necesar?
Pentru obiecte din clasa Complex, functionarea este aceeasi, atat in situatia in care in clasa nu este definit un constructor de copiere, si deci el este generat de catre compilator, cat si daca acesta a fost definit in clasa. Mesajele care se afiseaza la consola daca functia fc3() este executata dupa introducerea constructorului de copiere sunt:

Constructor cu 2 arg
Constructor copiere
Constructor copiere
4 5

Cu totul alta este situatia in cazul in care un obiect contine date alocate dinamic in memoria heap. Constructorul de copiere generat implicit de compilator copiaza doar datele membre declarate in clasa (membru cu membru) si nu stie sa aloce date dinamice pentru obiectul nou creat. Folosind un astfel de constructor, se ajunge la situatia ca doua obiecte, cel nou creat si obiectul referinta, sa contina pointeri cu aceeasi valoare, deci care indica spre aceeasi zona din memoria heap. O astfel se situatie este o sursa puternica de erori de executie subtile si greu de depistat.
Exemplul urmator evidentiaza aceasta problema.

? Exemplul 2.8

Se considera clasa DStack prezentata in mai sus si o functie fd2() definita astfel:

void fd2()A
DStack stack1(100);
DStack stack2(stack1); stack1.push(11); stack1.push(12); stack2.push(21); stack2.push(22); int x = stack1.pop(); int y = stack1.pop(); cout << x << “ ” << y << endl; // afiseaza 22 21
S

Problema care apare este evidenta: deoarece prin copierea membru cu membru pointerul pvect al obiectului stack2 primeste valoarea pointerului pvect al obiectului stack1, instructiunile stack2.push(21) si stack2.push(22) scriu peste valorile introduse mai inainte in stiva stack1, astfel incat extragerile din stiva stack1 gasesc valorile introduse in stiva stack2. Diferitele combinatii de operatii pot da cele mai variate rezultate.
O alta problema apare la iesirea din functia fd2(). Pentru clasa DStack a fost definit un destructor care sterge vectorul pvect din memorie folosind operatorul delete. La distrugerea obiectului stack2 este sters din memorie vectorul indicat de pointerul pvect al acestui obiect, iar la distrugerea obiectului stack1 se incearca din nou stergerea aceleiasi zone de memorie, dat fiind ca cei doi pointeri aveau valoare egala. O astfel de operatie produce eroare de executie si abandonarea programului, ceea ce se si poate observa la executia functiei fd2().
Solutia o reprezinta definirea unui constructor de copiere care sa previna astfel de situatii. Un constructor de copiere definit de programator trebuie sa aloce spatiu pentru datele dinamice create in memoria heap si dupa aceea sa copieze valorile din obiectul de referinta. Un exemplu de constructor de copiere definit pentru clasa DStack va explicita mai usor acest aspect.

? Exemplul 2.9

Se defineste constructorul de copiere al clasei DStack astfel:

DStack::DStack(DStack &r)A size = r.size; tos = r.tos; pvect = new intasizei; for (int i=0; i< size; i++) pvectaii = r.pvectaii;
S

Bineinteles, declaratia acestuia trebuie sa apara in interiorul clasei DStack.
Fie functia:

void fd3()A
DStack stack1(100); stack1.push(11);
DStack stack2(stack1); stack1.push(12); stack2.push(21); int x = stack1.pop(); int y = stack1.pop(); cout << x << “ ” << y << endl; // afisaeza 12 11 x = stack2.pop(); y = stack2.pop(); cout << x << “ ” << y << endl; // afiseaza 21 11
S

Executia acesteia se termina normal; dupa constructia prin copiere a obiectului stack2, acesta are propriul vector de numere intregi, in care a preluat o valoare introdusa in stiva stack1 inainte de copiere (valoarea 11), si el continua operatiile de introducere si extragere din acest punct. Cele doua obiecte sunt complet independente si asupra lor se pot executa operatii in mod separat, inclusiv operatia de distrugere care se executa in mod implicit la sfarsitul functiei. Acest lucru este evidentiat de mesajele afisate.
?
Se poate stabili cu certitudine ca programatorul trebuie sa prevada un constructor de copiere “inteligent”, in orice clasa in care exista date alocate dinamic. Daca un astfel de constructor este definit, compilatorul nu mai genereaza constructorul implicit de copiere membru cu membru si sunt evitate erorile de tipul celor descrise mai sus.

Erori care au originea in constructia obiectelor prin copiere membru cu membru (deci folosind constructorul de copiere implicit generat de compilator) pot sa apara in orice situatie in care se construieste un obiect prin copiere. Astfel de situatii vor fi prezentate in exemplele care urmeaza.

? Exemplul 2.10

La trasmiterea unui obiect ca argument prin valoare unei functii, se construieste un obiect local functiei folosindu-se constructorul de copiere. De aici pot proveni toate problemele de tipul celor descrise mai sus. Se pot observa mai intuitiv, daca se executa functia fd4() in situatia in care nu s-a definit constructorul de copiere al clasei DStack, si in situatia in care s-a definit ca mai sus un astfel de constructor.

void g(DStack ds)A int x = ds.pop(); int y = ds.pop(); cout << x << “ ” << y << endl; // afiseaza 66 55

S void fd4()A
DStack stack(100); stack.push(55); stack.push(66); g(stack);
S

Executia corecta a functiei fd4() care apeleaza functia g() avand ca argument un obiect de tip DStack are loc numai daca a fost definit (ca mai sus) constructorul de copiere al clasei DStack. Altfel apare eroare de executie.

? Exemplul 2.11

La returnarea unui obiect dintr-o functie se creaza un obiect temporar folosind constructorul de copiere. Pentru a observa comportarea obiectelor create se adauga mesaje de identificare in constructorii si destructorul clasei DStack astfel:

class DStackA int *pvect; int size; int tos; public:
DStack(int s)A
Cout << "Constructor initializare\n"; pvect = new intasi; size = s; tos = 0;
S
DStack(DStack &r);
IDStack(); void push(int x); int pop();
S;
DStack::DStack(DStack &r)A
Cout << "Constructor copiere\n"; size = r.size; tos = r.tos; pvect = new intasizei; for (int i=0; i< size; i++) pvectaii = r.pvectaii;
S
DStack::IDStack()A cout << "Destructor\n"; if (pvect)A delete pvect; pvect = NULL;
S
S
DStack h()A
DStack s(200); return s;
S void fd5()A h(); cout << "Revenire din h()\n";
S

La revenirea din functia h(), se construieste un obiect temporar folosind ca referinta obiectul de tip DStack returnat de functia h(). Mesajele care se afiseaza la executia functiei fd5() sunt urmatoarele:

Constructor initializare
Constructor copiere
Destructor
Destructor
Revenire din h()

Primul mesaj indica construirea obiectului s in functia h() folosind constructorul de initialzare al clasei; al doilea mesaj se afiseaza la constructia unui obiect temporar avand ca referinta obiectul returnat de functia h(). Aceste obiecte sunt distruse inainte de iesirea din functia fds5().
Daca a fost definit constructorul de copiere al clasei (ca mai sus), executia este corecta. Daca se elimina acest constructor, apare eroare de executie, datorita tentativei de a sterge a doua oara vectorul de numere, care este acelasi pentru cele doua obiecte.

2.3.4 Conversia datelor prin constructori

Conversia unei variabile intre doua tipuri dintre care cel putin unul este un tip definit de utilizator (clasa) se poate face prin constructori sau prin supraincarcarea operatorului de conversie. In aceasta sectiune se prezinta cazul de conversie prin constructori care este o conversie de la un tip de date predefinit la un tip definit de utilizator (clasa). Conversia prin supraincarcarea operatoruilor este descrisa in sectiunea 4.
Un constructor cu un argument T al unei clase X foloseste valoarea acestuia pentru initializarea obiectului de clasa X construit. Acest mod de initializare poate fi privit ca o conversie de la tipul de date T, la tipul de date X. Daca T este un tip predefinit, un astfel de constructor este definit simplu, ca membru nestatic al clasei :

class XA public:
X(T t);
// …………
S;
X::X(T t)A
// initializare obiect X
S

Cazul in care si T este un tip definit de utilizator este prezentat in sectiunea 4.
Daca ne referim la clasa Complex definita in aceasta sectiune, instructiunea:
Complex c1 = 7.8; este o conversie de la tipul double la tipul Complex: se creeaza obiectul c1 de tip Complex cu valori ale datelor membre initializate folosind data de tip double din care se face conversia. Daca implementarea clasei Complex este cea din Exemplul 2.6, care contine cate un mesaj de identificare pentru fiecare constructor, atunci, la executia acestei instructiuni se afiseaza mesajul:
Constructor cu 1 arg
Operatia de conversie printr-un constructor cu un argument are loc direct, fara alte operatii intermediare, daca intervine in declararea obiectului, asa cum este in exemplul dat mai sus. In alte modalitati de declarare apar operatii suplimentare. De exemplu, instructiunile:

Complex c2; c2 = 9.3;

creaza mai intai obiectul c2 de tip Complex, folosind constructorul implicit al clasei; dupa aceasta este creat un obiect temporar folosind constructorul cu un argument, pentru conversia de la valoarea 9.3 de tip double si acest obiect este utilizat pentru operatia de asignare al carui membru stang este obiectul c2.
Se poate remarca ineficienta unei astfel de sectiuni de program. Mai mult, asignarea intre obiecte de tip definit de utilizator ridica aceleasi probleme ca si copierea prin constructorii de copiere: operatia de asignare predefinita pe care o executa compilatorul este o asignare prin copiere membru cu membru a datelor. Se poate intui ca problemele care apar sunt aceleasi ca si in cazul constructorilor de copiere: pentru obiectele care contin date alocate dinamic in memoria heap, copierea membru cu membru conduce la situatia ca doua obiecte, cel asignat (membru stanga) si cel din care se executa asignarea (membru dreapta) sa contina pointeri cu aceeasi valoare, deci care indica spre aceeasi zona din memoria heap. De aici, evident, apar numeroase probleme.
Se poate observa acest comportament folosind conversii si asignari a obiectelor din clasa DStack:

DStack stack1 = 8;//corect, conversie prin constructor
DStack stack2; // construire cu constructor implicit stack2 = 7; // conversie, apoi asignare
// la asignare apare eroare de executie

Solutia de a supraincarca operatorul de asignare este prezentata in sectiunea 4.

2.4 Obiecte de tipuri definite de utilizator membre ale claselor. Clase locale. Clase imbricate

Datele membre ale unei clase pot fi atat variabile de tipuri predefinite cat si obiecte de tipuri definite de utilizator. Membrii care sunt de tip definit de utilizator (clasa) trebuie sa fie obiecte de tipuri (clase) definite mai inainte, nu doar declarate ca nume.
Daca o clasa contine obiecte membre de tipuri definite de utilizator, argumentele necesare pentru construirea acestora sunt plasate in definitia (nu in declaratia) constructorului clasei care le contine. Fie urmatoarele definitii de clase si functii:

class XA int *px; public:
X();
X(int sx);
IX();
S; inline X::X()A cout << "Constructor X implicit\n"; px = NULL;
S inline X::X(int sx)A cout << "Constructor X cu 1 arg\n"; px = new intasxi;
S inline X::IX()A cout << "Destructor X\n"; if (px)A delete px; px = NULL;
S
S class YA int *py;
X x; // data membra de clasa X public:
Y(int sy);
Y(int sx, int sy);
IY();
S; inline Y::Y(int sy)A cout << "Constructor Y cu 1 arg\n"; py = new intasyi;
S inline Y::Y(int sx, int sy):x(sx)A cout << "Constructor Y cu 2 arg\n"; py = new intasyi;
S inline Y::IY()A cout << "Destructor Y\n"); if (py)A delete py; py = NULL;
S
S void fx()A
Y y1(7);
Y y2(4,5);
S

La executia functiei fx(), data membra x de clasa X a obiectului y2 se initializeaza folosind argumentul 4 transmis prin intermediul constructorului clasei Y. Mesajele care se afiseaza la executia functiei f6() sunt urmatoarele:

Constructor X implicit
Constructor Y cu 1 arg
Constructor X cu 1 arg
Constructor Y cu 2 arg
Destructor Y
Destructor X
Destructor Y
Destructor X

Se observa ca se construieste mai intai data membra x si apoi obiectul de clasa Y care o contine. Daca sunt mai multe date membre care se initializeaza, acestea se pot trece in orice ordine, separate prin virgula in definitia constructorului obiectului care le contine. Constructorii obiectelor membre sunt apelati in ordinea in care acestea sunt specificate in declaratia clasei. La distrugerea unui obiect, se executa mai intai destructorul obiectului si apoi, in ordinea inversa declaratiei, destructorii datelor membre ale acestuia.

2.4.1 Ordinea de executie a constructorilor si destructorilor

Un constructor este apelat la declararea obiectului, iar destructorul este apelat atunci cand obiectul este distrus. Daca exista mai multe declaratii de obiecte, atunci ele sunt construite in ordinea declaratiei si sunt distruse in ordinea inversa a declaratiei.
Obiectele membre ale unei clase se construiesc inaintea obiectului respectiv. Destructorii sunt apelati in ordine inversa: destructorul obiectului si apoi destructorii membrilor (un exemplu este dat in subsectiunea urmatoare)
Functiile constructor a obiectelor globale sunt executate inaintea executiei functiei main(). Constructorii obiectelor globale din acelasi fisier sunt executati in ordinea declaratiilor, de la stanga la dreapta si de sus in jos. Este greu de precizat ordinea de apel a constructorilor globali distribuiti in mai multe fisiere. Dectructorii obiectelor globale sunt apelati in ordine inversa, dupa incheiere functiei main().
Alte precizari cu privire la ordinea de executie a constructorilor si destructorilor se vor face in sectiunea 5, dedicata claselor derivate.

3.3.1 Clase locale. Clase imbricate

O clasa locala este o clasa definita in interiorul unei functii. O astfel de clasa este cunoscuta numai in interiorul acelei functii si este supusa mai multor restrictii: toate functiile clasei locale trebuie sa fie definite in interiorul clasei; clasele locale nu admit variabile de tip static; clasa locala nu are acces la variabilele locale ale functiei in care a fost declarata. Din cauza acestor restrictii, clasele locale sunt rar utilizate in programarea C++.

O clasa imbricata este o clasa definita in interiorul altei clase. O astfel de clasa este cunoscuta numai in domeniul clasei in care a fost definita, de aceea numele acesteia trebuie sa fie calificat cu numele clasei care o contine folosind operatorul de rezolutie (::). Utilizarea specifica a claselor imbricate este in mecanismele de tratare a exceptiilor. Deoarece exceptiile sunt definite pentru o anumita clasa, este mai normal ca tipul exceptiei (definit ca o clasa) sa apartina clasei care o defineste. Un exemplu de clasa imbricata folosita in tratarea exceptiiloe este prezentat in sectiunea 8.

2.5 Functii si clase friend

Este posibil sa fie admis unei functii nemembre sa acceseze datele private sau protected ale unei clasei prin declararea acesteia de tip friend a clasei. Pentru declararea unei functii f() de tip friend a clasei X se include prototipul functiei f(), precedat de specificatorul friend in definitia clasei X, iar functia insasi se defineste in alta parte in program astfel:

class XA
//…….. friend tip_returnat f(lista_argumente);
S;
………………………..…… tip_returnat f(lista_argumente)A
// corpul functiei
S

Pentru evidentierea utilitatii functiilor de tip friend se considera urmatorul exemplu.

? Exemplul 2.12

Se considera problema de inmultire a unei matrice cu un vector. Astfel de operatii sunt deosebit de frecvente, in multe domenii: fizica, proiectare automata, grafica, etc. Cele doua clase care descriu o matrice 4x4 (folosita in transformarile grafice tridimensionale) si un vector de dimensiune 4 sunt definite astfel: class Matrix A double ma4ia4i; public:
Matrix();
Matrix(double pmaia4i); void set(int i, int j, double e) A maiiaji=e;S double get(int i, int j)constAreturn maiiaji;S
S;
Matrix::Matrix() A for (int i=0;i<4;i++) for (int j=0;j<4;j++) if (i==j)maiiaji = 1.0; else maiiaji = 0.0;
S
Matrix::Matrix(double pmaia4i)A for (int i=0;i<4;i++) for (int j=0;j<4;j++) maiiaji = pmaiiaji;
S class Vector A double va4i; public:
Vector();
Vector(double *pv); void set(int i, double e) Avaii=e;S double get(int i) const Areturn vaii;S
S;
Vector::Vector()A for (int i=0;i<3;i++) vaii=0.0; va3i = 1.0;
S
Vector::Vector(double *pv)A for (int i=0;i<4;i++) vaii= pvaii;
S

Fiecare din cele doua clase are prevazut un constructor implicit si un constructor de initializare. Modificatorul const care este prezent in unele declaratii va fi precizat in subsectiunea urmatoare.
Dat fiind ca o functie nu poate fi membra a doua clase, cel mai natural mod de inmultire a unei matrici cu un vector ar parea sa fie prin definirea unei functii nemembre multiply()care acceseaza elementele celor doua clase (clasa Matrix si clasa Vector) prin functiile de interfata get() si set().

Vector Matrix::multiply(const matrix &mat, const Vector &vect)A
Vector vr; for (int i=0;i<4;i++)A double x =0.0; for (int j=0;j<4;j++) x += vect.get(j)*mat.get(i,j); vr.set(i,x);
S return vr;
S

Dar acest mod de operare poate fi foarte ineficient, daca functiile de interfata get() si set()ar verifica incadrarea argumentului (indicele) in valorile admisibile. Daca o astfel de verificare nu se face, alte programe care le foloseste (in afara de functia multiply() ) ar putea provoca erori in program.
Solutia pentru aceasta problema o constitue declararea functiei multiply() ca functie friend in cele doua clase. Modificarile care se intoduc in cele doua clase si in functia multipy() arata astfel:

class Vector; class Matrix A
//…..……… friend Vector multiply(const Matrix &mat, const Vector &vect);
S; class Vector A
//…..……… friend Vector multiply(const Matrix &mat, const Vector &vect);
S;

Declaratia friend poate fi plasata in orice parte, public, private sau protected a clasei. Functia multiply() se rescrie pentru accesul direct la elementele vectorului si matricei astfel:

Vector multiply(const Matrix &mat, const Vector &vect)A
Vector vr; for (int i=0;i<4;i++) A double x = 0.0; for (int j=0;j<4;j++) x += vect.vaji * mat.majiaii; vr.vaii = x;
S return vr;
S

Apelul acestei functii de inmultire multiply() intr-o functie oarecare fm() este urmatorul:


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