http://alpha-thema-online.uv.ro
Fa-ti si tu propriul forum, pe www.myforum.ro!
Pagina de start a forumului http://alpha-thema-online.uv.ro Autentificare Inregistrare FAQ Membri Cautare

Pagina de start a forumului http://alpha-thema-online.uv.ro » C / C++ / C# » [C++]Manual C++ part. 4
Creaza un subiect nou  Raspunde la subiect Subiectul anterior :: Subiectul urmator 
[C++]Manual C++ part. 4
MesajTrimis: 24/08/2007 13:46 Raspunde cu citat (quote)
Ad_Infinitum
Administrator
Administrator
Ad_Infinitum
Data inscrierii: 23/Iun/2007
Mesaje: 212




Constructori ºi destructori

Utilizarea unor funcþii membre ale unei clase, aºa cum este funcþia set() din clasa Complex, pentru iniþializarea obiectelor este inelegantã ºi permite strecurarea unor erori de programare. Deoarece nu existã nici o constrângere din partea limbajului ca un obiect sã fie iniþializat (de exemplu, nu apare nici o eroare de compilare dacã nu este apelatã funcþia set() pentru un obiect din clasa Complex), programatorul poate sã uite sã apeleze funcþia de iniþializare sau sã o apeleze de mai multe ori. În cazul rudimentar al clasei prezentate ca exemplu pânã acum, acest fapt poate cauzează doar erori care se evidenþiazã uºor. În schimb, pentru alte clase, erorile de iniþializare pot fi dezastruoase sau mai greu de identificat.
Din aceastã cauzã, limbajul C++ prevede o modalitate elegantã ºi unitarã pentru iniþializarea obiectelor de tipuri definite de utilizator, prin intermediul unor funcþii speciale numite funcþii constructor (sau, mai scurt, constructori).

2.1.1Constructori

În exemplul urmãtor se defineºte o clasã care descrie o stivã de numere întregi, clasa IntStack. Detalii asupra acestui tip de date se pot citi mai jos, în subsecþiunea 2.7.

Exemplul 2.5

#include <iostream.h>
#define MAX_SIZE 1000
class IntStack {
int vect[MAX_SIZE];
int tos;
public:
IntStack(){tos = 0;}
void push (int v);
int pop();
};
void IntStack::push(int v){
if (tos < MAX_SIZE)
vect[tos++] = v;
}
int IntStack::pop(){
if (tos > 0)
return vect[--tos];
else return –1;
}
void fs1(){
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
}


În clasa IntStack este definit un vector de numere întregi de o dimensiune maximã definitã în program, vect[MAX_SIZE], în care se intoduc ºi se extrag numere în ordinea ultimul intrat-primul extras (last in-first out). Douã funcþii membre ale clasei, push() ºi pop() realizeazã introducerea, respectiv extragerea, unui numãr întreg din obiectul de tip IntStack pentru care sunt apelate. În funcþia push() se previne introducerea unui nou numãr dacã stiva este plinã. În funcþia pop() se returneazã o valoare corectã numai dacã stiva nu este goalã; dacã nu existã nici un numãr în stivã, nu se citeºte nimic din memorie ºi se returneazã – 1. Aceastã tratare a situaþiei de eroare nu este suficientã, deoarece valoarea – 1 poate fi returnatã ºi ca datã corectã. O tratare completã a situaþiilor de eroare la execuþia funcþiilor membre ale unei clase este prezentatã în secþiunea 8.
Variabila tos indicã prima poziþie liberã din stivã ºi ea trebuie neapãrat sã fie iniþializatã la 0 (începutul stivei) cândva ca stiva sã poatã fi folositã, altfel pot apare erori de execuþie impredictibile (scriere la adrese de memorie necontrolate). Iniþializarea s-ar putea face printr-o funcþia membrã care sã fie apelatã explicit, dar o modalitate mai bunã este aceea de a folosi o funcþie membrã specialã pentru iniþializare, denumitã funcþie constructor.
Un constructor este o funcþie cu acelaºi nume cu numele clasei, care nu returneazã nici o valoare ºi care iniþializazã datele membre ale clasei.
De exemplu, în clasa de mai sus constructorul:
IntStack(){tos=0;}
iniþializeazã la 0 variabila tos.
Pentru aceeaºi clasã pot fi definite mai multe funcþii constructor, ca funcþii supraîncãrcate, care pot fi selectate de cãtre compilator în funcþie de numãrul ºi tipul argumentelor de apel, la fel ca în orice supraîncãrcare de funcþii.
Un constructor implicit pentru o clasã X este un constructor care poate fi apelat fãrã nici un argument. Deci un constructor implicit este fie un constructor care are lista de argumente vidã, fie un constructor cu unul sau mai multe argumente, toate fiind prevãzute cu valori implicite. De exemplu, X::X(int=0) este un constructor implicit, pentru că el poate fi apelat fãrã nici un argument, având definitã o valoare implicitã a argumentului.
În general, constructorii se declarã de tip public, pentru a putea fi apelaþi din absolut orice punct al domeniului de definiþie al clasei respective. La crearea unui obiect dintr-o clasã oarecare este apelat implicit acel constructor al clasei care prezintã cea mai bunã potrivire a argumentelor. Dacã nu este prevãzutã nici o funcþie constructor, compilatorul genereazã un constructor implicit de tip public, ori de câte ori este necesar.
In Exemplul 2.6 se definesc pentru clasa Complex mai multe funcþii constructor: constructor implicit, cu un argument ºi cu douã argumente.

 Exemplul 2.6

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

