You are on page 1of 94

1.

INTRODUCERE N C++
Exista limbaje concepute strict pe baza conceptelor programrii orientate pe obiecte (POO), de exemplu Simula sau Smalltalk. O alt abordare este de a aduga unor limbaje cu o popularitate bine stabilit, de exemplu Pascal si C, elementele tehnicii programrii orientate pe obiecte. C++ combin avantajele oferite de limbajul C (eficien, flexibilitate i popularitate) cu avantajele oferite de tehnica POO (Programrii Orientate pe Obiecte). Dei adopt principiile POO, C++ nu impune aplicarea lor strict (se pot scrie programe fr elemente POO). Conceptul fundamental n C++ este clasa. C++ este o extensie a limbajului C, creat de Bjarne Stroustrup. Cea mai important extensie pe care aceasta a fcut-o limbajului su s-a produs n momentul introducerii claselor. Clasele reprezint o extensie a structurilor din C. Cu acestea, programatorul i poate crea tipuri de date complexe, ce cuprind att datele ct i operaiile ce acioneaz asupra acestora. Variabilele create din clase se numesc obiecte, iar programarea utiliznd aceste concepte, programare orientat pe obiecte (Object Oriented Programming). Cum este i firesc, C++ posed toate capacitile limbajului C. n plus utiliznd clasele, se pot dezvolta programe de un de un nalt grad al complexitii. Tipurile definite prin clase pot fi integrate n programe aproape la fel de uor ca n cazul structurilor.

1.1. Ce este programarea orientat pe obiecte ?


Programarea orientat pe obiecte (OOP) este o nou cale de abordare a programrii. Modalitile de programare s-au schimbat imens de la inventarea calculatorului, n primul rnd pentru a se acomoda creterii complexitii programelor. De exemplu, la nceput, cnd au fost inventate calculatoarele, programarea se fcea introducndu-se instruciuni n maina de cod binar cu ajutorul panoului frontal al calculatorului. Acest lucru a fost convenabil att timp ct programele aveau doar cteva sute de instruciuni. O dat cu mrirea programelor, au fost inventate limbajele de asamblare, astfel nct programatorii se puteau descurca cu programe mai mari, cu complexitate crescut, folosind reprezentarea simbolic a instruciunilor pentru main. Deoarece programele continuau s creasc, au fost introduse limbajele de nivel nalt care ofer programatorului mai multe unelte cu care s fac fa complexitii. Primul limbaj larg rspndit a fost, desigur, FORTRAN. Chiar dac el a fost un prim pas foarte impresionant, este departe de a fi un limbaj care ncurajeaz programe clare, uor de neles. Anii 60 au dat natere programrii structurate. Aceasta este metoda ncurajat de limbaje precum sunt C i Pascal. Utilizarea limbajelor structurate face posibil scrierea destul de uoar a unor 1

programe relativ complexe. Totui, chiar folosind metodele programrii structurate, un proiect nu mai poate fi controlat odat ce atinge anumite mrimi (adic odat ce complexitatea sa o depete pe cea pe care o poate controla un programator). Luai n calcul c pentru fiecare realizare din dezvoltarea programrii au fost create metode care s permit programatorului s se descurce cu complexitate crescut. La fiecare pas al drumului noua abordare a preluat cele mai bune elemente ale metodelor anterioare i a continuat drumul. Astzi multe proiecte sunt aproape sau n punctul n care programarea structurat nu mai face fa. Pentru a rezolva aceast problem, a fost inventat programarea orientat pe obiecte. Programarea orientat pe obiecte a preluat cele mai bune idei ale programrii structurate i le combin cu mai multe concepte noi, mai puternice, care ncurajeaz abordarea programrii ntr-un mod nou. n general, cnd se programeaz n modul orientat pe obiecte, o problem este mprit n subgrupe de seciuni nrudite, care in seama att de codul ct i de datele corespunztoare din fiecare grup. Apoi, se organizeaz aceste subgrupe ntr-o structur ierarhic. n sfrit, subgrupele se transform n uniti de sine stttoare numite obiecte. Toate limbajele de programare orientate pe obiecte au trei caracteristici comune: ncapsularea, polimorfism i motenire.

ncapsularea
ncapsularea este un mecanism care leag mpreun cod i date i le pstreaz pe ambele n siguran fa de intervenii din afar i de utilizri greite. Mai mult, ncapsularea este cea care permite crearea unui obiect. Spus simplu, un obiect este o entitate logic ce ncapsuleaz att date ct i cod care manevreaz aceste date. ntr-un obiect o parte din cod i/sau date pot fi particulare acelui obiect i inaccesibile pentru orice din afara sa. n acest fel, un obiect dispune de un nivel semnificativ de protecie care mpiedic modificarea accidental sau utilizarea incorect a prilor proprii obiectului de ctre seciuni ale programului cu care nu are legtur. n cele din urm, un obiect este o variabil de un tip definit de utilizator. La nceput poate s apar ciudat ca un obiect, care leag att cod ct i date, s fie considerat ca fiind o variabil. Totui, n programarea orientat pe obiecte aa stau lucrurile. Cnd se definete un obiect, implicit se creaz un nou tip de date.

Polimorfism
Limbajele de programare orientate pe obiecte admit polimorfismul, care este caracterizat prin fraza o interfa, metode multiple. Mai clar, polimorfismul este caracteristica ce permite unei interfee s fie folosit cu o clas general de aciuni. Aciunea specific selectat este determinat de 2

natura precis a situaiei. Un exemplu din practica zilnic pentru polimorfism este un termostat. Nu are importan ce combustibili este utilizat pentru nclzirea casei (gaze, petrol, electricitate, etc.) termostatul lucreaz n acelai fel. n acest caz, termostatul (care este interfaa) este acelai indiferent de combustibil (metod). De exemplu, dac temperatura dorit este de 22 de grade, se va regla termostatul la 22 de grade. Nu are importan combustibilul care produce cldura. Acelai principiu de poate aplica i programrii. De exemplu, se poate implementa un program care definete trei tipuri de memorie stiv. Una este folosit pentru valori ntregi, una pentru valori tip caracter i una pentru valori n virgul mobil. Datorit polimorfismului se pot crea trei perechi de funcii numite push() i pop() cte una pentru fiecare tip de date. Conceptul general (interfaa) este cel de a introduce i de a scoate date dintr-o memorie stiv. Funciile definesc calea specific (metoda) care se folosete pentru fiecare tip de date. Cnd se introduc date n memoria stiv, tipul de date va fi cel care va determina versiunea particular a lui push() care va fi apelat. Primele limbaje de programare orientate pe obiecte au fost interpretoarele, astfel nct polimorfismul a fost admis, desigur, n timpul rulrii. Dar C++ este un limbaj de compilare. Astfel, n C++, polimorfismul este admis att n timpul rulrii ct i n timpul compilrii.

Motenirea
Motenirea este procesul prin care un obiect poate s preia prototipul altui obiect. Acest lucru este important deoarece se admite conceptul de clasificare. Dac v gndii puin, majoritatea cunotinelor despre lumea nconjurtoare sunt accesibile deoarece sunt clasificate ierarhic. De exemplu, un cine ciobnesc face parte din clasa cine, care la rndul su face parte din clasa mamifere care se afl n marea clas animale. Fr utilizarea claselor, fiecare obiect ar trebui definit explicitnduse toate caracteristicile sale. ns, prin folosirea clasificrilor, un obiect are nevoie doar de definirea acelor caliti care l fac unic n clasa sa. Mecanismul motenirii este acela care face posibil ca un obiect s fie un exemplar specific al unui caz mai general. Animale

Mamifere

Psri

Cini

Cibnesc 3

1.2. Extensiile C++


Toate elementele limbajului C sunt valabile i n C++; atribuiri, bucle for, while, do while, instruciunile if, swich, funcii, variabile, structuri. Standardul limbajului C este ANSI C, iar C++ include i aceast standardizare . Elemente noi pe care C++ le aduce pot fi grupate n trei categorii: extensii la nivel de limbaj extensii privind o tratare superioar a tipurilor definite de utilizator. cele care ating nivelul programrii orientate pe obiecte: clasele - permit gruparea datelor si operaiilor ntr-un acelai tip de date, n vederea obinerii obiectelor; controlul accesului - accesul la elementele interioare unei clase este selectiv; clasele derivate - se obin moteniri sau reutilizri ale claselor existente, fiind posibil extinderea lor n diferite aplicaii; funciile virtuale - constituie un mecanism mult mai puternic de apelare a funciilor, acestea determinndu-se n momentul execuiei programului si nu n cel al compilrii; prietenii - permit accesul ctre elemente interzise; constructorii - rutine pentru iniializarea obiectelor; destructorii - rutine pentru distrugerea obiectelor.

2. FUNDAMENTELE LIMBAJULUI C++


2.1. Cuvintele cheie n C++
n plus fa de cele 32 de cuvinte - cheie ale limbajului C, standardul ANSI propus pentru C++ adaug nc 30. Aceste cuvinte cheie sunt prezentate n continuare: asm bool catch class const_cast delete dynamic_cast explicit false friend inline mutable namespace new operator overload private protected public reinterpret_cast static_cast template this throw true try typeid using virtual wchar_t

2.2. Tipuri de date fundamentale


Tipurile fundamentale ale limbajului C++ sunt urmtoarele: char, reprezentnd tipul caracter, pe octet, int, ntreg pe 2 octei , long ntreg pe 4 octei, float, nsemnnd un numr real pe octei double, ataat unui numr real pe 8 octei. Aceste tipuri fundamentale admit diferite variante, numite tipuri de baz de date. Tipurile de baz ale limbajului C++ sunt prezentate n tabelul urmtor: Tip signed char unsigned char char short, short int signed short unsigned short unsigned short int int, signed int unsigned int Nr. octei 1 1 1 2 2 2 2 2 2 Domeniu de valori -128.. .127 0...255 -128... 127 -32768... 32767 -32678... 32767 0... 65535 0... 65535 -32678... 32767 0... 65535 5

long, long int signed long signed long int unsigned long unsigned long int float double long double

4 4 4 4 4 4 8 10

-2147483648...2147483647 -2147483648.. .2147483647 -2 1 47483648. . .21 47483647 0... 4294967295 0... 4294967295 3.4E-38...3.4E+38 1.7E-308...1.7E+308 3.4E-4932...1.1E+4932

2.3. Tipuri derivate


Pornind de la tipurile de baz, putem obine diverse tipuri derivate. Tipurile derivate acceptate de C++ sunt: pointeri, referine, masive, enumerri, structuri, uniuni si clase, constante. 2.3.1. Pointeri Variabila este o locaie de memorie care poate gzdui un obiect, ales dintr-o mulime prestabilit de obiecte, manevrat n cadrul unui program. Mulimea este domeniul de definiie al variabilei, iar locaia este o zon de memorie capabil s memoreze orice valoare din domeniul de definiie. Referirea la o variabil se realizeaz prin: utilizarea identificatorilor, expresiile indexate, expresiile selectoare (de tipul lui nume.cmp unde nume este o variabil complex), expresiile referine. Folosirea exact a numelui simbolic necesit cunoaterea echivalentelor acestuia pe nivelul conceptual i cel al implementrii. Conceptual, legnd noiunea de variabil de existena unei locaii de memorie, apare o dubl ipostaz a variabilei: cea de pstrtoare de date si cea de data nsi, deoarece identificarea locaiei printre celelalte reprezint o informaie si, implicit, o dat. La nivelul implementrii, unei variabile i corespunde o zon din memoria calculatorului. 6

Unei variabile i corespund dou valori: stng (l-value), dat de adresa zonei de memorie (referin), dreapt (r-value), valoarea memorat n zona respectiv

Identificator l value r value

int i,j; int *pi;

id = i &i 10 &j

id = j

id = pi &pi

n cazul tablourilor, se va memora si un descriptor (ablon al elementelor memorate n acesta). 2.3.2. Referine n programele C, pointerii constituie o legtur ntre diferite variabile. n C++ exist o a doua cale de a fi n legtur cu o variabil, i anume prin intermediul referinelor. Referinele, ca i pointerii, conin adrese. Spre deosebire de pointeri, n a cror declarare era utilizat simbolul *", pentru a defini o referin vom folosi simbolul & ". int i; int *p =&i; int &r = i; // Declararea unui intreg // Definirea unui pointer la i // Definirea unei referinte la i

Atunci cnd accesm o variabil prin intermediul referinei, nu este necesar utilizarea adresei, aceasta realizndu-se automat. n exemplul urmtor, att p, ct i r acioneaz asupra lui i. i=55; *p=13; r=20; // actiune asupra lui i // actiune asupra lui i // actiune asupra lui i

