Sunteți pe pagina 1din 17

Cap.

2
C++ i programarea orientat pe obiecte
Dei ideea programrii orientate pe obiecte (POO) a aprut n anii 1960, fiind pus n practic prin intermediul limbajelor SIMULA (1967) i SMALLTALK (1975), totui, acesta a avut o dezvoltare rapid odat cu apariia limbajului C++. De fapt, C++, proiectat de un colectiv condus de Bjarne Stroustrup, a aprut prin mbinarea caracteristicilor limbajului C cu mecanismele de modelare ale limbajului SIMULA 67. Primul compilator realizat de Bjarne Stroustrup s-a numit C with Classes ; prima versiune comercial a acestuia a aprut la AT&T n 1983, cu denumirea modificat n cea actual, C++ (sugerat de Rik Masciti, un colaborator apropiat a lui B. Stroustrup). Denumirea de C++ semnific de fapt multiplele faciliti adugate limbajului C. Scopul acestui capitol este de a face o scurt trecere n revist a celor mai importante caracteristici i concepte introduse de limbajul C++, fr a se prezenta detaliile sintactice ale acestuia.

2.1. Extensii ale limbajului C n limbajul C++


Limbajul C++ este un supraset al limbajului C. El ofer suport att pentru programarea procedural ct i pentru abstractizarea datelor, dar scopul principal este suportul oferit pentru programarea orientat pe obiecte. Extensiile limbajului C se refer la dou aspecte: adugarea unor faciliti care nu sunt legate direct de programarea orientat pe obiecte; adugarea elementelor de baz pentru a oferi suport programrii orientate pe obiecte. n primul caz este vorba despre elemente precum: tipul referin, substituia in-line a funciilor etc., pe cnd n cazul al doilea este vorba despre elemente precum: noiunea de clas, motenire, polimorfism etc. n acest paragraf se vor discuta aspectele legate de extensiile limbajului C++ ce nu privesc programarea orientat pe obiecte, urmnd ca n paragraful urmtor, prin cteva exemple simple, s se prezinte cteva aspecte legate de programarea orientat pe obiecte. 2.1.1. Noi tipuri de date Limbajul C++ extinde tipurile de date fundamentale ale limbajului C cu nc dou tipuri de date: bool i wchar_t. n plus, C++ permite utilizarea a nc unui tip de date numit string, pentru care a fost creat o clas string. Declaraiile acestei clase se afl n fiierul string.h. Despre clasa string se va discuta ntr-un capitol ulterior. A) Tipul bool reprezint valorile logice (booleene), pentru care se utilizeaz constantele predefinite true i false. Exist o asemnare din acest punct de vedere cu limbajul Pascal, care are tipul predefinit Boolean, precum i cu Java, care are tipul boolean. Spre deosebire de Pascal ns, exist o compatibilitate ntre tipul bool i tipurile ntregi de date. Astfel, variabilele de tipul bool pot primi valori de tip ntreg, deoarece compilatorul efectueaz n mod automat o conversie de la tipul ntreg respectiv la tipul bool. De exemplu, pentru secvena: bool boolVar; int intVar; // ... boolVar = intVar; se genereaz o instruciue echivalent de forma: boolVar = intVar ? true : false; n mod asemntor, valorile de tip bool pot fi utilizate n locul valorilor de tip ntreg, compilatorul realiznd o conversie automat de forma:

2 -1