La execuþia funcþiei fc2(), sunt afisate urmãtoarele mesaje:

Constructor implicit
Constructor cu 1 arg
Constructor cu 2 arg

În fiecare dintre aceste situaþii a fost creat un obiect de tip Complex, ca obiect local funcþiei fc2() (c1, c2, c3) ºi de fiecare datã a fost apelat constructorul care are acelaºi numãr ºi tip de argumente cu cele de apel.


Un constructor este apelat ori de câte ori este produs un obiect dintr-o clasã care are un constructor (definit sau generat de compilator). Un obiect poate fi conceput într-unul din urmãtoarele moduri:

ca variabilã globalã,
ca variabilã localã,
prin întrebuințarea explicitã a operatorului new,
ca obiect temporar,
prin apelul explicit al unui constructor.

În fiecare situaþie, constructorul are rolul de a crea structura de bazã a obiectului (sã construiascã obiectele care reprezintã date membre nestatice, sã construiascã tabelele care se referã la derivare ºi moºtenire, dacã este cazul) ºi, în final, sã execute codul specificat în corpul constructorului. Un constructor generat de compilator pentru o clasã X are forma generalã X::X(){}, adicã nu prevede execuþia unui cod, ci doar asigurã construcþia obiectelor membre ºi ale tabelelor de derivare, dacã este cazul.
În toate situaþiile afarã de ultima, constructorul este apelat în mod implicit, atunci îndată ce se creazã obiectul. Apelul explicit al unui constructor, deºi admis de limbaj, este puþin utilizat.

Pe lângã iniþializarea datelor membre, în constructori se executã, atunci când este necesar, operaþiile de alocare dinamicã a unor date.
De exemplu, implementarea clasei IntStack prezentatã în Exemplul 2.5 poate produce un mare consum de memorie, în mod nejustificat: orice obiect de tipul IntStack este realizat cu un vector de date de dimensiunea maximã MAX_SIZE definitã ca o constantã în program, chiar dacã un numãr mare de obiecte ar necesita dimensiuni mult mai reduse. Alocarea dinamicã a spaþiului strict necesar pentru vectorul de numere întregi se poate efectua la crearea obiectului, prin funcþiile constructor. Exemplul urmãtor prezintã aceastã posibilitate.

Exemplul 2.7

Se reia implementarea tipului de date stivã de numere întregi cu alocarea dinamicã a vectorului în memoria liberã, întrebuințând clasa DStack:

class DStack{
int *pvect;
int size;
int tos;
public:
DStack(){
pvect = NULL;
size = 0;
tos = 0;
}
DStack(int s){
pvect = new int[s];
size = s;
tos = 0;
}
void push(int x);
int pop();
};
void DStack::push(int x){
if (tos < size)
pvect[tos++] = x;
}
int DStack::pop(){
if (tos>0)
return pvect[--tos];
else return -1;
}
void fd1(){
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
}

La declararea unui obiect de clasã DStack, se oferă ca argument măsură doritã a stivei, iar constructorul alocã spaþiul necesar în memoria heap. În rest, implementarea clasei DStack este asemãnãtoare clasei IntStack, prezentatã mai sus.


2.1.2Destructori

Multe din clasele definite într-un program necesitã o operaþie inversã celei efectuate de constructor, pentru ºtergerea completã a obiectelor atunci când sunt distruse (eliminate din memorie). O astfel de operaþie este efectuatã de o funcþie membrã a clasei, numitã funcþie destructor. Numele destructorului unei clasei X este ~X() ºi este o funcþie care nu primeºte nici un argument ºi nu returneazã nici o valoare.
În implementarea din Exemplul 2.7 a stivei de numere întregi, la ieºirea din funcþia fd1(), obiectele stack1 ºi stack2 sunt eliminate din memorie, dar vectorii corespunzãtori lor, alocaþi dinamic în memoria heap, trebuie sã fie ºi ei ºterºi, pentru a nu ocupa în mod inutil memoria liberã. Aceastã operaþie se poate executa în funcþia destructor astfel:

class DStack{
//…….
public:
~DStack(){
if (pvect){
delete pvect;
pvect = NULL;
}
//………..
};

Destructorii sunt apelaþi implicit în mai multe situaþii:

 atunci când un obiect local sau temporar iese din domeniul de definiþie;
 la sfârºitul programului, pentru obiectele globale;
 la apelul operatorului delete, pentru obiectele alocate dinamic.

Apelul explicit al unui destructor este rar utilizat. Dacã o clasã nu are un destructor, compilatorul genereazã un destructor implicit.

2.1.3Constructori de copiere

Funcþia principalã a unui constructor este aceea de a iniþializa datele membre ale obiectului creat, utilizând pentru aceastã operaþie valorile primite ca argumente. Exemple de astfel de iniþializãri se gãsesc în toþi constructorii definiþi pânã în prezent.
O altã formã de iniþializare care se poate face la crearea unui obiect este prin copierea datelor unui alt obiect de acelaºi tip. Aceastã operaþie este posibilã prin intermediul unui constructor mai distinct al clasei, numit constructor de copiere. Forma generalã a constructorului de copiere al unei clase X este:

X::X(X& r){
// initializare obiect folosind referinþa r
}

Constructorul primeºte ca argument o referinþã r la un obiect din clasa X ºi iniþializazã obiectul nou creat folosind datele conþinute în obiectul referinþã r. Pentru crearea unui obiect printr-un constructor de copiere, argumentul transmis trebuie sã fie o referinþã la un obiect din aceeaºi clasã.
De exemplu, pentru obiecte de tip Complex:

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

La crearea primului obiect (c1) este este apelat constructorul cu douã argumente al clasei Complex. Cel de-al doilea obiect (c2) este creat prin apelul constructorului de copiere al clasei Complex, avînd ca argument referinþa la obiectul c1. Este posibilã ºi declaraþia (definiþia) de forma Complex c3 = c2; a unui obiect prin care se apeleazã, de asemenea, constructorul de copiere.
Constructorul de copiere poate fi definit de programator; dacã nu este definit un constructor de copiere al clasei, compilatorul genereazã un constructor de copiere care copiazã datele penis cu membru din obiectul referinþã în obiectul nou creat. Aceastã modalitate de copiere mai este denumitã copie la nivel de biþi sau copie bit cu bit (bitwise copy).
Clasa Complex poate fi completatã cu un constructor de copiere astfel:

class Complex {
//…………..
public:
Complex(Complex &r);
};
Complex::Complex(Complex &r){
re = r.re;
im = r.im;
}

Se pune întrebarea urmãtoare: de ce mai este nevoie sã fie definit un constructor de copiere dacã el este oricum generat de compilator atunci când este necesar?
Pentru obiecte din clasa Complex, funcþionarea este aceeaºi, atât în situaþia în care în clasã nu este definit un constructor de copiere, ºi deci el este generat de cãtre compilator, cât ºi dacã acesta a fost definit în clasã. Mesajele care se afiºeazã la consolã dacã funcþia fc3() este executatã dupã introducerea constructorului de copiere sunt:

Constructor cu 2 arg
Constructor copiere
Constructor copiere
4 5

Cu totul alta este situaþia în cazul în care un obiect conþine date alocate dinamic în memoria heap. Constructorul de copiere generat implicit de compilator copiazã doar datele membre declarate în clasã (membru cu membru) ºi nu stie sã aloce date dinamice pentru obiectul nou creat. Folosind un astfel de constructor, se ajunge la situaþia cã douã obiecte, cel nou creat ºi obiectul referinþã, sã conþinã pointeri cu aceeaºi valoare, deci care indicã spre aceeaºi zonã din memoria heap. O astfel se situaþie este o sursã puternicã de erori de execuþie subtile ºi greu de depistat.
Exemplul urmãtor evidenþiazã aceastã problemã.

Exemplul 2.8

Se considerã clasa DStack prezentatã în mai sus ºi o funcþie fd2() definitã astfel:

void fd2(){
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
}

Problema care apare este evidentã: fiindcă prin copierea membru cu membru pointerul pvect al obiectului stack2 primeºte valoarea pointerului pvect al obiectului stack1, instrucþiunile stack2.push(21) ºi stack2.push(22) scriu peste valorile introduse mai înainte în stiva stack1, astfel încât extragerile din stiva stack1 gãsesc valorile introduse în stiva stack2. Diferitele combinaþii de operaþii pot da cele mai variate rezultate.
O altã problemã apare la ieºirea din funcþia fd2(). Pentru clasa DStack a fost definit un destructor care ºterge vectorul pvect din memorie folosind operatorul delete. La distrugerea obiectului stack2 este ºters din memorie vectorul indicat de pointerul pvect al acestui obiect, iar la distrugerea obiectului stack1 se încearcã din nou ºtergerea aceleiaºi zone de memorie, dat fiind cã cei doi pointeri aveau valoare egalã. O astfel de operaþie realizează eroare de execuþie ºi abandonarea programului, ceea ce se ºi poate observa la execuþia funcþiei fd2().
Soluþia o reprezintã definirea unui constructor de copiere care sã previnã astfel de situaþii. Un constructor de copiere definit de programator trebuie sã aloce spaþiu pentru datele dinamice lucrate în memoria heap ºi dupã aceea sã copieze valorile din obiectul de referinþã. Un exemplu de constructor de copiere definit pentru clasa DStack va explicita mai uºor acest aspect.

Exemplul 2.9

Se defineºte constructorul de copiere al clasei DStack astfel:

DStack::DStack(DStack &r){
size = r.size;
tos = r.tos;
pvect = new int[size];
for (int i=0; i< size; i++)
pvect[i] = r.pvect[i];
}

Bineînþeles, declaraþia acestuia trebuie sã aparã în interiorul clasei DStack.
Fie funcþia:

void fd3(){
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
}

Execuþia acesteia se terminã normal; dupã construcþia prin copiere a obiectului stack2, acesta are propriul vector de numere întregi, în care a preluat o valoare introdusã în stiva stack1 înainte de copiere (valoarea 11), ºi el continuã operaþiile de introducere ºi extragere din acest punct. Cele douã obiecte sunt complet independente ºi asupra lor se pot executa operaþii în mod separat, inclusiv operaþia de distrugere care se executã în mod implicit la sfârºitul funcþiei. Acest lucru este evidenþiat de mesajele afiºate.

Se poate stabili cu certitudine cã programatorul trebuie sã prevadã un constructor de copiere “inteligent”, în indiferent ce clasã în care existã date alocate dinamic. Dacã un astfel de constructor este definit, compilatorul nu mai genereazã constructorul implicit de copiere membru cu membru ºi sunt evitate erorile de tipul celor descrise mai sus.

Erori care au originea în construcþia obiectelor prin copiere membru cu membru (deci folosind constructorul de copiere implicit generat de compilator) pot sã aparã în orice situaþie în care se construieºte un obiect prin copiere. Astfel de situaþii vor fi prezentate în exemplele care urmeazã.

Exemplul 2.10

La trasmiterea unui obiect ca argument prin valoare unei funcþii, se construieºte un obiect local funcþiei folosindu-se constructorul de copiere. De aici pot proveni toate problemele de tipul celor descrise mai sus. Se pot observa mai intuitiv, dacã se executã funcþia fd4() în situaþia în care nu s-a definit constructorul de copiere al clasei DStack, ºi în situaþia în care s-a definit ca mai sus un astfel de constructor.

void g(DStack ds){
int x = ds.pop();
int y = ds.pop();
cout << x << “ ” << y << endl; // afiseaza 66 55
}
void fd4(){
DStack stack(100);
stack.push(55);
stack.push(66);
g(stack);
}

Execuþia corectã a funcþiei fd4() care apeleazã funcþia g() conținând ca argument un obiect de tip DStack are loc numai dacã a fost definit (ca mai sus) constructorul de copiere al clasei DStack. Altfel apare eroare de execuþie.

Exemplul 2.11

La returnarea unui obiect dintr-o funcþie se creazã un obiect temporar folosind constructorul de copiere. Pentru a observa comportarea obiectelor produse se adaugã mesaje de identificare în constructorii ºi destructorul clasei DStack astfel:

class DStack{
int *pvect;
int size;
int tos;
public:
DStack(int s){
Cout << "Constructor initializare\n";
pvect = new int[s];
size = s;
tos = 0;
}
DStack(DStack &r);
~DStack();
void push(int x);
int pop();
};
DStack::DStack(DStack &r){
Cout << "Constructor copiere\n";
size = r.size;
tos = r.tos;
pvect = new int[size];
for (int i=0; i< size; i++)
pvect[i] = r.pvect[i];
}
DStack::~DStack(){
cout << "Destructor\n";
if (pvect){
delete pvect;
pvect = NULL;
}
}
DStack h(){
DStack s(200);
return s;
}
void fd5(){
h();
cout << "Revenire din h()\n";
}

La revenirea din funcþia h(), se construieºte un obiect temporar folosind ca referinþã obiectul de tip DStack returnat de funcþia h(). Mesajele care se afiºeazã la execuþia funcþiei fd5() sunt urmãtoarele:

Constructor initializare
Constructor copiere
Destructor
Destructor
Revenire din h()

Primul mesaj indicã construirea obiectului s în funcþia h() folosind constructorul de iniþialzare al clasei; al doilea mesaj se afiºeazã la construcþia unui obiect temporar având ca referinþã obiectul returnat de funcþia h(). Aceste obiecte sunt distruse înainte de ieºirea din funcþia fds5().
Dacã a fost definit constructorul de copiere al clasei (ca mai sus), execuþia este corectã. Dacã se eliminã acest constructor, apare eroare de execuþie, datoritã tentativei de a ºterge a doua oarã vectorul de numere, care este acelaºi pentru cele douã obiecte.

2.1.4Conversia datelor prin constructori

Conversia unei variabile între douã tipuri dintre care cel puþin unul este un tip definit de utilizator (clasã) se poate face prin constructori sau prin supraîncãrcarea operatorului de conversie. În aceastã secþiune se prezintã cazul de conversie prin constructori care este o conversie de la un tip de date predefinit la un tip definit de utilizator (clasã). Conversia prin supraîncãrcarea operatoruilor este descrisã în secþiunea 4.
Un constructor cu un argument T al unei clase X foloseºte valoarea acestuia pentru iniþializarea obiectului de clasã X construit. Acest mod de iniþializare poate fi privit ca o conversie de la tipul de date T, la tipul de date X. Dacã T este un tip predefinit, un astfel de constructor este definit simplu, ca membru nestatic al clasei :

class X{
public:
X(T t);
// …………
};
X::X(T t){
// initializare obiect X
}

Cazul în care ºi T este un tip definit de utilizator este prezentat în secþiunea 4.
Dacã ne referim la clasa Complex definitã în aceastã secþiune, instrucþiunea:
Complex c1 = 7.8;
este o conversie de la tipul double la tipul Complex: se creeazã obiectul c1 de tip Complex cu valori ale datelor membre iniþializate folosind data de tip dublu din care se face conversia. Dacã implementarea clasei Complex este cea din Exemplul 2.6, care conþine câte un mesaj de identificare pentru fiecare constructor, atunci, la execuþia acestei instrucþiuni se afiºeazã mesajul:
Constructor cu 1 arg
Operaþia de conversie printr-un constructor cu un argument are loc direct, fãrã alte operaþii intermediare, dacã intervine în declararea obiectului, aºa cum este în exemplul dat mai sus. În alte modalitãþi de declarare apar operaþii suplimentare. De exemplu, instrucþiunile:

Complex c2;
c2 = 9.3;

creazã mai întâi obiectul c2 de tip Complex, folosind constructorul implicit al clasei; dupã aceasta este creat un obiect temporar folosind constructorul cu un argument, pentru conversia de la valoarea 9.3 de tip double ºi acest obiect este întrebuințat pentru operaþia de asignare al cãrui membru stâng este obiectul c2.
Se poate remarca ineficienþa unei astfel de secþiuni de program. Mai mult, asignarea între obiecte de tip definit de utilizator ridicã aceleaºi probleme ca ºi copierea prin constructorii de copiere: operaþia de asignare predefinitã pe care o executã compilatorul este o asignare prin copiere membru cu membru a datelor. Se poate intui cã problemele care apar sunt aceleaºi ca ºi în cazul constructorilor de copiere: pentru obiectele care conþin date alocate dinamic în memoria heap, copierea membru cu membru conduce la situaþia cã douã obiecte, cel asignat (membru stânga) ºi cel din care se executã asignarea (membru dreapta) sã conþinã pointeri cu aceeaºi valoare, deci care indicã spre aceeaºi zonã din memoria heap. De aici, evident, apar multiple probleme.
Se poate observa acest comportament folosind conversii ºi asignãri 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

Soluþia de a supraîncãrca operatorul de asignare este prezentatã în secþiunea 4.


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

Datele membre ale unei clase pot fi atât variabile de tipuri predefinite cât ºi obiecte de tipuri definite de utilizator. Membrii care sunt de tip definit de utilizator (clasã) trebuie sã fie obiecte de tipuri (clase) definite mai înainte, nu doar declarate ca nume.
Dacã o clasã conþine obiecte membre de tipuri definite de utilizator, argumentele necesare pentru construirea acestora sunt plasate în definiþia (nu în declaraþia) constructorului clasei care le conþine. Fie urmãtoarele definiþii de clase ºi funcþii:

class X{
int *px;
public:
X();
X(int sx);
~X();
};
inline X::X(){
cout << "Constructor X implicit\n";
px = NULL;
}
inline X::X(int sx){
cout << "Constructor X cu 1 arg\n";
px = new int[sx];
}
inline X::~X(){
cout << "Destructor X\n";
if (px){
delete px;
px = NULL;
}
}
class Y{
int *py;
X x; // data membra de clasa X
public:
Y(int sy);
Y(int sx, int sy);
~Y();
};
inline Y::Y(int sy){
cout << "Constructor Y cu 1 arg\n";
py = new int[sy];
}
inline Y::Y(int sx, int sy):x(sx){
cout << "Constructor Y cu 2 arg\n";
py = new int[sy];
}
inline Y::~Y(){
cout << "Destructor Y\n");
if (py){
delete py;
py = NULL;
}
}
void fx(){
Y y1(7);
Y y2(4,5);
}