Exist ns o diferen major ntre p si r, nu numai n modul de apelare, ci i datorit faptului c p poate, la un moment dat, s fie n legtur cu o alt variabil, a crei locaie de memorie o va conine, diferit de cea a lui i, n timp ce r nu i poate schimba referina, acesta nefiind altceva dect o redenumire (alias) a variabilei i. n ceea ce privete utilizarea referinelor, va trebui s inem cont de urmtoarele restricii: referinele trebuie iniializate chiar n momentul declarrii lor, odat iniializate, referinelor nu li se pot schimba locaiile la care se refer, i nu sunt permise referinte la referine si pointeri ctre referine, dar putem avea o referin la un pointer toate acestea nefiind altceva dect consecine fireti ale faptului c o referin este o adres ce nu poate fi schimbat. Referinele pot fi utilizate drept constante, pot fi iniializate cu constante, funcii sau chiar structuri. Transmiterea parametrilor prin referin n limbajul C, toi parametrii sunt transmii prin valoare. Adic, fiecrei funcii i sunt pasate valorile efective ale parametrilor. De aceea n C este de preferat s utilizm pointeri ca parametri. Prin folosirea parametrilor formali referin, se permite realizarea transferului prin referin (transmiterea adresei) de o manier similar celei din Pascal (cu parametrii VAR). Se elimin astfel necesitatea recurgerii la artificiul din C, adic utilizarea parametrilor formali pointeri, n cazul n care modificrile fcute n interiorul funciei asupra parametrilor trebuie s ramna i dup revenirea din funcie. Exemplu: void interschimba_C(int *pa, int *pb) { //varianta pentru C int aux=*pa; *pa=*pb; *pb=aux; } void interschimba_Cpp(int &a, int &b) { int aux=a; a=b; b=aux; } //apel int a=9,b=5; interschimba_C(&a,&b); interschimba_Cpp(a,b); Transferul prin referin este util i atunci cnd parametrul are dimensiune mare (struct, class) i crearea n stiv a unei copii a valorii ar reduce viteza de execuie si ar ncrca stiva, mai performant 8

fiind transmiterea prin referin; problema incrcarii stivei trebuie analizat mai ales n cazul programelor recursive. Pentru a se putea face o comparaie ntre modul de transmitere al parametrilor prin valoare, respectiv prin referin s-a considerat urmtorul exemplu n care, n cadrul funciei urmtoare, t este transmis prin referin, prin intermediul unui pointer, iar s prin valoare. void copiaza1 (int *t, int s)
{ *t=s; }

Utilizarea pointerilor pentru a obine apelul de referin pare oarecum artificial. n C++ putem s utilizm chiar argumente de tip referin. Un astfel de argument este un parametru al crui tip este referin ctre o variabil. O alt variant pentru funcia anterioar este urmtoarea: void copiaza2 (int &t,int s) { t=s; } n aceast variant, t este un parametru de tip referin. Exist diferene ntre apelul cu pointeri i apelul cu referine, n cel din urm caz fiind utilizat operatorul de aparen . i nu ->, ca n cazul pointerilor. Bineneles c putem utiliza oricare dintre cele dou metode, dar se pare c funciile utiliznd referinele sunt mai clare la apelare. int i ; copiaza1 (&i, 25) ; copiaza2 (i, 25) ; Returnarea de referine O interesant aplicaie a referinelor const n utilizarea acestora la ieirea din funcii i returnarea lor n locul tipurilor cunoscute. S considerm, mai nti, un exemplu n care o funcie returneaz tipul referin. Urmtoarea funcie preia un ir de caractere i returneaz o copie a celui de al n lea element al su. char element (char *s, int n) { return s[n]; } // Se transmite adresa lui i // Nu se mai transmite adresa

Putem utiliza funcia de mai sus n urmtorul context: char c ; char *s=Borland C++; c=element (s,8); Funcia element() poate fi extins n vederea verificrii dimensiunii vectorului i evitarea utilizrii unui index n afara domeniului permis. n plus, deoarece returnm numai o copie a celui de al n-lea caracter, nu putem modifica valoarea acestuia i actualiza irul de caractere. De exemplu, codul urmtor char c; char *s=Borland C++; c=element(s,8); c=A; nu modific pe s n Borland A++, deoarece c este doar o copie a celui de al 8-lea element a lui s i nu acioneaz asupra acestuia. Putem corecta aceast situaie prin modificarea valorii returnate a funciei. char *element(char *s,int n) {return &s[n];} iar acum putem scrie char *c; char *s=Borland C++; c=element (s,8); *c=A; // rezult s=Borland A++ Putem folosi urmtoarea variant: *element (s,8)=A; Exist o variant chiar mai elegant. n locul returnrii unui pointer, vom returna o referin. Acest proces este ilustrat n figura urmtoare. Tipul returnat: Operatorul referin char & char &element(char *s,int n) { return s [n] ; } 10 Returneaz adresa lui s[n]

Funcia element() rescris utiliznd tipul referin pentru valoarea de retur este prezentat n continuare: char &element (char *s, int n) { return s[n]; } element (s,8)=A; Cu toate c instruciunea de revenire pare s transmit o copie a celui de al n-lea element al irului, datorit faptului c tipul returnat este o referin, putem asigna o valoare rezultatului funciei, aciune actualiznd irul transmis ca parametru efectiv, aa cum am vzut mai nainte. 2.3.3. Masive Masivele de date sau tablourile, din rndul crora provin vectorii si matricile, sunt tipuri de date foarte apropiate pointerilor si referinelor. Vom vedea c orice operaie care poate fi rezolvat prin indexarea tablourilor poate fi rezolvat si cu ajutorul pointerilor. Versiunea cu pointeri va fi, n general, mai rapid dar, pentru nceptori, mai greu de neles imediat. Astfel, declaraia char a[80];

Definete a ca fiind un ir de 80 de caractere si, n acelai timp, a va constitui un pointer la caracter. Dac pc este un pointer la un caracter, declarat prin char *pc;

atunci atribuirea pc=&a[0]; face ca pc s refere primul element al tabloului linie (de indice zero); aceasta nseamn c pc conine adresa lui a[0]. Acum, atribuirea char c=*pc; va copia coninutul lui a[0] n c. 11

Din moment ce numele unui tablou este sinonim cu locaia elementului su zero, asignarea pc=&a[0]; poate fi scris i pc=a; Evalund elementul a[i], C++ l convertete n *(a+i), cele dou forme fiind echivalente. Aplicnd operatorul & ambilor termeni ai acestei echivalene, rezult c &a[i] este identic cu a+i; a+i fiind, deci, adresa elementului i din tabloul a. Reciproc: dac pc este un pointer, el poate fi utilizat n expresii cu un indice, pc[i] fiind identic cu *(pc+i). 2.3.4. Enumerri Tipurile enumerate sunt introduse prin sintaxa enum nume {membrul,membru2,...} var1, var2,...; De exemplu, enum CULORI {ROSU,VERDE,ALBASTRU} enum CULORI penita, culoare_fond; culoare_punct,culoare_linie;

definete tipul de date CULORI si declar variabilele culoare_punct si culoare_linie, urmate de declarrile a nc dou variabile, penita si culoare_fond. n limbajul C++, numele ce urmeaz cuvntului enum este chiar numele tipului de dat i nu o etichet de tip, aa cum se ntmpl n limbajul C. Deci, dac nu exist riscul apariiei confuziilor, este permis a se declara variabile i prin sintaxa urmtoare: CULORI cerneala;

Membrii unui tip enumerat sunt numai de tip ntreg. Valoarea fiecruia este obinut prin incrementarea cu 1 a valorii membrului anterior, primul membru avnd, implicit, valoarea 0. Este permis iniializarea unui membru cu o valoare oarecare, avndu-se n vedere c doi membri ai

12

aceluiai tip nu pot avea aceeai valoare. Valorile membrilor urmtori se vor stabili conform regulilor menionate. enum ANOTIMP {IARNA=1, PRIMAVARA, VARA, TOAMNA}; enum BOOLEAN (fals,adevarat} conditie; enum DIRECTIE {UP,DOWN,RIGHT,LEFT,NONE=0}; // ilegal De asemenea, putem defini tipuri enumerate fr a specifica numele acestora. Procednd astfel, putem grupa un set de constante fr a denumi acea mulime. enum {bine,foarte_bine,cel_mai_bine}; n ceea ce privete utilizarea variabilelor de tip enumerat, att limbajul C, ct si C++ permit atribuiri de tipul conditie=0, dar, C++ fiind mult mai orientat pe tipul de data, acest tip de atribuiri genereaz avertismente din partea compilatorului. De aceea este bine ca astfel de atribuiri s fie nsoite de conversia de tip corespunztoare. conditie=fals; conditie=(enum BOOLEAN) 0; Observaie: Enumerrile definite n interiorul structurilor C++ nu sunt vizibile n afara acestora. Prin structuri, ne referim att la structurile introduse prin cuvintele cheie struct si union, ct, mai ales, la cele definite prin class. 2.3.5. Structuri i uniuni O structur este o colecie de date, eventual de tipuri diferite, i care pot fi referite att separat, ct i mpreun. Definirea unei structuri se realizeaz cu ajutorul cuvntului cheie struct. struct punct { float x,y;} p; Astfel am definit p ca fiind de tip punct, punctul fiind compus din dou elemente, x si y, reale. Asupra componentelor unei structuri putem aciona prin intermediul operatorului de apartenen, .". p.x=10; p.y=30; Exist posibilitatea efecturii de operaii cu ntreaga structur, atribuirea fiind una dintre ele:

13

p={10,30}; O declaraie de structur care nu este urmat de o list de variabile nu produce alocarea memoriei, ci descrie organizarea structurii. Odat definit o structur, aceasta va constitui un nou tip de data, putndu-se defini continuare pointeri la acea structur, masive ale cror elemente sunt de tipul acestei structuri i, chiar mai mult, elemente de acest tip pot interveni n definirea altor structuri. n cazul definirii unui pointer la o structur, accesul la componentele acelei structuri se va efectua prin expresii de forma: punct *pptr; pptr->x=10; (*pptr).y=30; parantezele neavnd dect rolul de a indica ordinea n care acioneaz cei doi operatori, * si .", prioritar fiind *". Unele elemente ale unei structuri pot fi cmpuri de bii. Un cmp de bii este o configuraie de bii adiaceni, ce apar ntr-un element de tip int. Cmpurile sunt declarate de tip unsigned, iar numele cmpului este urmat de dou puncte :" i un numr ce reprezint numrul de bii ocupai de cmpul respectiv: unsigned nume_camp:nr_biti; Cmpurile pot fi accesate ca orice alt element de structur. Orice cmp trebuie s aib toi biii n interiorul unei zone de tip int (nu poate avea bii n dou cuvinte diferite). Un caz special de structuri l constituie uniunile. Acestea sunt structuri alternative pentru care dimensiunea spaiului necesar memorrii lor este egal cu cea mai mare dimensiune necesar memorrii unei componente a acelei structuri. Astfel, toate componentele uniunii ocup aceeai zon n cadrul memoriei. Tocmai de aceea, la un moment dat, spre deosebire de structuri, este accesibil o singur component a unei uniuni. Uniunile se definesc n aceeasi manier ca i structurile, cuvntul cheie utilizat fiind union. union un_tip { int uint; float ufloat; char uchar; 14

}; un_tip o_variabila; Dac, la un moment dat, variabila o_variabila are ncrcat elementul uint, presupunem cu valoarea 15897, atunci accesul ctre celelalte dou componente este dac nu ilegal, cel puin lipsit de sens. i aceasta deoarece n spaiul rezervat n memorie variabilei respective se afl un numr ntreg i deci, nu are sens s cutm caracter sau un numr real. n timp ce, n C o structur sau o uniune nu poate conine dect cel mult pointeri la funcii, n C++ aceasta poate conine chiar funcii. struct punct { float x,y; void set (float,float); }; Un alt tip de date structurat, specific C++ este tipul class, care va fi prezentat ulterior.

2.4. Parametrii cu valori implicite


C++ ofer posibilitatea declarrii funciilor cu valori implicite ale parametrilor. La apelarea unei astfel de funcii se poate omite specificarea parametrilor efectivi pentru acei parametri formali care au declarate valori implicite i se transfer automat valorile respective. Se pot specifica mai multe argumente cu valori implicite pentru o funcie. Este obligatoriu ca numai ultimele argumente sa aib astfel de valori i nu este permis alternarea argumentelor cu i fr valori implicite. Exemplu: void fct(int, int=10); void tipareste(int i){ printf("%d ",i); } void fct(int p1, int p2){ tipareste(p1); tipareste(p2); } void main() { fct(1,2); 15

fct(3); }

//implicit

al

doilea

parametru

actual

este

10

Rezultatul rulrii acestui program va fi tiprirea numerelor: 1 2 3 10

2.5. Funcii inline


Exist situaii n care se utilizeaz expresiile macro n vederea simulrii apelurilor funciilor. Iat o situaie posibil: #define INC(i) i++ ... k=INC(j); Iar problemele vin tocmai din faptul c aceste apeluri seamn cu apelul funciilor, dar nu sunt funcii. Parametrii expresiilor macro sunt nlocuii inline atunci cnd o astfel de expresie este ntlnit de ctre compilator. n schimb, parametrii funciilor sunt verificai din punct de vedere al tipului i pasai utiliznd scopul fiecruia. n vederea rezolvrii unor astfel de situaii, C++ a fost nzestrat cu funciile inline. O funcie inline este o combinaie ntre expresiile macro i funcii. Asemeni expresiilor macro, apelul unei funcii inline este expandat prin nlocuirea apelului su cu corpul corespunztor implementrii funciei. Spre deosebire de expresiile macro ns, se execut verificarea tipurilor parametriilor, acetia din urm fiind transmii ca unei funcii normale. Putem declara orice funcie inline prin utilizarea cuvntului cheie inline, exact naintea definiiei funciei. S contruim funcia inline inc(). inline int inc(int n) { return n++; } Observai c am plasat cuvntul inline naintea tipului returnat de ctre funcie. Iat acum i apelul funciei. int i ; 16

i=inc(3+5); n momentul apelrii funciei inc(), compilatorul expandeaz funcia inline, dar nu nainte de a aduna 3 cu 5 i a depune rezultatul ntr-o variabil temporar. Rezultatul acestor adunri este apoi incrementat i atribuit lui i. Un cod echivalent cu apelul anterior ar putea fi: int temp=3+5; i=temp++; Utiliznd cuvntul inline, nu putem fi siguri c funcia creia i-am ataat acest cuvnt va fi considerat de ctre compilator astfel. Asemeni cuvntului register, inline constituie mai degrab o recomandare fcut compilatorului, noi considernd c funcia este suficient de mic pentru a fi expandat inline. Compilatorul poate s ia n considerare sau nu aceast recomandare. De asemenea, unele compilatoare nu pot introduce inline toate tipurile de funcii. De exemplu, n general, un compilator nu o face pentru o funcie recursiv. Un alt exemplu este prezentat n programul urmtor, n care funcia max() este dezvoltat inline n loc s fie apelat. #include <iostream.h> inline int max(int a, int b) { return a>b ? a : b; } void main() { cout << max(10, 20); cout << " " << max(99, 88); } n ceea ce privete compilatorul, programul precedent este echivalent cu acesta : #include <iostream.h> void main() { cout << (10>20 ? 10 : 20); cout << " " << (99>88 ? 99 : 88); 17

} Motivul pentru care funciile inline sunt o facilitate n plus n C++ este c ele permit crearea unor coduri foarte eficiente. De vreme ce pentru clase este tipic ca, deseori, s solicite executarea frecvent a funciilor de interfa (care asigur accesul la datele particulare), eficiena acestor funcii este o cerin esenial n C++. Dup cum se tie, la fiecare apelare a unei funcii se genereaz o cantitate de munc suplimentar prin mecanismul de apelare i returnare. n mod normal, cnd este apelat o funcie, argumentele sunt puse n memoria stiv i sunt salvate mai multe registre i apoi rememorate cnd funcia se returneaz. Problema este c aceste instruciuni iau timp. Dar, cnd o funcie se dezvolt inline, nu mai apare nici una din aceste operaii. Chiar dac dezvoltarea inline a funciilor poate determina timpi de rulare mai scuri, deseori rezult dimensiuni mai mari de coduri datorit instruciunilor duplicate. Din acest motiv, este bine s introducem inline doar funcii foarte mici. Mai mult, o idee bun este, de asemenea, s dezvoltai inline doar funciile care vor avea un impact mare asupra performanelor programului dvs.

2.6. Rescrierea funciilor (Funcii suprancrcate overloaded)


n C++ este permis utilizarea mai multor funcii avnd aceleai nume, asemenea funcii purtnd numele de funcii redefinite (rescrise, overloaded sau suprapuse). Pentru a vedea de ce sunt importante funciile suprancrcate, s considerm pentru nceput trei funcii care se gsesc, probabil, n biblioteca standard a tuturor compilatoarelor de C/C++ : abs(), labs() i fabs(). Funcia abs() returneaz valoarea absolut a unui ntreg, labs() returneaz valoarea absolut a unei variabile float, iar fabs() pe cea a unei variabile double. Chiar dac aceste funcii efectueaz aciuni aproape identice, n C trebuie s fie folosite toate trei numele, puin diferite, pentru a reprezenta aceste sarcini, similare n esen. Conceptual, situaia devine mai complex dect este de fapt. Dei ideea de baz a fiecrei funcii este aceeai, programatorul trebuie s-i aminteasc trei lucruri, nu doar unul singur. n C++, ns, putei folosi doar un singur nume pentru toate trei funciile, aa cum ilustreaz urmtorul program : #include <iostream.h> //abs este definita in trei feluri int abs (int i); double abs (double d); 18

long abs (long l); void main () { cout << abs(-10) << "\n"; cout << abs(-11.0) << "\n"; cout << abs(-9L) << "\n";

} int abs (int i) { cout << " utilizarea abs() pentru intregi \n"; return i<0 ? -i : i; } double abs (double d) { cout << "utilizarea abs()pentru double \n"; return d<0 ? -d : d; } long abs (long l) { cout << "utilizarea abs()pentru long \n"; return l<0 ? -l : l; } Acest program creeaz trei funcii asemntoare, dar totui diferite, numite abs(), fiecare din ele returnnd valoarea absolut pentru argumentul su. Compilatorul tie ce funcie s apeleze n fiecare situaie datorit tipului argumentului. Importana funciilor suprancrcate este aceea c ele permit ca seturi de funcii nrudite s fie accesibile printr-un singur nume. De aceea numele abs() reprezint 19

aciunea general care este efectuat. Este lsat pe seama compilatorului s aleag metoda particular corect pentru situaii specifice. Programatorul trebuie s-i aminteasc doar aciunea general care trebuie efectuat. Datorit polimorfismului, se reduce numrul de lucruri care trebuie reinute de la trei la unul. Acest exemplu este aproape banal, dar dac extindei conceptul, putei vedea cum polimorfismul poate s v ajute s tratai programele foarte complexe. n general, pentru a suprancrca o funcie, se declar pur i simplu diferite versiuni ale ei. De restul are grij compilatorul. La suprancrcarea unei funcii, trebuie reinut o restricie important: tipul i/sau numrul de parametrii al fiecrei funcii suprancrcate trebuie s difere. Nu este suficient pentru dou funcii s difere doar prin tipul returnat de ele. Ele trebuie s difere prin tipul sau numrul de parametrii (tipul returnat nu asigur suficiente informaii n toate cazurile pentru ca un compilator s decid ce funcie s foloseasc). Desigur, funciile suprancrcate pot s difere i prin tipul returnat. Un alt exemplu este urmtorul: s considerm trei funcii aduna(), fiecare pentru cte unul dintre tipurile, int, double i char * (iruri de caractere). #include <stdio.h> #include <string.h> int aduna (int a, int b) { return a+b; } double aduna(double a, double b) { return a+b; } char * aduna(char *a, char *b) { strcat(a,b); return a; } void main( ) { int i =aduna(42,17); double d=aduna(42.0,17.0); char s1[80]= C++; char s2[80]=Este orientat obiect!; printf (\ni=%d \nd=%f \n%s\n,i, d, aduna (s1,s2)); } Nu este neaprat nevoie s specificm acelai numr de parametri ntr-un set de funcii redefinite. De exemplu, putem scrie nc o funcie aduna(), care s accepte un singur parametru, fr a fi considerat ca fiind o funcie eronat:

20