intVal = boolVal ? 1 : 0 ; n acest mod exist o compatibilitate cu regulile C de evaluare a expresiilor condiionale din anumite instruciuni precum instruciunile repetitive i instruciunea if. Avantajul utilizrii tipului bool const n faptul c permite scrierea unui cod mai general i mai intuitiv dect utilizarea tipului int. De exemplu, pentru funcia cu prototipul urmtor: bool Apartine(double x, double a, double b); se poate ti faptul c ea returneaz o valoare logic, pe cnd un prototip de forma: int Apartine(double x, double a, double b); nu ofer o asemenea certitudine. B) Tipul wchar_t (wide character) este o extensie a tipului char, care permite utilizarea mulimilor de caractere reprezentate intern pe doi octei (cum este Unicode). Pentru acest tip, sizeof(wchar_t) are valoarea 2, ceea ce permite utilizarea a peste 64000 de caractere diferite. 2.1.2. Declararea variabilelor Spre deosebire de limbajul C, n care declaraiile locale trebuie fcute doar la nceputul unui bloc, nainte de nceputul instruciunilor, n limbajul C++ declaraiile locale pot fi fcute oriunde n cadrul unui bloc, acolo unde sunt permise instruciuni. Domeniul de referin (vizibilitate) al unor astfel de variabile declarate local reprezint o parte din blocul n care au fost declarate, ncepnd din linia declaraiei respective i pn la sfritul blocului curent. Exemplu. void Prelucrare() { int k = 5 ; // incepe domeniul variabilei k // ... k = k + 3 ; // ... float x = 7 ; // incepe domeniul variabilei x // ... // incepe domeniul variabilei i for (int i=0 ; i<k ; i++) printf(%d, i) ; // ... // se termina domeniul variabilelor k, x si i } 2.1.3. Referine Unul dintre marile dezavantaje ale limbajului C fa de alte limbaje apropiate precum Pascal sau Ada l constituie modul de transmitere a parametrilor la apelul funciilor. Limbajul C permite doar transferul prin valoare, ceea ce face necesar utilizarea pointerilor n cazul n care o funcie modific valoarea unui anumit parametru. Limbajul C++ introduce n plus noiunea de referin. O referin este un nume alternativ (un alias) pentru o variabil. Un asemenea tip de date este un tip derivat, care se obine dintr-un tip de baz cu ajutorul operatorului &. Dac T este un tip de date, notaia T& reprezint tipul referin derivat din T, adic mulimea tuturor elementelor referin la tipul T. Valorilor elementelor de tip referin sunt asemntoare pointerilor, n sensul c o referin are ca valoare adresa de memorie a unei variabile ce aparine tipului de baz. Exist ns cteva deosebiri importante ntre pointeri i referine: a) O referin trebuie ntotdeauna iniializat la declarare. De exemplu, declaraia: int k ; int &r = k ; declar variabila referin r ca avnd o valoare egal cu adresa de memorie a variabilei k. Aceast iniializare se deosebete de atribuire, ea asigurnd doar faptul c variabila de baz (k) are un nou

2 -2

nume (r). n cadrul blocului unde au fost declarate cele dou variabile, se poate folosi att numele k ct i r pentru a referi acelai obiect. b) n momentul utilizrii ntr-un program, referinele sunt n mod automat dereferite, nemaifiind nevoie de operatorul * pentru dereferire. Exemplu: int k = 5 ; int &r = k, *p ; p = &k ; r = r + 1 ; // aceasta inseamna k = k + 1 *p = *p + 1 ; Cele dou observaii anterioare impun cteva precizri: valoarea unei referine nu poate fi modificat dup iniializare, ea referind ntotdeauna acelai obiect cu care a fost iniializat. De exemplu, instruciunea r=r+1; nu nseamn c se adaug 1 la valoarea lui r, ci la obiectul referit de r; operatorii nu acioneaz asupra referinei, ci asupra variabilei la care aceasta face referire. Principala utilizare a referinelor o constituie transferul parametrilor funciilor. n acest caz, un parametru formal de tip referin reprezint un alt nume pentru parametrul actual corespunztor n cazul unui apel. Orice modificare a valorii parametrului formal nseamn de fapt modificarea valorii parametrului actual respectiv. Exemplul 2.1. Interschimbarea a dou valori. Funcia Interschimbare1 utilizeaz pointeri, pe cnd Interschimbare2 utilizeaz referine. void Interschimbare1(int *a, int *b) { int c = *a ; *a = *b ; *b = c ; } void Interschimbare2(int &a, int &b) { int c = a ; a = b ; b = c ; } void Prelucrare() { int x = 7, y = 5 ; Interschimbare1(&x, &y) ; printf(%d %d\n, x, y) ; x = 7 ; y = 5 ; Interschimbare2(x, y) ; printf(%d %d\n, x, y) ; } n momentul apelului, parametrii formali de tip referin se iniializeaz cu adresa parametrilor actuali corespunztori, ceea ce impune ca aceti parametri actuali s fie nume de variabile cu un tip de date identic cu tipul de baz al referinei lor i nu adrese de memorie. 2.1.4. Funcii inline n cazul funciilor mici, cu un numr restrns de instruciuni, mecanismul de apel (crearea cadrelor pe stiv, transferul parametrilor etc.) poate deveni semnificativ n raport cu timpul de execuie al funciei, ceea ce duce la mrirea timpului de execuie al programului i scderea eficienei acestuia. O altenativ o poate constitui folosirea macrodefiniiilor, care sunt substituite de compilator n etapa de preprocesare. Exemplulu 2.2. n secvena urmtoare se utilizeaz o macrodefiniie pentru determinarea minimului a dou valori. #define minim(a, b) ((a < b) ? a : b)
2 -3

void Prelucrare() { int x = 7, y = 5, z ; z = minim(x, y) ; // ... } Limbajul C++ ofer n plus posibilitatea expandrii inline a funciilor. Expandarea inline nseamn generarea de ctre compilator a codului corespunztor funciei, fr s se mai genereze o secven de apel. Declararea unei funcii inline se face prin specificarea cuvntului cheie inline naintea definiiei acesteia, iar pentru funciile membre ale unei clase prin includerea corpului funciei n cadrul declaraiei clasei. Exemplul 2.3. Funcia inline minim este echivalent cu macrodefiniia anterioar: inline int minim(int a, int b) { return ((a < b) ? a : b) ; } n cazul funciilor inline, compilatorul ncearc s plaseze o instan a funciei inline n acelai segment de cod ca i cel al funciei apelante, dar nu se garanteaz ns acest lucru. Pentru funcii complicate (care conin instruciuni repetitive sau care sunt recursive) nu se realizeaz mecanismul inline. n general, utilizarea funciilor inline este mai eficient dect a funciilor obinuite, dar mai puin eficient dect utilizarea macrodefiniiilor. Exemplul 2.4. Pentru exemplificare, se va utiliza aceeai operaie de ridicare la putere a unor numere prin trei metode diferite: macrodefiniie, funcie inline i funcie uzual. Se va prelua timpul sistem pentru fiecare din cele trei funcii (f1, f2, f3), astfel nct s se determine timpul de execuie al fiecrei funcii. # include <time.h> # define SQR(x) (x)*(x) inline float Sqr (float x) { return ( x*x ); } float sqr (float { return ( x*x void f1() { float x = 2.5, for (long k=0; y = SQR(x); } void f2() { float x = 2.5, for (long k=0; y = Sqr(x); } void f3() { float x = 2.5, for (long k=0; y = sqr(x); } void main() { time_t ltime; time(&ltime); printf("Timpul x) ); } y; k<10000000; k++) // macrodefinitie // functie inline // functie uzuala

y; k<10000000; k++)

y; k<10000000; k++)

in secunde:\t%ld\n", ltime);

2 -4