La execuþia funcþiei fx(), data membrã x de clasã X a obiectului y2 se iniþializeazã folosind argumentul 4 transmis prin intermediul constructorului clasei Y. Mesajele care se afiseazã la execuþia funcþiei f6() sunt urmãtoarele:

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 observã cã se construieºte mai întâi data membrã x ºi apoi obiectul de clasã Y care o conþine. Dacã sunt mai multe date membre care se iniþializeazã, acestea se pot trece în orice ordine, separate prin virgulã în definiþia constructorului obiectului care le conþine. Constructorii obiectelor membre sunt apelaþi în ordinea în care acestea sunt specificate în declaraþia clasei. La distrugerea unui obiect, se executã mai întâi destructorul obiectului ºi apoi, în ordinea inversã declaraþiei, destructorii datelor membre ale acestuia.

2.1.5Ordinea de execuþie a constructorilor ºi destructorilor

Un constructor este apelat la declararea obiectului, iar destructorul este apelat atunci când obiectul este distrus. Dacã existã mai multe declaraþii de obiecte, atunci ele sunt construite în ordinea declaraþiei ºi sunt distruse în ordinea inversã a declaraþiei.
Obiectele membre ale unei clase se construiesc anterior obiectului respectiv. Destructorii sunt apelaþi în ordine inversã: destructorul obiectului ºi apoi destructorii membrilor (un exemplu este dat în subsecþiunea urmãtoare)
Funcþiile constructor a obiectelor globale sunt executate premergător execuþiei funcþiei main(). Constructorii obiectelor globale din acelaºi fiºier sunt executaþi în ordinea declaraþiilor, de la stânga la dreapta ºi de sus în jos. Este greu de precizat ordinea de apel a constructorilor globali distribuiþi în mai multe fiºiere. Dectructorii obiectelor globale sunt apelaþi în ordine inversã, dupã încheiere funcþiei main().
Alte precizãri cu privire la ordinea de execuþie a constructorilor ºi destructorilor se vor face în secþiunea 5, dedicatã claselor derivate.