int aduna(int i) { return i+42;

21

3. PROGRAMAREA N C++
Deoarece C++ este mai mult dect C, se pot scrie programe n C++ care s arate exact ca i cele din C. Totui, procednd n acest fel, se pierd toate avantajele limbajului C++. Astfel, cei mai muli programatori n C++ folosesc stilul i anumite caracteristici care sunt unice n C++. Majoritatea diferenelor de stil ntre programele C i cele n C++ se regsete n avantajul pe care l are C++ prin capacitile de orientare pe obiecte. Dar un alt avantaj al folosirii stilului de programare propriu C++ este acela c ajut programatorul s gndeasc n C++, i nu n C (asta nseamn c, adoptnd un stil diferit cnd scriei coduri n C++, v vei spune c nu mai gndii n C i s ncepei s gndii n C++). Deoarece este important pentru un programator s nvee s scrie programe n C++ care s arate ca fiind n C++, acest paragraf introduce cteva din caracteristicile necesare. Examinai acest program n C++ : # include <iostream.h> void main() { cout << Iat ieirea.\n; //un comentariu de o linie /*se pot folosi comentariile n stilul C */ int i; // se introduce un numr folosind >> cout << introducei un numr:; cin >> i; // se scrie un numr folosind << cout << i << la ptrat este << i*i << \n ; } Dup cum se observ, acest program arat mult mai diferit fa de programele C standard. La nceput este inclus fiierul antet iostream.h. El este definit de C++ i folosit pentru a admite operaiile de I/O n stilul C++ (iostream.este pentru C++ ceea ce este stdio.h pentru C). O diferen semnificativ fa de C standard se afl pe linia aceasta : cout << Iat ieirea.\n; //un comentariu de o linie Aceast linie introduce dou noi caracteristici din C++. Prima, instruciunea cout << Iat ieirea.\n; 22

face s fie afiat pe ecran textul Iat ieirea., urmat de o combinaie nceput de rnd/linie nou. n C++, << are un rol extins. El este nc operatorul de deplasare la stnga, dar, cnd este folosit ca n exemplul prezentat, este, totodat, un operator de ieire. Cuvntul cout este un specificator n legtur cu ecranul (de fapt, ca i C, C++ admite indirectarea I/O, dar aici, cout se refer la ecran). Se poate utiliza cout i << pentru a produce la ieire orice tipuri de date ncorporate n C++, precum i iruri de caractere. De reinut este faptul c se poate utiliza n continuare printf() sau oricare alte funcii de I/O din C n programele C++. Dar cout << este mult mai aproape de stilul C++. Mai mult, chiar dac folosirea funciei printf() pentru ieirea irului este virtual echivalent n acest caz cu utilizarea operatorului <<, sistemul de I/O din C++ poate s se extind pentru a efectua operaii asupra obiectelor pe care le definete programul (ceea ce nu poate face printf()). Ceea ce urmeaz expresiei pentru ieire este un comentariu tip C++. n C++ comentariile sunt definite n dou moduri. n primul rnd se poate folosi un comentariu tip C, care lucreaz n C++ la fel ca n C. ns, n C++ se poate defini un comentariu cu o singur linie folosind //. Cnd se ncepe un comentariu cu //, orice urmeaz este ignorat de compilator pn la sfritul liniei. n general, programatorii n C++ folosesc comentariile de tip C cnd creeaz comentarii cu mai multe linii iar pe cele de tip C++ cnd este suficient una singur. n continuare, programul solicit utilizatorului un numr. Numrul este citit de la tastatur cu urmtoarea instruciune : cin >> i; n C++ operatorul >> i mai menine semnificaia de deplasare la dreapta. Dar cnd este utilizat ca n exemplul precedent, el este i operatorul de intrare pentru C++. Aceast instruciune determin ca lui i s i se dea o valoare citit de la tastatur. Specificatorul cin se refer la echipamentul de intrare standard, care, de obicei, este tastatura. n general, putei s folosii cin>> pentru a introduce o variabil de oricare tip de baz, inclusiv una de tip ir.

3.1. Operatorii de I/O


Cum s-a mai spus, cnd sunt folosii pentru I/O, operatorii << i >> sunt capabili s manevreze orice tip de date ncorporat n C++. De exemplu, acest program introduce o variabil float, una double i un ir, apoi le afieaz : # include <iostream.h> 23

void main() { float f; char sir[80]; double d; cout << Introduceti doua numere in virgula mobila:; cin >> f >> d; cout << Introduceti un sir:; cin >> sir; cout << f << << d << << sir; } Cnd rulai acest program, ncercai, la solicitarea unui ir, s introducei Acesta este un test. Cnd programul va reafia informaia pe care ai introdus-o, va fi afiat doar cuvntul Acesta. Restul irului nu este afiat deoarece operatorul >> lucreaz cu irurile n acelai fel n care o face i specificatorul %s pentru scanf(). El ncheie citirea intrrii cnd este ntlnit primul caracter de spaiu liber. De aceea, este un test nu va fi citit niciodat de ctre program. Acest program, ilustreaz, de asemenea, faptul c se pot nirui cteva operaii de intrare ntr-o singur instruciune.

3.3. Declararea variabilelor locale


O alt diferen ntre cum scriei codul de C i cel de C++ este locul n care pot fi declarate variabilele locale. n C, trebuie s declarai toate variabilele locale dintr-un bloc la nceputul acestuia. Nu putei s declarai o variabil local ntr-un bloc dup ce a aprut o instruciune de aciune. De exemplu, n C, acest fragment de cod este incorect : /* Incorect in C. Corect in C++. */ void functie() { int i,k; for k = int j = . . (i=0;i<10;i++){ i+1; j; /* nu va fi compilat ca program in C */ i*2;

24

. } } Deoarece o instruciune de aciune precede declararea lui j, un compilator C va afia o eroare i va refuza s compileze aceast funcie. Dar, n C++, acest fragment este perfect acceptabil i va fi compilat fr eroare. n C++ se pot declara variabile n orice punct dintr-un bloc nu doar la nceput.

Forma general a unui program n C++


Chiar dac stilurile individuale difer, majoritatea programelor n C++ vor avea aceast form general : # include declaraii clase de baz declaraii clase derivate prototipuri de funcii nemembre main() { . . . } definiii de funcii ne membre n majoritatea proiectelor mari, toate declaraiile pentru class vor fi puse ntr-un fiier antet i incluse n fiecare modul.

25

4. PREZENTAREA CLASELOR C++


n mod obinuit, orice program C++ este compus din dou pri distincte: datele (de intrare i/sau ieire) i operaiile ce acioneaz asupra acestora. Este evident c se va specifica modul n care se va realiza reprezentarea datelor, prin intermediul tipurilor de date i modalitile de acionare asupra datelor, prin intermediul funciilor. Pn n prezent, aceste dou elemente, care sunt inseparabile i, n aceeai msur, vitale pentru un program, au fost tratate separat. n cele ce urmeaz vom vedea cum se poate obine o tratare unitar a acestora. O caracteristic important a unui puternic limbaj de programare este aceea de a oferi programatorului posibilitatea de a-i construi tipuri de date diferite de cele standard, acestea numindule user defined, deci tipuri definite de utilizator. Limbajul C posed acest caracteristic i i-o manifest prin intermediul cuvintelor cheie struct i typedef. Odat intrai n lumea tipurilor abstracte de date, obinem echivalena celor dou elemente, datele i operaiile. Cuvntul cheie class este cel care permite definirea tipurilor abstracte de date. O clas reunete datele i operaiile n cadrul aceluiai tip de dat. n plus, exist posibilitatea protejrii elementelor componente, att date ct i funcii, nu n vederea necunoaterii acestora de ctre utilizatorii ulteriori, ci mpotriva distrugerii lor accidentale (cunoscut sub denumirea de ncapsulare). Asfel, putem scrie: Clas=Date+Operaii.

4.1. Un exemplu de tip abstract de date


Presupunem c dorim s implementm o structur de date numit Persoana. Implementnd acest tip de dat, n limbajul C obinem: #include <stdio.h> #include <conio.h> struct Persoana { int varsta; char nume [40]; long int telefon; }; void Scrie(Persoana *p) { printf("Numele: %s\n", p->nume); 26

printf("Varsta: %3d Telefonul: %ld\n", p->varsta,p->telefon); getch( ); } void Citeste(Persoana *p) { printf("Numele:"); scanf("%s", p->nume);fflush(stdin); printf("Varsta: "); scanf("%d",&p->varsta);fflush(stdin); printf("Telefonul: "); scanf("%ld",&p->telefon);fflush(stdin); } void main () { Persoana p; Citeste(&p); Scrie(&p); } Se poate remarca ct de puin comunic datele i funciile n cadrul acestui scurt program, chiar dac ele au fost utilizate pentru a da natere unui acelai obiect, p. i la fel se prezint lucrurile cu orice element n care nu sunt ncapsulate.

4.2. Un exemplu de clas


Abordnd aceeai problem prin intermediul claselor, vom defini un nou tip de dat care va reuni att datele, variabile de instaniere, ct i funciile funcii membre. Pentru a crea un obiect n C++ trebuie definit mai nti forma sa general folosind cuvntul cheie class. O class este similar sintactic cu o structur. #include <stdio.h> #include <conio.h> #include <string.h> class Persoana { public: char nume[40]; long int telefon; int varsta; void Scrie ( ); int Citeste ( ); };

27

void Persoana:: Scrie ( ) { for(int i=0;i<80;i++) printf("#"); printf("\nNumele:%40s",nume); printf("Telefonul :%ld\n", telefon); printf("\nVarsta:%d,varsta); } int Persoana :: Citeste( ) { clrscr( ); printf("Numele: "); if (scanf ("%s",nume)) { fflush(stdin); printf("Telefonul: "); scanf("%ld",&telefon); fflush(stdin); printf("\nVarsta:); scanf("%d",&varsta); return 1; } fflush (stdin); return 0; } void main ( ) { Persoana p; p.Citeste ( ); p.Scrie ( ); } Ca observaie definiia unei clase const din dou pri: declaraia i implementarea sa. Declaraia este o niruire a elementelor componente ale unei clase (date i funcii). Implementarea const n acea seciune n care se implementeaz funciile prezentate ca aparinnd clasei. Fiind membre ale unui tip de date, accesul la aceste elemente, ca la oricare astfel de element, se realizeaz prin intermediul operatorului de apartenen .. Notm utilizarea cuvntului cheie public n cadrul declaraiei clasei. Acesta indic gradul de libertate de acces la elementele componente ale clasei. Prin public, toate elementele sunt disponibile n exteriorul clasei, o astfel de clas fiind asemntoare tipului de date introdus de cuvntul cheie struct. O alt observaie se poate face cu privire la utilizarea funciei fflush(stdin). n acest context funcia a fost utilizat pentru a goli bufferul tastaturii astfel nct s se poat efectua citirile de la tastatur corect. Urmtoarea clas definete un tip numit stiva, care este utilizat pentru a crea o memorie stiv : # define SIZE 100

28

// Aceasta creeaza clasa numita stiva. class stiva { int stiv[SIZE]; int top; public: void init(); void push(int i); int pop(); } ; O clas poate conine att seciuni particulare ct i publice. Implicit, toate elementele definite ntr-o clas sunt particulare. De exemplu, variabilele stiv i top sunt particulare. Aceasta nseamn c o funcie care nu face parte din acea clas nu poate avea acces la ele. Iat unul din modurile de realizare a ncapsulrii accesul la anumite elemente poate fi strict controlat pstrndu-le particulare. Chiar dac nu apare n exemplu, se pot defini de asemenea, funcii particulare, care apoi pot fi apelate doar de ali membri ai clasei. Pentru a crea pri publice ale unei clase (accesibile altor pri ale programului), trebuie s fie declarate dup cuvntul cheie public. La toate variabilele sau funciile definite dup public pot s aib acces toate celelalte funcii din program. n esen, restul programului are acces la un obiect prin funciile sale publice. Ar trebui menionat acum c, dei pot exista ntr-o clas variabile publice, teoretic ar trebui s fie limitat sau eliminat utilizarea lor. Ar trebui s fie toate datele particulare iar accesul pentru a le controla s fie permis prin funcii publice. De reinut: cuvntul cheie public este urmat de dou puncte. Funciile init(), push() i pop() sunt denumite funcii membre, deoarece ele fac parte din clasa stiva. Variabilele stiv i top sunt denumite variabile membre (sau date membre). Doar funciile membre au acces la membrii particulari ai clasei n care sunt declarate. De aceea, doar init(), push() i pop() pot s aib acces la stiv i top. O dat ce a fost definit o clas, se poate crea un obiect de acel tip folosind numele clasei. n esen, numele clasei devine un nou specificator de tip de date. De exemplu, linia urmtoare creeaz un obiect numit stivamea de tip stiva. stiva stivamea; Se pot, de asemenea, crea variabile cnd este definit o clas punnd numele lor dup nchiderea acoladei, exact n acelai fel n care se lucreaz cu o structur. n concluzie n C++ class creeaz un nou tip de date care pot fi folosite pentru a construi obiecte de acelai tip. De aceea, un obiect este un exemplar (o instaniere) al unei clase exact n acelai fel n 29

care alt variabil este, de exemplu, un exemplar al tipului de date int. Altfel spus, o clas este o abstractizare logic, iar un obiect este real (asta nseamn c un obiect exist n memoria calculatorului). Forma general a declarrii unei clase este : class nume-clas { date i funcii particulare public : date i funcii publice } lista de obiecte Desigur, lista de obiecte poate fi vid. n interiorul declaraiei pentru stiva, elementele de tip funcie au fost specificate folosindu-se prototipurile lor. n C++, toate funciile trebuie s aib prototipuri. Ele nu sunt opionale.

4.3. Declararea funciilor membre


Faptul c o funcie aparine unei clase, deci este funcie membr a clasei, este specificat prin plasarea prototipului funciei n cadrul declaraiei clasei respective. n mod normal corpul funciei va fi definit n afara clasei, aceasta realizndu-se prin intermediul operatorului de scop ::, aa cum se observ i n exemplul anterior. O alt caracteristic a funciilor membre, i vom vedea c i a operatorilor, este aceea de a accepta mai multe implementri n cadrul aceleiai clase, fr a exista pericolul ambiguitii. Un exemplu de program n care se utilizeaz dou funcii cu acelai nume este urmtorul: #include <stdio.h> #include <conio.h> #include <string.h> class Persoana { public: char nume[40]; long int telefon; void Scrie ( ); int Citeste ( ); int Citeste (char *n, long t=0); }; void Persoana:: Scrie ( ) { printf("\nNumele: %s",nume); 30

printf("\nTelefonul: %ld\n", telefon); } int Persoana :: Citeste( ) { clrscr( ); printf("Numele: "); if (scanf ("%s",nume)) { fflush(stdin); printf("Telefonul: "); scanf("%ld",telefon); fflush(stdin); return 1; } fflush (stdin); return 0; } int Persoana:: Citeste (char *n, long t) { strcpy (nume,n); telefon=t; return 1; } char *Nume="Ionescu Vasile"; long Telefon=897867; void main ( ) { Persoana p; int t ; p.Citeste (Nume, Telefon); p.Scrie ( ); } n C++ clasele formeaz baza programrii orientate pe obiecte. Caracteristic este faptul c o clas este folosit pentru a defini natura unui obiect. De fapt, n C++ clasa este unitatea de baz pentru ncapsulare. 4.3.1. Definirea funciilor inline ntr-o clas Este posibil definirea unor funcii scurte ntr-o declarare de clas. Cnd o funcie este definit ntr-o declarare a unei clase, ea este automat transformat ntr-o funcie inline (dac este posibil). Nu este necesar (dar nici greit) s precedai declararea sa cu cuvntul cheie inline. De exemplu, programul urmtor conine definirile pentru init() i arata() incluse n declararea clasei clasamea.

31

#include <iostream.h> class clasamea { int a, b; public: // inline automat void init(int i, int j) {a=i; b=j;} void arata() {cout <<a<< " " << b << "\n";} }; void main() { clasamea x; x.init(10, 20); x.arata(); } De reinut forma codului funciei din clasamea. Deoarece funciile inline sunt, de obicei, scurte, acest stil de codare din cadrul unei clase este aproape tipic. Totui, se poate folosi orice form. Practic, dezvoltarea inline a funciei arata() este fr sens deoarece (n general) timpul luat de instruciunea de I/O va depi mult pe cel al apelrii unei funcii. Totui, este foarte uzual n C++ ca toate funciile membre s fie definite n interiorul clasei lor. Se pot introduce definiii complete de funcii n interiorul clasei (deci funcii inline doar dac acestea sunt suficient de scurte i nu conin bucle. O explicaie a prezenei a acestor funcii poate fi dat gndindu-ne la faptul c aceste funcii sunt expandate de ctre compilator n expresii macro, deci n acel moment trebuie cunoscut corpul funciei, att ca form (lucru care este ndeplinit), ct i din punct de vedere al coninutului (condiie care nu poate fi ndeplinit n cazul utilizrii buclelor). 4.3.2. Durata vieii unei funcii membre Ca orice funcie sau variabil, elementele i funciile membre ale unei clase au domenii de valabilitate (scop) bine delimitate. Aa cum elementele unei structuri (struct sau union) erau recunoscute numai ataate unei variabile de tip structurat, tot aa i elementele membre ale unei clase, n particular funciile, vor fi recunoscute numai n interiorul clasei. Deseori funciile membre deschid accesul spre elementele clasei. De exemplu, elementele nume i telefon sunt accesate de funciile Citeste() i Scrie(). n exemplul nostru, aceste elemente membre ale clasei pot fi accesate i n mod direct, utilizndu-se n acest scop operatorul de apartenen ., avnd sens s scriem, de exemplu: p.telefon=799086; p.Citeste();p.Scrie(); 32

deoarece telefon este o dat membr public.

4.4. Pointerul this


Revenind la cele dou implementri ale aceleiai clase, i anume Persoana, una structurat i cealalt orientat pe obiecte, putem observa nc o diferen de form. n timp ce prima se bazeaz pe transmiterea parametrilor prin adres, cea de a doua pare s renune la acest tip de apelare. n realitate ns, lucrurile nu stau aa. Fiecare funcie membr posed un argument ascuns, numit this, care este pasat automat funciei de compilator. Acest argument nu este altceva dect un pointer ctre obiectul care cheam funcia. Astfel, dac ar trebui s scriem o form echivalent a unei funcii membre, n limbajul C, aceasta va fi: void Scrie (struct Persoana *this) { printf(\nNumele :%s\n, this->nume); printf (Telefonul:%ld\n, this->telefon); } Spre deosebire de pointerii utilizai n mod uzual, acesta realizeaz uniunea dintre membrii unei clase i funciile care acioneaz asupra acestora, deci funciile membre. nelegerea modului de acionare a acestui pointer este util n nelegerea modului de funcionare a funciilor membre. Utilizarea pointerului this Cu toate c utilizarea explicit a acestuia este rar ntlnit, acesta poate fi menionat n cadrul unei funcii membre: void Persoana:: Scrie ( ) { printf(\nNumele:%40s,this->nume); printf(Telefonul :%ld\n,this-> telefon); } utilizarea sa, n acest caz, fiind, de fapt, redundant. Exist ns situaii n care se impune utilizarea pointerului this. O astfel de situaie este cea n care unei funcii membre a unui obiect trebuie s-i transmitem adresa obiectului respectiv, implementarea unei stive fiind unul dintre cazurile care fac apel la aceste elemente.

33