f1(); time(&ltime); printf("Timpul in secunde:\t%ld\n", ltime); f2(); time(&ltime); printf("Timpul in secunde:\t%ld\n", ltime); f3(); time(&ltime); printf("Timpul in secunde:\t%ld\n", ltime); } 2.1.5. Argumente implicite ale funciilor n mod uzual, o regul de baz pentru multe dintre limbajele de programare impune ca la definiia i la apelul unei funcii s fie utilizat acelai numr de parametri. Limbajul C permite definirea (destul de dificil) a unor funcii cu numr variabil de parametri, cu ajutorul operatorului . Sarcina de tratare a parametrilor revine total programatorilor, compilatorul neefectund nici o verificare. Limbajul C++ ofer n plus fa de limbajul C o modalitate mai simpl i mai eficient de tratare a funciilor cu un numr variabil de parametri: este vorba despre funcii cu valori implicite pentru parametri. Un parametru cu valoare implicit este declarat n mod obinuit n antetul unei funcii prin nume i tip de date, dar n plus el este iniializat direct n antet. Dac un apel al funciei respective conine un parametru actual cu o alt valoare dect cea specificat la iniializare, valoarea actual este folosit ca valoare de inializare; dac ns parametrul actual corepunztor lipsete, ca valoare de iniializare, se consider valoarea specificat n antet. Exemplul 2.5. Funcia Distanta poate determina att distana dintre dou puncte n plan, ct i ntre un punct i origine. double Distanta(double x, double y, double x0 = 0, double y0 = 0); { return sqrt((x-x0)*(x-x0)+(y-y0)*(y-y0)) ; } void Prelucrare() { double x1 = 3, y1 = 5, x2 = 4, y2 = 6, d1, d2 ; // distanta intre (x1,y1) si origine d1 = Distanta(x1, y1); // distanta intre (x1, y1) si (x2, y2) d2 = Distanta(x1, y1, x2, y2); // ... } Observaii: a) un parametru implicit poate fi iniializat doar cu o expresie constant, care se poate evalua la compilare; b) se pot specifica mai muli parametri implicii n cadrul unei funcii; ei trebuie s ocupe ns ultimele poziii, pentru c altfel, n cazul unui apel, nu se pot determina corect parametri actuali corespunztori. 2.1.6. Funcii suprancrcate Suprancrcarea numelui funciilor nseamn de fapt posibilitatea existenei mai multor funcii cu acelai nume care efectueaz operaii diferite. n exemplul urmtor se definte funcia add utilizat att pentru operaia de adunare a numerelor (ntregi, reale, complexe), ct i pentru operaia concatenare a irurilor de caractere. Limbajul C++ permite, n plus, definirea unor funcii utilizator suprancrcate. Exemplul 2.6. Se definesc mai multe funcii cu numele add: int add(int a, int b) { return a + b ; } double add(double a, double b) { return a + b ; }

2 -5

char* add(char *a, char *b) { strcat(a, b) ; return a ; } struct complex { double re ; double im ; } ; complex add(complex a, complex b) { complex c ; c.re = a.re + b.re ; c.im = a.im + b.im ; return c ; } void Prelucrare() { int k = add(5, 1) ; double s = add(1.5, 8.4) ; char *s1 = abc, *s2 = xyz, *s3 = add(s1, s2) ; // ... } Compilatorul determin funcia efectiv ce va fi apelat n funcie de tipul parametrilor i de numrul acestora. Observaii: a) Pentru a defini dou funcii suprancrcate diferite, trebuie s difere numrul parametrilor sau cel puin tipul de date al unuia dintre parametri. b) Deoarece nu se verific i tipul valorii returnate, dou funcii suprancrcate nu pot diferi doar prin tipul valoarii returnate. 2.1.7. Operatori de gestiune a memoriei n cazul utilizrii obiectelor dinamice n cadrul limbajului C, crearea i distrugerea acestora nseamn de fapt alocarea i dealocarea memoriei pentru acestea. n mod uzual se folosesc funciile standard malloc i free. Limbajul C++ posed n plus doi operatori reprezentai prin cuvintele cheie new i delete. Sintaxa uzual de folosire este urmtoarea: <nume pointer> = new [(] <tip> [)] ; delete <nume pointer> ; Exemple : double *p = new double ; double *p = new(double) ; Se observ superioritatea operatorului new fa de funcia malloc, prin faptul c el poate determina singur cantitatea de memorie ce trebuie alocat, precum i tipul pointerului ce se returneaz. Operatorul new se poate utiliza i pentru alocarea memoriei pentru elemente compuse. n cazul tablourilor, trebuie specificat numrul de componente ce se dorete alocat. Exemple: struct punct { double x, y ; } ; //se aloca o structura cu 2 componente struct punct *p = new struct punct(2, 4) ; //se aloca un tablou cu 10 componente double *q = new double[10] ; //se aloca un tablou de 10 pointeri la int int **pp = new int*[10] ; Observaii. a) Ca i funcia malloc, operatorul new aloc memorie n zona heap a programului. b) n cazul n care tipul de date este o clas de obiecte, operatorul apeleaz n mod implicit constructorul clasei.

2 -6

Complementarul lui new este delete. Aciunea efectuat de acesta este asemntoare cu a funciei free, elibernd zona de memorie spre care indic un anumit pointer. n plus, fa de free, el ofer protecie n cazul ncercrii eliberrii memoriei pentru un pointer NULL. Utilizarea lui delete are un dezavantaj n cazul n care se ncearc dealocarea unui tablou. De exemplu: int *p = new int[10]; delete p ; n acest caz nu se elibereaz efectiv dect zona ocupat de primul element al tabloului. Pentru a se elibera ntreaga zon, trebuie specificat numrul de componente ale tabloului care trebuie eliberate. Acest numr se specific la sfritul cuvntului delete ntre paranteze drepte, ca n exemplul urmtor: delete[10] p ; Pentru a evita eventualele erori ce pot apare n cazul n care se dealoc un alt numr de componente dect cel alocat, se poate utiliza sintaxa simplificat: delete[] p ; caz n care, numrul de elemente dealocate este determinat n mod automat de ctre compilator. Operatorul new se poate utiliza i pentru crearea tablourilor multidimensionale, caz n care trebuie specificate toate dimensiunile tabloului. De exemplu, expresia: new int[2][3][4] creeaz n memorie dou tablouri consecutive de tipul: int [3][4] i returneaz un pointer la primul tablou, adic un pointer de tipul: int (*)[3][4] Exemple: int a[2][4] = {1, 2, 3, 4}, (*p)[4]; p = new int[2][4]; for (int i=0; i<2; i++) for (int j=0; j<2; j++) p[i][j] = a[i][j]; // ... delete[] p; Observaie. Indiferent de numrul de dimensiuni al unui tablou alocat cu operatorul new, sintaxa pentru dealocarea lui cu operatorul delete este aceeai (o singur pereche de paranteze drepte). Ca i new, operatorul delete apeleaz implicit destructorul unei clase, dac pointerul asupra cruia se aplic indic spre o instan a unei anumite clase. 2.1.8. Funcii template Limbajul C++ ofer suport pentru abstractizarea i parametrizarea datelor. Principalele noiuni adugate sunt cele de funcii template i clase template. Despre aceste noiuni se va discuta ntr-un capitol separat, n acest paragraf se vor specifica doar cteva noiuni privind funciile generice. O funcie template conine cel puin un tip de date generic (nespecificat), ceea ce mrete gradul de generalitate al acesteia. Sintaxa de definire a unei funcii template impune prezena construciei: template < class < nume> > nainte de antetul funciei. n aceast contrucie, <nume> reprezint numele tipului de date (sau clasei) ce este parametru pentru funcie (a nu se confunda cu parametri formali) i poate fi utilizat n corpul funciei. O funcie template descrie o clas de funcii, iar fiecare apel al unei asemenea funcii reprezint o instan a funciei respective. Sintaxa pentru instanierea funciei n mod uzual este aceeai ca i pentru apelul unei funcii obinuite. Anumite compilatoare impun specificarea explicit a tipului de date cu care se realizeaz instanierea.
2 -7

Exemplul 2.7. Funcia Interschimb interschimb valorile a doi parametri la care tipul de date este generic. n funcia main se vor apela dou instane ale acestei funcii, pentru care tipul generic Tip este instaniat la int i respectiv la double. #include <iostream.h> template <class Tip> void Interschimb(Tip & a, Tip & b) { Tip temp; temp = b; b = a; a = temp; } void main() { int a=3, b=5; double x=33.3, y=55.5; Interschimb(a,b); //Este corect si: Interschimb <int>(a, b); Cout << a << " " << b << endl; Interschimb(x,y); //Este corect si: Interschimb <double>(a, b); Cout << x << " " << y << endl; } 2.1.9. Operatori de intrare-ieire Ca i limbajul C, limbajul C++ posed instruciuni specifice pentru operaiile de intrare i ieire. n afara funciilor specifice limbajului C, limbajul C++ pune n plus la dispoziie dou ierarhii de clase pentru realizarea acestor operaii. Conceptul de baz n aceste ierarhii este cel de stream, care poate fi asimilat cu un flux de date ntre un program i un dispozitiv periferic. Operaiile de intrare-ieire specifice limbajului C++ se vor descrie ntr-un capitol separat, n acest paragraf se vor prezenta doar cteva elemente de baz pentru a putea fi folosite pentru citirea i scrierea datelor. Exist dou clase importante, numite istream i ostream, folosite pentru gestionarea operaiilor de intrare i de ieire; de asemenea exist clasa iostream (derivat din istream i ostream) folosit pentru ambele tipuri de operaii. Declaraiile principalelor clase, o serie de constante i obiecte folosite pentru aceste operaii se afl n fiierul header iostream.h. Cele mai utilizate obiecte sunt urmtoarele: a) cin folosit pentru citirea datelor de la dispozitivul standard de intrare; b) cout folosit pentru scrierea datelor la dispozitivul standard de ieire; c) cerr - folosit pentru afiarea mesajelor de eroare; d) clog utilizat ca i cerr, dar pentru fiecare mesaj de eroare, zona tampon nu se golete. Pentru realizarea efectiv a operaiilor de intrare-ieire s-au definit anumii operatori, care reprezint suprancarcrea unor operatori standard ai limbajului C. De exemplu, pentru a citi valori de la tastatur a fost suprancrcat operatorul de deplasare dreapta (>>), iar pentru operaia de scriere a fost suprancrcat operatorul <<. Astfel, pentru a citi o valoare de la tastatur i a o atribui unei variabile de tip ntreg n, se poate scrie: cin >> n ; iar pentru a afia pe display valoarea lui n se poate scrie: cout << n ; Aceti operatori au fost suprancrcai pentru toate tipurile de date predefinite, inclusiv pentru iruri de caractere. n acest fel operanzii celor doi operatori pot avea orice tip de date predefinit. Deoarece operatorii returneaz iruri de date, ei pot fi concatenai, astfel nct se pot citi sau scrie mai multe date cu ajutorul unui singur operator. De exemplu: int n = 7 ;

2 -8

double x = 4.5 ; cout << n << x ; Exemplul 2.8. Program care determin suma elementelor unui tablou cu valori citite de la tastatur. #include <iostream.h> void main() { int n = 10 ; double s = 0, x[10] ; cout << Introduceti elementele sirului: ; for (int i = 0 ; i < n ; i++) { cout << x[ << i << ]= ; cin >> x[i] ; s += x[i] ; } cout << endl << s = << s << endl ; } Observaie. Cuvntul rezervat endl reprezint caracterul \n. Limbajul C++ posed de asemenea funcii membru ale claselor de intrare-ieire ce pot fi utilizate pentru citirea i scrierea datelor. De exemplu, funciile get (insereaz un caracter n irul de intrare) i put (insereaz urmtorul caracter n irul de ieire) au urmtoarele declaraii: ostream& put(char c) ; istream& get(signed char &c) ; Exemplul 2.9. Program pentru copierea fiierului standard de intrare n fiierul standard de ieire: #include <iostream.h> void main() { int c ; while ( (c = cin.get()) != EOF) cout.put(c) ; }

2.2. Elemente preliminare privind programarea orientat pe obiecte


n acest paragraf, prin intermediul unor exemple simple, se vor prezenta cteva aspecte legate de programarea orientat pe obiecte. 2.2.1. Diferena dintre clas i structur Listingul programului din Exemplul 2.10 evideniaz o serie de concepte din C++ cum ar fi: clas, structur, constructor, obiect etc. Exemplul 2.10. Diferena dintre clas i structur # include <iostream.h> # include <stdio.h> class CLS { int a, b; // a si b sunt de tip private public: CLS (int z = 0) { a = b = z; } // Constructorul clasei CLS void Imp(char *mesaj = " ") { printf ("%s a si b = %d %d\n", mesaj, a, b); } }; struct STRU { int a, b; // a si b sunt de tip public aici STRU (int z = 0) {a = b = z;} // Constructorul structurii STRU };

2 -9

// Programul principal void main (void) { CLS ob_c(1); // Se declara obiectul ob_c STRU ob_s(10); // Se declara obiectul ob_s ob_c.Imp("Dupa creare, datele obiectului ob_c devin: "); // ob_c.a = 111; ar conduce la eroarea // CLS :: a is not accessible cout << "Datele obiectului ob_s inainte de modificare = "\ << ob_s.a << " " << ob_s.b << endl; ob_s.a = 100; ob_s.b = 1; cout << "si dupa = " << ob_s.a << " " << ob_s.b << endl; } Observaie. n C++ spre deosebire de C, comentariile sunt precedate de //. Dar n C++ se accept i forma comentariului din C: /* text comentariu */. Construcia: class CLS { int a, b; // a i b sunt de tip private public: CLS (int z = 0) { a = b = z; } // Constructorul clasei CLS void Imp(char *mesaj = " ") { printf ("%s a si b = %d %d\n", mesaj, a, b); } }; reprezint declararea clasei CLS. Aceast construcie pune n eviden un ablon care va servi la crearea ulterioar a obiectelor. Obiectul ob_c ce aparine clasei CLS este declarat n programul principal prin: CLS ob_c(1); Expresia ob_c(1) din aceast instruciune instruiete compilatorul ca s iniializeze ambele variabile de tip ntreg a i b cu valoarea 1. Funcia care este automat apelat la declararea obiectului ca s realizeze aceast iniializare este constructorul clasei CLS, denumit la fel ca i clasa, CLS. n acest exemplu nu apare i funcia destructor al clasei. n program ntlnim i linia de comentariu // ob_c.a = 111; ar conduce la eroarea CLS::a is not accessible Eroarea menionat ar aprea pentru c accesul direct la elementele (variabilele) a i b situate deasupra cuvntului cheie public este ngrdit, ele fiind de tipul private. Dac n funcia main() am fi ntlnit linia surs: CLS ob_c; ea nu ar fi fost refuzat din punct de vedere sintactic. n acest caz, a i b ar fi fost iniializate cu valoarea asumat 0 (se vede c n interiorul constructorului CLS, z = 0). Corpul funciei CLS este delimitat, ca oricare funcie din C, de acolade, iar aici, ca unic instruciune se ntlnete dubla atribuire a = b = z; echivalent cu instruciunile a = z; b = z;. Deci, n C++, orice linie surs se termin prin separatorul ; ca i n C. Programul mai conine construcia: struct STRU { int a, b; // a i b sunt de tip public aici STRU (int z = 0) {a = b = z;} // Constructorul structurii STRU }; care este ablonul unei structuri cu numele STRU ce conine dou variabile a i b tot de tip ntreg (ca i variabilele a i b din structura CLS) i o funcie (constructorul structurii STRU, denumit tot STRU). Obiectul ob_s creat n linia

2 -10

STRU ob_s(10); va iniializa variabilele a i b cu valoarea 10. i aici, n lipsa unei valori explicite, a i b vor fi iniializate cu valoarea asumat n constructor, 0. De data aceasta variabilele a i b din structura STRU fiind de tip public sunt accesibile din orice instruciune din funcia main(), fr a apela la serviciile constructorului. Deci, putem scrie: ob_s.a = 100; i variabila a din obiectul ob_s devine egal cu 100, n loc de 10. Se observ de asemenea c, att constructorul clasei CLS ct i constructorul structurii STRU sunt definii n interiorul acestora, sau altfel spus sunt definii n modul inline. n sfrit, acest program ne arat i modalitatea afirii datelor; este vorba de liniile n care apare cuvntul cheie cout, echivalentul funciei printf() din limbajul C, dar mai comod dect aceasta. Funcia printf() este acceptat i n C++ i pstreaz ncorsetrile din C. De exemplu, n C, pentru afiarea valorii variabilei ntregi j, se recurge la secvena: # include <stdio.h> // Prototipul functiei printf() este definit n // fisierul antet stdio.h ... int j = 10; printf (j = %d\n, j); Funcia printf() are dou argumente: j = %d\n i j. Primul argument conine un ir de caractere i un caracter de conversie. Acesta este precedat de caracterul %. Cnd compilatorul ntlnete %d acesta este atenionat c va fi afiat un ntreg n format zecimal, n cazul nostru al doilea argument al funciei printf(), adic ntregul j. Ieirea se efectueaz la fiierul logic stdout (de regul atribuit ecranului videoterminalului). Dac j ar fi fost de tipul double (virgul mobil, dubl precizie), formatul trebuia modificat din %d n %f (sau %g). Deci, am fi avut: ... double j = 10; printf (j = %f\n, j); n contextul limbajului C++, cout nlocuiete stdout. Acesta este alctuit din literalul "Datele obiectului ob_s inainte de modificare:", urmat de variabila ob_s.a, apoi de un al doilea literal " " (adic un ir de spaii), i n sfrit de variabila ob_s.b. Dac a i b ar fi fost de tip double, nu mai trebuia schimbat nimic n linia surs n care apare obiectul cout, membru al clasei ostream; aceasta, deoarece pentru fiecare tip de informaie, C++ pune la dispoziie n contextul obiectului cout o metod (funcie), care analizeaz i nelege mesajul (inclusiv tipurile variabilelor care trebuie afiate) i realizeaz conversiile adecvate. Elementele clasei de ieire ostream se afl n fiierul antet iostream.h, inclus n exemplul de fa prin linia: # include <iostream.h>. Funcia membr a clasei. Pentru a afia variabilele a i b ale clasei CLS, aceasta conine funcia Imp definit prin: void Imp(char *mesaj = " ") { printf ("%s a si b = %d %d\n", mesaj, a, b); } i numit funcie membru (member) a clasei. Se observ c aceasta este definit n corpul clasei CLS, deci inline, n poriunea public a acestei clase, accesibil din orice funcie, inclusiv din funcia main(). Cuvntul cheie void din faa funciei Imp arat c aceasta nu ntoarce nici un rezultat. De altfel, din corpul funciei Imp lipsete instruciunea return. Prezena sa ar fi ilegal, dac s-a stabilit c funcia Imp nu ntoarce nici un rezultat. Variabila mesaj este un pointer (notaie *mesaj) la un ir de caractere. Formatul listrii unui ir de caractere este %s. irul asumat, n lipsa unui text atribuit lui mesaj, este un ir de lungime nul (ir vid). Variabilele a i b fiind de tipul ntreg, fiecare vor fi

2 -11

asociate unui format %d. Notaia \n este caracterul de tip spaiu alb (white space) numit avans la linie nou (line feed, cod 0x0a n hexazecimal sau 10 n zecimal). Funciile de tip membru au privilegiul de a avea acces direct la variabilele clasei, inclusiv la cele de tipul private. Nu a mai fost nevoie de construcii de genul ob_c.a sau ob_c.b, ci referirea s-a fcut direct la a sau b. Pentru aflarea valorilor variabilelor a i b ale obiectului ob_c, n funcia main() a fost adugat linia urmtoare: ob_c.Imp("Dupa creare, datele obiectului ob_c devin:"); plasat undeva dup linia: CLS ob_c(1); O alt variant a funciei Imp() este cea n care nu se mai recurge la serviciile funciei printf(), ci la cout: void Imp(char *mesaj = " ") { cout << mesaj << a << << b << endl; } unde cuvntul endl este echivalentul lui \n din C. Funciile inline au avantajul c la apelarea lor nu se mai parcurg secvenele de salvare/refacere n/din stiv a parametrilor de intrare, execuia crescnd n rapiditate. 2.2.2. Redefinirea funciilor i operatorilor Pentru a prezenta alte caracteristici ale limbajului C++, considerm listingul programului din Exemplul 2.11. Exemplul 2.11. O clas cu constructor, destructor, o funcie membru redefinit i o alt funcie care redefinete operatorul || pentru concatenarea unor iruri # include <string.h> # include <stdio.h> class SIR { char *text; public: SIR(char *sir) ; // Constructorul clasei ~SIR() { delete text; } // Destructorul clasei int compar(SIR &s1, SIR &s2); int compar(SIR &s1, SIR &s2, unsigned int ncar); void operator || (SIR &s) {strcat (text, s.text);} void Imp(char *mesaj = " ") { printf("%s %s\n", mesaj, text); } }; SIR :: SIR(char *sir) { text = new char[strlen(sir)]; strcpy(text, sir); } // Definirea constructorului

int SIR :: compar(SIR &s1, SIR &s2) { return strcmp(s1.text, s2.text); } int SIR :: compar(SIR &s1, SIR &s2, unsigned ncar) { return strncmp(s1.text, s2.text, ncar); } // Programul principal void main (void)
2 -12

// Se creeaza 2 obiecte de tip sir { SIR sir1("abcd"), sir2("abcdef"); sir1.Imp("Primul sir este: "); sir2.Imp("Al doilea sir este: "); int rez1, rez2; rez1 = sir1.compar(sir1, sir2); printf("Rezultatul primei comparatii este %d\n", rez1); rez2 = sir1.compar (sir1, sir2, 4); printf("Rez. celei de-a doua comparatii este %d\n", rez2); sir1 || sir2; sir1.Imp("Sir1 dupa concatenare este: "); } Programul conine clasa SIR n care singura dat de tip private este pointerul la un ir de caractere *text. Constructorul SIR i funcia compar() sunt numai declarate n cadrul clasei, pe cnd destructorul ~SIR i funcia denumit operator||() sunt definite n mod inline. Faptul c denumirea compar este ntlnit de dou ori nu este o eroare. Funcia compar() este redefinit. Ea are dou abloane (prototipuri). Primul ablon este utilizat cnd se compar dou iruri de lungimi diferite sau nu, iar al doilea cnd se compar numai primele ncar caractere ale acestora. Notaiile SIR &s1, SIR &s2 reprezint referinele celor dou iruri ce trebuie comparate. Funcia SIR este definit n afara clasei prin: SIR::SIR(char *sir) // Definirea constructorului { text = new char[strlen(sir)]; strcpy(text, sir); } n care apare notaia :: prin care se precizeaz apartenena unei funcii la o anumit clas. Instruciunea text = new char[strlen(sir)]; arat c, ncepnd de la adresa text, operatorul new va aloca un numr de caractere egal cu lungimea efectiv a irului sir. Lungimea irului este returnat de funcia strlen() (string length). n limbajul C pentru alocarea dinamic a unui spaiu de memorie se folosete funcia malloc() care cere specificarea numrului de octei. Deci, n C scriem: text = malloc(strlen(sir)); fapt acceptat i n C++. Instruciunea strcpy(text, sir) realizeaz copierea irului sir n spaiul alocat la adresa text. Prototipurile funciilor strlen() i strcpy() (string copy) sunt declarate n fiierul antet string.h. Operatorul delete din definiia destructorului ~SIR este opusul lui new i are ca efect eliberarea zonei de memorie ocupat de obiect. n C, opusul funciei malloc() este funcia free(). Deci, echivalena din C a liniei delete sir este linia free(sir). Funcia compar() ntoarce un rezultat de tip ntreg. Celor dou prototipuri declarate n clasa SIR le corespund dou definiii. Primul prototip trateaz cazul comparrii a dou iruri de lungime oarecare: int SIR :: compar(SIR &s1, SIR &s2) { // Se compar d.p.d.v. lexicografic dou iruri de caractere return strcmp(s1.text, s2.text); } Funcia strcmp() cu prototip n fiierul antet string.h, ntoarce rezultatul comparaiei a dou iruri de caractere. Pentru compararea primelor ncar caractere din dou iruri, definiia este urmtoarea:

2 -13

int SIR :: compar(SIR &s1, SIR &s2, unsigned ncar) { // Comparatia primelor ncar caractere din doua siruri return strncmp(s1.text, s2.text, ncar); } Valoarea rezultatului comparaiei cu funciile strcmp() i strncmp() se obine prin scderea nedistructiv a caracterelor celor dou iruri aflate pe aceeai poziie relativ. Dac: s1 < s2, rezultatul < 0 s1 == s2, rezultatul = 0 s1 > s2, rezultatul > 0 Al treilea membru al clasei SIR redefinete operatorul || (operatorul SAU logic), adic asociaz operatorului || un sens nou. Prin construcia void operator || (SIR &s) se anun compilatorul C++ c operatorului || i-a fost temporar asociat sensul descris n corpul funciei: {strcat(text, s.text);}. Instruciunea dintre acolade are ca efect concatenarea a dou iruri. Lista de parametri text, s.text ascunde dou referine la irurile obiectului curent (text), respectiv al obiectului s (s.text). Se alipete deci irul punctat de ctre s.text la irul punctat de pointerul text. Funcia strcat() al crui prototip se afl n fiierul string.h nu ntoarce nici un rezultat. n programul principal se creeaz dou obiecte sir1 i sir2 cu structura sir1 = a b c d \0, respectiv sir2 = a b c d e f \0. n linia rez1 = sir1.compar(sir1, sir2) se realizeaz compararea celor dou iruri de lungime diferit. Se va utiliza prima form a funciei compar(). irurile nefiind identice, funcia va ntoarce n variabila rez1 o valoare negativ. Se remarc construcia: nume_obiect.nume_metoda(lista_de_argumente). Linia rez2 = sir1.compar(sir1, sir2, 4) conduce la o comparaie conform celei de a doua metode, care se bazeaz pe funcia strncmp. Cum, primele 4 poziii ale celor dou iruri coincid, n variabila rez2 se va gsi valoarea 0. n urma concatenrii, sir1 se va prelungi, n el regsind acum valoarea a b c d a b c d e f \0, fapt atestat prin executarea liniei sir1.Imp("Sir1 dupa concatenare este: "). i aici se observ prezena construciei nume_obiect.nume_metoda (lista_de_argumente) n care operatorul punct (.) unete obiectul cu funcia apartenent la clasa care la creat. 2.2.3. Despre motenire, clase derivate i reutilizarea codului Considerm un program care conine clasa RAND_UNIF, prin care se genereaz numere aleatoare cu o distribuie uniform. Exemplul 2.12. Generarea unor numere aleatoare cu distribuie uniform prin metoda liniar congruenial # include <limits.h> class RAND_UNIF { long x; void gen_nr() { x = x*1103515245 + 12345; } public: RAND_UNIF(long n = 0) {x = n;} // Constructorul clasei void seed(long n); unsigned int ui_unif () { gen_nr(); return x & LONG_MAX; } double d_unif() { gen_nr(); return (x & LONG_MAX) / (double) LONG_MAX;

2 -14

} }; // Generarea a 10 numere aleatoare intregi si in virgula mobila, // care sunt uniform distribuite in gamele [0...LONG_MAX] si [0...1] # include <stdio.h> void main (void){ RAND_UNIF aleator; // Se creeaza obiectul aleator unsigned int nai; double nad; for (int i = 0; i < 10; i++) { nai = aleator.ui_unif (); nad = aleator.d_unif (); printf ("i numere aleat. unif. distrib. = %d %u %f\n", i, nai, nad); } } n clasa RAND_UNIF, n seciunea private, pe lng variabila x de tip long este ncapsulat i funcia gen_nr() de tip inline. Funcia membru ui_unif() este definit n modul inline. Ea apeleaz la serviciile funciei gen_nr(), cu care genereaz numere ntregi fr semn cu relaia: x = x*1103515245 + 12345. Deoarece prima dat seed() este 0, x va fi egal cu 12345. Valorii x i se aplic o masc binar LONG_MAX (o constant definit n fiierul antet limits.h, a crei valoare depinde de tipul calculatorului; de exemplu, n cazul unui calculator care permite un ntreg lung pe 32 bii, LONG_MAX = 231-1). Datorit acestei mti se asigur tiere bitului de semn. n cadrul funciei main() se creeaz mai nti obiectul cu numele aleator, dup care, utiliznd bucla for se genereaz primele 10 numere aleatoare. S presupunem acum c dorim s generm numere aleatoare uniform distribuite, ns ntre dou limite stabilite de noi. Mai mult, nu vrem s modificm clasa RAND_UNIF, ci s utilizm att structura de date, ct i codul anterior. De asemenea, se dorete realizarea unui generator de numere cu o distribuie exponenial n jurul unei valori medii. Pentru aceasta, vom alctui dou clase derivate din clasa de baz RAND_UNIF. Listingul n care apar aceste clase denumite R_UNIF_LIM i R_EXP_LIM este prezentat n Exemplul 2.13. Exemplul 2.13. Declararea si definirea clasei de baz pentru generarea unor numere aleatoare cu distributie uniforma prin metoda liniar congruentiala # include <limits.h> # include <math.h> class RAND_UNIF { long x; void gen_nr() { x = x*1103515245 + 12345; } public: RAND_UNIF(long n = 0) {x = n;} // Constructorul clasei void seed(long n); unsigned int ui_unif() { gen_nr(); return x & LONG_MAX; } double d_unif() { gen_nr(); return (x & LONG_MAX) / (double) LONG_MAX;
2 -15

} }; // Declararea si definirea claselor derivate struct R_UNIF_LIM : public RAND_UNIF { int inf, sup; R_UNIF_LIM(int i, int s) {inf = i; sup = s;} unsigned int ui_unif(); }; struct R_EXP_LIM : public RAND_UNIF { int media; R_EXP_LIM(int m) { media = m; } unsigned int ui_unif(); }; unsigned int R_UNIF_LIM :: ui_unif() // Se redefineste ui_unif() { return (unsigned int) ((RAND_UNIF::ui_unif() % (sup-inf)) + inf); } unsigned int R_EXP_LIM :: ui_unif() // Se redefineste ui_unif() { return (unsigned int) (-media*log(1.0-RAND_UNIF::d_unif()) + 0.5); } // Generarea a 100 numere aleatoare uniform distribuite in gama // [INF...SUP] si exponential distribuite in jurul valorii MEDIA # define INF 10 # define SUP 100 # define MEDIA 500 # include <stdio.h> void main (void) { R_UNIF_LIM n(INF,SUP); // Se creeaza obiectul n R_EXP_LIM m( MEDIA ); // Se creeaza obiectul m unsigned int nai; unsigned int naexp; for (int i = 0; i < 100; i++) { nai = n.ui_unif(); naexp = m.ui_unif(); printf ("i nr. aleat. unif. distrib. si exp. distrib. = %3d %3u %4u \n", i, nai, naexp ); } } n acest exemplu se evideniaz modul declarrii unei clase derivate, astfel: struct R_UNIF_LIM : public RAND_UNIF{ // }; Tipul clasei derivate este struct, adic structur. Variabilele inf i sup ale structurii sunt publice, deci direct accesibile din program. Se observ c funcia ui_unif() va fi reutilizat de ctre cele dou clase derivate, conform definiiilor precizate mai sus. Cele dou redefiniri ale funciei ui_unif() permit determinarea distribuiilor menionate mai sus. De fapt, pentru determinarea unor numere uniform distribuite n gama [INF...SUP] i exponenial distribuite n jurul valorii MEDIA, am motenit metoda de determinare a numerelor uniform distribuite ntre limitele 0, ..., LONG_MAX i cu ea am creat dou metode noi pentru determinarea distribuiilor precizate.

2 -16

2.2.4. Caracteristici ale limbajului C++ - limbaj orientat pe obiecte 1. Clasa este o structur de date abstract, un ablon, care, pe lng date, conine i funcii, fapt neobinuit n limbajul C; 2. Obiectul este creat de ctre productorul (constructorul) clasei conform ablonului acesteia; 3. Mecanismul de ascundere a datelor const n tehnica de plasare a valorilor n variabilele de tip private ale obiectului; 4. Motenirea este proprietatea unui obiect de a prelua anumite proprieti ale obiectelor apartenente unei clase de baz (ierarhic superioare). 5. Obiectele comunic ntre ele prin mesaje. Funciile membre ale clasei (metodele) interpreteaz mesajele (valorile argumentelor) i asigur comportarea corespunztoare a obiectelor. Polimorfismul, o noiune care este corelat cu comportamentul diferit al unei metode n raport cu tipul obiectului va fi explicat n cursurile urmtoare. Constructorul i destructorul sunt funcii speciale ale unei clase. Constructorul este automat apelat la crearea obiectelor, iar destructorul elibereaz resursele de memorie ocupate de acel obiect la momentul ncheierii existenei acelui obiect.

2 -17

S-ar putea să vă placă și