3.3.1Clase locale. Clase imbricate

O clasã localã este o clasã definitã în interiorul unei funcþii. O astfel de clasã este cunoscutã numai în interiorul acelei funcþii ºi este supusã mai multor restricþii: toate funcþiile clasei locale trebuie sã fie definite în interiorul clasei; clasele locale nu admit variabile de tip static; clasa localã nu are acces la variabilele locale ale funcþiei în care a fost declaratã. Din cauza acestor restricþii, clasele locale sunt rar utilizate în programarea C++.

O clasã imbricatã este o clasã definitã în interiorul altei clase. O astfel de clasã este cunoscutã numai în domeniul clasei în care a fost definitã, de aceea numele acesteia trebuie sã fie calificat cu numele clasei care o conþine folosind operatorul de rezoluþie (:Smile. Utilizarea specificã a claselor imbricate este în mecanismele de tratare a excepþiilor. Căci excepþiile sunt definite pentru o anumitã clasã, este mai normal ca tipul excepþiei (definit ca o clasã) sã aparþinã clasei care o defineºte. Un exemplu de clasã imbricatã folositã în tratarea excepþiiloe este prezentat în secþiunea 8.


Funcþii ºi clase friend

Este posibil sã fie admis unei funcþii nemembre sã acceseze datele private sau protected ale unei clasei prin declararea acesteia de tip friend a clasei. Pentru declararea unei funcþii f() de tip friend a clasei X se include prototipul funcþiei f(), precedat de specificatorul friend în definiþia clasei X, iar funcþia însãºi se defineºte în altã parte în program astfel:

class X{
//……..
friend tip_returnat f(lista_argumente);
};
………………………..……
tip_returnat f(lista_argumente){
// corpul functiei
}

Pentru evidenþierea utilitãþii funcþiilor de tip friend se considerã urmãtorul exemplu.

Exemplul 2.12

Se considerã problema de înmulþire a unei matrice cu un vector. Astfel de operaþii sunt deosebit de frecvente, în multe domenii: fizicã, proiectare automatã, graficã, etc. Cele douã clase care descriu o matrice 4x4 (folositã în transformãrile grafice tridimensionale) ºi un vector de dimensiune 4 sunt definite astfel:

class Matrix {
double m[4][4];
public:
Matrix();
Matrix(double pm[][4]);
void set(int i, int j, double e) { m[i][j]=e;}
double get(int i, int j)const{return m[i][j];}
};
Matrix::Matrix() {
for (int i=0;i<4;i++)
for (int j=0;j<4;j++)
if (i==j)m[i][j] = 1.0;
else m[i][j] = 0.0;
}
Matrix::Matrix(double pm[][4]){
for (int i=0;i<4;i++)
for (int j=0;j<4;j++)
m[i][j] = pm[i][j];
}
class Vector {
double v[4];
public:
Vector();
Vector(double *pv);
void set(int i, double e) {v[i]=e;}
double get(int i) const {return v[i];}
};
Vector::Vector(){
for (int i=0;i<3;i++)
v[i]=0.0;
v[3] = 1.0;
}
Vector::Vector(double *pv){
for (int i=0;i<4;i++)
v[i]= pv[i];
}

Fiecare din cele douã clase are prevãzut un constructor implicit ºi un constructor de iniþializare. Modificatorul const care este prezent în unele declaraþii va fi precizat în subsecþiunea urmãtoare.
Dat fiind cã o funcþie nu poate fi membrã a douã clase, cel mai natural mod de înmulþire a unei matrici cu un vector ar pãrea sã fie prin definirea unei funcþii nemembre multiply()care acceseazã elementele celor douã clase (clasa Matrix ºi clasa Vector) prin funcþiile de interfaþã get() ºi set().

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

Dar acest mod de operare poate fi foarte ineficient, dacã funcþiile de interfaþã get() ºi set()ar verifica încadrarea argumentului (indicele) în valorile admisibile. Dacã o astfel de verificare nu se face, alte programe care le foloseºte (în afarã de funcþia multiply() ) ar putea provoca erori în program.
Soluþia pentru aceastã problemã o constitue declararea funcþiei multiply() ca funcþie friend în cele douã clase. Modificãrile care se intoduc în cele douã clase ºi în funcþia multipy() aratã astfel:

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

Declaraþia friend poate fi plasatã în orice parte, public, private sau protected a clasei. Funcþia multiply() se rescrie pentru accesul direct la elementele vectorului ºi matricei astfel:

Vector multiply(const Matrix &mat, const Vector &vect){
Vector vr;
for (int i=0;i<4;i++) {
double x = 0.0;
for (int j=0;j<4;j++)
x += vect.v[j] * mat.m[j][i];
vr.v[i] = x;
}
return vr;
}

Apelul acestei funcþii de înmulþire multiply() într-o funcþie oarecare fm() este urmãtorul:

void fm(){
double v[]={1,2,3,4};
double m[][4] = {1,2,3,4,5,6,7,8,
9,10,11,12,13,14,15,16};
Matrix m1(m);
Vector v1(v);
Vector v2 = multiply(m1,v1);
}


Este posibil ca o funcþie membrã a unei clase sã fie declaratã friend în altã clasã. De asemenea este posibil ca o întreagã clasã sã fie declaratã friend a unei alte clase ºi, în acestã situaþie, toate funcþiile membre sunt funcþii friend. De exemplu:

class Y{
//……….
};
class X{
friend class Y;
//……….
};

Aceastã declaraþie face ca toate funcþiile membre ale clasei Y sã fie funcþii friend ale clasei X.


Alocarea dinamicã a obiectelor

Obiectele (instanþe ale claselor) se pot aloca în memoria heap folosind operatorul new. La alocarea memoriei pentru un singur obiect se pot transmite argumente care sunt folosite pentru iniþializarea obiectului, prin apelul acelei funcþii constructor a clasei care prezintã cea mai bunã potrivire cu argumentele de apel. Eliberarea memoriei ocupatã de un obiect se realizeazã prin operatorul delete, care apeleazã implicit destructorul clasei.
De exemplu, alocãri ºi eliberãri pentru obiecte de tipurile Complex ºi DStack:

Complex *pc0 = new Complex; // apel constr. implicit
Complex *pc1 = new Complex(2);// apel constr. cu 1 arg
Complex *pc2 = new Complex(3.5,2.9); //apel constr. 2 arg
delete pc0; // apel implicit destructor
delete pc1; // apel implicit destructor
delete pc2; // apel implicit destructor
DStack *pstack = new DStack(100); // creare stivã dim 100
pstack->push(5);
int x = pstack->pop();
delete pstack; // apel implicit destructor

Perntru alocarea dinamicã a unui vector de obiecte de tipul X, trebuie sã existe un constructor implicit al clasei X, care este apelat pentru fiecare din elementele vectorului creat. Pentru ºtergerea unui vector de obiecte se foloseºte operatorul delete [] care apeleazã implicit destructorul clasei pentru fiecare din elementele vectorului alocat. Dacã, pentru un vector alocat dinamic, se apeleazã operatorul delete (în loc de delete[]), se apeleazã destructorul clasei o singurã datã, dupã care rezultatul execuþiei este imprevizibil, cel mai adesea se produce eroare de execuþie ºi abandonarea programului. Câteva exemple de alocãri ºi eliberãri de vectori de obiecte:

Complex *pv1 = new Complex[4];
delete []pv1;
DStack *stack1 = new DStack[3];
delete []stack1;

Pentru fiecare element al fiecãruia dintre vectori, este apelat constructorul implicit la creare ºi destructorul la ºtergere.
Operatorii new ºi delete prezintã mai multe avantaje faþã de funcþiile similare malloc() ºi free(). În debutantul rând, new alocã automat spaþiul necesar pentru memorarea unui obiect de tipul specificat, fãrã sã necesite folosirea operatorului sizeof. Apoi, pointerul returnat de new este de tipul specificat, ne mai fiind nevoie de conversie de la tipul void*, aºa cum se procedeazã la apelul funcþiei malloc(). În sfârºit, operatorii new ºi delete pot fi supraîncãrcaþi, permiþând modalitãþi diferenþiate de alocare dinamicã pentru tipurile definite de utilizator.



Exerciþii

E2.1 Sã se defineascã o clasã Date pentru memorarea datei sub forma (zi, lunã, an). Clasa va conþine atâþia constructori, cât sunt necesari pentru urmãtoarele definiþii de obiecte:
Date data1(15, 3, 99);// 15 martie 99
Date data2(18); // 18 luna curentã, anul curent
Date data3(20, 4); // 20 aprilie, anul curent

E2.2 Sã se defineascã o clasã CharStack pentru o stivã de caractere, a cãrei dimensiune se stabileºte printr-un parametru transmis la construcþie. În programul de frunte se vor crea douã stive de dimensiune 100, respectiv 200 caractere. Inseraþi un caracter în întâia stivã, dupã aceea îl extrageþi din aceasta ºi-l introduceþi în cea de-a doua stivã. Afiºaþi rezultatul unei operaþii de extragere din a doua stivã.

E2.3 Sã se construiascã o clasã de obiecte Contor pentru urmãrirea funcþionãrii unui aparat de înregistrare (contor) cu cifre de unitãþi, zeci, sute. Pentru aceastã clasã se vor defini constructori, destructorul, funcþia avans() care semnificã avansul cu o turã ºi funcþia display() de afiºare a stãrii curente a contorului. Sã se defineascã un obiect de tip Contor poziþionat pe valoarea 972. Sã se transnitã contorului 5 semnale de avans ºi sã se afiºeza poziþia în care se aflã.

E2.4 Se considerã urmãtorul program în care o variabilã globalã numbers memoreazã numãrul obiectelor “în viaþã” la un moment dat:

#include <iostream.h>
int numbers = 0;
class Item {
public:
Item(){// constructorul creste cu 1 nr obiectelor
numbers++;
cout << "Nr. obiecte " << numbers << endl;
}
~Item() {// destructorul descreste cu 1 nr ob.
numbers--;
cout << "Nr. obiecte " << numbers << endl;
}
};
void main() {
Item ob1, ob2, ob3;
{ // se deschide un nou bloc –
// un nou domeniu de vizibilitate
Item ob4, ob5;
}
Item *pob6 = new Item[3];
delete [] pob6;
}

Care sunt mesajele care apar la consolã la execuþia acestui program? Sã se explice evoluþia numãrului de obiecte în viaþã în cursul execuþiei.

E2.5 Se se modifice programul de mai sus astfel încât sã se foloseascã un membru static al clasei Item pentru contorizarea obiectelor în viaþã. Sã se verifice echivalenþa mesajelor afiºate la consolã.

E2.6 Se defineºte o clasã de obiecte IntArray care permite reprezentarea unor vectori de numere întregi astfel:

class IntArray{
int* p;
int count; // numar de elemente ale listei
int size; // dimensiunea vectorului
int grows; // nr. elem de crestere a dimensiunii
public:
IntArray(){
count = 0;
size = grows;
grows = 4;
p = new int[grows];
}
~IntArray() { delete p;}
int GetCount() {return count;}
int Add(int x);
int GetAt(int i, int *v) {
if (i >=0 && i < count)
return p[i];
else return -1; // err,index in afara vector
}
};
int IntArray::Add(int x){ // adauga elem. la sf. vect.
// creste size. cu grows
// elemente, dacã este nevoie
if (count < size)
p[count++] = x;
else {
int *p1 = p;
p = new int[count+grows];
for (int i=0; i<count;i++)
p[i] = p1[i];
delete p1;
p[count++] = x;
}
return count-1;
}

Aceastã clasã va imită în mare mãsurã clasa CUIntArray din MFC, dacã i se vor mai adãuga ºi alte funcþii.
Vectorul conþine count elemente (întregi) ºi ocupã un spaþiu în memorie necesar pentru un numãr de întregi egal cu size. Atunci când este necesar, creºterea sizeiunii vectorului se face cu grows elemente.

(a) Se cere sã se redea mesajele care se afiºeazã la consolã la execuþia urmãtoarei funcþii:

void fci() {
IntArray array1;
array1.Add(11);
array1.Add(22);
array1.Add(33);
array1.Add(44);
array1.Add(55);
int count = array1.GetCount();
for (int i = 0; i < count; i++)
cout << array1.GetAt(i) << " ";
cout << endl;
}

(b) Ce se întâmplã dacã pentru copierea în noul vector p se înlocuieºte instrucþiunea
p[i] = p1[i]; cu *p++ = *p1++;
Sã se explice diferenþa ºi cauza erorii, dacã aceasta apare la compilare sau la execuþie.

E2.7 Sã se defineascã pentru clasa IntArray o funcþie int InsertAt(int x, int i) care sã permitã inserarea în vector a unui numãr întreg x în poziþia datã i, ºi o altã funcþie int RemoveAt(int i) care sã elimine un element din vector de la poziþia datã i. Folosind aceste funcþii, sã se insereze în vectorul făcut mai sus un element cu valoarea 0 între numerele 22 ºi 33 ºi sã se elimine numãrul 44 din vector. Sã se afiºeze la consolã vectorul rezultat.

E2.8 Pentru operaþii asupra unui vector de întregi de tipul IntArray se defineºte o funcþie fint() care are ca argument de apel un obiect din clasa IntArray, astfel:

void fint(IntArray a){
// corpul functiei
}

Ce se întâmplã la apelul acestei funcþii din funcþia main()? Ce fel de eroare poate sã aparã ºi de ce? Care sunt remediile posibile?

E2.9 Sã se defineascã o funcþie membrã int Append(IntArray &array) a clasei IntArray care sã adauge un vector la sfârºitul unui alt vector. În funcþia main() se va crea un vector cu conþinutul 1 2 3 4 5 ºi un alt vector cu conþinutul 11 12 13 14 15. Sã se ataºeze primul vector la sfârºitul celui de-al doilea vector. ºi sã se afiºeze vectorul rezultat obþinut.

E2.10 Sã se definescã o funcþie membrã int InsertAt(int i, IntArray &array) a clasei IntArray care sã permitã inserarea unui vector în interiorul altui vector, începând cu o poziþie datã. În funcþia main() se va crea un vector cu conþinutul 1 2 3 4 5 ºi un vector cu conþinutul 11 12 13 14 15. Sã se insereze primul vector între elementele 12 ºi 13 ale celui de-al doilea vector ºi sã se afiºeze vectorul rezultat obþinut.

E2.11 Se modificã funcþia:
int InsertAt(int i, IntArray &array); astfel:
int InsertAt(int i, const IntArray &array).
Ce modificãri trebuie sã fie aduse celorlate funcþii membre ale clasei IntArray pentru a se compila ºi executa programul de mai sus?
Vezi profilul utilizatorului Trimite mesaj privat

[C++]Manual C++ part. 4
 Pagina de start a forumului http://alpha-thema-online.uv.ro » C / C++ / C#
Nu puteti crea un subiect nou in acest forum
Nu puteti raspunde in subiectele acestui forum
Nu puteti modifica mesajele proprii din acest forum
Nu puteti sterge mesajele proprii din acest forum
Nu puteti vota in chestionarele din acest forum
Ora este GMT + 3 ore  
Pagina 1 din 1  

  
  
 Creaza un subiect nou  Raspunde la subiect  


Fa-ti si tu propriul forum GRATUIT, pe www.myforum.ro
    Powered by phpBB © 2001-2004 phpBB Group. phpBB Skin by Vjacheslav Trushkin.