struct informaie { void actiune(); }; void push (informatie *inf_ptr); void informatie ::actiune( ) { // se executa o actiune asupra informatiei // urmata de salvarea acesteia push (this); }

4.5. Ascunderea informaiei


ncapsularea presupune mult mai mult dect simpla reuniune dintre date i funcii, ea invoc i fenomenul de ascundere a informaiei. Acest lucru este posibil prin limitarea accesului la datele membre ale unei clase, uneori chiar funcii, acestea fiind componente a cror comportare poate s rmn ascuns exteriorului clasei, interesnd numai efectul lor sau intervenind n diferite aciuni caracteristice clasei respective. Este sugestiv exemplul unui ceas. Cu toate mecanismele sale complicate acesta este uor de mnuit, noi avnd acces numai la cteva butoane, deci acionnd asupra ctorva elemente. Acestea la rndul lor, provoac declanarea unui proces care se va finaliza prin modificarea orei, respectiv a datei. Nu ne intereseaz structura intim a obiectului. Revenind la exemplul utilizat pn acum, s vedem o variant de ascundere a datelor. class Persoana { private : char nume [40]; long int telefon; public: void Scrie ( ); int Citeste ( ); int Citeste (char *n, long t=0); }; 4.5.1. Funcii de acces n acest caz singurele elemente ale clasei la care utilizatorul are acces direct sunt funciile membre declarate publice, prin cuvntul cheie public, restul de elemente fiind manevrate prin intermediul acestora. Vom nzestra clasa Persoana cu noi funcii (implementarea vechilor funcii fiind cea cunoscut): 34

class Persoana { private : char nume [40]; long int telefon; public: void Scrie ( ); long int Telefon ( ); char *Nume( ); int Citeste ( ); int Citeste (char *n, long t=0); }; long { char { int Persoana :: Telefon( ) return telefon; } * Persoana ::Nume( ) return nume; }

Funciile care nu fac altceva dect s returneze valorile unor date, private, se numesc funcii de acces. Acestea sunt utile datorit faptului c permit inspectarea coninutului datelor, eventual naintea unei modificri. Un aspect deosebit de important, este faptul c modificarea unor date private se poate realiza numai prin intermediul unor funcii membre. 4.5.2. Niveluri de acces n cadrul declarrii unei clase, exist trei variante, trei grade de blocare a accesului la elementele componente, date sau funcii. Aceste niveluri sunt: private protected public membrii fiind accesibili numai funciilor membre i celor de tip friend ; posednd caracteristicile lui private numai c, n plus, membrii sunt accesibili i claselor derivate; aceti membrii fiind accesibili oricrei apelri exterioare.

De reinut c ordinea n care sunt utilizate aceste trei grade ale accesibilitii nu este impus, dar membrii unei clase, neinclui n nici unul dintre aceste niveluri, sunt presupui a fi, n mod implicit, privai, deci private. n mod frecvent, accesul la datele membre se realizeaz prin apelarea unor funcii de acces, aa cum s-a procedat n exemplul anterior. Acest aspect este caracteristic ncapsulrii, dar, n acelai timp,

35

este un consumator de timp, apelul unei funcii fiind un mai mare consumator de timp. De aceea se poate face un compromis ntre cele dou caracteristici, menionnd n dreptul funciei cuvntul inline. ntr-o declarare de clas se poate modifica specificatorul de clas orict de des este necesar. Aceasta nseamn c se pot declara public pentru unele elemente i apoi s se revin la private. Declaraia de clas din urmtorul exemplu ilustreaz aceast facilitate. #include <iostream.h> #include <string.h> class angajat { char nume[80]; public: void danume(char *n); char * Nume();//functie de acees private: double plata; public: void daplata(double w); double iaplata(); }; void angajat::danume(char *n) { strcpy(nume, n); } char* angajat::Nume() { return nume; } void angajat::daplata(double w) { plata = w; } double angajat::iaplata() { return plata; } void main() { angajat teo; teo.danume("Teo Ionescu"); teo.daplata(75000);

36

cout <<teo.Nume() << " are "; cout << teo.iaplata() << " $ pe an. "; } Aici, angajat este o simpl clas care ar putea fi folosit pentru a memora numele i salariul angajatului. Se observ c specificatorul de acces public este folosit de dou ori. De fapt, majoritatea programatorilor n C++ vor scrie clasa angajat aa cum se arat n continuare, cu toate elementele particulare grupate mpreun i toate elementele publice grupate separat. class angajat { char nume[80]; double plata; public: void danume(char *n); void citnume(); void daplata(double w); double iaplata(); }; Chiar dac se pot folosi specificatorii de acces ntr-o declaraie de clas orict de des, singurul avantaj de a o face este acela de a vedea grupri ale diferitelor pri ale unei clase, putnd face mai uoar citirea i nelegerea programului de ctre altcineva. Totui, pentru compilator, nu are importan utilizarea mai multor specificatori de acces identici. De fapt, majoritatea programatorilor se gsesc c este mai uor s aib n fiecare clas doar cte o singur seciune private, protected i public. n general, trebuie s facei membrii unei clase s fie particulari acelei clase. Aceasta este o parte a modului n care se realizeaz ncapsularea. Totui, pot exista situaii n care vei avea nevoie s facei una sau mai multe variabile publice (de exemplu, poate fi necesar ca o variabil folosit intens s fie accesibil global, pentru a obine timpi de rulare mai mici). Cnd o variabil este public, ea poate fi accesat direct ca oricare seciune a programului. Sintaxa pentru a avea acces la o dat membru public este aceeai ca pentru apelarea unei funcii: specificai numele obiectului, operatorul punct i numele variabilei. Forma general a unei clase, prezentat n paragraful anterior este o form simplificat de declarare a unei clase. Iat forma general complet a unei declaraii de clas care nu motenete nici o alt clas. class nume_clasa { date i funcii particulare 37

specificator de acces: date i funcii specificator de acces: date i funcii . . . specificator de acces: date i funcii } lista de obiecte; Lista de obiecte este opional. Dac exist, ea declar obiecte din acea clas. Aici, specificator de acces este unul dintre cele trei cuvinte cheie din C++: public private protected Implicit, funciile i datele declarate ntr-o clas sunt proprii acelei clase i doar membrii si pot s aib acces la ele. Totui, folosind specificatorul de acces public, permitei funciilor sau datelor s fie accesibile altor seciuni ale programului dvs. O dat utilizat un specificator de acces, efectul su dureaz pn cnd ori se ntlnete altul, ori se ajunge la sfritul declaraiei pentru clas. Pentru a ne rentoarce la declararea particular, putei folosi specificatorul de acces private. Specificatorul protected este necesar cnd este implicat motenirea. Funciile care sunt declarate ntr-o clas sunt numite funcii membre. Ele pot s aib acces la orice element al clasei din care fac parte. Aceasta include toate elementele de tip private. Variabilele care sunt membre ale unei clase sunt numite variabile membre sau date membre. n general, orice element al unei clase este numit membru al acelei clase.

4.6. Crearea si initializarea obiectelor


4.6.1. Listele de instanieri Odat ce tipul de dat este implementat, este posibil definirea de variabile avnd acest tip i, evident, iniializarea lor. Reamintim ca obiectul este o variabil avnd drept tip de dat o clas, deci un tip abstract de date.

38

Aa cum tablourile, structurile i chiar uniunile pot fi iniializate chiar n momentul declaraiei lor, prin intermediul listelor de iniializare, tot aa se pot iniializa i obiectele. Exemplu: class Punct { public: int x,y; void Afiseaza(); void Citete(int,int); }; Punct p={1,9}; Exist anumite restricii in legtur cu obiectele iniializate astfel. Obiectele nu pot : avea membri privai; poseda constructori; conine funcii virtuale; proveni din clase derivate.

4.6.2. Constructori Este un lucru obinuit pentru unele pri ale unui obiect s necesite iniializare nainte de a putea fi folosite. Constructorii nu numai c atribuie valori iniiale elementelor membre, dar pot efectua diferite alte operaii, cum ar fi alocarea dinamic de memorie, deschideri de fiiere, reprezentri grafice etc.
Un constructor este un tip special de funcie membr, avnd acelai nume cu numele clasei a crei membr este i nereturnnd nici o valoare (nici mcar de tip void).

De exemplu, n cazul unei clasei stiva, nainte ca memoria stiv s poat fi folosit, top trebuie s fie iniializat cu zero. Acest lucru a fost realizat prin utilizarea funciei init(). Deoarece cerinele de iniializare sunt att de uzuale, C++ permite obiectelor s se iniializeze singure, atunci cnd sunt create. Aceast iniializare automat este efectuat prin intermediul unei funcii constructor. De exemplu, iat cum arat clasa stiva transformat pentru a folosi o funcie constructor pentru iniializare : class stiva { int stiv [100]; int top; 39

public: stiva(); //functie constructor void push(int i); int pop(); }; De reinut faptul c funcia constructor stiva() nu are specificat un tip de returnat. n C++ aceste funcii nu pot s returneze valori i, de aceea, nu conin un tip de returnat. Funcia stiva() are urmtorul cod : // functia constructor pentru stiva stiva::stiva() { top = 0; cout << Stivaa fost initializata \n; } Mesajul Stiva a fost initializata este afiat ca un mod de semnalare a constructorului. n practica curent, majoritatea funciilor constructor nu vor afia nimic. Ele vor efectua pur i simplu diverse iniializri. Un constructor al unui obiect este apelat automat atunci cnd este creat acel obiect. Aceasta nseamn c el este apelat cnd se execut declararea sa. Este o mare deosebire ntre o instruciune de declarare de tip C i una de tip C++. n C declaraiile de variabile sunt pasive i rezolvate n marea majoritate n timpul compilrii. Altfel spus, n C, declaraiile de variabile nu sunt gndite ca fiind instruciuni executabile. Dar, n C++, ele sunt instruciuni active, care sunt executate, de fapt, n timpul execuiei. Un motiv pentru a fi aa este acela c o declarare a unui obiect poate necesita apelarea unui constructor, aceasta determinnd executarea unei funcii. Un alt exemplu de clas n care s-a utilizat un constructor: class Persoana { private: char nume[40]; long int telefon; public: Persoana(char *,long); long Telefon( ) { return telefon; } char *Nume ( ) { return nume; } int Citete(char *n,long t=0); }; Persoana::Persoana(char *n,long l) { 40

strcpy (nume,n); telefon = l; } int Persoana :: Citeste(char *n,long t) { strcpy (nume, n); telefon=t; } n cazul constructorului pot fi apelate alte funcii, membre sau nu. Apelul constructorilor Apelul constructorilor se va produce n locul declarrii unui obiect. n cazul nostru putem declara o variabil de tip persoan, fie prin Persoana fie prin Persoana p (Mircea , 524376); p = Persoana (Mircea, 524376);

Aceasta din urm fiind un mod compact, dar la fel de corect ca i primul de apelare a constructorului.

Restricii asupra constructorilor Constructorii suport dou restricii avnd acelai grad de importan: - acetia nu vor returna nici un tip de dat, nici mcar tipul void i - nu pot fi virtuali i cum un constructor este, n particular, o funcie membr, acesta se poate afla sub incidena celor trei grade de accesibilitate, private, protected sau public. n mod normal constructorii sunt declarai ca fiind publici, rare fiind situaiile n care ei sunt privai (private). Exist dou astfel de situaii, deci n care constructorii, privai fiind, pot fi apelai de o alt funcie membr sau de o funcie prieten, de tip friend. Astfel, numai funciile membre i friend pot crea obiecte avnd constructori privai. Clasele cuprinse n aceast categorie se numesc clase private.

Redefinirea constructorilor 41

Cum funciile membre admit prototipuri multiple ataate aceleiai funcii, constructorii posed i ei, aceast proprietate. Ca i n cazul funciilor, listele de parametrii ale diferitelor variante de implementare trebuie s fie diferite, astfel nct compilatorul s le poat distinge.

Dup cum se tie, variabilele de tipul struct sau union sunt considerate ca fiind obiecte n C++. Cum vom putea rezolva problema constructorului n cazul n care avem de-a face cu o variabil de tip union? Rspunsul la aceast ntrebare ne este furnizat tocmai de fapul c putem avea prototipuri multiple pentru acelai constructor. Deci, vom ti care membru al uniunii este instaniat, n funcie de constructorul apelat, fiecare membru al uniuni avnd un constructor ataat. Funcii constructor cu parametri Este posibil s se transmit argumente ctre funciile constructor. Tipic, aceste argumente sunt folosite pentru a contribui la iniializarea unui obiect atunci cnd este creat. Pentru a crea un constructor cu parametrii, i se adug simplu parametrii ca i pentru orice alt funcie. Funcia este n aa fel creat nct parametrii sunt folosii pentru a iniializa obiectul. Iat, de exemplu, o class simpl care include constructori cu parametri : #include <iostream.h> class clasamea { int a, b; public: clasamea(int i, int j) {a=i; b=j;} void arata() {cout << a << " " << b;} }; void main() { clasamea ob(3, 5); ob.arata(); } De reinut c n definirea lui clasamea(), parametri i i j sunt folosii pentru a da valori iniiale n a i b. Programul ilustreaz cea mai uzual cale de a specifica argumente atunci cnd declarai un obiect care folosete o funcie constructor cu parametri. Instruciunea : clasamea ob(3, 4);

42

determin crearea unui obiect numit ob i se paseaz argumentele 3 i 4 ctre parametrii i i j din clasamea. Se pot pasa argumente folosind i acest tip de instruciune de declarare : clasamea ob = clasamea(3, 4); Totui, prima metod este una general utilizat, iar ea va fi utilizat n majoritatea exemplelor. De fapt, exist practic o mic diferen ntre cele dou tipuri de declaraii care se refer la copierea funciei constructor. Iat un exemplu care folosete o funcie constructor cu parametrii. Ea creeaz o class care pstreaz informaii despre crile din bibliotec : #include <iostream.h> #include <string.h> #define ESTE 1 #define IMPRUM 0 class carte { char autor[40]; char titlu[40]; int stare; public: carte(char *n, char *t, int s); int ia_stare() {return stare;} void da_stare(int s) {stare = s;} void arata(); }; carte::carte(char *n, char *t, int s) { strcpy(autor, n); strcpy(titlu, t); stare = s; } void carte::arata() { cout << titlu << " de " <<autor; cout << " este "; if(stare==ESTE) cout << "aici.\n"; else cout << "data.\n"; } void main() 43

{ carte b1("Creanga", "Amintiri din copilarie", ESTE); carte b2("Tudoran", "Toate panzele sus", IMPRUM); carte b3; // incorect b1.arata(); b2.arata(); } Funciile constructor cu parametri sunt foarte folositoare deoarece ele permit evitarea unui apel n plus a funciei doar pentru a iniializa una sau mai multe variabile dintr-un obiect. Fiecare apel a funciei care poate fi evitat face programul mai eficient. De asemenea, reinei c funciile da_stare() i ia_stare() sunt definite n interiorul clasei carte. Aceasta este o practic foarte uzual n scrierea programelor n C++. Funciile constructor cu un parametru: un caz special Dac o funcie constructor are doar un parametru, exist o a treia cale de a-i pasa o valoare iniial. S lum, de exemplu, urmtorul program scurt : #include <iostream.h> class X { int a; public: X(int j) {a = j;} int cit_a() {return a;} }; void main() { X ob = 99; // paseaza 99 lui j X ob1(99); cout << ob.cit_a(); // afiseaza 99 } Aa cum arat exemplul acesta, n cazul n care constructorul are doar un argument, se poate folosi pur i simplu forma de iniializare normal. Compilatorul de C++ va atribui automat valoarea din dreapta semnului = parametrului. Constructori duplicai. Copierea constructorilor apare atunci cnd se dorete instanierea unui obiect cu un altul, aparinnd aceleiai clase. Pentru astfel de constructori, deci care sunt activai de mai mult de dou ori, vom utiliza denumirea de constructori duplicai. Acetia au ca prim argument o 44

referin ctre clasa ai cror constructori sunt. n cazul existenei mai multor parametri, toi ceilali trebuie s fie implicii. Un astfel de exemplu este prezentat n programul urmtor: #include<iostream.h> #include<math.h> class Punct { private: double x,y; public: Punct(){x=0;y=0;} Punct( double x1,double y1){x=x1;y=y1;} Punct ( Punct * p){x=p->x; y=p->y;} void Afisare(); }; void Punct::Afisare() { cout<<"\nx="<<x<<" y="<<y; } void main() { Punct p1(1,1),p2(10,10); Punct p3(p1); Punct p4 ; p1.Afisare(); p2.Afisare(); p3.Afisare(); p4.Afisare() ; } 4.6.3. Destructori Complementul constructorului este destructorul. n multe cazuri, un obiect va trebui s efectueze o anumit aciune sau unele aciuni atunci cnd este distrus. Obiectele locale sunt create atunci cnd se intr n blocul lor i sunt distruse cnd blocul este prsit. Obiectele globale sunt distruse cnd se termin programul. Cnd este distrus un obiect, este apelat destructorul (dac exist unul). Exist multe motive pentru care poate fi necesar o funcie destructor. De exemplu, un obiect trebuie s cedeze memoria care i-a fost alocat, sau s nchid un fiier pe care l-a deschis. n C++, cea care trateaz evenimentele de la dezactivare este funcia destructor.

45

Destructorul conine comenzi care dezactiveaz toate funciile unui obiect, acesta fiind astfel distrus. n mod uzual, destructorii sunt utilizai n cadrul obiectelor ai cror constructori realizeaz alocri dinamice de memorie. Astfel, destructorii, ca i constructorii, pot efectua orice operaiuni. Destructorul are acelai nume cu cel al clasei din care face parte, fiind precedat ns de caracterul tilda ~. De fapt, negm un constructor prin intermediul operatorului de negaie ~. Se menioneaz c apelul are loc n cadrul execuiei programului, n momentul prsirii domeniului de valabilitate al obiectului respectiv. Astfel, n cazul unui obiect local unei funcii, destructorul acestuia este apelat chiar naintea ieirii din aceast funcie, iar cazul unui obiect static naintea terminrii execuiei programului principal. n ceea ce privete ordinea de apelare a destructorilor, aceasta este invers ordinii de apelare a constructorilor.
Destructorii suport cteva restricii n ceea ce privete apelarea lor:

o clas nu poate avea mai mult de un destructor, destructorii nu pot avea parametri, i nu returneaz nimic, nici mcar void.

Ca i n cazul constructorilor, destructorii sunt n general declarai publici, acesta nefiind o regul impus. Nimeni nu ne oprete s avem un destructor particular, accesul la acesta fiind permis, numai funciilor membre ale obiectului sau funciilor prietene.

De exemplu, iat clasa stiva i funciile sale constructor i destructor (clasa stiva nu necesit un destructor, cel prezentat aici este doar pentru demonstraie). // Aceasta creeaza clasa stiva. class stiva { int stiv[SIZE]; int top; public: stiva(); // constructor ~stiva(); // destructor void push (int i); int pop(); } // functia destructor pentru stiva stiva::~stiva(); { cout << Stiva distrusa \n; } Observai c, la fel ca i funciile constructor, funciile destructor nu au valori de returnare. Iat o nou versiune a programului stiva, studiat mai devreme, n acest capitol, modificat acum pentru a arta cum lucreaz funciile constructor i destructor. Notai c nu mai este necesar init().

46

#include <iostream.h> #define SIZE 100 class stiva { int stiv[SIZE]; int top; public: stiva(); //constructor ~stiva(); //destructor void push(int i); int pop(); }; //functia constructor pentru stiva stiva::stiva() { top = 0; cout << " Stiva initializata \n"; } //functia destructor pentru stiva stiva::~stiva() { cout << " Stiva distrusa \n"; } void stiva::push(int i) { if(top==SIZE) { cout << " Stiva este plina."; return; } stiv[top++] = i; } int stiva::pop() { 47

if(top==0){ cout << "Depasire inferioara."; return 0; } top--; return stiv[top]; } void main() { stiva a, b; a.push(1); b.push(2); a.push(3); b.push(4); cout cout cout cout << << << << a.pop() a.pop() b.pop() b.pop() << << << << " "; " "; " "; "\n";

// creeaza doua obiecte de tip stiva

} Acest program afieaz urmtoarele : Stiva Stiva 3 1 4 Stiva Stiva initializata initializata 2 distrusa distrusa

Construirea i distrugerea obiectelor


48

Exist dou ci de a iniializa membrii unui obiect prin intermediul constructorului. Prima dintre acestea este, n mod evident, utilizarea instruciunilor de atribuire, iar a doua utilizeaz listele de iniializare.

Liste de iniializare
Aceast metod utilizeaz listele de instaniere. O list de instaniere a membrilor unui obiect conine o list a membrilor ce urmeaz a fi iniializai i apare, n cadrul implementrii constructorului, ntre lista de parametri i corpul acestuia. Ea va fi specificat cu ajutorul operatorului :.

class punct { public: int x,y; punct (int x1,int y1); }; punct :: punct(int x1,int y1) : x(x1), y(y1) //Lista de initializare a membrilor {return; }
Se remarc modul n care este menionat fiecare membru prin nume i este transmis acestuia un parametru, n maniera funciei. n cazul exemplului, ambii membri fiind iniializai n cadrul listei, nu mai rmne de iniializat nimic n corpul constructorului.

Putem scrie o variant inline a acestui constructor. class punct {


public:

int x,y; punct (int x1, int y1):x(x1), y(y1) {return;} }; Un element important al listei de iniializare l constituie ordinea n care se vor efectua instanierile n cadrul su, deseori astfel de operaii utiliznd valorile unor membri pentru iniializarea altora. n acest sens, ordinea este indus de cea n care membri sunt declarai n cadrul clasei i nu n lista de instaniere, n exemplele de mai jos x fiind primul instaniat. // varianta 1 punct :: punct(int x1, int y1) : x(x1), y(x) {return;} // varianta 2 punct :: punct(int x1, int y1) : y(x), x(x1) {return;}
Un alt caz n care ordinea instanierilor este important, apare atunci cnd unele iniializri au loc n cadrul listei, iar altele n corpul constructorului, cum ar fi exemplul urmtor:

49

punct :: punct(int x1, int y1) : x(x1) {y = y1;} Ordinea n astfel de situaii, se va stabili innd cont c lista de instaniere este parcurs naintea execuiei corpului constructorului.
Dar nu ntodeauna vom avea de instaniat obiecte avnd structuri att de simple. n cazul n care exist membri de tip clas, instanierea unui obiect va presupune i instanierea acestora, listele de iniializare fiind utile i n astfel de situaii.

class Punct { public: int x,y; Punct (int x1,int y1) {x=x1; y=y1;} }; class Dreptunghi { public: Punct stanga_sus,dreapta_jos; Dreptunghi (int xss,int yss, int xdj, int ydj); }; Dreptunghi :: Dreptunghi (int xss,int yss, int xdj,int ydj) : stanga_sus(xss,yss), dreapta_jos(xdj,ydj) {return;}
n ceea ce privete ordinea instanierilor, aceasta va fi aceeai cu cea n care sunt declarai membri n cadrul claselor. Dac vreun membru al clasei, fiind de tip clas, are constructor implicit, acesta nu va mai trebui iniializat, putnd deci lipsi din lista de instanieri.

Iniializarea n cazul n care exist pointeri, referine i constante membre


Dac un membru al unei clase este pointer la o clas, atunci nu putem apela constructorul acelei clase n cadrul listei de instaniere. Constructorii nu pot fi apelai pentru pointeri la obiecte, ci numai pentru obiecte.

Este posibil ca un membru s fie o referin la obiect. n acest caz, referina trebuie iniializat n momentul n care este creat obiectul care conine referina. Aceast cerin poate fi satisfcut astfel: class tip { public: int inf; int &ad_info; tip(int m); 50

}; tip::tip (int m ) :inf(m),ad_info(inf); {return;} O situaie similar este cea n care avem membri declarai drept constante. Asemenea referinelor, constantele trebuie iniializate n momentul declarrii lor. Deoarece membrii de tip referin sau constante trebuie iniializai n cadrul unei liste de instaniere, clasele avnd astfel de membri vor avea, n mod obligatoriu, un constructor.

Apelul destructorilor obiectelor membre


Destructorii ataai unor obiecte membre se vor apela atunci cnd obiectul cruia i aparin obiectele membre urmeaz s fie distrus. Cum este i normal, destructorii sunt apelai n ordine invers apelrii constructorilor, operaie care se va executa de la sine, nefiind necesar apelul explicit al vreunui destructor.

Altfel spus: - destructorii obiectelor membre sunt apelai dup ce destructorul obiectului principal a fost executat. Dac obiectul membru este compus din alte obiecte, atunci se va proceda la execuia destructorilor obiectelor incluse. Destructorii obiectelor membre sunt, n mod evident, apelai n ordinea invers n care acestea apar n declaraia clasei.

Funciile constructor pentru obiectele globale sunt executate naintea lui main(). Constructorii globali din acelai fiier sunt executai n ordine, de la stnga la dreapta i de sus n jos. Nu putei s tii ordinea execuiei constructorilor globali mprtiai prin mai multe fiiere. Destructorii globali se execut n ordine invers dup ce se ncheie main(). Urmtorul program ilustreaz executarea constructorilor i destructorilor. #include <iostream.h> class clasamea { public: int cine; clasamea(int ex); ~clasamea(); } glob_ob1(1), glob_ob2(2); clasamea::clasamea(int ex) { cout << "Initializare" << ex << "\n"; 51

cine = ex; } clasamea::~clasamea() { cout << "Distrugere" << cine << "\n"; } main() { clasamea local_ob1(3); cout << "Aceasta nu va fi prima linie afisata.\n"; clasamea local_ob2(4); } El afieaz aceast ieire : Initializare 1 Initializare 2 Initializare 3 Aceasta nu va fi prima linie afisata. Initializare 4 Distrugere 4 Distrugere 3 Distrugere 2 Distrugere 1

Tablouri de obiecte
Aa cum crem tablouri de structuri i putem crea i masive de obiecte. De exemplu, putem imagina o linie poligonal ca fiind un vector de puncte.

Punct triunghi [3];

n plus tabloul de obiecte poate fi iniializat chiar n momentul declarrii sale, innd cont de restriciile impuse acestui tip de instaniere.

Punct triunghi [3]={{0,0},{100,90},{50,80}};

52

Dac dorim, totui s iniializm un tablou de obiecte n cadrul declarrii sale, iar clasa obiectului de baz se ncadreaz ntr-una din acele clase supuse restriciilor, se impune crearea unei liste de instaniere avnd drept elemente constructori ai obiectului.

Punct triunghi [3]={Punct (1,2), Punct (30,90), Punct (20,20) }; identificarea constructorilor realizndu-se n funcie de lista de instanieri:

class Punct { public: int x,y; Punct(int x1,int y1) {x=x1; y=y1; } Punct (int x1) }; Punct 1, Punct(2,1) , triunghi [3] = { // apelul constructorului Punct (1) // apelul constructorului Punct (2,1) {x=x1; y=x1; }

8 };

// apelul constructorului Punct (8)

Un caz aparte l constituie constructorul implicit, acesta neavnd parametri. O modalitate de a instania un tablou de obiecte avnd numai constructor implicit este: Punct triunghi [3]; Deseori, n cazul utilizrii masivelor de date, n cadrul declarrii acestora, dimensionarea lor se face avndu-se n vedere situaii dintre cele mai nefavorabile. Astfel, nu ntotdeauna vom ajunge s i utilizm ntreg tabloul, fiindu-ne suficiente doar cteva elemente ale sale. n acest sens, este permis o instaniere parial a unui tablou. class Punct { public: int x,y; Punct(int x1,int y1) Punct(int x1)

{x=x1; y=y1; } {x=x1; y=x1; } 53

Punct( ) };

{x=0; y=0 ;

Punct triunghi [3] ={1, Punct (2,1)}; Observaie: Instanierile pariale sunt posibile numai n cazul prezenei constructorului implicit.

Manevrarea dinamic a obiectelor


Pentru gestionarea memoriei n C++, avnd de manevrat obiecte , ne sunt pui la dispoziie doi operatori asemntori funciilor C, malloc() i free(), dar mai evoluai ca domeniu de aciune, new i delete.

Operatorul new

Superioritatea acestui operator n raport cu funcia malloc() const n faptul c: - new tie ct memorie trebuie alocat oricrui tip de variabil, funciei malloc() trebuie s i se specifice aceast dimensiune; new poate produce apelul unui constructor n timp ce malloc() nu are aceast calitate.

/* Varianta C */ int *p=(int *)malloc (sizeof (int)); // Varianta 1 C++ int *p=new int; // Varianta 2 C++ int *p=new(int); Cu toate c este operator, new poate aciona i ca funcie, aa cum se observ cea de a doua variant. Operatorul new poate fi utilizat n declararea de masive. int *p=new int[10];

54

O declaraie ciudat la prima vedere ar fi: Punct *p=new Punct(10,10); n astfel de situaii, se va rezerva memorie pentru obiectul specificat, dup care se va apela constructorul implicit, neputndu-se transmite parametri acestuia. Punct *poligoane = new Punct[10];

Operatorul delete

Operatorul delete este analogul funciei free( ) din C avnd drept scop eliberarea memorie ocupate cu ajutorul operatorului new. Aa cum new era utilizat, n general n interiorul constructorului, tot aa i delete i are locul n cadrul destructorilor. Cum e i firesc, nainte de a se elibera memoria alocat de obiectul n cauz, se va apela destructorul acestuia. Cu toate c delete permite eliberarea selectiv a spaiului alocat, acest operator nu este la fel de puternic ca new. Astfel, am fi tentai s credem c n cadrul exemplului urmtor se realizeaz, pe rnd, alocarea spaiului necesar memorrii a 10 ntregi, iar apoi eliberarea acestui spaiu. int *m=new int [10]; delete m; n realitate, am eliberat spaiul ocupat de un singur ntreg, m[0], restul locaiilor rmnnd ocupate. Pentru a elibera complet spaiul alocat se procedeaz astfel: int *m=new int[10] delete [] m; Elementele claselor

Membri statici
n mod normal, fiecare obiect posed o copie proprie a variabilelor instaniate ale clasei din care provine. Exist ns posibilitatea de a defini membri care sunt comuni tuturor obiectelor provenind din aceeai clas.

55

Cnd precedai o declaraie a unei variabile membru cu static, i spunei compilatorului c va exista doar o copie a acelei variabile i va fi folosit de toate obiectele clasei. Spre deosebire de membrii tip date obinuii, nu sunt create copii individuale ale variabilelor membre static pentru fiecare obiect. Nu are importan cte obiecte de acel tip de clas sunt create, va exista doar o singur copie a membrilor date de tip static. De aceea, toate obiectele acelei clase folosesc aceeai variabil. Cnd este creat primul obiect, toate variabilele de tip static sunt iniializate cu zero.
Cnd declarai o dat membru ca fiind static ntr-o clas, nu o definii. Cu alte cuvinte, nu i alocai memorie (n limbajul C++, o declaraie descrie ceva. O definire face ca ceva s existe). n schimb, trebuie s dai o definire global a membrilor de date de tip static undeva, n afara clasei. Aceasta se face redeclarnd variabila static folosind operatorul de specificare a domeniului pentru a preciza clasa creia i aparine; ca urmare se va aloca memorie pentru variabil (amintii-v c o declarare de class este o simpl construcie logic ce nu are suport material). Astfel membri se numesc membri statici i se vor declara utiliznd cuvntul cheie static. De exemplu, definind clasa:

class sir { public : char *continut; int dim, lung; static char eos; sir (int dm); ~sir( ); void copy(sir &s);
void print( )

Sir_1 Continut1 Dim1 Lung1

Sir_2 Continut2 Dim2 Lung2

Sir_3 Continut3 Dim3 Lung3

eos

};
fiecare obiect de tip sir posed propriul vector de octei, continut are o lungime, lung, i o dimensiune proprie, dim dar toate obiectele vor avea un acelai terminator de ir de caractere, eos, acesta fiind declarat static. Nu trebuie s confundm utilizarea cuvntului static pentru membri statici ai unei clase, cu variabile statice.

Declarnd un obiect prin

sir s(80); Vom putea stabili valoarea lui eos prin

s.eos=$; sau sir :: eos=$;

n cea de a doua situaie, nu am folosit un nume de variabil, ci chiar numele clasei, efectul fiind, bineneles, acelai. A doua metod este, ns mult mai sugestiv, fiind mult mai clar faptul c acionm tuturor obiectelor avnd acest tip. class sir { public: static char eos;
. . .

}; char sir :: eos=$;

56

Membri statici nu pot fi instaniai n cadrul declarrii clasei. Ca regul general, membrii statici se vor instania n acelai fiier n care se implementeaz metodele clasei, niciodat n cadrul headerului. n plus, n momentul iniializrii unui membru static, nu se va specifica cuvntul cheie static.
Membrii statici privai se vor iniializa ca i cnd nu ar fi astfel, accesul ctre ei respectnd, ns, protocolul elementelor private.

Funcii membre statice


Accesul la o astfel de funcie se realizeaz n mod identic cu cel ctre un membru static. class cursor { public: static int xactiv, yactiv; int x, y; cursor (int xi, int yi) {x=xi; y=yi;} static void origine (int xs, int ys); void coordonate (int &xa, int &ya); }; void cursor :: origine (int xs, int ys) {xactiv=xs; yactiv=ys;} void cursor :: coordonate (int &xa, int &ya) { xa=x+xactiv; ya=y+yactiv; } void main ( ) { cursor ::origine (0,0); cursor g(10,10); } Funciile membre statice se deosebesc de funciile membre nestatice prin aceea c nu posed pointerul this. Ca urmare, o funcie static membr nu va putea accesa, n mod direct, dect alte elemente statice ale clasei. Singura modalitate ca astfel de funcii, deci statice, s aib acces ctre un membru ordinar al clasei este cea prin care i se transmit, ca parametrii, obiecte, accesul ctre membrii vizai realizndu-se prin intermediul acestora. Cu toate c nu posed pointerul this, este permis apelul unei funcii statice dintr-un obiect, la fel cum se realizeaz accesul ctre un element static. cursor g(42,17) ; g.origine(14,14);

Funcii membre constante

57

Putem declara obiecte constante: const Punct origine(0,0);

Declarnd un obiect constant, nu este permis apelarea funciilor membre ale acestuia, cu excepia celor declarate constante. O funcie membr este declarat constant prin utilizarea cuvntului cheie const, imediat dup nchiderea parantezelor ce cuprind parametri funciei. Dintre funciile membre ale unui obiect constant, care pot fi apelate, se numr i constructorii i destructorii, fr ns ca acestea s fie declarate constante. #include <stdio.h> class numar { private: int data; public: numar (int i) { data=i;} void actiune (int i) {data=data+i; } int valoare ( ) const {return data; } }; void main ( ) { const numar x(42); x.actiune(17); //Ilegal int k=x.valoare( ); printf(Valoarea numarului este %d\n,k); }

Funcii prietene friend


Un alt tip de funcii asociate claselor sunt funciile prietene. Acestea posed capacitatea de a ignora bariera de tip private, n cea ce privete accesul la acest tip de membri ai unei clase, nefiind ns membre ale clasei. Funciile prietene apar atunci cnd este necesar un astfel de acces, dar, din diferite motive nu pot face parte din clas. Situaia cea mai frecvent este cea n care o funcie membr a unei clase este prieten altei clase. Astfel, prima clas poate utiliza membrii privai ai celeilalte clase. Prototipul unei funcii prietene unei clase se va afla n cadrul declaraiei acelei clase, precedat de cuvntul cheie friend.
Accentum faptul c funciile prietene unei clase nu sunt membre ale acelei clase, clasa fiind cea care specific funciile care i sunt prietene. Astfel, indiferent de poziia declaraiei unei asemenea funcii n cadrul declaraiei unei clase, funcia va fi public.

#include <iostream.h> class Punct { 58

private: int x,y; public: Punct (int xi,int yi) {x=xi; y=yi;} friend int Compara(Punct &,Punct &); }; int Compara (Punct &a,Punct &b) { //Returneaza <0 daca a este mai aproape de origine // >0 daca b este mai aproape de origine // =0 daca a si b sunt egal departate. return a.x*a.x+a.y*a.y-b.x*b.x-b.y*b. y; } void main( ) { Punct p(14,17) , q(57,30); if (Compara(p,q)<0) cout<<"p este mai apropiat da origine\n"; else cout<<"q este mai apropiat de origine.\n"; } Este clar c orice funcie prieten unei clase poate fi transformat ntr-o funcie membr a acelei clase, renunndu-se ns la gradul de prietenie. #include<iostream.h> class Punct { private: int x,y; public: Punct(int xi,int yi) { x=xi; y=yi; } int Compara (Punct &b) ; }; int Punct::Compara(Punct &b) { return x*x+y*y-b.x*b.x-b.y*b.y; } void main( ) { Punct p(14,17), q(57,30); if (p.Compara(q)<0) cout<<"p este mai apropiat de origine\n"; else 59

cout<<"q este mai apropiat de origine.\n"; } Exist ns situaii n care funciile prietene sunt singurele soluii. Situaiile din aceast categorie sunt cele n care avem de-a face cu o funcie n a crei list de parametri apar cel puin doi din aceeai clas iar funcia acceseaz membri privai. Funcii membre ca prietene Trebuie s remarcm c orice funcie membr nu poate fi prieten aceleiai clase dar este posibil s fie prietena altei clase. Astfel, funciile friend constituie o punte de legtur ntre clase. Exist dou moduri de a face ca o funcie membra a unei clase, s fie prietena altei clase. Prima variant : class B; class A void A1 (B { &x) ; &x) ;

}; class B{ friend void A::A1(B } ;

A doua variant este aceea de a declara o ntreag clas prieten, astfel c toate funciile sale membre sunt, de fapt, prietene clasei n care un obiect de tipul primei clase este declarat friend. class B; class A void Al (B }; class B { friend A; }; Indiferent de metod, se impune predeclararea clasei care va fi prieten sau va ine funcii care sunt prietene unei alte clase. { &x) ;

Clase imbricate
Este posibil s definii o clas n cadrul alteia. Procednd astfel, creai clase imbricate. Deoarece o declaraie a unei class definete, de fapt, un domeniu de influen, o clas imbricat este valid doar n interiorul clasei ce o conine. Clasele imbricate sunt folosite rareori. Datorit flexibilitii i puterii mecanismului de motenire, practic nu este nevoie de clase imbricate.

Clase locale
60

O clas poate fi definit n interiorul unei funcii. De exemplu, acesta este un program C++ valid : #include <iostream.h> void f() { class clasamea { int i; public: void init_i(int n) {i=n;} int I() {return i;} } ob; ob.init_i(10); cout << ob.I(); } void main() { f(); // clasamea nu este cunoscuta aici } Cnd o clas este declarat ntr-o funcie, ea este cunoscut doar acelei funcii i necunoscut n afara ei. Claselor locale li se aplic mai multe restricii. Prima, toate funciile membre trebuie definite n interiorul declaraiei pentru class. Clasa local nu poate s foloseasc sau s aib acces la variabilele locale ale funciei n care este declarat (cu excepia variabilelor locale de tip static declarate n interiorul funciei). n interiorul unei clase locale nu poate fi declarat nici o variabil de tip static. Din cauza acestor restricii, clasele locale nu sunt uzuale n programarea n C++.

Transmiterea obiectelor ctre funcii


Obiectele pot fi transmise (pasate) ctre funcii exact ca oricare alt tip de variabil. Obiectele sunt pasate funciilor prin utilizarea mecanismului standard apelare prin valoare, adic prin copiere. Dar efectuarea unei copii nseamn practic crearea unui alt obiect. Aceasta ne face s ne ntrebm dac este executat funcia constructor a obiectului la crearea copiei i dac este executat funcia destructor la distrugerea copiei. Pentru nceput, iat un exemplu : #include <iostream.h> class clasamea { int i; public: clasamea(int n); ~clasamea(); void init_i(int n) {i=n;} 61

int I() {return i;} }; clasamea::clasamea(int n) { i = n; cout << "Construieste" << i << "\n"; } clasamea::~clasamea() { cout << "Distruge" << i << "\n"; } void f(clasamea ob) { ob.init_i(2); cout << "Acesta este i local:" << ob.I(); cout << "\n"; } void main() { clasamea o(1); f(o); cout << "Acesta este i din main():"; cout << o.I() << "\n"; } Acest program are urmtoarea ieire : Construieste 1 Acesta este i local: 2 Distruge 2 Acesta este i din main(): 1 Distruge 1 Reinei c sunt executate dou apelri ale funciei destructor, dar una singur a funciei constructor. Dup cum ilustreaz ieirea, funcia constructor nu este apelat cnd copia lui o (din main()) este transmis lui ob (din f()). Motivul pentru care ea nu este apelat cnd este fcut copia obiectului este uor de neles. Cnd transmitei un obiect unei funcii, dorii starea curent a obiectului. Dac funcia constructor este apelat cnd este creat obiectul, acesta va fi iniializat, fiind posibil modificarea sa. De aceea, funcia constructor nu poate fi executat atunci cnd este generat copia unui obiect printr-o apelare de funcie.

62

Dei nu este apelat funcia constructor cnd un obiect este pasat unei funcii, la distrugerea copiei, este, totui, necesar s apelm destructorul (copia este distrus ca oricare alt variabil local, atunci cnd se ncheie funcia). O copie a obiectului exist att timp ct este executat funcia. Aceasta nseamn c acea copie poate s efectueze operaii care, atunci cnd copia este distrus, s necesite apelarea unei funcii destructor. De exemplu, este perfect valid s se aloce memorie copiei, dup care s se elibereze la distrugerea copiei. Din acest motiv, funcia destructor trebuie executat cnd este distrus copia. n rezumat, funcia constructor a unui obiect nu este apelat atunci cnd este creat o copie a obiectului prin transmiterea sa ctre o funcie. ns, atunci cnd este distrus copia obiectului din interiorul funciei, este apelat funcia ei destructor. Implicit, cnd este fcut o copie a unui obiect, apare o copie la nivel de bii. Aceasta nseamn c noul obiect este un duplicat identic cu originalul. Acest fapt poate s devin, n timp, o surs de neplceri. Chiar dac obiectele sunt pasate funciilor prin mecanismul normal de transmitere a parametrilor prin valoare care, teoretic, protejeaz i izoleaz argumentul apelat, este nc posibil apariia efectelor secundare care pot afecta, sau chiar distruge, obiectul folosit ca argument. De exemplu, dac unui obiect utilizat ca argument i se aloc memorie ce se elibereaz cnd este distrus, atunci copia sa din interiorul funciei va elibera aceeai memorie cnd va fi apelat destructorul su. Aceasta va face ca obiectul iniial s fie distrus efectiv, fcndu-l inutilizabil. Dup cum s-a vzut, este posibil s prevenii acest tip de probleme definind operaiile de copiere n cadrul claselor dvs., prin crearea unui tip special de constructor, numit constructor duplicat

Returnarea obiectelor
O funcie poate returna un obiect rutinei care a apelat-o. De exemplu, acest program este valid n C++ : #include <iostream.h> class clasamea { int i; public: void init_i(int n) {i=n;} int I() {return i;} }; clasamea f()// returneaza obiect de tipul clasamea { clasamea x; x.init_i(1); return x; } void main() 63

{ clasamea o; o = f(); cout << o.I() << "\n"; } Cnd un obiect este returnat de o funcie, este creat automat un obiect temporar, care conine valoarea returnat. Acesta este, de fapt, obiectul care este returnat de ctre funcie. Dup ce valoarea a fost returnat, acest obiect este distrus. Distrugerea obiectului temporar poate determina, n unele situaii, efecte secundare neateptate. De exemplu, dac obiectul care a fost returnat are un destructor care elibereaz memoria dinamic alocat, acea memorie va fi eliberat chiar dac obiectul care primete valoarea returnat nc o mai folosete. Dup cum vei vedea, exist ci de prevenire a acestei probleme care folosesc suprancrcarea operatorului de atribuire i definirea unui constructor de copii.

Atribuirea obiectelor
Presupunnd c ambele obiecte sunt de acelai tip, putei s atribuii un obiect altuia. Aceasta va determina ca datele obiectului din membrul drept s fie copiate n datele obiectului din membrul stng. De exemplu, urmtorul program va afia 99 : #include <iostream.h> class clasamea { int i; public: void init_i(int n) {i=n;} int I() {return i;} }; void main() { clasamea ob1, ob2; ob1.init_i(99); ob2 = ob1; // atribuie datele din ob1 lui ob2 cout << "Acesta este i din ob2:" << ob2.I(); } Implicit, toate datele dintr-un obiect sunt atribuite celuilalt, prin folosirea copierii bit cu bit. Dar, putei s suprancrcai operatorul de atribuire i s stabilii o alt procedur de atribuire. 64

MOTENIREA I DERIVAREA CLASELOR


1. Ce este motenirea ?
Motenirea este o relaie ntre entiti, caracterizat prin trecerea atributelor de la o clas, de baz, la alta, derivat, tot aa cum clasa insectelor este clas de baz att pentru clasa albinelor, ct i pentru cea a tnarilor. Deci clasele derivate posed toate caracteristicile clasei de baz. n plus derivatele pot fi mbogite structural i funcional. Pe de alt parte, ncercnd o reprezentare a claselor, observm o ierarhizare, o aezare pe niveluri a acestora, asemntoare arborilor. Spunem asemntoare, datorit faptului c exist posibilitatea ca o clas derivat, deci un FIU, s aib mai multe clase de baz, deci mai muli TAI, ordinea importanei nivelelor rmnnd ns aceiai.

2. Avantaje ale motenirii


Chiar dac la un prim contact cu acest concept, ar prea c motenirea este asemntoare cu procesul de includere a obiectelor n obiecte, exist cteva elemente caracteristice care nu se regsesc n cazul compunerii: codul poate fi comun mai multor clase, clasele pot fi extinse, deseori fr a se recompila clasele originale, funciile utiliznd obiecte din clasa de baz pot utiliza n mod automat i obiecte derivate ale acelei clase.

3. Reutilizarea codului
Nu de puine ori, scriind programe n manier clasic eram pui n situaia de a rescrie, readapta proceduri scrise anterior, deseori oprindu-ne asupra metodelor utilizate n cadrul acestora. Era clar c acea etap a procesului de implementare dura mai mult timp dect ar fi fost necesar. n plus exista riscul apariiei de erori, din diferite motive. Soluia n aceast direcie ne este oferit tot de programarea orientat pe obiecte, oferindu-ne dou modaliti de a reutiliza codul: prin compunere :deci includerea de obiecte n cadrul altor obiecte, prin motenire :deci prin creerea unor obiecte pornind de la colecia de obiecte existente, completnd-o, astfel pe cea din urm.

3.1. Compunerea
Prin includerea unui obiect n cadrul altuia, acesta din urm va avea drept elemente membre toate elementele membre ale obiectului inclus. Este o situaie asemntoare cu includerea de structuri n cadrul structurilor.

65

Accesul ctre elementele membre ale obiectului inclus se va realiza, evident prin intermediul acestuia. Un astfel de exemplu l poate constitui structura Persoana. Un alt exemplu, poate mai simplu dar la fel de clar. struct Punct { float x,y; void Incarc_punct(float xi, float yi) {x=xi; y=yi;} }; struct Cerc { Punct centru ; float raza; void Incarc_raza(float r ) {raza=r; } }; Cerc cerc; Cerc.Incarc_raza(10); // Efectul =cerc.raza=10; cerc.centru.Incarc_punct(15.0, 7.0); // Efectul=cerc.centru.x=15.0; // cerc.centru.y=7.0;

3.2. Motenirea
O variant mult nbuntit de reutilizare a codului este derivarea. Ea const n proprietatea unor obiecte de a moteni proprietile altor obiecte construite anterior, n vederea obinerii de noi obiecte. Pentru a deriva o clas dintr-o clas de baz, numele clasei de baz se va preciza imediat dup numele clasei n devenire, iar sintaxa utilizat este: struct nume_clasa_derivata : nume_clasa_de_baz { noi_membrii }; sau class nume_clasa_derivata : nume_clasa_de_baza {noi_membri }; Exemplu: struct Punct { float x,y; void Incarc_punct (float xi, float yi) {x=xi; y=yi;} }; struct Cerc: Punct { float raza; void Incarca_raza(float r) {raza =r;} };

66

n felul acesta, elementele membre ale clasei de baz devin elemente membre i ale clasei derivate, obiectul intermediar disprnd. Clasele derivate sunt tratate ca subclase ale claselor de baz. Pornind de la acest aspect, obiectele derivate pot fi atribuite obiectelor de baz Punct p; Cerc c; p = c; fr a fi necesar conversia de tip, typecast. n astfel de atribuiri, se vor copia numai membrii clasei de baz. p.x=c.x; p.y=c.y; n plus nu putem efectua atribuiri de acest tip unor obiecte ce sunt legate prin relaia de motenire. Regula de compatibilitate se poate extinde la pointeri i la referine la obiecte. Punct *pptr; Cerc *cptr; pptr=cptr; Punct p; Punct &p_ref=p; Cerc c; Cerc &c_ref=c; p_ref=c_ref; Atribuirea va funciona ntr-o singur direcie. Se poate atribui o clas derivat unei clase de baz, un obiect derivat unui obiect de baz, dar nu i invers, instruciunile de forma c=p; cptr=pptr; c_ref=p_ref; fiind ilegale. Explicaia acestei restricii const n faptul c, dac am putea atribui, de exemplu, un Punct unui Cerc, raza cercului va avea o valoare aleatoare, necontrolabil.

4. Reutilizarea funciilor n obiecte derivate


Graie compatibilitii n operaia de atribuire, obiectele create prin derivare pot fi utilizate n orice context pe poziia celor de baz. De exemplu orice funcie care utilizeaz Punct va putea opera i asupra unui obiect de tip Cerc. i aceasta datorit faptului c Cerc include tot ceea ce conine Punct. S considerm, de exemplu, funcia distanta(), ce furnizeaz distana dintre cele dou puncte. float distanta (Punct &a, Punct &b) { float dx=a.x-b.x; float dy=a.y-b.y; return sqrt (dx*dx+dy*dy); } Punct p1, p2; float dp=distanta(p1,p2); Ea poate fi aplicat i obiectelor de tip Cerc.

67

Cerc c1,c2; float dc=distanta(c1,c2); Aceast facilitate nu numai c permite ca obiectele derivate s reutilizeze codul aflat n clasa de baz, ci i utilizarea oricrei funcii ce acceseaz obiecte din clasa de baz. n continuare, vom vedea modul de organizare a fiierelor, astfel nct funcia distanta(), conceput pentru a funciona cu obiecte de tip Punct, s poat fi utilizat i pentru obiecte derivate din aceast clas, fr a recompila codul iniial al funciei. // cod deja compilat // punct.h struct Punct { float x,y ; void Incarc_punt(float xi, float yi) {x=xi; y=yi;} }; float distanta (Punct &, Punct &); // dist.cpp #include punct.h float distanta(Punct &a, Punct &b) {.} // cod nou // cerc.h #include punct.h struct Cerc: Punct { float raza; void Incarc_raza(float r) {raza=r;} }; // prog.cpp #include cerc.h void main ( ) { Cerc c1, c2; float d=distanta (c1, c2); }

5. Polimorfismul
Deseori, motivul pentru care derivm o clas nu este acela de a o mbunti din punct de vedere structural, ci de a redefini, respectiv rescrie, funcionalitatea acesteia. Atunci cnd funciile unei clase de baz sunt rescrise ntr-una derivat, spunem c aceste clase sunt polimorfe. Acest denumire trebuie s ne sugereze faptul c vom avea o singur denumire i mai multe aciuni, altfel spus, o singur interfa, metode multiple. S vedem cum arat obiectele polimorfe. Pentru aceasta, s adugm claselor Punct i Cerc funcia membr, Aria(), ce va avea efect determinarea ariei obiectului respectiv. Cum Cerc provine din Punct, Cerc i Punct sunt obiecte din aceeai categorie, altfel spus, sunt polimorfe. Pentru a obine 68

obiecte polimorfe va trebui s construim o ierarhie de clase i apoi s redefinim funciile aparinnd clasei de baz n clasele derivate. Aceast operaie poate fi realizat n dou moduri: rescriind funciile respective, procednd deci la o nou implementare a acestora, sau utiliznd funcii virtuale.

5.1. Rescrierea funciilor


// Exemplu de redefinire a functiilor #include <stdio.h> struct Punct { float x,y; void Incarc_punct(float xi, float yi ) {x=xi; y=yi;} float Aria ( ) {return 0.0;} }; const float pi=3.14159; struct Cerc : Punct { float raza ; void Incarc_raza(float r) {raza = r;} // Functia redefinita float Aria ( ) {return pi*raza*raza;} }; void main ( ) { Punct p; float a=p.Aria ( ); printf (Aria unui punct este : %5.2f\n,a); Cerc c ; c.Incarc_raza (3.65637) ; a=c.Aria ( ) ; printf (Aria cercului este : %5.2f\n,a) ; } Este evident c funcia membr Aria() a clasei Cerc este motenit de clasa Punct, dar nu convine din punct de vedere al valorii returnate. Astfel, s-a impus reimplementarea acesteia. Dei la prima vedere, codul dat spre exemplu pare s fie eronat, el va fi compilat cu succes. Aceasta se datoreaz faptului c funciile Aria() posed pointerul this, neexistnd pericolul confuziei ntre Punct::Aria() i Cerc::Aria(), chiar dac listele de parametrii sunt identice. Punct *p; Cerc c; p=&c; float aria=p->Aria ( );

69

Dac pointerul this clarific situaia n care avem de-a face cu obiecte acionate direct, n cazul accesrii indirecte, deci prin intermediul pointerilor i a referinelor, lucrurile se complic. Astfel, n linia n care este apelat funcia Aria(), este apelat funcia Punct::Aria() i nu Cerc::Aria(), cum am fi tentai s credem. i aceasta deoarece funcia care se apeleaz este determinat static, deci la momentul compilrii, i nu dinamic, iar p indic spre Punct. Rezolvarea acestei neconcordane se va obine n continuare.

5.2. Funcii virtuale


Funciile virtuale sunt utilizate pentru reimplementarea unor funcii membre ale unor clase de baz, astfel nct aceast redefinire de funcii s funcioneze n ambele situaii, att pentru cele statice, ct i pentru cele dinamice. O funcie membr poate fi fcut virtual prin plasarea cuvntului cheie virtual naintea prototipului su. struct Punct { float x,y; void Incarc_punct ( float xi, float yi ); virtual float Aria ( ) {return 0.0;} }; Odat definit ca fiind virtual, o funcie membr a clasei de baz, va fi virtual n toate clasele derivate. n aceste clase ne se va mai utiliza ns cuvntul virtual. const float pi=3.14159 ; struct Cerc :Punct { float raza; void Incarc_raza(float r); // Functie redefinita float Aria ( ) {return pi*raza*raza;} }; n schimb, n momentul rescrierii unei funcii virtuale, n mod obligatoriu noua variant va avea exact aceeai declaraie ca n forma iniial (tip returnat, nume list de parametri). #include <stdio.h> struct Punct { float x,y; void Incarc_punct(float xi, float yi) {x=xi;y=yi;} virtual float Aria ( ) {return 0.0; } }; const float pi=3.14159; struct Cerc :Punct { 70

float raza; void Incarc_raza(float r) {raza=r;} // Functia redefinita float Aria ( ){return pi*raza*raza;} }; void main ( ) { Punct p; Cerc c; c.Incarc_raza(3.65637); Punct *p_ptr; p_ptr=&p; float ap=p_ptr->Aria ( ); p_ptr=&c; float ac=p_ptr->Aria ( ); printf ("Aria punctului este :%5.2f\n",ap); printf ("Aria cercului este :%5.2f\n",ac) ; } 6. Apelul funciilor motenite Uneori prin derivare, nu facem altceva dect s rescriem funciile membre ale clasei de baz, dar la fel de adevrat este c nemodificarea codului implementat n clasa de baz nu face dect s ne ajute la implementrile ulterioare. S considerm urmtoarea clas derivat const float pi=3.14159; struct Cilindru : Cerc { float inaltime ; void Incarca_inaltime (int h) {inaltime=h;} float Aria ( ); }; float Cilindru :: Aria ( ) { return 2*pi*raza*inaltime+2*Cerc:: Aria ( ); } Observm c, n implementarea noii versiuni a funciei membre Aria(), utilizm o versiune anterioar, motenit. n momentul dezvoltrii ierarhiei claselor, ncercai ca funciile motenite s fie ct mai complete. Astfel, versiunile derivate nu vor mai avea de fcut dect operaii caracteristice obiectului respectiv. O variant la fel de valabil a funciei Cilindru::Aria() este: 71

float Cilindru:: Aria( ) { return 2*pi*raza*inaltime+ 2*Cerc::Aria ( )+ Punct ::Aria ( ); } Evident c Punct::Aria() nu va modifica rezultatul furnizat, aportul su la acesta fiind nul, dar se sugereaz c este permis apelarea funciilor moteite, aflate pe oricare nivel al ierarhiei, bineneles innd cont de nivelurile de protecie, private i/sau protected.

8. Funciile virtuale i funciile redefinite


Atunci cnd lucrm cu clase derivate, putem foarte uor grei transformnd o funcie virtual n funcie redefinit. i aceasta datorit faptului c cele dou categorii se aseamn. Totui, exist cteva diferene, nu fr importan, i anume: Funciile membre redefinite sunt ataate obiectului urmnd procedura static (la compilare), n timp ce funciile virtuale fac parte din cea de a dou categorie, legturile cu obiectele fiind realizate dinamic (n timpul execuiei). Funciile membre redefinite pot avea liste diferite de parametri, n timp ce funciile virtuale trebuie s posede aceiai list de parametri. Reamintim c ambele tipuri de funcii sunt nzestrate cu pointerul this. Deci, dac n cadrul ierarhiei de clase, lucrurile nu se comport exact cum vrem noi, va trebui s verificm toate funciile virtuale pentru a ne asigura c ntr-adevr sunt virtuale i nu redefinite. #include <stdio.h> struct scade { virtual int executa (unsigned char c) {return --c;} }; struct aduna : scade { int executa (char c) {return ++c;} }; void main ( ) { scade *p=new aduna; int k=p->executa (43); printf (k=%d\n,k); } Ce va afia programul anterior? Contrar ateptrilor programul nu va afia valoareea 44. Privii cu atenie declaraia Versiunea scade a funciei executa() accept un argument de tip unsigned char, iar versiunea aduna a aceleiai funcii accept un argument de tip char. Datorit acestei nepotriviri de tip, a doua versiune a funciei executa() nu este virtual, ea este redefinit. Chiar dac prin intermediul lui p acionm asupra unui obiect aduna, apelnd funcia executa() ne referim la versiunea scade. Deci, programul va afia valoarea 42. 72

9. Interaciunea dintre funcii virtuale i non-virtuale


Iat cteva reguli ce se aplic n orice combinaie de funcii virtuale cu non-virtuale: Funciile virtuale nu pot fi redefinite, Funciile virtuale pot apela funcii non-virtuale, Funciile non-virtuale pot apela funcii virtuale, Funciile virtuale pot apela orice alt funcie virtual, Funciile virtuale pot apela funcii redefinite. #include <iostream.h> struct Mesaje_bune { virtual void act1 ( ) {cout <<"Printul vede printesa\n";act2 ( ) ;} void act2 ( ) {cout <<"Printul o saruta\n";act3 ( ) ;} virtual void act3 ( ) {cout <<"Printesa se trezeste\n";act4 ( ) ;} virtual void act4 ( ) {cout <<"Si au trait fericiti\n";act5 ( ) ;} void act5 ( ) {cout <<"Sfarsit !\n";} }; struct Mesaje_rele: Mesaje_bune { void act3 ( ) {cout <<"Printesa ramane teapana\n";act4 ( ) ;} void act4 ( ) {cout <<"Printul fuge ingrozit\n";act5 ( ) ;} void act5 ( ) {cout <<"Un sfirsit, nefericit !\n";} } ; void main ( ) { char c ; Mesaje_bune *mes ; cout <<"Care varianta vreti sa o vedeti (B/R) ?\n"; cin>>c; if ((c=='B') || (c=='b')) mes=new Mesaje_bune; else mes=new Mesaje_rele; mes->act1 ( ); delete mes; } 73

Pentru a nelege modul de utilizare a funciilor virtuale, este necesar a se cunoate modul de implementare a acestora. Aa cum se tie fiecare funcie membr are ataat pointerul this. Dar n cazul funciilor virtuale, ceea ce utilizm nu este un pointer, ci o tabel de pointeri ctre funcii, numit tabel de funcii virtuale. Fiecare clas care are cel puin o funcie virtual, posed i o astfel de tabel asociat ei. Tabela are o intrare pentru fiecare funcie virtual din clas, incluzndu-le pe cele motenite de la clasa de baz. Fiecare astfel de intrare este un pointer ctre actuala funcie chemat de funcia virtual corespunztoare. Un obiect creat dintr-o clas avnd funcii virtuale posed un pointer suplimentar memorat n el, prin intermediul cruia se acceseaz tabela virtual a clasei. Ori de cte ori este apelat o funcie virtual pentru acel obiect, pointerul va localiza, n tabel, funcia corespunztoare. (*act1)() (*act3)() (*act4)()

Mesaje_bune Tabela de Functii Virtuale Mesaje_rele

void Mesaje_bune::act1 ( ) {...} void Mesaje_bune::act3 ( ) {...} void Mesaje_bune::act4 ( ) {...}

(*act1)() (*act3)() (*act4)()

void Mesaje_rele::act3 ( ) {...} void Mesaje_rele::act4 ( ) {...}

n aceast figur observm cum ambele tabele conin acelai pointer ctre act1(), iar act2() i act5() nici nu apar.

10. Constructori i destructori n clase derivate


Pentru nceput, s nzestrm clasele Punct i Cerc cu constructori. struct Punct { float x,y; // Constructorul are acelasi nume cu clasa Punct (float xi, float yi) {Incarca_punct(xi,yi);} void Incarca_punct(float xi, float yi ) {x=xi; y=yi;} }; struct Cerc : Punct { float raza; Cerc (float xi, float yi, float r); void Incarca_raza(float r) {raza = r;} }; Cerc:: Cerc(float xi, float yi, float r) : Punct (xi, yi); { Incarca_raza(r); }

74

n ceea ce privete clasa de baz, lucrurile nu sunt noi. n schimb, n cadrul clasei derivate Cerc, constructorul acesteia apeleaz constructorul clasei de baz. Sintaxa este asemntoare cu cea de iniializare a membrilor, prin listele de instaniere: :Punct (xi, yi) // Apelul constructorului clasei de baz Putem considera atributele motenite ca membri ascuni. De exemplu, putem considera c Punct este membru ascuns n clasa Cerc i deci, va fi iniializat prin apelul contructorului su. Neavnd nume, membrul ascuns va fi instaniat prin utilizarea numelui clasei sale. Elementele pe care le posed Cerc vor fi instaniate dup iniializarea celor motenite, ordinea fiind aceasta, totdeauna. Astfel, ne asigurm de completitudinea iniializrii poriunii de baz, naintea celei derivate. n cazul n care clasa de baz are un constructor implicit apelul acestuia n cadrul contructorului clasei derivate nu mai este necesar, apelul realizndu-se automat. Astfel, adugnd clasei Punct constructorul implicit struct Punct { Punct ( ) {Incarca_punct (0,0); } }; putem extinde clasa Cerc astfel: struct Cerc : Punct { Cerc (float r ); }; Cerc :: Cerc (float r ) {Incarca_raza(r); } membrii motenii x i y fiind iniializai cu 0 prin intermediul constructorului implicit al clasei Punct, urmnd ca raza s fie iniializat n cadrul constructorului clasei derivate Cerc. Regul privitoare la constructorii claselor de baz i celor derivate: Dac toi constructorii clasei de baz necesit parametri, atunci clasa derivat trebuie s aib un constructor, acesta trebuind s apeleze unii dintre constructorii clasei de baz. Observm, deci c prezena constructorului n clasa derivat, Cerc, nu este opional. Ea impunndu-se, apelul este executat cu lista corespunztoare de parametri. Referitor la destructori, acetia neacceptnd parametri, nu se impun prin prezen. Atunci cnd un obiect aparinnd unei clase derivate este pe punctul de a fi distrus, destructorul clasei derivate este primul executat, operaiune urmat de apelul destructorilor eventualilor membri ai clasei derivate, ajungndu-se, n cele din urm, la apelul destructorului clasei de baz. #include <stdio.h> struct Calculator_Ordinar { Calculator_Ordinar ( ) { printf (Pornit\n);} Calculator_Ordinar ( ) {printf (Oprit\n);} float aduna (float a, float b ) {return a+b;} }; struct Calculator : Calculator_Ordinar { float memorie; Calculator ( ); 75

Calculator ( ); void Incarc_memoria (float m) {memorie=m; } float Aduna_memoriei (float f ) { return memorie+=f; } float Spune_memoria ( ) {return memorie; } }; Calculator :: Calculator ( ) {printf (Start Calculator\n); memorie=0; } Calculator ::Calculator ( ) {printf (Stop Calculator\n); memorie=0; }; void main ( ) { Calculator i; i. Incarc_memoria (20.0); i. Aduna_memoriei (5.0); i. Aduna_memoriei(17.0); printf (Totalul :%f\n, i.Spune_memoria ( ) ); }

11. Constructori i destructori virtuali


Mai devrerme sau mai trziu vom avea nevoie ca destructorii i constructorii s fie metode virtuale. Iat cteva reguli importante n acest sens: Constructorii nu sunt motenii. Deci, nu putem apela un constructor din clasa de baz pentru a iniializa o ntreag clas derivat. Dac clasa de baz are constructor cu parametri (deci nefiind implicit), atunci i clasa derivat trebuie s posede contructorul su, iar acesta, n mod obligatoriu, l va apela pe cel al clasei de baz. Dac clasa de baz are constructor implicit, atunci n clasa derivat constructorul poate lipsi. n orice caz, constructorul clasei derivate poate apela constructorul implicit al clasei de baz. Constructorii nu pot fi virtuali. Acest aspect se datoreaz tocmai faptului c ei nu pot fi motenii, metodele virtuale putnd fi. Destructorii pot, i probabil vor fi, virtuali. Dac redefinim un destructor, atunci va tebui s-l definim virtual, pentru a ne asigura de corectitudinea apelrii sale.

12. Motenirea i ascunderea informaiei


Pn acum am evitat s declarm vreun membru privat; toi membrii claselor erau publici. n continuare, vom vedea cum putem controla accesul la membrii claselor, de baz sau derivate. 76

Dac un membru este privat n clasa de baz, atunci el nu este accesibil n clasele derivate. Elementele publice n clasa de baz sunt accesibile i n derivri ale acesteia. Dac o clas derivat este declarat prin intermediul cuvntului struct, atunci, implicit, membrii publici din clasa de baz sunt publici i n cea derivat. Dac ns, clasa derivat este declarat cu ajutorul cuvntului class, atunci, prin, convenie membrii publici ai clasei de baz sunt privai n cea derivat. struct Cod { int cod ; }; class Cod_secret : Cod { public: int alt_cod; void Incarc_cod ( ) {alt_cod=cod; } }; void main() { Cod cd; Cod_secret sc; cd.cod=42; sc.cod=42;

// Legal // Ilegal, clasei derivate acest // element ii este privat sc.alt_cod=42; // Legal; } Utiliznd public naintea clasei de baz, transformm aceast clas ntr-un public, adic, n clasa derivat, membrii clasei de baz declarai publici vor fi publici. class Cod_secret : public Cod { public : int alt_cod; void Incarc_cod ( ) { alt_cod=cod; } }; Cod_secret sc; Sc.cod=42; // Legal

Utiliznd ns private, clasa de baz devine, i ea, private, i astfel, toi membrii publici ai si devin privai n clasa derivat. struct Punct { float x,y ; } ; struct Cerc : private Punct { float raza ; 77

void Incarca_raza (float r) {raza=r; } }; Punct p; Cerc c; p.x=0.0; // Legal p.y=100.0; // Legal c.x=10.0; // Ilegal, membrul fiind private c.y=0.0; // Ilegal, membrul fiind private c.raza=3000.0; // Legal

13. Utilizarea membrilor protejai


Uneori dorim ca anumii membri s fie accesibili att clasei de baz, ct i celor derivate lor, dar s semene oarecum cu cei privai, deci s nu poat fi manevrai de utilizator dect prin intermediul funciilor membre. Utiliznd cuvntul protected, membrii nsoii de acesta au proprieti dorite. class Punct { protected : float x,y; public : void Incarc_punct (float xi, float yi ) {x=xi; y=yi; } float Spune_x ( ) {return x; } float Spune_y ( ) {return y; } }; class Cerc : public Punct { public : float raza; }; n legtur cu membrii protejai, mai trebuie menionat c : Dac o clas de baz, public, are membri protejai, acetia rmn protejai i n clasele derivate. Dac o clas de baz, privat, conine membri prtejai, acetia, n clasele derivate, devin privai. De regul, clasele de baz sunt publice, pentru o mai bun flexibilitate a claselor derivate.

14. Clase virtuale


Din cauza motenirii multipie se poate ntmpla ca o clas de baz s fie prezent n mai multe exemplare ntr-o clas derivat. S considerm urmtorul exemplu: Animal Domestic Cine 78 Mamifer

Figura 1. Date membru motenite n don exemplare n acest caz datele membru ale clasei animul vor fi motenite n dou exemplare de ctre clasa cine. Primul exemplar se motenete prin clasa domestic iar cel de al doilea prin clasa mamifer. Aceste date membru pot fi accesate folosind operatorul de rezoluie precedat de numele clasei prin care se face motenirea: #include <iostream.h> #include <string.h> #include <conio.h> class animal { protected: char nume[20] ; animal (char* n) ; }; class mamifer : public animal { protected: int greutate; public: mamifer(char* n, int g) ; }; class domestic : public animal { protected: int comportament; public: domestic(char* n, int c) ; }; class caine : public mamifer, public domestic { protected: int latra; public: caine(char* n, int g, int c, int l) ; void scrie_date() ; }; animal::animal(char* n) { strcpy(nume, n) ; } mamifer::mamifer(char* n, int g): animal(n) { greutate = g; } domestic::domestic(char* n, int c): animal(n) { 79

comportament = c ; } caine::caine(char* n, int g, int c, int l): mamifer(n, g), domestic(n, c) { latra = l; } void caine::scrie_date() { cout << "Numele (mostenit prin clasa mamifer): " << mamifer::nume << endl ; cout << "Numele (mostenit prin clasa domestic): " << domestic::nume << endl ; cout << "Greutatea: " << greutate << endl ; cout << "Comportmentul: " << comportament << endl ; if ( latra ) cout << "Latra: da" << endl; else cout << "Latra; nu" << endl ; } void main( ) { clrscr( ) ; caine B ("boxer", 12, 9, 1) ; B.scrie_date ( ) ; } Observm c data membru nume s-a motenit n dou exemplare de ctre clasa cine, i referirea la aceste date s-a facut prin mamifer::.nume respectiv domestic::nume. Prin execuie se va afia data membru nume de dou ori, motenit prin cele dou clase. De asemenea se va afia greutatea i comportamentul cinelui, i faptul c latr sau nu. Ar fi de dorit ns ca numele s fie memorat numai ntr-un singur exemplar n obiectele clasei cine. Acest lucru se poate realiza cu ajutorul claselor virtuale. Dac nu se dorete ca datele membru a unei clase de baz s fie prezente n mai multe exemplare ntr-o clas derivat, atunci se folosesc clase virtuale. Clasele de baz devin virtuale prin motenire, dac se specific acest lucru prin plasarea cuvntului cheie virtual n faa numelui clasei, n lista claselor de baz. Astfel clasa de baz respectiv va deveni virtual. referitor la clasa derivat. Exemplul de mai sus se modific n modul urmtor: #include <iostream.h> #include <string.h> #include <conio.h> class animal { protected: char nume [20] ; public: animal(char* n) ; } ; class mamifer : virtual public animal { //clasa animal va fi 80

protected: //virtuala referitor int greutate; //la clasa mamifer public: mamifer(char* n, int g) ; } ; class domestic : virtual public animal { //clasa animal va fi protected: //virtuala referitor int comportament; //la clasa domestic public: domestic(char* n, int c) ; }; class caine : public mamifer, public domestic { protected: int latra; public: caine(char* n, int g, int c, int l) ; void scrie_date() ; }; animal::animal(char* n) { strcpy (nume, n) ; } mamifer::mamifer(char* n, int g): animal(n) { greutate = g; } domestic::domestic(char* n, int c): animal(n) { comportament = c; } caine::caine(char* n, int g, int c, int l): animal(n), //trebuie apelat constructorul clasei animal mamifer(n, g),//in mod explicit, deoarece ea fiind virtuala domestic(n, c)//constructorul nu se va apela prin unul din { latra =l; } //clasele mamifer sau domestic void caine::scrie_date() { cout << "Numele: " << nume << endl; cout << "Greutatea: " << greutate << endl; cout << "Comportamentul: " << comportament << endl ; if ( latra ) cout << "Latra: da" << endl; else cout << "Latra: nu" << endl ; } main() { clrscr() ; 81

caine B("boxer", 12, 9, 1); B.scrie_date() ; } ntr-o ierarhie complicat de clase unele clase de baz pot fi motenite n mai multe exemplare ntr-o clas derivat. ntre aceste exemplare pot fi virtuale i nevirtuale. S considerm urmtorul exemplu referitor la acest lucru : A B C D G Figura 2. Ierarhie de clase n aceast ierarhie de obiecte clasa de baz A se motenete n mod virtual de ctre clasele B i C i n mod nevirtual de ctre clasa D. Clasele de baz ale clasei G sunt B, C, D, E i F, toate motenirile fiind nevirtuale cu excepia clasei F. Se pot pune atunci urmtoarele probleme. n ce ordine vor fi executate funciile constructor n cazul n care se creaz un obiect al clasei G? Dac o clas de baz este motenit n mai multe exemplare ntr-o clas derivat printre care pot fi att virtuale ct i nevirtuale, atunci de cte ori se va apela constructorul clasei de baz? Pentru a da rspuns la aceste ntrebri s considerm urmtorul program. #include <iostream.h> #include <string.h> #include <conio.h> class A { public: A(char* a) { cout << "Se apeleaza constructorul clasei A" << " cu parametrul " << a << endl ; }} ; class B: virtual public A { public: B(char* b) : A(b) { cout<< "Se apeleaza constructorul clasei B" << " cu parametrul " << b << endl; } }; class C: virtual public A { public: C(char* c): A(c) { cout << "Se apeleaza constructorul clasei C" << " cu parametrul " << c << endl ; }}; class D: public A { public: D(char* d) : A(d) { cout << "Se apeleaza constructorul clasei D" 82 E F

<< " cu parametrul " << d << endl; }}; class E { public: E(char* e) { cout << "Se apeleaza constructorul clasei E" << " cu parametrul " << e << endl; }}; class F { public: F(char* f) { cout << "Se apeleaza constructorul clasei F" << " cu parametrul " << f << endl ; }}; class G: public B, public C, public D, public E, virtual public F { public: G(char* g) : A(g), B (g) , C(g) , D(g) , E(g) , F(g) { cout << "Se apeleaza constructorul clasei G" << " cu parametrul " << g << endl ; }}; int main() { clrscr () ; G ob("obiect") ; return 0 ; } Prin execuie se obine urmtorul rezultat: Se apeleaza constructorul clasei A cu parametrul obiect Se apeleaza constructorul clasei F cu parametrul obiect Se apeleaza constructorul clasei B cu parametrul obiect Se apeleaza constructorul clasei C cu parametrul obi.ect Se apeleaza constructorul clasei A cu parametrul obiect Se apeleaza constructorul clasei D cu parametrul obiect Se apeleaza constructorul clasei E cu parametrul obiect Se apeleaza constructorul clasei G cu parametrul obiect Acest rezultat se datoreaz urmtoarelor reguli. n cazul n care se creaz un obiect al clasei derivate, mai nti vor fi executate funciile constructor ale claselor de baz virtuale n ordinea din lista_claselor_de_baz (vezi declaraia claselor derivate), apoi constructorii claselor de baz nevirtuale n ordinea din lista_claselor_de_baz. Dac ntr-o ierarhie de clase, o clas de baz se motenete n mai multe exemplare ntr-o clas derivat, atunci la crearea unui obiect al clasei derivate se va executa constructorul clasei de baz o dat pentru toate exemplarele virtuale, i nc de attea ori cte exemplare nevirtuale exist. Deci n cazul exemplului de mai sus, mai nti se execut constructorii claselor A i F (ele fiind virtuale), apoi constructorii claselor B, C, D i E (clase nevirtuale). Constructorul clasei D apeleaz 83

mai nti constructorul clasei A (ea fiind nevirtual de aceast dat). De aceea ordinea de apelare a constructorilor va fi: A, F, B, C, A, D, E i G. Clase abstracte. Funcia membru virtual pur n cazul unei ierarhii de clase mai complicate, clasa de baz poate avea nite proprieti generale despre care tim, dar nu le putem defini numai n clasele derivate. De exemplu s considerm ierarhia de clase din figura 3. Observm c putem determina nite proprieti referitoare la clasele derivate. De exemplu greutatea medie, durata medie de via i viteza medie de deplasare. Aceste proprieti se vor descrie cu ajutorul unor funcii membru. n principiu i pentru clasa animal exist o greutate medie, durat medie de via i vitez medie de deplasare. Dar aceste proprieti ar fi mult mai greu de determinat i ele nici nu sunt importante pentru noi ntr-o generalitate de acest fel. Totui pentru o tratare general ar fi bine, dac cele trei funcii membru ar fi declarate n clasa de baz i redefinite n clasele derivate. n acest scop s-a introdus noiunea de funcie membru virtual pur. Animal

Porumbel

Urs

Cal

Figura 3. Ierarhie de clase referitoare la animale Funcia virtual pur este o funcie membru care este declarat, dar nu este definit n clasa respectiv. Ea trebuie definit ntr-o clas derivat. Funcia membru virtual pur se declar n modul urmtor. Antetul obinuit al funciei este precedat de cuvntul cheie virtual i antetul se termin cu = 0. Dup cum arat numele i declaraia ei, funcia membru virtual pur este o funcie virtual deci selectarea exemplarului funciei din ierarhia de clase se va face n timpul executiei programului. Clasele care conin cel puin o funcie membru virtual pur se vor numi clase abstracte. Deoarece clasele abstracte conin funcii membru care nu sunt definite, nu se pot crea obiecte aparinnd claselor abstracte. Dac funcia virtual pur nu s-a definit n clasa derivat atunci i clasa derivat va fi clas abstract i ca atare nu se pot defini obiecte aparinnd acelei clase. S considerm exemplul de mai sus i s scriem un program, care referitor la un porumbel, urs sau cal determin dac el este gras sau slab, rapid sau ncet, respectiv tnr sau btrn. Afiarea acestui rezultat se va face de ctre o funcie membru a clasei animal care nu se suprancarc n clasele derivate. #include <conio.h> #include<iostream.h> class animal { protected: double greutate; // kg double varsta; // ani double viteza; // km / h public: animal( double g, double vl, double v2) ; virtual double greutate_medie( ) = 0; virtual double durata_de_viata_medie( ) = 0; 84

virtual double viteza_medie( ) = 0; int gras( ) { return greutate > greutate_medie(); } int rapid( ) { return viteza > viteza_medie( ); } int tanar( ) { return 2 * varsta < durata_de_viata_medie(); } void afiseaza( ); }; animal::animal( double g, double vl, double v2) { greutate = g; varsta = vl; viteza = v2 ; } void animal::afiseaza () { cout << ( gras() ? "gras, " : "slab, " ); cout << ( tanar() ? "tanar, " : "batran, " ) ; cout << ( rapid( ) ? "rapid" : "incet" ) << endl; } class porumbel : public animal { public: porumbel ( double g, double vl, double v2): animal(g, vl, v2) {} double greutate_medie( ) { return 0.5; } double durata_de_viata_medie() { return 6; } double viteza_medie( ) { return 90; } }; class urs: public animal { public: urs( double g, double vl, double v2): animal(g, vl, v2) {} double greutate_medie() { return 450; } double durata_de_viata_medie() { return 43; } double viteza_medie() { return 40; } } ; class cal: public animal { public: cal( double g, double vl, double v2): animal(g, vl, v2) {} double greutate_medie() { return 1000; } double durata_de_viata_medie() { return 36; } double viteza_medie () { return 60; } }; void main( ) { clrscr ( ) ; porumbel p(0.6, 1, 80); urs u(500, 40, 46) ; 85

cal c(900, 8, 70) ; p.afiseaza( ) ; u .afiseaza( ) ; c.afiseaza( ) ; } Observm c dei clasa animal este clas abstract, este util introducerea ei, pentru c multe funcii membru pot fi definite n clasa de baz i motenite far modificri n cele trei clase derivate.

Operatorii ca funcii
Considerm operatorul +=. Aciunea acestui este aceea de a aduga o valoare unui numr de tip char, int, float sau double. Putem redefini acest operator pentru a opera asupra obiectelor de tip Persoana: class Persoana { private: char nume[40]; long int telefon; int varsta; public: Persoana (char *, long, int); long Telefon ( ) { return telefon; } char *Nume ( ) { return nume ; } int Varsta ( ) { return varsta; } int Citeste (char *n, long t=0, int v=0); int Seteaza_Varsta (int v) { return varsta=v; } }; Persoana :: Persoana (char *n, long t, int v) { strcpy (nume,n); telefon=t; varsta=v; } Pentru aceasta, vom defini o funcie avnd numele operator +=(), i doi parametri. Aceti parametri sunt cei doi operanzi ai operatorului +=. void operator += (Persoana &p, int v) { p.Seteaza_Varsta(p.Varsta () +v ); } Odat operatorul definit utilizarea se realizeaz prin Persoana p (Mircea,0,9); p+=5; sau apelndu-l ca funcie operator+=(p,5); Funcia operator poate fi membr a clasei asupra creia acioneaz: 86

class Persoana { void operator+=(int v); }; void Persoana ::operator +=(int v) { Seteaza_Varsta(varsta+v); } Funciile operator membre vor avea un argument mai puin dect cele non-membre, argumentul ascuns al funciilor membre fiind furnizat de pointerul this. Apelul unui astfel de operator membru va fi p+=5; sau p.operator+=(5);

Declararea funciilor operator


Sintaxa sub care se introduc funciile operator este Pentru cele membre tip_returnat class ::operatorOP(Arg, ...) { corp operator } Pentru cele non-membre: tip_returnat operatorOP(Arg, ) { corp operator }

Aa cum se vede, o funcie operator are aceleai componente pe care le are orice alt funcie, incluznd un nume, un tip returnat, argumente, corp i, eventual, apartenena la o clas. Prin ce difer aceste funcii fa de cele cu care ne-am obinuit este tocmai denumirea operatorOP unde OP este unul dintre operatorii din C++, enumerai n continuare + - * / % ^ & ! , = < > <= >= ++ -- << >> = = != && += -= /= %= ^= &= = <<= >>= [] ( ) -> ->* new delete Exist trei elemente care trebuie clarificate atunci cnd declarm o astfel de funcie: aritate, poziie i domeniu de aciune. Deci va trebui s putem spune dac Este operator unar sau binar ? Este funcie membr sau nu? Aceast clarificare se impune deoarece aceste trei elemente determin att numrul ct i tipul argumentelor funciei. n total, exist ase variante de a declara funcii operator. Le vom exemplifica utiliznd clasa Persoana:

Operatori Unari
Prefix Membri Non-membri void Persoana ::operator ++( ) void operator ++ (Persoana &p) { Seteaza_Varsta(varsta+1); } { p.Seteaza_Varsta(p.Varsta ( ) +1 ); 87

Postfix

void Persoana ::operator ++(int) { Seteaza_Varsta(num+1); }

} void operator ++ (Persoana &p, int ) { p.Seteaza_Varsta(p.Varsta ( ) +1 ); }

Operatori Binari
void Persoana ::operator +=(int v) { Seteaza_Varsta(varsta+v); } void operator += (Persoana &p, int v) { p.Seteaza_Varsta(p.Varsta ( ) +v ); }

Apelul funciilor operator


Modul de efectuare a apelurilor funciilor operator este prezentat n tabelul urmtor: Operator Binar Funcie membr Sintax Apel DA X Op Y X.operatorOp(Y) NU X Op Y operatorOp(X,Y) Operator Unar Funcie membr Sintax Apel DA OpX X.operatorOp( ) DA XOp X.operatorOp(int) NU OpX operatorOp(X) NU XOp operatorOp(X,int) Se observ modul n care, pentru funciile membre, primul operand al unui operator devine obiectul care, nzestrat cu acest operator, acioneaz asupra operandului al doilea. n cazul operatorilor unari, n vederea distingerii cazurilor prefixate de cele postfixate, este adugat un argument suplimentar operatorilor postfixai, simulndu-se oarecum modul de acionare al acestora. class Persoana { int operator++( ); int operator++(int); }; int Persoana:: operator++() { return Seteaza_Varsta (varsta+1); } int Persoana::operator++(int v) { int v=varsta; Seteaza_Varsta(varsta+1); return v; } void main ( ) { Persoana p(Mircea, 0,9);

88

p++; / / Apel x.operator++(int) ++p; / / Apel x.operator++( ) }

Redefinirea funciilor operator


Ca orice alt funcie C++, funciile operator pot fi redefinite. Pentru aceasta nu trebuie dect s respectm regulile de redefinire a funciilor. class Persoana { void operator+=(int ); void operator+=(Persoana & ); }; void Persoana ::operator +=(int v) { Seteaza_Varsta(varsta+v); } void Persoana ::operator +=(Persoana &p) { Seteaza_Varsta(varsta+p.varsta); } Persoana a(Alex,0,9) , b(Mircea,1,30); a+=2; a+=b;

Utilizarea referinelor ca argumente


void Persoana::operator+=(Persoana &p); Utilizarea referinei ca argument se datoreaz faptului c asupra obiectului furnizat prin referin funcia opereaz mult mai natural. S presupunem, spre exemplu, c nu am utiliza variabilele prin referin. Atunci operandul operatorului +=() va fi pasat prin valoare, ceea ce presupune existena fie a unei copii a acestuia, fie a unui pointer ctre acesta. Se consider varianta utiliznd pointeri void Persoana ::operator+=(Persoana *p) n acest caz, apelul operatorului se va realiza prin : a+=&b ; Restricii Nu este permis introducerea de noi operatori, Nu se poate schimba aritatea (unar sau binar) unui operator, 89

Cel puin un argument al unei funcii operator trebuie s fie obiect sau funcia trebuie s fie membr a unei clase, Operatorii nu pot fi combinai, n vederea obinerii de noi operatori, Operatorii =,[],() i -> trebuie s fie funcii membre nestatice. Toate aceste restricii au ca puncte comune atingerea urmtoarelor scopuri Evitarea ambiguitilor provocate de apariia unor noi operatori sau de schimbrile de aritate, Asigurarea unor protecii n ceea ce privete aciunea operatorilor asupra datelor de baz.

Redefinirea operatorului de atribuire =


Poate cel mai important operator pe care l putem redefini este cel de atribuire. Cel mai simplu mod de a efectua atribuirea ntre obiecte este de a lsa pe seama compilatorului execuia ntregii operaii, operaie care va decurge pe componente. Rezultatele atribuirii sunt conform cu cele ateptate numai n cazul aciunii operatorului asupra obiectelor neavnd ca membre constante, referine sau asupra obiectelor avnd incorporat operatorul = ntre elementele sale private. Ce se ntmpl, de exemplu, n cazul n care vrem s asignm unui obiect de tip Persoana un altul de acelai tip? Pentru ca atribuirea s funcioneze conform celor propuse, se impune redefinirea operatorului de atribuire. class Persoana { void operator= (Persoana &s); }; void Persoana :: operator=(Persoana &s) { Seteaza_Varsta (s.Varsta( )); strcpy(nume, s.Nume( )); telefon=s.Telefon( ); } Avnd acest nou operator, putem efectua atribuiri de tipul celei amintite, astfel de atribuiri acionnd ca mai jos. Persoana p (Alex, 0,5),s(Mircea,0,19); p=s; / / ceea ce este echivalent cu p.operator=(s) ;

Funcii generice sau funcii template


n programarea uzual, funciile trebuiau n permanen rescrise, readaptate diferitelor tipuri de date, indiferent dac principiul i etapele funcionrii lor rmneau neschimbate de la o versiune la alta. Un exemplu de funcie de sortare a unui vector de numere ntregi este prezentat n continuare:

90

#include <stdio.h> #include <iostream.h> void Bubblesort(int *a, int st, int sf) { int aux,gata,i; do { gata=1; for(i=st;i<sf-1;i++) if (a[i]>a[i+1]) {aux=a[i]; a[i]=a[i+1]; a[i+1]=aux; gata=0;} } while (gata==0); } int vector[5]={42,7,12,90,54}; void main() { Bubblesort(vector,0,4); for (int i=0;i<4;i++) cout<<vector[i]<<'\t'; cout<<'\n'; } Este evident c algoritmul implementat va funciona i n cazul numerelor reale sau n cazul unei liste de persoane, cheia de sortare fiind vrsta persoanelor sau numele acestora. Dar suntem nevoii s rescriem de fiecare dat aceast dat aceast funcie, singurele modificri, de la o variant la alta, aprnd numai n cadrul listei de parametri ai funciei i n cadrul declaraiei aux. De exemplu, pentru a ordona un ir de numere reale, declaraiile vor fi: void Bubblesort (float *a, int st, int sf) ... float aux; Chiar i aa, existena acestor dou linii modificate impune rescrierea, sau copierea, unei versiuni anterioare i redenumirea acesteia. O operaie incomod. Funciile template vin s simplifice situaiile de genul celei prezentate mai sus, acestea fiind asemntoare expresiilor macro, utiliznd diferii parametri. Aceti parametri vor fi furnizai unei funcii generice, template, prin urmtoarea sintax: template <par1, par2, > Parametrii pot fi de dou categorii: tipuri de date sau constante. Tipurile sunt furnizate prin numerele lor, prefixat de cuvntul cheie class. Orice parametru care nu are drept prefix cuvntul class este considerat, n mod automat constant. template <class T> template <int Dim_Vect, int Dim_Elem> template <class Tip_Elem, long Dim, class Comparator> 91

O funcie generic de sortare Pentru a transforma funcia Bubblesort() ntr-o funcie generic, va trebui s parametrizm tipul elementelor ce urmeaz a fi sortate. Astfel, vom utiliza numele acestui parametru n locul numelui tipului menionat a fi modificat. template <class T> void Bubblesort(T *a, int st, int sf) { int gata,i; T aux ; do { gata=1; for(i=st;i<sf-1;i++) if (a[i]>a[i+1]) {aux=a[i]; a[i]=a[i+1]; a[i+1]=aux; gata=0;} } while (gata==0); } n primul rnd, declarm tipul T ca fiind parametru al funciei i-l vom utiliza n locul argumentului int. Avnd aceast funcie generic, putem sorta iruri de diferite tipuri de date: int i_vect[3]={2,3,1}; float f_vect[3]={0.2,0.3,0.1}; Bubblesort (i_vect,0,2); Bubblesort (f_vect,0,2); n momentul n care compilatorul detecteaz apelurile funciei Bubblesort(), acesta va instania, de fiecare dat, tipul parametrului tip, T, cu tipul indicat de apel, urmnd ca, apoi, s identifice o implementare a funciei corespunztoare acestui tip. n caz c aceast cutare eueaz, va genera automat implementarea respectiv, n continuare funcia fiind tratat ca orice alt funcie. Elemente ascunse n funciile template Actuala funcie de sortare are pretenia de a fi ctigat o total independen fa de tipul elementelor supuse aciunilor ei. Bineneles c numerele sunt ordonate, n mod natural, prin intermediul operatorului de inegalitate >. Dar o list de persoane? Abordnd problema sortrii unor tipuri de date, structurate, se impune, n mod evident, nzestrarea acestora cu operatorul >, adaptat fiecrei structuri n parte. n general, o clas va trebui s conin eventualele redefiniri ale unor operatori a cror aciune va fi diferit de cea uzual. De exemplu, considernd tipul de dat Persoana, lista tuturor persoanelor poate fi sortat alfabetic, n funcie de vrst sau, n general, dup orice component membr a clasei.

92

#include <string.h> #include<iostream.h> template <class T> void Bubblesort(T *a,int st,int sf) { int gata,i; T aux ; do { gata=1; for(i=st;i<sf-1;i++) if (a[i]>a[i+1]) {aux=a[i]; a[i]=a[i+1]; a[i+1]=aux; gata=0;} } while (gata==0); } class Persoana { public: char nume[16]; int varsta; Persoana( ); Persoana (char *, int); Persoana &operator=(Persoana &); friend int operator >(Persoana &, Persoana &); friend int Scrie (Persoana &); }; Persoana :: Persoana ( ) { varsta=0; } Persoana :: Persoana (char *n, int a ) { strcpy(nume, n); varsta=a; } Persoana &Persoana::operator=(Persoana &p) { strcpy(nume,p.nume); varsta=p.varsta; return *this; } int operator>(Persoana &a, Persoana &b) { return a.varsta>b.varsta; } int Scrie (Persoana &p) { return printf ("%s %d",p.nume, p.varsta); }

93

int i_vect[5]={ 42,17,55,75,25}; Persoana p_vect[3]={ Persoana ("Ion",13), Persoana ("Vasile",28), Persoana("Mircea",6) }; void main ( ) { Bubblesort(i_vect, 0,4); for (int i=0; i<5; i++) cout<<i_vect[i]<<'\t'; cout<<'\n'; Bubblesort (p_vect,0,2); for (i=0;i<3;i++) { Scrie(p_vect[i]);cout<<'\t';} cout<<'\n'; }

94

You might also like