90
UNIVERSITATEA TITU MAIORESCU DIN BUCURE TI FACULTATEA DE INFORMATICĂ Programarea orientată pe obiecte în limbajul C++ -suport curs pentru forma de învatamânt ID - Lector univ. drd. Dăscălescu Ana Cristina - 2010 - 1

Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Embed Size (px)

Citation preview

Page 1: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

UNIVERSITATEA TITU MAIORESCU DIN BUCURE�TI FACULTATEA DE INFORMATICĂ

Programarea orientată pe obiecte în limbajul C++

-suport curs pentru forma de învatamânt ID -

Lector univ. drd. Dăscălescu Ana Cristina

- 2010 -

1

Page 2: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Cuprins Cap. 1 Introducere in programarea orientata pe obiecte ..................................................................3

1.2 Concepte ale programãrii orientate pe obiecte.......................................................................5 1.3 Elemente introductive ale programãrii orientate pe obiecte în limbajul C++......................7 1.4 Tipuri de date .........................................................................................................................9 1.5 Operatori specifici C++........................................................................................................17

Cap.2 Clase si Obiecte .................................................................................................................21 2.1 Definirea unei clase..............................................................................................................23 2.2 Constructori si destructor .....................................................................................................25 2.3 Tablouri de obiecte...............................................................................................................31 2.4 Funcţii şi clase friend ...........................................................................................................32 2.5 Funcţii inline .......................................................................................................................33 2.6 Date si functii statice ............................................................................................................34

Capitolul 3 .Mostenire....................................................................................................................41 3.1 Relatia de mostenire. Clase de bazã si clase derivate ..........................................................41 3.2 Constructori şi destructori în clasele derivate ......................................................................44 3.3 Controlul accesului la membrii clasei de bază .....................................................................45 3.4 Mostenirea multipla..............................................................................................................47 3.4 Clase de bază virtuale...........................................................................................................50 3.6 Funcţii virtuale şi polimorfism.............................................................................................51 3.7 Clase abstracte......................................................................................................................54 3.8 Polimorfism..........................................................................................................................56

Cap. 4 Facilitãti ale limbajului C++...............................................................................................64 4.1 Supraincarcarea operatorilor ................................................................................................64 4.2 Funcţii operator membre ale claselor ...................................................................................64 4. 3 Template-uri ........................................................................................................................68 4.4 Stream-uri de I/E ..................................................................................................................69 4.5 Funcţii de I/O pentru tipurile predefinite .............................................................................69 Funcţii de I/O pentru tipuri definite de utilizator .......................................................................71 4. 6 Procesarea fisierelor ............................................................................................................72

Teste recapitulative: .......................................................................................................................76

2

Page 3: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Cap. 1 Introducere in programarea orientata pe obiecte

Obiective: · Conceptul programãre orientata pe obiecte; · Paralela între programarea proceduralã si cea orientatã pe obiecte; · Tipuri de date abstracte. Abstractizare; · Concepte de bazã ale programãrii orientate pe obiecte; · Scurta incursiuni în programarea orientatã pe obiecte prin intermediul limbajului C++. Modalităţile (tehnicile, paradigmele) de programare au evoluat de-a lungul anilor, reflectând trecerea de la programe de dimensiuni reduse, la programe şi aplicaţii de dimensiuni foarte mari, pentru coordonarea cărora sunt necesare tehnici evoluate. Software-ul de dimensiuni mari, care nu poate fi realizat de o singură persoană, intră în categoria sistemelor complexe, cu o mare diversitate de aplicaţii. Situaţiile reale şi experienţe ale psihologilor au relevat limitele capacităţii umane în perceperea sistemelor complexe, adică imposibilitatea unei persoane de a percepe şi controla simultan un număr mare de entităţi de informaţie. De aceea, este esenţială descompunerea şi organizarea sistemelor complexe, pentru a fi percepute, proiectate sau conduse. Modul în care este abordată programarea, din punct de vedere al descompunerii programelor, defineşte mai multe tehnici de programare , care s-au dezvoltat şi au evoluat odată cu evoluţia sistemelor de calcul. Programarea procedurală este prima modalitate de programare care a fost şi este încă frecvent folosită. În programarea procedurală accentul se pune pe descompunerea programului în proceduri (funcţii) care sunt apelate în ordinea de desfăşurare a algoritmului. Limbajele care suportă această tehnică de programare prevăd posibilităţi de transfer a argumentelor către funcţii şi de returnare a valorilor rezultate. Limbajul Fortran a fost primul limbaj de programare procedurală. Au urmat Algol60, Algol68, Pascal, iar C este unul din ultimele invenţii în acest domeniu. Programarea modulară. În cursul evoluţiei programării procedurale, accentul în proiectarea programelor s-a deplasat de la proiectarea procedurilor către organizarea datelor, această deplasare reflectând creşterea dimensiunilor programelor. O mulţime de proceduri corelate, împreună cu datele pe care le manevrează, sunt organizate ca un modul. Tehnica de programare modulară decide descompunerea unui program în module, care încorporează o parte din datele programului şi funcţiile care le manevrează. Această tehnică este cunoscută ca tehnică de ascundere a datelor (data-hiding). În programarea modulară stilul de programare este în

3

Page 4: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

continuare procedural, iar datele şi procedurile sunt grupate în module, existând posibilitatea de ascundere a unor informaţii definite într-un modul, faţă de celelalte module. Gruparea de date şi proceduri în module nu implică şi o asociere strictă între acestea.

Programarea orientatã pe obiecte apeleaza la o modalitate nouã de gândire a unei probleme. Spre deosebire de programarea proceduralã care se concentreazã pe structuri de date si algoritmi, programarea orientatã pe obiecte se concentreazã pe definirea de obiecte care modeleazã problema ce trebuie rezolvatã. În programarea orientatã pe obiecte (POO) un program are rolul de a simula stãrile si activitãtile obiectelor lumii reale. Pe lângã structuri de date (care descriu stãrile obiectelor, atributele acestora) trebuie incluse si metodele asociate obiectelor, adicã acele functii care modificã atributele obiectelor si care descriu comportamentul lor. 1.1 Abstractizarea datelor. Tipuri de date abstracte Primul pas pe care il facem cand scriem un program care sa realizeze diferite operatii, este sa gasim un model care simplifica realitatea, prin separearea detaliilor care interreseaza, de cele care nu afecteaza problema pe care o rezolvam asfel, datele cu care se lucreaza, operatiile , tin de specificul fiecarei probleme tratate. Acest proces de grupare a datelor si metodelor de prelucrare specifice rezolvarii unei probleme se numeste abstractizare. În cazul dezvoltãrii unui produs software, abstractizarea se poate defini ca fiind o structurare a unei probleme în entitãti bine precizate prin definirea datelor si a operatiilor asociate. Aceste entitãti combinã date si operatii care sunt necuplate între ele. Procedurile si functiile au fost primele douã mecanisme de abstractizare larg rãspândite în limbajele de programare. Procedura a reprezentat si prima modalitate de ascundere a informatiei (interfata cu procedura). Modulele au reprezentat urmãtorul mecanism de abstractizare prin gruparea procedurilor si functiilor ce sunt relationate. În continuare, tipurile de date abstracte au realizat un pas important cãtre dezvoltarea unui software de calitate si au permis trecerea la abordarea orientatã pe obiecte a problemelor. Un tip de date abstract (TDA) constã dintr-o structurã de date abstractã si o multime de operatii. Interfata unui TDA este definitã de multimea de operatii si reprezintã acea portiune a unui TDA care este vizibilã din exterior. Conceptul de tip de date abstract presupune existenta unui mecanism de separare a specificatiei de implementare. Aceasta înseamnã cã un utilizator al unui TDA trebuie sã cunoascã doar modul de utilizare al tipului de date, nu si detaliile de implementare. Un tip de date abstract (TDA) este caracterizat de urmãtoarele proprietãti: 1. exportã un tip; 2. exportã o multime de operatii (furnizând interfata TDA); 3. singurul mecanism de acces la structura de date a tipului este furnizat de operatiile definite în interfatã; 4. axiomele si preconditiile definesc domeniul deaplicatie al tipului.

4

Page 5: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

De exemplu, dorim sa construim TDA persona. Sa presupunem ca realizam o aplicatie necesara pentru realizarea recesamnatului populatiei, atunci datele care interseaza pentru tipul persoana sunt : nume, prenume, loc_nastere, adresa etc. Daca aplicatia presupune, in schimb, gestiunea intretinerii pentru o asociatie de locatari, atunci pentru tipul persoana sunt necesare si date cum ar fi : spatiul_locuit, nr_persoane etc. Deci, prin procesul de abstarctizare, separam datele care intereseaza de cele care nu fac obiectul aplicatiei. Construim, ulterior, un tip abstract de date care inglobeaza o structura de date impreuna ca operatii aupra datelor ( ex: calculul intretinerii pentru o persoana, nr_persoane pe fiecare judet etc.). Exemplu: TDA_persona nume

structura de date

prenume spatiu_locuit calcul intretinere; - operatii asupra datelor Programarea orientatã pe obiecte este programarea cu tipuri de date abstracte, care combinã functionalitãtile acestora pentru rezolvarea problemelor. În programarea orientatã pe obiecte, TDA-urile sunt numite clase. 1.2 Concepte ale programãrii orientate pe obiecte Conceptele programãrii orientate pe obiecte au apãrut din dezvoltãrile realizate în cadrul limbajelor moderne de programare. Astfel, limbajele orientate pe obiecte au noi structuri care îmbunãtãtesc întretinerea programului si fac ca portiuni mari de program sã fie reutilizabile, conducând astfel la scãderea costului de dezvoltare a produselor software. Cele sase concepte de bazã ce caracterizeazã programarea orientatã pe obiecte sunt :

Obiectele; Clasele; Mesajele Incapsularea; Mostenirea Polimorfismul.

Obiectele Un obiect poate fi considerat ca fiind o entitate care încorporeazã atât structuri de date (denumite atribute) cât si comportament (actiuni). Obiectele sunt inteligente prin faptul cã realizeazã anumite actiuni si “stiu” cum sã execute aceste actiuni. Inteligenta unui obiect reprezintã o formã de abstractizare prin faptul cã presupune cã un obiect poate executa o actiune si ascunde detaliile referitoare la modul în care se va realiza efectiv actiunea. Obiectele pot fi de tipuri diferite: entitãti fizice, algoritmi, relatii sau subsisteme. Practic, obiectele sunt componente de software reutilizabil care modeleazã elemente din lumea realã. Pentru tipul de date abstract persoana definit mai sus un exemplu de obiect poate fi definit astfel :

5

Page 6: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Popescu Ion, 34, 56 m2 . Clasele Clasele desemnează o colecţie de obiecte (de natură materială sau spirituală) care au în comun faptul că pot fi caracterizate similar din punct de vedere informaţional şi comportamental. Este evident faptul că identificarea unei clase este în mod normal, rezultatul unui demers cognitiv care presupune caracterizarea unui obiect prin însuşirile lui (informaţionale şi comportamentale) care îi definesc apartenenţa la o anumită clasă de obiecte. Aşadar, conceptul de clasă adună laolaltă datele şi metodele de prelucrare a acestora. O clasa reprezinta de fapt o implemntare a tipului abstract de date. O declarare a unei clase defineşte un tip nou care reuneşte date şi funcţii. Acest tip nou poate fi folosit pentru a declara obiecte de acest tip, deci un obiect este un exemplar (o instanţă) a unei clase. Mesajele Obiectele pot comunica între ele prin intermediul mesajelor. Trimiterea unui mesaj care cere unui obiect sã aplice o anumitã actiune numitã metodã este similarã apelului de procedurã din limbajele de programare proceduralã. În programarea orientatã pe obiecte se considerã cã obiectele sunt autonome si pot comunica între ele prin interschimb de mesaje. Obiectele reactioneazã atunci când primesc mesaje, aplicând o anumitã metodã, de exemplu. Ele pot refuza executarea metodei respective dacã, de exemplu, obiectului care este apelat nu i se permite sã execute metoda cerutã. Un mesaj este o cerere adresatã unui obiect pentru a invoca una din metodele sale. Astfel, un mesaj contine numele metodei si argumentele metodei. Exemplul : calculul intretinerii pentru obiectul Popescu Ion. Incapsularea Înţelegerea acestui principiu presupune două nivele de abordare.

Ca metodă de concepţie, încapsularea se referă la capacitatea de a separa aspectele externe ale unui obiect (interfaţa), accesibile altor obiecte, de aspectele implementaţionale, interne obiectului, care sunt ascunse faţă de celelalte obiecte. Utilizatorul unui obiect poate accesa doar anumite metode ale acestuia, numite publice, în timp ce atributele şi celelalte metode îi rămân inaccesibile (acestea se numesc private). Încapsularea este foarte importantă atunci când dorim să schimbăm implementarea anumitor metode (cu scopul de a optimiza un algoritm sau de a elimina posibile erori). Încapsularea ne va împiedica să modificăm toate caracteristicile obiectului iar aplicaţiile care utilizează obiectul nu vor avea de suferit deoarece protocolul de comunicaţie al obiectului moştenit de la interfaţa clasei (rezultatul încapsulării ca metodă de concepţie) nu s-a schimbat.

6

Page 7: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Ca implementare, la nivelul unui limbaj de programare, încapsularea este asigurată de exigenţele sintactice specifice.

Mostenirea Mecanismul derivarii permite crearea facila de noi clasa, care preiau caracteristicile unor clase de baza, deja definite. Derivarea are ca obiectiv reutilizarea soft-ului, prin folosirea uneor functii deja scrise pentru clasele existente si eliminarea redundantei descrierilor, in cazul claselor care au elemente comune, functii sau date. Acest concept este prezentat in detaliu in moulul 3. Polimorfismul Termenul polimorfism se referã la comportamente alternative între clase derivate înrudite. În cazul în care mai multe clase mostenesc atributele si comportamentele unei clase de bazã, pot apare situatii în care comportamentul unei clase derivate ar trebui sã fie diferit de cel al clasei de bazã sau de cel al clasei derivate de tip frate (de pe acelasi nivel). Aceasta înseamnã cã un mesaj poate avea efecte diferite în functie de clasa obiectului care primeste mesajul. De exemplu sa considerãm trei clase: clasa de bazã Fisier si clasele derivate FisierASCII si FisierBitmap care mostenesc toate atributele si comportamentele clasei Fisier cu exceptia comportamentului Tipãrire. Un mesaj care va activa comportamentul Tipãrire al unui obiect al clasei Fisier poate determina afisarea atributelor mãrime fisier, tip fisier si data/ora creãrii/ultimei modificãri a fisierului. Acelasi mesaj trimis unui obiect al clasei FisierASCII va determina afisarea textului din fisier, în timp ce dacã va fi trimis unui obiect al clasei FisierBitmap va determina executia unui program de afisare graficã.

1.3 Elemente introductive ale programãrii orientate pe obiecte în limbajul C++

Limbajul C++ este unul dintre cele mai utilizate limbaje de programare orientate pe obiecte; compilatoare, biblioteci şi instrumente de dezvoltare a programelor C++ sunt disponibile atât pentru calculatoare personale cât şi pentru cele mai dezvoltate sisteme şi staţii de lucru. Limbajul C++ este o versiune extinsă a limbajului C elaborata de către B. Stroustrup în anul 1980 în laboratoarele Bell din Murray Hill, New Jersey. Extensiile dezvoltate de Stroustrup pentru limbajul C++ permit programarea orientată pe obiecte, păstrând eficienţa, flexibilitatea şi concepţia de bază a limbajului C. Numele iniţial a fost “C cu clase”, numele de C++ fiindu-i atribuit în anul 1983. Scopul pentru care a fost creat C++ este acelaşi cu scopul pentru care este abordată în general programarea orientată pe obiecte: dezvoltarea şi administrarea programelor foarte mari. Chiar dacă superioritatea limbajului C++ este evidentă în cazul dezvoltării programelor foarte mari, nu există limitări în a fi folosit în orice fel de aplicaţie, deoarece C++ este un limbaj tot atât de eficient ca şi limbajul C.

7

Page 8: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

De la apariţia sa, C++ a trecut prin trei revizii, în 1985, 1989 şi ultima, prilejuită de definirea standardului ANSI pentru acest limbaj. O primă versiune a standardului a fost publicată în anul 1994, iar următoarea versiune este încă în lucru. În general, limbajul C++ prevede mai multe facilităţi şi mai puţine restricţii decât limbajul C, astfel încât majoritatea construcţiilor din C sunt legale şi au aceeaşi semnificaţie şi în C++. În acest capitol sunt prezentate unitar şi concis conceptele de bază în programarea C++, atât cele care sunt preluate din limbajul C cât şi cele nou introduse. Multe dintre ele sunt reluate şi dezvoltate pe parcursul secţiunilor următoare. Operatii de intrare/iesire. Stream-uri Cel mai scurt program C++ este:

main(){ }

Acesta defineşte o funcţie numită main (), care nu primeşte nici un argument, nu execută nimic şi nu returnează nici o valoare. Dacă se doreşte ca programul să scrie un mesaj la consolă (de tipul Primul program in C++!), pe lângă utilizarea funcţiei obişnuite din limbajul C (funcţia printf()), în C++ se poate utiliza şi o funcţie de scriere la ieşirea standard (cout). Această funcţie este funcţia operator de scriere << care este definită în fişierul antet iostream.h. Un astfel de program este următorul:

#include <iostream.h> void main(){

cout << “Primul program in C++!”<< endl; }

Prima operaţie de scriere afişează la consolă şirul, Primul program in C++!, iar următoarea (endl) introduce caracterul de linie nouă. Operaţia de citire de la tastatură poate fi realizată în C++ printr-o instrucţiune care foloseşte funcţia operator de citire >>. De exemplu, în instrucţiunile care urmează se citeşte de la tastatură un număr întreg:

int i; cin >> i;

Desigur, funcţia scanf() de citire de la tastatură din limbajul C poate fi în continuare folosită, dar pe parcursul exemplificărilor se vor folosi mai mult aceste funcţii C++ de citire şi de scriere la consolă. Programul P1.1 prezintã un exemplu de utilizare a operatiilor de intrare/iesire în limbajul C++.

// fisierul sursa P1_1.cpp #include <iostream.h> void main() {

8

Page 9: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

int a, b; float m; cout << "\n Introduceti un numar intreg a = "; cin >> a; cout << "\n Introduceti un numar intreg b = "; cin >> b; m = (float) (a+b)/2; cout << "\n Media aritmetica este " << m; }

Programul P1.1 citeste douã numere întregi, a si b si calculeazã media lor aritmeticã, m, pe care o afiseazã pe ecran. Citirea celor douã numere se poate realiza si cu o singurã operatie de citire asa cum se observã în programul P1.1_v2

// fisierul sursa P1_1_v2.cpp #include <iostream.h> void main() { int a, b; float m; cout << "\n Introduceti doua numere intregi: "; cin >> a >> b; m = (float) (a+b)/2; cout << "\n Media aritmetica este " << m; }

Sintaxa operatiei de intrare (citire):

cin >> var_1 >> var_2 >> … >> var_n; Se citesc de la dispozitivul de intrare valorile variabilelor var_1, var_2, …, var_n. Sintaxa operatiei de iesire (scriere, afisare):

cout << expr_1 << expr_2 << … << expr_p; Se afiseazã pe dispozitivul de iesire valorile expresiilor expr_1, expr_2, …, expr_p. Expresiile pot fi, de exemplu, expresii aritmetice, variabile sau pot contine text, care este marcat între ghilimele (“…”). Textul poate contine secvente de tipul \n (trecere la linie nouã), \t (afisarea se face la dreapta în pozitia datã de tab), \a (avertizare sonorã) etc. 1.4 Tipuri de date În C++ sunt definite următoarele tipuri fundamentale:

Tipuri de întreg, pentru definirea numerelor întregi de diferite dimensiuni (ca număr de octeţi ocupaţi în memorie):

9

Page 10: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

short int 2 octeţi int 2 sau 4 octeţi long 4 sau 8 octeţi

Tipuri de numere flotante, pentru definirea numerelor reale (reprezentate ca numere cu virgulă flotantă):

float 4 octeţi double 8 octeţi long double 12 sau 16 octeţi

Aceste tipuri sunt denumite împreună tipuri aritmetice. Pentru tipurile întreg, există variante de declaraţie cu semn (signed) şi fără semn (unsigned).

Tipul caracter, pentru definirea caracterelor char 1 octet

Tipul void specifică o mulţime vidă de valori. Nu se poate declara un obiect cu acest tip, dar acest tip poate fi utilizat în conversii de pointeri şi ca tip de returnare al unei funcţii

Pe langa tipurile de date fundamentale enumerate, se pot defini conceptual un număr infinit de tipuri derivate pornind de la tipurile fundamentale. Tipurile derivate sunt:

• tablouri de obiecte, • pointeri la obiecte, • referinţe, • funcţii, • constante simbolice, • clase, structuri, uniuni, • pointeri la membrii claselor.

În continuare se vor prezenta primele cinci tipuri derivate, iar celelate vor fi introduse pe parcursul secţiuniunilor următoare. Ca terminologie, clasele (împreună cu structurile şi uniunile) sunt denumite tipuri definite de utilizator (user-defined types), iar celelate tipuri sunt denumite tipuri predefinite (built-in types). Tablouri de obiecte Un tablou (array) de obiecte poate fi construit din obiecte dintr-un tip fundamental (cu excepţia tipului void), din pointeri, din enumeraţii sau din alte tablouri. În traducere, pentru array se mai întâlnesc termenii vector şi matrice. În acest text sunt folosiţi termenii tablou (pentru array multidimensional) şi vector (pentru array unidimensional). Declaraţia: T D[expresie] introduce un tablou de obiecte de tipul T, cu numele D şi cu un număr de elemente al tabloului dat de valoarea expresiei, care trebuie să fie de tip constant întreg. Pentru valoarea N a acestei expresii, tabloul are N elemente, numerotate de la 0 la N-1. Un tablou bidimensional se poate construi printr-o declaraţie de forma:

10

Page 11: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

11

T D[dim1][dim2];

şi reprezintă dim1 tablouri unidimensionale, fiecare de dimensiune dim2. Elementele tabloului bidimensional se memorează cu valori succesive pentru indicele din dreapta astfel:

D[0][0], D[0][1], … D[0][dim2-1], D[1][0], D[1][1], … D[1][dim2-1],………. D[dim1-1][0], D[dim1-1][1],… D[dim1-1][dim2-1].

Într-un mod asemănător se pot construi tablouri multidimensionale, cu o limitare a numărului de dimensiuni care depinde de implementare. Pointeri Pentru majoritatea tipurilor T, T* este un tip denumit “pointer la T”, adică o variabilă de tipul T* memorează adresa unui obiect de tipul T. Operaţia fundamentală asupra unui pointer este operaţia de dereferenţiere (dereferencing), adică accesarea obiectului a cărui adresă o reprezintă pointerul respectiv. Operatorul de dereferenţiere este operatorul unar *. De exemplu:

char c1 = ‘a’; // variabila c1 char* p1 = &c1; // p memorează adresa lui c1 char c2 = *p1; // dereferentiere, c2 = ‘a’;

Operatorul & este operatorul adresă, care se utilizează pentru a obţine adresa unei variabile. Tipul void* este folosit pentru a indica adresa unui obiect de tip necunoscut. Asupra pointerilor sunt admise unele operaţii aritmetice. De exemplu, se consideră un vector de caractere dintre care ultimul este caracterul 0 (se mai numeşte şir de caractere terminat cu nul). Pentru calculul numărului de caractere se pot folosi operaţii cu pointeri astfel:

int strlen(char* p){ int i = 0; while (*p++) i++; return i; }

Funcţia strlen() returnează numărul de caractere ale şirului, fără caracterul terminal 0, folosind operaţia de incrementare a pointerului şi operaţia de dereferenţiere pentru a testa valoarea caracterului. O altă implementare posibilă a funcţiei este următoarea:

int strlen(char* p){ char* q = p; while (*q++); return q-p-1; }

În C++, ca şi în limbajul C, pointerii şi tablourile sunt puternic corelate. Un nume al unui tablou poate fi folosit ca un pointer la primul element al tabloului. De exemplu, se poate scrie:

char alpha[] = “abcdef”;

Page 12: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

char* p = alpha; char* q = &alpha[0]; // p = q

Rezultatul aplicării operatorilor aritmetici +, -, ++, -- asupra pointerilor depinde de tipul obiectului indicat. Atunci când se aplică un operator aritmetic unui pointer p de tip T*, se consideră că p indică un element al unui tablou de obiecte de tip T; p+1 va indica următorul element al tabloului, iar p-1 va indica elementul precedent al tabloului. Acest lucru înseamnă că valoarea lui p+1 este cu sizeof(T) octeţi mai mare decât valoarea lui p. Referinţe O referinţă (reference) este un nume alternativ al unui obiect. Utilizarea principală a referinţelor se face pentru specificarea argumentelor şi a valorilor returnate de funcţii, în general, şi pentru supraîncărcarea operatorilor în special. Notaţia X& înseamnă referinţă la un obiect de tipul X. De exemplu:

int i = 1; int& r = i; // r şi i se referă la aceeaşi entitate int x = r; // x = 1 r++; // i = 2;

Implementarea obişnuită a unei referinţe se face printr-un pointer constant care este dereferenţiat de fiecare dată când este utilizat. Aşa cum se poate observa, pentru definirea unei referinţe se foloseşte operatorul adresă &, dar diferă tipul construcţiei în care este folosit. De exemplu:

int a = 5; int* pi = &a; // & calculează adresa; // pi este adresa lui a int& r = a; // & introduce o referinta; // r este o referinţă (alt nume) pt. a

O referinţă este utilizată ca argument pentru o funcţie care poate să modifice valoarea acestui argument. De exemplu:

void incr(int& x) {x++;} void f(){ int i = 1; incr(i); // i = 2; }

O altă utilizare importantă a referinţelor este pentru definirea funcţiilor care pot fi folosite atât ca membru drept cât şi ca membru stâng al unei expresii de asignare. De exemplu:

#include <iostream.h> int& fr(int v[], int i){ return v[i]; } void main(){

12

Page 13: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

int x[] = {1,2,3,4}; fr(x,2) = 7; cout <<fr(x,0)<<fr(x,1)<<fr(x,2)<< fr(x,3)<<endl; }

La execuţia acestui program se obţine mesajul: 1 2 7 4 Deoarece valoarea returnată de funcţie este referinţa (numele) unui element al vectorului, acesta poate fi modificat prin folosirea funcţiei fr() ca membru stâng al egalităţii. Funcţii Funcţiile sunt tipuri derivate şi reprezintă una din cele mai importante caracteristici ale limbajelor C şi C++. Forma generală de definire a unei funcţii este:

tip_returnat nume_func(tip1 arg1,tip2 arg2,……,tipn argn) { //corpul functiei }

Funcţia cu numele nume_func returnează o valoare de tip tip_returnat şi are un număr n de argumente formale declarate ca tip şi nume în lista de argumente formale. Argumentele formale din declaraţia unei funcţii se mai numesc şi parametrii funcţiei. Dacă o funcţie nu are argumente, atunci lista din parantezele rotunde este vidă. Notaţia din C: f(void) este redundantă. O funcţie este un nume global, dacă nu este declarată de tip static. O funcţie declarată static are domeniul de vizibilitate restrâns la fişierul în care a fost definită. Corpul funcţiei este propriu ei şi nu poate fi accesat din afara acesteia (nici printr-o instrucţiune goto). Corpul unei funcţii este o instrucţiune compusă, adică o succesiune de instrucţiuni şi declaraţii incluse între acolade. În corpul funcţiei se pot defini variabile, care sunt locale şi se memorează în segmentul de stivă al programului. Dacă nu sunt declarate static, variabilele locale se crează la fiecare apel al funcţiei şi se distrug atunci când este părăsit blocul în care au fost definite. Nu se pot defini funcţii în interiorul unei funcţii. Argumentele formale ale unei funcţii sunt considerate variabile locale ale funcţiei, ca orice altă variabilă definită în funcţia respectivă. Dacă o funcţie nu are de returnat nici o valoare, atunci tip_returnat din declaraţia funcţiei este tipul void şi nu este necesară o instrucţiune de returnare (return) în funcţie. În toate celelalte cazuri, în corpul funcţiei trebuie să fie prevăzută returnarea unei variabile de tipul tip_returnat, folosind instrucţiunea return. Dacă în definiţie nu este prevăzut un tip_returnat, se consideră implicit returnarea unei valori de tip întreg. Prototipurile funcţiilor. Pentru apelul unei funcţii este necesară cunoaşterea definiţiei sau a prototipului acesteia. Prototipul unei funcţii este de forma:

tip_returnat nume_func(tip1 arg1,……., tipn argn);

Numele argumentelor formale sunt opţionale în prototipul unei funcţii. Prototipurile permit compilatorului să verifice tipurile argumentelor de apel şi să semnaleze eroare la conversii

13

Page 14: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

ilegale. Spre deosebire de limbajul C, unde este admisă şi simpla declaraţie a numelui funcţiei (fără tipurile argumentelor de apel), utilizarea prototipurilor este obligatorie în C++. De exemplu:

double f2(int, double); // prototip functie f2 double f3(int a, double f){ // definitie functie f3 /*.…….*/ double t = f/a; return t; } void fp(){ double r1 = f1(7, 8.9); // eroare, // identificator nedeclarat double r2 = f2(7, 8.9); // corect, fol. prototipul char str[] = "abcde"; double r3 = f3(7, str); // eroare de tip argument } double f1(int a, double f) { /*……..*/ double t = a + f; return t; } double f2(int a, double f) { // definiţie funcţie f2() /*.……..*/ double t = a*f; return t; }

La compilare apare o eroare datorită apelului funcţiei f1(), care nu este definită, nici declarată prin prototip în domeniul funcţiei apelante fp() şi o eroare datorată apelului funcţiei f3() cu un argument (argumentul al doilea) care nu poate fi convertit la tipul argumentului formal. Transferul argumentelor funcţiilor. La apelul unei funcţii, argumentele de apel (se mai numesc şi argumente reale sau efective) iniţializează argumentele formale din declaraţia funcţiei, în ordinea din declaraţie. Argumentele unei funcţii se pot transfera în două moduri: apelul prin valoare şi apelul prin referinţă. În apelul prin valoare se copiază valoarea argumentului real în argumentul formal corespunzător al funcţiei. În acest caz, modificările efectuate asupra argumentului funcţiei nu modifică argumentul real. În apelul prin referinţă este accesată direct variabila din argumentul real transmis funcţiei, care poate fi deci modificată. Ca exemplificare, se defineşte o funcţie swap() care realizează intershimbul între valorile a două variabile. Dacă nu se folosesc referinţe, argumentele de apel ale funcţiei trebuie să fie pointeri la variabilele respective. Pointerii, ca argumente de apel, nu vor fi modificaţi, dar variabilele indicate de aceştia pot fi modificate. Funcţia swap() cu argumente pointeri arată astfel:

void swap(int* x, int* y){ int t;

14

Page 15: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

t = *x; // dereferentiere *x = *y; *y = t; }

Aceeaşi funcţie swap(), folosind argumente de tip referinţă, arată astfel: void swap(int& x, int& y){ int t; t = x; x = y; y = t; }

Se poate observa perfecta simetrie între cele două implementări şi că, în mod evident, referinţa foloseşte adresa variabilei pentru a o putea modifica (deci un pointer). Dar, în cazul referinţelor, pointerul şi defererenţierea sunt ascunse, programatorul nu trebuie să le prevadă explicit, programul rezultat este mai concis şi mai clar. Referinţele sunt deosebit de utile în apelul funcţiilor ale căror argumente sunt obiecte de dimensiuni mari şi copierea lor în argumentele formale (plasate în segmentul de stivă al programului) ar fi foarte ineficientă. Argumente implicite ale funcţiilor. Se întâmplă frecvent ca o funcţie să aibă un număr mai mare de argumente decât sunt necesare în cazurile simple dar frecvente de apel. Dacă nu este necesar să fie transmisă întotdeauna valoarea reală a unui argument şi acesta poate lua, de cele mai multe ori, o valoare implicită, atunci în declaraţia funcţiei se prevede o expresie de iniţializare a acestui argument, iar din apel poate să lipsească valoarea argumentului corespunzător. De exemplu, o funcţie pentru stabilirea datei calendaristice, care prevede valori implicite pentru argumentelelunaşian:

struct data{ int zi; int luna; int an; } g_data;

void setdata(int zi, int luna=9, int an =1999){ g_data.zi = zi; g_data.luna = luna; g_data.an = an; }

void main(){ setdata(15); // 15 9 1999 setdata(21,7); // 21 7 1999 setdata(20,1,2000); // 21 1 2000 }

Numai argumentele de la sfârşitul listei pot fi argumente implicite. De exemplu, este eronată următoarea declaraţie:

15

Page 16: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

void setdata(int zi, int luna=9, int an); // eroare

Constante simbolice O constantă simbolică (sau constantă cu nume) este un nume a cărui valoare nu poate fi modificată în cursul programului. În C++ există trei modalităţi de a defini constante simbolice:

• Orice valoare, de orice tip care poate primi un nume, poate fi folosită ca o constantă simbolică prin adăugarea cuvântului-cheie const în declaraţia acesteia.

• Orice nume de funcţie sau de tablou este o constantă simbolică. • O enumeraţie defineşte o mulţime de constante întregi.

De exemplu, următoarele declaraţii introduc constante simbolice prin folosirea cuvântului-cheie const:

const int val = 100; const double d[] = {1.2, 2.8, 9.5};

Deoarece constantele nu pot fi modificate, ele trebuie să fie iniţializate în declaraţie. Încercarea de modificare ulterioară este detectată ca eroare în timpul compilării:

val++; // eroare d = 200; // eroare

Cuvântul-cheie const modifică tipul obiectului, restricţionând modul în care acesta poate fi folosit. Un aspect interesant şi intens folosit în programare, este acela de a declara pointeri la constante. Atunci când se foloseşte un pointer, sunt implicate două obiecte: pointerul însuşi şi obiectul către care indică pointerul. Prin prefixarea declaraţiei unui pointer cu cuvântul const, obiectul indicat este făcut constant, nu pointerul însuşi. De exemplu:

const char* pc = “abcd”;// pc este pointer la o constantă pc[2] = ‘m’; // eroare, nu se poate modifica // obiectul constant pc = “ghij”; // corect, este modificată // valoarea pointerului pc++; // corect Pentru ca pointerul însuşi să fie constant, se foloseşte operatorul *const: char *const cp = “abcd”;// cp este pointer constant; cp[2] = ‘m’; // corect, modifica valoarea cp++; // eroare, pointer constant

16

Page 17: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Posibilitatea de declarare a pointerilor la constante este folosită în special pentru transmiterea argumentelor funcţiilor. Prin declararea unui argument de tip pointer la constantă, este interzisă modificarea de către funcţie a obiectului indicat. De exemplu:

char* strcpy(char* d, const char* s); În această funcţie şirul s nu poate fi modificat. În mod asemănător, specificatorul const care însoţeşte un argument tip referinţă la apelul unei funcţii, împiedică modificarea acestuia de către funcţia respectivă. 1.5 Operatori specifici C++ Majoritatea operatorilor C++ sunt preluaţi din limbajul C, cu aceeaşi sintaxă şi reguli de operare. În plus faţă de operatorii C, în C++ mai sunt introduşi următorii operatori:

• operatorul de rezoluţie (::) • operatorii de alocare-eliberare dinamică a memoriei new şi delete.

Operatorul de rezoluţie Operatorul de rezoluţie (::) este folosit pentru modificarea domeniului de vizibilitate al unui nume. Pentru acest operator (scope resolution operator), în traduceri se mai întâlnesc termenii de operator de domeniu sau operator de acces. Operatorul de rezoluţie permite folosirea unui identificator într-un bloc în care el nu este vizibil. Dacă operatorul de rezoluţie nu este precedat de nici un nume de clasă, atunci este accesat numele global care urmează acestui operator. De exemplu:

int g = 10; int f(){ int g = 20; //………………… return ::g; } void main(){ cout << f() << endl; // afiseaza 10 }

În acest exemplu operatorul de rezoluţie a fost folosit pentru a accesa variabila globală g, ascunsă de variabila locală cu acelaşi nume din funcţie. Deoarece utilizarea cea mai extinsă a operatorului de rezoluţie este legată de utilizarea claselor, el va fi reluat pe parcursul secţiunilor următoare. Operatorii new şi delete În limbajul C se pot aloca dinamic zone în memoria liberă (heap) folosind funcţii de bibliotecă (de exemplu, malloc(), calloc(), realloc()) şi se pot elibera folosind funcţia free(). La aceste posibilităţi, care se păstrează în continuare în C++, se adaugă operatorii de alocare şi eliberare dinamică a memoriei, new şi delete. Aceşti operatori unari prezintă avantaje substanţiale faţă de funcţiile de alocare din C şi de aceea sunt în mod evident preferaţi în programele scrise în C++.

17

Page 18: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

18

Pentru alocarea unei singure date (obiect), operatorul new are următoarea formă generală:

tip_data* p = new tip_data(initializare); unde tip_data este un tip de date predefinit sau definit de utilizator (clasă), p este pointerul (adresa de început) a zonei alocate în memoria liberă, returnat la execuţia operatorului new, iar initializare este o expresie care depinde de tipul datei şi permite iniţializarea zonei de memorie alocate. Dacă alocarea nu este posibilă, pointerul returnat este NULL. Forma de utilizare a operatorului new pentru alocarea unui vector de date (tablou unidimensional) de dimensiune dim, este următoarea: tip_data* p = new tip_data[dim];

La alocarea unui vector nu se poate transmite o expresie de iniţializare a zonei de memorie alocată. Operatorul delete eliberează o zonă din memoria heap. El poate avea una din următoarele forme:

delete p; delete []p;

Prima formă se utilizează pentru eliberarea unei zone de memorie ocupată de o singură dată (obiect), nu de un vector. Pointerul p trebuie să fie un pointer la o zonă de memorie alocată anterior printr-un operator new. Operatorul delete trebuie să fie folosit doar cu un pointer valid, alocat numai cu new şi care nu a fost modificat sau nu a mai fost eliberată zona de memorie mai înainte (cu un alt operator delete sau prin apelul unei funcţii free()). Folosirea operatorului delete cu un pointer invalid este o operaţie cu rezultat nedefinit, cel mai adesea producând erori de execuţie grave. Cea de-a doua formă a operatorului delete[] se foloseşte pentru eliberarea unei zone de memorie ocupată de un vector de date. Pentru tipurile de date predefinite ale limbajului, se poate folosi şi prima formă pentru eliberarea unui vector, dar, în cazul obiectelor de tipuri definite de utilizator, acest lucru nu mai este valabil. Această situaţie va fi detaliată în secţiunea următoare. Câteva exemple de utilizare a operatorilor new şi delete:

int *pi = new int(3); // alocare int şi iniţializare

double *pd = new double; // alocare double neinitializat

char *pc1 = new char[12]; // vector de 12 caractere

char *pc2 = new char[20]; // vector de 20 caractere

delete pi;

deletepd

delete pc1; //corect, char e tip predefinit

delete []pc2; // corect, elibereaza vector

În legătură cu cele două metode de alocare dinamică, prin operatorii new-delete şi prin funcţiile de bibliotecă malloc-free, fără să fie o regulă precisă, se recomandă evitarea combinării lor, deoarece nu există garanţia compatibilităţii între ele.

Page 19: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Teste de autocontrol 1.1 Care din afirmatiile urmãtoare sunt adevãrate si care sunt false? Justificati rãspunsul în cazul afirmatiilor care sunt false.

(a) Toate variabilele trebuie declarate înainte de a fi utilizate. (b) Un program C++ care afiseazã trei linii pe ecran trebuie sã continã 3 instructiuni de afisare cout. (c) Comentariile dintr-un program C++ determinã afisarea textului aflat dupã // pe ecran la executia programului. (d) În limbajul C++ toate variabilele locale trebuie declarate la începutul functiei de care apartin.

1.2 Descrieţi pe scurt diferenţa dintre funcţiile care returnează valoare şi cele care returnează

referinţă.

1.3 Presupunând cã variabilele a si b au valorile 3, respectiv 4, sã se precizeze ce anume se va afisa pe ecran ca urmare a executiei urmãtoarelor instructiuni:

(a) cout << a+b; (b) cout << “a=”; (c) cout << “a=” << a; (d) cin >> a >> b; (e) // cout << “a+b=” << a+b; (f) cout << “\n\t”; (g) cout << a*b << “=” << b*a; (h) cout << a; (i) s = a+b;

1.4 Care sunt principalele diferente între programarea orientatã pe obiecte si programarea proceduralã? 1.5 Care sunt principalele mecanisme de abstractizare? 1.6 Cum se numesc TDA-urile în programarea orientatã pe obiecte? 1.7 Care sunt elementele principale ale unui TDA? 1.8 De ce sunt considerate a fi inteligente obiectele? 1.9 Dati exemple de clase si obiecte ale acestora. 1.10 Obiectele pot comunica între ele? Justificati rãspunsul. 1.11 Care din urmãtoarele concepte sunt concepte de bazã ale programãrii orientate pe obiecte?

(a) obiect (b) mostenire (c) metodã

19

Page 20: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

(d) încapsulare (e) modul (f) procedurã (g) polimorfism (h) stream (i) cout

1.12 Definiti tipul de date abstract (TDA). 1.13 Definiti urmãtoarele tipuri de date abstracte: (a) LISTA (b) COADA Se vor specifica: structura de date, operatorii de bazã si cei suplimentari si axiomele în mod similar cu definitia TDA STIVA. 1.14 Să se scrie declaraţiile pentru următoarele tipuri de variabile: pointer la un caracter, un vector de 10 valori întregi, pointer la un vector de 10 valori întregi, un pointer la un pointer la un caracter. 1. 15 Să se scrie un program care tipăreşte dimensiunea tipurilor fundamentale de date. Se va folosi operatorul sizeof.

20

Page 21: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Cap.2 Clase si Obiecte Obiective · Definirea notiunilor de clasã, obiect, atribut, metodã; · Întelegerea notiunilor de instantã, instantiere, constructor, destructor; · Însusirea modului în care se realizeazã accesarea membrilor unei clase; · Definirea constructorilor unei clase (implicit, cu parametri, de copiere, de convertire si atribuire); · Definirea destructorului unei clase; · Definirea si utilizarea claselor compuse si a obiectelor compuse; · Pointerului this; · Particularitãti ale limbajului C++ (functii friend, functii inline, membrii statici, tablouri de obiecte, pointeri la metode) Un tip de date într-un limbaj de programare este o reprezentare a unui concept. De exemplu, tipul float din C++, împreună cu operaţiile definite asupra acestuia (+, -, *, etc.) reprezintă o versiune a conceptului matematic de numere reale. Pentru alte concepte, care nu au o reprezentare directă prin tipurile predefinite ale limbajului, se pot defini noi tipuri de date care să specifice aceste concepte. O clasă este un tip de date definit de utilizator. O declarare a unei clase defineşte un tip nou care reuneşte date şi funcţii. Acest tip nou poate fi folosit pentru a declara obiecte de acest tip, deci un obiect este un exemplar (o instanţă) a unei clase. Definitie. O clasã este implementarea unui TDA. Ea defineste atribute si metode care implementeazã structuri de date si operatii ale TDA-ului. Definitie. Un obiect este o instantã a unei clase. El este unic identificat de numele lui si defineste o stare care este reprezentatã de valorile atributelor, la un moment dat. Starea unui obiect se schimbã în raport cu metodele care îi sunt aplicate. Definitie. Comportamentul unui obiect este definit de multimea metodelor care îi pot fi aplicate. Definitie. O metodã este o functie asociatã unei clase. Un obiect apeleazã (invocã) o metodã drept reactie la primirea unui mesaj.

21

Page 22: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Primul pas pentru gruparea datelor si metodelor de prelucrare, l-au reprezentat stucturile; ele permiteau declararea unor ansambluri eterogene de date ce erau manipulate unitar. Consideram structura angajat care curinde urmatoarele date: nume, varsta, salariul.

struct angajat { char nume[20]; int varsta; float salariul; }

Declaratii de variabile: angajat a1; // o structura de tip angajat p1 angajat tablouangajat[10]; // un tablou cu zece elemente de tip angajat angajat *a2; // un pointer catre o structura de tip angajat a2 angajat &a3=a1; // o referinta catre o structura de tip angajat a3 Membrii unei structuri sau ai unei clase sunt accesati cu ajutorul operatorilor de acces: operatorul “.”, respectiv, operatorul “->”.

Operatorul “.” acceseazã o structurã sau un membru al unei clase prin numele variabilei pentru obiect sau printr-o referintã la obiect.

Operatorul “->” acceseazã un membru al unei structuri sau clase printr-un pointer la obiect (a2 = &a1)

Programul P2.1 implementeazã tipul de date angajat cu ajutorul unei unei structuri C. Functia afisare( ) are rolul de a afisa datele unui angajat. Programul realizeazã constructia tipului de data abstract angajat prinn definirea unei structuri , setarea valorilor pentru câmpurile structurilor (membrii structurilor) si afisarea acestora.

// fisierul sursa P2_1.c #include <stdio.h> #include <conio.h> #include <string.h> // definitia structurii angajat struct angajat { char num[20]; int varsta; float salariul; } a1 = {"Pop", 28,1530}; // definitia functiei de afisare a datelor unei angajat void afisare(struct angajat a) { printf("\n Nume \t Varsta \t Salariul"); printf("%s\t%i\t%d",a.nume,a.varsta,a.salariul); } void main()

22

Page 23: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

{ struct angajat a2; strcpy(a2.nume, "Ion"); a2.varsta = a1.varsta; a2.salariul = 1600; printf("\n Angajat nr 1 \n"); afisare(a1); printf("\n Angajat nr 2 \n"); afisare(a2); getch(); }

2.1 Definirea unei clase Clasele permit programatorului sã modeleze obiectele care au atribute (reprezentate de datele membru) si comportament (reprezentat de functiile membru, numite si metode). Practic, o clasã încapsuleazã o multime de valori si o multime de operatii. Sintaxa definirii unei clase:

class <nume_clasã> { private: // membrii privati public: // membrii publici protected: // membrii protejati };

Cuvântul-cheie class introduce declaraţia clasei (a tipului de date) cu numele nume_clasa. Dacă este urmată de corpul clasei (cuprins între acolade), această declaraţie este totodată o definiţie. Dacă declaraţia conţine numai cuvântul-cheie class şi numele clasei, atunci aceasta este doar o declaraţie de nume de clasă. Corpul clasei conţine definiţii de date membre ale clasei şi definiţii sau declaraţii de funcţii membre ale clasei, despărţite printr-unul sau mai mulţi specificatori de acces. Un specificator_acces poate fi unul din cuvintele-cheie din C++:

public private protected

Specificatorii private şi protected asigură o protecţie de acces la datele sau funcţiile membre ale clasei respective, iar specificatorul public permite accesul la acestea şi din afara clasei. Efectul unui specificator de acces durează până la următorul specificator de acces. Implicit, dacă nu se declară nici un specificator de acces, datele sau funcţiile membre sunt de tip private. De aceea, toate datele sau funcţiile declarate de la începutul blocului clasei până la primul specificator de acces sunt de tip private. Într-o declaraţie de clasă se poate schimba specificatorul de acces ori de câte ori se doreşte: unele declaraţii sunt trecute public, după care se poate reveni la declaraţii private, etc. Diferenţa între tipurile de acces private şi protected constă în modul în care sunt moştenite drepturile de acces în clase derivate.

23

Page 24: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Definirea obiectelor Dupã definirea unei clase C, aceasta poate fi utilizatã ca tip în definirea obiectelor, tablourilor de obiecte si a pointerilor, astfel:

C o1, // obiect de tip C C Tablou[20], // tablou de obiecte de tip C C *ptrC, // pointer la un obiect de tip C C &tC = o1; // referinta la un obiect de tip C

Numele unei clase devine specificator de tip si se pot defini, teoretic, o infinitate de obiecte ale clasei. Accesarea membrilor unei clase Membrii publici ai unei clase pot fi accesati cu ajutorul operatorilor “.” si “->”, dupã cum obiectul de care apartin este desemnat prin nume sau printr-un pointer. Variabilele membru private nu pot fi accesate decât în cadrul metodelor clasei respective. Programul 2.2 implementeazã tipul de date angajat sub forma unei clase C++. Clasa angajat cuprinde datele membre: varsta, salariul, nume si functii membre: init() pentru initializarea datelor membre, functiia membra de acces spune_varsta(), functia afisare() pentru afisarea datelor membre.

class angajat { private: int varsta; protected float salariul; public: char nume[20]; void init ( char n[]=”Anonim”, int v=0, float s=0) {strcpy(nume,n); varsta=v; salariul=s;

} int spune_varsta() { return varsta}; void afisare() {cout<<”nume: ”<<nume<<endl; cout<<”varsta: ”<<varsta<<endl; cout<<”salariul: ” <<salariul; }

}

void main() { angajat a; a.init(); // apelul functiei membre init() pentru obiectul a

24

Page 25: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

cout<<a.spune_varsta();//apelul functiei membre soune_varsta() pentru obiectul a }

Dupa rolul pe care il joaca in cadrul clasei, functiile membre ale unei clase se impart in patru categorii:

constructori, responsabili cu crearea obiectelor; destructor, reponsabil cu distrugere obiectelor si eliberarea memoriei ocupate de acesta; functii de acces, care mediaza legatura obiectului cu exteriorul; metode, functii care introduc operatiile si prelucraile specifice obiectului.

2.2 Constructori si destructor Utilizarea unor funcţii membre ale unei clase, aşa cum este funcţia init() din clasa angajat, pentru iniţializarea obiectelor este neelegantă şi permite strecurarea unor erori de programare. Deoarece nu există nici o constrângere din partea limbajului ca un obiect să fie iniţializat (de exemplu, nu apare nici o eroare de compilare dacă nu este apelată funcţia init() pentru un obiect din clasa angajat), programatorul poate să uite să apeleze funcţia de iniţializare sau să o apeleze de mai multe ori. În cazul simplu al clasei prezentate ca exemplu până acum, acest lucru poate produce doar erori care se evidenţiază uşor. În schimb, pentru alte clase, erorile de iniţializare pot fi dezastruoase sau mai greu de identificat. Din această cauză, limbajul C++ prevede o modalitate elegantă şi unitară pentru iniţializarea obiectelor de tipuri definite de utilizator, prin intermediul unor funcţii speciale numite funcţii constructor (sau, mai scurt, constructori). Constroctorul este o functie membra speciala care este apelata automat atunci cand este creat un obiect. El poarta numele clasei si nu are nuci un tip la intoarcere, deoarece este apelat automat. Principalele motivatii pentru care se utilizeaza constructorii sunt:

- complexiatatea structurii obiectelor date de de existenta variabilelor si functiilor, de existenta sectiunii privata, publica si protejata, face dificila initializarea directa a obictelor;

- exista situatii in care doar unele date membre trebuie intializate, altele sunt incarcate in urma apelarii unor metode;

- datele de obicei sunt declarate in sectiunea private si deci nu pot fi accesate din exterior ci prin intermediul metodelor.

O clasa poate mentiona mai multi construcori, prin supraincarcare, folosirea unuia dintre ei la declararea unei variabile de clasa data, fiind dedusa in functie de numarul si tipul parametrilor de apel. Vom redefinii calsa angajat pentru care vom defini doi constructori asfel:

class angajat {int varsta; float salariul; char nume[20]; public: angajat() { strcpy(nume,”Noname”); varsta=0;salariul=0}

25

Page 26: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

angajat(char *n, int v, float s) {strcpy(nume,n);varsta=v;salariul=s;} char *spune_nume(){return nume;} int spune_varsta(){return varsta;} }

Se observa ca in clasa angajat s-au definit doi constructori: - unul care nu are nici un parametru, angajat() care intilaizaza datele membre cu

valori constante; constructorul fara parametrii se numeste constructor impicit; - al doilea constructor primeste trei parametri si are ca scop initializarea datelor

membre din variabile elemntare; un asfel de constructor se numeste constructor cu argumente.

- Constructorii definiti mai sus se pot apela asfel:

angajat a1 // aplelul constructorului implicit angajat a2(„Pop”,28,1200) //apelul constructorului cu argumente.

Daca clasa nu mentioneaza nici un constructor, atunci se defineste automat de catre compilator un constructor care este utilizat pentru generarea de obiecte ale casei respective. In multe aplicatii este recomandat ca o clasa sa mentoineze mai multi constructori deoarece modurile de intializare a datelor membre sun diverse:

- initializarea membrilor cu constante; - intializarea din datele elementare; - initializarea prin citire de la tastatura; - initializarea prin citire din fisier; - initializarea din datele unui obiect existent.

Observatie. Pentru un obiect este selectat un singur constructor care este apleat o singura data. Programul 2.2 impementeaza clasa Complex care descrie un numar cpmplex. Clasa contine datele membre partea reala a numarului complex re, partea imaginara a numarului complex im. Sunt definiti trei constructori Complex(), Complex (double v), Complex ( double x, double y) precum si o functie membra care permite afisarea datelor unui numar complex.

#include <iostream.h> class Complex{ double re; double im; public: Complex(){cout << "Constructor fara argumente\n"; re = 0; im = 0; } Complex(double v){cout << "Constructor cu 1 arg\n"); re = v; im = v; } Complex(double x, double y){

26

Page 27: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

cout << "Constructor cu 2 arg\n"; re = x; im = y; } void afisare() {cout<<”partea reala”<<re<<endl; Cout<<”partea imaginara”<<im; }; void main (){ Complex c1; Complex c2(2); Complex c3(3,5); }

La execuţia funcţiei main(), sunt afisate următoarele mesaje: Constructor fara argumente Constructor cu 1 arg Constructor cu 2 arg În fiecare dintre aceste situaţii a fost creat un obiect de tip Complex, c1, c2, c3 şi de fiecare dată a fost apelat constructorul care are acelaşi număr şi tip de argumente cu cele de apel. Dintre modurile de intializare amintite mai sus, un rol important o are intializarea unui obiect prin copierea datelor unui alt obiect de acelaşi tip. Această operaţie este posibilă prin intermediul unui constructor mai special al clasei, numit constructor de copiere. Forma generală a constructorului de copiere al unei clase X este:

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

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

void main(){ Complex c1(2,3); // Constructor initializare Complex c2(c1); // Constructor copiere Complex c3 = c2; // Constructor copiere c3.afisare(); // afiseaza 2 3 }

La crearea primului obiect c1 este apelat constructorul de iniţializare cu două argumente al clasei Complex. Cel de-al doilea obiect c2 este creat prin apelul constructorului de copiere al clasei Complex, avînd ca argument referinţa la obiectul c1. Este posibilă şi declaraţia de forma Complex c3 = c2; a unui obiect prin care se apelează, de asemenea, constructorul de copiere.

27

Page 28: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Constructorul de copiere poate fi definit de programator; dacă nu este definit un constructor de copiere al clasei, compilatorul generează un constructor de copiere care copiază datele membru cu membru din obiectul referinţă în obiectul nou creat. Această modalitate de copiere mai este denumită copie la nivel de biţi (bitwise copy) sau copie membru cu membru. Pentru clasa Complex, constructorul de copiere generat implicit de compilator sau definit de programator arată astfel:

Complex(Complex &r) {

cout << “Constructor copiere\n”; re = r.re; im = r.im; }

Importanta constructorului de copiere este subliniata de situatia in care datele membre ale unei clase sunt alocate dinamic. Constructorul de copiere generat implicit de compilator copiază doar datele membre declarate în clasă (membru cu membru) şi nu ştie să aloce date dinamice pentru obiectul nou creat. Folosind un astfel de constructor, se ajunge la situaţia că două obiecte, cel nou creat şi obiectul referinţă, să conţină pointeri cu aceeaşi valoare, care indică spre aceeaşi zonă din memorie. O astfel se situaţie este o sursă puternică de erori de execuţie subtile şi greu de depistat. Exemplificam acest caz definind clasa angajat asfel:

class angajat {float salariul; public:

char *nume; angajat(char *n,int s) {int nr=strlen(n); nume=new char[nr];// alocare dinamica pentru sirul nume strcpy(nume,n); salariul=s; }

int spune_salariul(){return salariul;} }

void main() { angajat a1(„Popescu”,35); angajat a2=a1; strcpy(a2.nume,”Ion”); cout<<a1.nume<<” ”<<a1.salariul<<endl; cout<<a2.nume<<” ”<<a2.salariul; }

Dupa rularea programului se va afisa: Ion 35

Ion 35

La construirea obiectului a2 s-a aplelat constructorul de copiere implicit care a copiat membrul nume al obiectului a1, dar nu a realizat alocarea dinamica. Asfel, obiectele a1 si a2 folosesc aceeasi zona de memorie alocata pentru campul nume.

28

Page 29: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Se observa asfel, necesiatea de a introduce in clasa amgajat un constructor de copiere care sa aloce explitit memorie , dupa care sa faca copierea continutului zonei de memorie a obiectului sursa la destinatie:

class angajat {float salariul; public:

char *nume; angajat(char *n,int s) {int nr=strlen(n); nume=new char[nr]; strcpy(nume,n); salariul=s; } angajat(angajat &a) { int nr= strlen(a.nume) strcpy(nume,p.nume); varsra=a.varsta; }

int spune_salariul(){return salariul;} }

void main() { angajat a1(„Popescu”,35); angajat a2=a1; strcpy(a2.nume,”Ion”); cout<<a1.nume<<” ”<<a1.salariul<<endl; cout<<a2.nume<<” ”<<a2.salariul; }

Rezultatul afisat prin rularea aceeluiasi program este: Popescu 35 Ion 35 In concluzie, pentru un obiect cu un membru pointer spre o zona alocata dinamic progrmatorul va furniza un contructor de copiere care sa aloce memorie pentru noul obiect. Multe clase definite într-un program necesită o operaţie care efectueza ştergerea completă a obiectelor. O astfel de operaţie este efectuată de o funcţie membră a clasei, numită funcţie destructor. Cand este declarat explicit in cadrul calsei, destructorul porta numele clasei, precedat de semnul ~ (de ex. ~angajat()). Destructorul, spre deosebire de constructor, este unic si nu are parametrii de apel. Dacã programatorul nu defineste un destructor explicit, compilatorul C++ va genera unul implicit. Destructorul generat de compilator apeleazã destructorii pentru variabilele membru ale clasei. Membrii unei clase sunt întotdeauna distrusi în ordinea inversã celei în care au fost creati.

29

Page 30: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

#include <iostream.h> class Complex {

double re; double im; public: Complex(double x, double y){ cout << "Constructor cu 2 arg\n"; re = x; im = y; } ~Complex(){cout << "Destructor";

} Void main() { Complex z(3,4); }

Rezultatul afisat dupa rularea programului este: Constructor cu 2 arg

Destructor Se observa in programul anterior ca nu s-a realizat un apel explicit al destructorului. Acesta a fost apelat automat la sfarsitul programului. Destructorii sunt apelaţi implicit si in alte situaţii, cum ar fi:

• atunci când un obiect local sau temporar iese din domeniul de definiţie; • la apelul operatorului delete, pentru obiectele alocate dinamic.

Pointerul this Fiecare obiect al unei clase are acces la propria adresã prin intermediul unui pointer numit this. Acest pointer este utilizat în mod implicit pentru referirea atât la membrii date cât si la functiile membru ale unui obiect. El poate fi utilizat si explicit cand se doreste folosirea adresei obiectului. Adresa obiectului, desi este transparenta pentru utilizator, exista memorata intr-un pointer numit this. Ca expemplul vom defini, in programul 2.3, o clasa care va contine o metoda ce utilizeza pointerul this.

class C { int x; public: C(int a){ x=a;} void afisare(){ cout<<this->x;} } void main() {C ob(7); ob.afisare; }

30

Page 31: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

2.3 Tablouri de obiecte În limbajul C++ implementarea tablourilor de obiecte se poate realiza cu conditia respectãrii urmãtoarei restrictii: clasa care include obiectul trebuie sã continã un constructor implicit. Aceastã restrictie se datoreazã imposibilitãtii de a furniza argumentele necesare constructorilor. Programul P2.4 defineste un tablou de obiecte de tip angajat. // fisierul sursa P24.cpp class angajat

{ private: int varsta;

float salariul; public: char nume[20]; angajat() {strcpy(nume,”Anonim”); varsta=0; salariul=0; } angajat( char n[]=”Anonim”, int v=0, float s=0) {strcpy(nume,n); varsta=v; salariul=s;

} void seteaza_valori()

{cout<<”Numele:” cin>>nume; cout>>”salariul:” cin>>salariul; cout<<”varsta” cin>>varsta; }

void afisare() {cout<<”nume: ”<<nume<<endl; cout<<”varsta: ”<<varsta<<endl; cout<<”salariul: ” <<salariul; }

} void main() { angajat tab[20]; // tablou static sau automatic int i, n; cout<<"\n Cate obiecte doriti sa creati?"; cin>>n; // initializeaza n obiecte for (i=0;i<n;i++)

31

Page 32: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

tab[i].seteaza_valori(); // afiseaza cele n obiecte for (i=0;i<n;i++) tab[i].afisare(); }

2.4 Funcţii şi clase friend Este cunscut deja faptul ca aceesul la mebrii unei clase declarati in sectiunile private si protected este restrictionat. Acestea pot fi utilizate doar de functiile membre ale clasei. Accesul la membrii unei clase poate fi ingadiut, totusi si unor functii externe clasei sau apartinand altor clase. Astfel de functii se numesc functii prietene clasei respective (functii friend) si sunt declarate astfel cu ajutorul specificatorului friend. Functiile friend raman externe, nefiind legate de clasa si cu atat mai putin de un obiect anume. Pentru a avea acces la datele unui obiect, functia friend trebuie sa primeasca drept parametru de intrare referinta la obiectul respectiv. Declararea unei funcţii f() de tip friend se realizeaza in interiorul clasei iar funcţia însăşi se defineşte în altă parte în program astfel:

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

Programul P2.5 prezintã un exemplu de declarare si utilizare a unei functii friend a clasei Nr_Complex, modulul(), care calculeazã modulul unui numar complex.

// fisierul sursa P2_5.cpp #include <iostream.h> #include <math.h> class Complex { double re, im; public: Complex(double x, double y) { real = x; imag = y; } friend double modul(Complex &); }; double abs(Nr_Complex& z) { return sqrt(z.real*z.real+z.imag*z.imag); }

32

Page 33: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

void main() {

Complex Z=Complex(3,4); cout<<"\n Modulul lui numarului complex z este "<< modul(Z); } Trebuie subliniat faptul ca utilizarea functiilor friend trebuie fãcutã cu mare grijã întrucât acestea încalcã principiul încapsulãrii, respectiv, ascunderea informatiei, principiu fundamental în POO. Avantajul principal al declarãrii unei functii friend este accesul rapid pe care aceasta îl are direct la membrii privati ai clasei cu care este prietenã.

2.5 Funcţii inline În programare aplelul functiilor poate produce un cost ridicat de execuţie, datorită operaţiilor necesare pentru rezervarea spaţiului în stivă, pentru transferul argumentelor şi returnarea unei valori. Pentru a reduce timpul de executii C++ pune la dispozitie functii inline. În general, o funcţie declarată inline se schimbă la compilare cu corpul ei, şi se spune că apelul funcţiei se realizează prin expandare. În felul acesta se elimină operaţiile suplimentare de apel şi revenire din funcţie. Dezavantajul funcţiilor inline este acela că produc creşterea dimensiunilor programului compilat, de aceea se recomandă a fi utilizate pentru funcţii de dimensiuni mici (maximum 3-4 instrucţiuni). În plus, mai există şi unele restricţii privind funcţiile inline: ele nu pot fi declarate funcţii externe, deci nu pot fi utilizate decât în modulul de program în care au fost definite şi nu pot conţine instrucţiuni ciclice (while, for, do-while). În cazul claselor, functiile membru care sunt definite în interiorul clasei devin în mod implicit inline. În cazul în care le definim în afara clasei si vrem sã fie inline trebuie sã utilizãm sintaxa declarãrii unei functii inline. Sintaxa declarãrii unei functii inline: inline <tip> <nume_functie>([<lp>]) unde <tip> reprezintã tipul întors de functie, iar <lp> lista de parametri. Programul P2.6 prezintã un exemplu de definire si utilizare a unei functii inline pentru calculul ariei unui patrat cu latura data, arie_patrat() .

// fisierul sursa P2_6.cpp

#include <iostream.h> inline double arie_patrat(double a) { return a*a; }

33

Page 34: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

void main() { double x; cout<<"\n Latura patratului:"; cin>>x; cout<<"\n Aria patratului este "<<arie_patrat(x); }

2.6 Date si functii statice Implicit membrii de date sunt alocati pe obiecte. De exemplul fiecare angajat al unei firme are propiul sau nume, cnp etc. Exista, insa unele propietati care sunt impartite de catre toate obiectele unei clase, cum ar fi de exemplul, pentru clasa angajat, numarul total de angajati ai unei firme, salariul mediu al firmei etc. O varinata posibila ar fi stocarea acestor informatii intr-o variabila globala uzuala. De exemplu, am putea utiliza o variabila de tip intreg pentru a pastra numarul de obiecte angajat. Problema acestei solutii este ca variabilele globale sunt declarate in afara clasei; pentru putea fi aduse in interiorul calsei aceste variabile trebuie sa fie declarate de tip static. Datele statice nu se regasesc in fiecare set de valori ale clasei, ci intr-un singur exemplar, pentru toate obiectele clasei. Datele ce apar in toate obiectele se aloca de catre un constructor , dar cum un membru static nu face parte din nici un obiect, nu se aloca prin constructor. Asfel, la definirea clasei, o data statica nu se considera definita, ci doar declarata, urmand a avea o definitie externa clasei. Legatura cu declaratia din interiorul clasei se face prin operatorul de rezolutie :: precedat de numele clasei din care face parte precum si de tipul variabilei statice, asfel: class angajat { //... Static int total_ang; //... } int angajat::total_ang=0;; Variabila total_ang va fi unica pentru toti angajatii. Se poate observa ca o data cu definirea varbilei statice s-a realizat si intializarea acesteia. Functiile membre statice efectueaza prelucrari care nu sunt individualizate pe obiecte, ci prelucrari care au loc la nivelul clasei. Functiile statice nu apartin unui obiect anume si deci nu beneficiaza de referinta implicita a obiectului asociat (pointerul this). Daca functia membra statica opereaza pe o data nestatica a clasei, obiectul trebuie transmis explicit ca parametru , in timp ce cu datele membre statice , lucreaza in mod direct. Programul 2.7 redefineste clasa angajat, prin adaugarea datelor membre statice total_ang si total_b care vor retine numarul total de angajati si numarul total de barbati. Clasa va contine si trei functii membre statice care vor returna valorile retinute in cele doua date statice.

34

Page 35: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

// fisierul sursa P2.7.cpp class angajat

{ private: int varsta;

float salariul; char gen; static int total_ang; static int total_b;

public: char nume[20]; angajat() {strcpy(nume,”Anonim”); varsta=0; salariul=0; total_ang++; }

~angajat(){total_ang--;} void seteaza_valori()

{cout<<”Numele:” cin>>nume; cout>>”salariul:” cin>>salariul; cout<<”varsta” cin>>varsta; }

static int spune_total_ang(){return total_ang;} static int spune_total_b(){return total_b;} static int numara_total_b(angajat *ang) {if(ang->gen==”B”) total_b++;} void afisare()

{cout<<”nume: ”<<nume<<endl; cout<<”varsta: ”<<varsta<<endl; cout<<”salariul: ” <<salariul; }

} int angajat::total_ang=0; int angajat::total_b=0;

void main() { angajat tab[20]; int i, n; cout<<"\n Cate obiecte doriti sa creati?"; cin>>n; for (i=0;i<n;i++) {tab[i].seteaza_valori(); angajat::numara_total_b(&tab[i]); }

35

Page 36: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

cout<<”firma are un total de angajati”<<angajat::spune_total_ang; cout<<”din care”<<angajat::spune_total_b<<” sunt barbati”; }

In programul de mai sus se remarca faptul ca intretinerea variabilei total_ang cade in sarcina constructorilor si a destructorului, care incrementeaza sau decrementreaza acesta variabila. De asemenea, functia membra statica numara_total_b() primeste ca parametru referinta la un obiect de tip angajat pentru ca utilizeaza data membra gen care nu este statica. Întrucât variabilele membru statice nu apartin unui obiect anume, ele pot fi accesate si prin prefixarea numelui clasei de operatorul “::”.

36

Page 37: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Teste de autocontrol 2.1 Care este valoarea de adevãr a afirmatiilor urmãtoare: (a) O clasã C++ si o structurã C definesc acelasi concept, de TDA. (b) O clasã C++ încorporeazã atât date cât si operatii asociate datelor, în timp ce o structurã C defineste doar datele, operatiile nefiind legate direct de datele structurii. (c) O clasã C++ ascunde datele în zona privatã, în timp ce structura C permite accesul direct la datele sale neasigurând consistenta acestora. (d) Structurile C nu pot fi afisate ca o singurã entitate si nu pot fi comparate decât membru cu membru. 2.2 Care este numãrul maxim de constructori si destructori care se pot defini într-o clasã? Selectati rãspunsul corect. (a) un constructor si un destructor (b) un constructor si mai multi destructori (c) 10 constructori si un destructor (d) o infinitate de constructori (teoretic) si un singur destructor 2.3 Considerãm clasa Carte definitã astfel:

class Carte { char* titlu; int an_aparitie; char* editura; char* ISBN; public: Carte(); Carte(char* T, int A, char* Ed, char* Nr); Carte(char* T, char* Ed); Carte(char* T, int A); ~Carte(); void afisare_date(); };

si obiectele ob1, ob2, ob3, ob4 definite astfel: Carte ob1=Carte(“Compilatoare”, 2000, “Teora”, “052-1432”); Carte ob2=Carte(“Limbajul Java”, “Editura Tehnica”); Carte ob3=Carte(“Programare logica”, 2000); Carte ob4; (a) Explicati cum va diferentia compilatorul cei trei constructori cu parametri, luând drept exemplu obiectele ob1, ob2 si ob3. (b) Ce constructor va aplica compilatorul pentru crearea obiectului ob4? 2.4 Care este rolul functiilor inline? 2.5 Care este valoarea de adevãr a urmãtoarelor afirmatii? (a) Functiile friend se pot declara numai în sectiunea privatã a clasei cu care sunt prietene. (b) Utilizarea functiilor friend încalcã principiul încapsulãrii, principiu fundamental în POO. (c) Avantajul principal al utilizãrii functiilor friend este accesul rapid la membrii privati ai clasei cu care sunt prietene. (d) Relatia de prietenie este simetricã si tranzitivã.

37

Page 38: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

2.6 Explicati de ce este necesarã definirea unui constructor implicit în cazul utilizãrii tablourilor de obiecte în limbajul C++? 2.7 Care este valoarea de adevãr a afirmatiilor urmãtoare: (a) Membrii statici ai unei clase pot fi atât variabile (date membru) cât si metode (functii membru). (b) Cuvântul cheie static desemneazã o proprietate a clasei si nu a unui obiect specific clasei. (c) Domeniul de valabilitate al membrilor statici este tot programul în care este definitã clasa din care fac parte. (d) În limbajul C++ datele statice trebuie initializate o singurã datã. 2.8 Să se definească o clasă Date pentru memorarea datei sub forma (zi, lună, an). Clasa va conţine atâţia constructori cât sunt necesari pentru următoarele definiţii de obiecte:

Date d1(15, 3, 99); // zi, lună, an Date d2(20, 4); // zi, lună, an curent Date d3(18); // zi, lună curentă, an curent Date d4; // zi curentă,lună curentă, an curent

2.9 Construiti clasa student care contine datele membre: nume, prenume, nr_matricol, an, grupa. a). Introduceti in clasa cel putin doi constructori. b). Realizati un tablou de obiecte de tipul clasei student; c). Afisati numarul studentilor din grupa 201, utilizand date si functii membre statice; 2.10 Se consideră următorul program în care o variabilă globală numbers memorează numărul obiectelor “în viaţă” la un moment dat:

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

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

38

Page 39: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

2.11 Construiti clasa credite care contine datele membre private val_credit, nr_cont, si date membre publice nume, tip_credit. a). Introduceti in clasa cel putin doi constructori pentru intializarea obectelor din clasa credite. b). Realizati un tablou cu 20 obiecte din clasa credite. c). Realizati functii de acces la datele membre private. d). Afisati numarul creditelor de tip ipotecar, utilizand date si functii membre. 2. 12 Definiţi clasa pacient care contine datele private: vârsta, profesie, salariul şi data membra publică nume. Realizaţi: a). un tablou de 10 obiecte de tipul clasei definită anterior; b). funcţii membre de acces la datele private ale clasei; c). numarul pacientilor care au varsta peste media vârstelor utilizând o funcţie membră statică; d). realizaţi o funcţie operator pentru a calcula suma veniturilor pentru pacienţii cu acelaşi nume 2. 13 Realizaţi clasa fractie care cuprinde datele membre private :numitor, numarator.

1. realizaţi funcţii de acces la datele membre ale clasei definită anterior; 2. realizaţi funcţii membre pentru a calcula suma si inmultirea a doua fractii, utlizand functii

operator; 3. construiţi un tablou cu 10 obiecte de tipul definit anterior; 4. realizaţi ordonarea fracţiilor in ordine crescatoare dupa numitor.

2.14 Spuneţi de câte ori se execută fiecare constructor în programul de mai jos şi în ce ordine.

#include <iostream.h> class cls1 { protected: int x; public: cls1(){ x=13; } }; class cls2: public cls1 { int y; public: cls2(){ y=15; } int f(cls2 ob) { return (ob.x+ob.y); } }; int main() { cls2 ob; cout<<ob.f(ob); return 0; }

2.15 Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează, altfel, spuneţi de ce nu este corect. #include <iostream.h>

class cls1 { int x; public: cls1(){ x=13; } int g(){ static int i; i++; return (i+x); } }; class cls2 { int x; public: cls2(){ x=27; } cls1& f(){ static cls1 ob; return ob; } };

39

Page 40: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

int main() { cls2 ob; cout<<ob.f().g(); return 0; }

2.16 Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează, altfel, spuneţi de ce nu este corect. #include <iostream.h>

class cls1 { protected: int x; public: cls1(int i=10) { x=i; } int get_x() { return x;} }; class cls2: cls1 { public: cls2(int i):cls1(i) {} }; int main() { cls d(37); cout<<d.get_x(); return 0; }

40

Page 41: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Capitolul 3 .Mostenire Obiective · Definirea notiunilor de clasã de bazã, clasã derivatã, ierarhie de clase, mostenire · Însusirea modalitãtii de creare de noi clase pornind de la clase existente · Întelegerea rolului membrilor protejati ai unei clase · Însusirea mecanismului de redefinire a membrilor unei clase într-o clasã derivatã 3.1 Relatia de mostenire. Clase de bazã si clase derivate Derivarea permite definirea într-un mod simplu, eficient şi flexibil a unor clase noi prin adăugarea unor funcţionalităţi claselor deja existente, fără să fie necesară reprogramarea sau recompilarea acestora. Clasele derivate exprimă relaţii ierarhice între conceptele pe care acestea le reprezintă şi asigură o interfaţă comună pentru mai multe clase diferite. De exemplu, entităţile cerc, triunghi, dreptunghi, sunt corelate între ele prin aceea că toate sunt forme geometrice, deci ele au în comun conceptul de formă geometrică. Pentru a reprezenta un cerc, un triunghi sau un dreptunghi, într-un program, trebuie ca aceste clase, care reprezintă fiecare formă geometrică în parte, să aibă în comun clasa care reprezintă în general o formă geometrică. Mostenirea reprezintã o formã de implementare a reutilizãrii codului. Ea apare în urma creãrii de noi clase prin operatia de derivare. Derivarea reprezintã definirea unei noi clase prin extinderea uneia sau a mai multor clase existente. Noua clasã se numeste clasã derivatã, iar clasele existente din care a fost derivatã se numesc clase de bazã. În cazul în care existã o singurã clasã de bazã, mostenirea se numeste mostenire singularã. Limbajul C++ acceptã existenta mai multor clase de bazã. O clasã derivatã mosteneste toti membrii tuturor claselor sale de bazã. Adicã, clasa derivatã contine toate variabilele membru continute în clasele de bazã si suportã toate operatiile furnizate de clasele de bazã. O clasã derivatã poate fi la rândul ei clasã de bazã pentru noi clase. Astfel se poate genera o ierarhie de clase (graf de mostenire). Sintaxa declarãrii unei clase derivate dintr-o clasã de bazã :

class <clasa_derivatã> : public <clasa_de_bazã> { // membrii clasei derivate };

Mostenirea sau relatia de derivare este indicatã în antetul clasei derivate prin cuvântul cheie public prefixat de caracterul “:” si urmat de numele clasei de bazã. În cadrul relatiei de mostenire poate apare în clasa de bazã o sectiune protejatã, marcatã prin cuvântul cheie protected, care permite accesul claselor derivate din ea la datele si functiile membru din sectiunea respectivã.

41

Page 42: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Observatii

O clasã derivatã nu poate accesa direct membrii privati ai clasei sale de bazã. Dacã s-ar permite asa ceva s-ar încãlca unul din principiile fundamentale ale POO (încapsularea).

O clasã derivatã poate accesa membrii privati doar prin intermediul functiilor publice si protejate ale clasei de bazã.

O clasã derivatã poate accesa membrii publici si protejati ai clasei de bazã Relatia de mostenire este tranzitivã. Functiile friend nu se mostenesc.

Se consideră un program P3.1 care descrie organizarea personalului unei instituţii fără folosirea claselor derivate. O clasă numită Angajat deţine date şi funcţii referitoare la un angajat al instituţiei:

class Angajat{ char *nume; float salariu; public: Angajat(); Angajat(char *n, float sal); Angajat(Angajat& r); void display(); }; Angajat::display(){ cout << nume << “ ” << salariu << endl; }

Diferite categorii de angajaţi necesită date suplimentare faţă de cele definite în clasa Angajat, corespunzătoare postului pe care îl deţin. De exemplu, un aministrator este un angajat (deci sunt necesare toate datele care descriu această calitate) dar mai sunt necesare şi alte informaţii, de exemplu precizare secţiei pe care o conduce. De aceea, clasa Administator trebuie să includă un obiect de tipul Angajat, la care adaugă alte date:

class Administrator{ Angajat ang; int sectie; public: void display(); }

Posibilitatea de a include într-o clasă date descrise într-o altă clasă are în limbajele orientate pe obiecte un suport mai eficient şi mai simplu de utilizat decât includerea unui obiect din tipul dorit: derivarea claselor, care moştenesc (date şi funcţii membre) de la clasa de bază. Un administrator este un angajat, de aceea clasa Administrator se poate construi prin derivare din clasa Angajat astfel:

42

Page 43: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

class Administrator : public Angajat {

int sectie; public: void display(); }

In clasa Administrtrator nu se pot aceesa datele private din clasa Angajat, chiar data tipul mostenerii este public. Asa cu s-a subliniat inainte, datele private ale clasei de baza nu pot fi utilizate de catre clasa derivata. Metoda cea mai adecvată de acces la membrii private clasei de bază din clasa derivată este prin utilizarea funcţiilor membre publice ale clasei de bază. De exemplu, nu se poate implementa funcţia display() din clasa Administrator prin accesarea membrilor private ai clasei Angajat:

void Administrator::display(){ cout << nume << “ ”<< salariu << endl; // eroare cout << sectie << endl; }

În schimb, se poate folosi funcţia membră publică display() a clasei Angajat:

void Administrator::display(){ Angajat::display(); cout << sectie << endl; }

Redefinirea funcţiei display() în clasa derivată ascunde funcţia cu acelaşi nume din clasa de bază, de aceea este necesară calificarea funcţiei cu numele clasei de bază folosind operatorul de rezoluţie: Angajat::display(). Din clasa derivată (în funcţii membre ale acesteia sau pentru obiecte din clasa derivată) este accesat membrul redefinit în clasa derivată. Se spune că membrul din clasa de bază este ascuns (hidden) de membrul redefinit în clasa derivată. Un membru ascuns din clasa de bază poate fi totuşi accesat dacă se foloseşte operatorul de rezoluţie (::) pentru clasa de bază. De exemplu:

class Base { public: int a, b; }

class Derived { public: int b, c; // b este redefinit }; void fb() { Derived d; d.a = 1; // a din Base d.Base::b = 2; // b din Base d.b = 3; // b din Derived d.c = 4; // c din Derived }

43

Page 44: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

3.2 Constructori şi destructori în clasele derivate Constructorii şi destructorii sunt funcţii membre care nu se moştenesc. La crearea unei instanţe a unei clase derivate (obiect) se apelează implicit mai întâi constructorii claselor de bază şi apoi constructorul clasei derivate. Ordinea în care sunt apelaţi constructorii claselor de bază este cea din lista claselor de bază din declaraţia clasei derivate. Constructorii nu se pot redefini pentru că ei, în mod obligatoriu, au nume diferite (numele clasei respective). La distrugerea unui obiect al unei clase derivate se apelează implicit destructorii în ordine inversă: mai întâi destructorul clasei derivate, apoi destructorii claselor de bază, în ordinea inversă celei din lista din declaraţie. La instanţierea unui obiect al unei clase derivate, dintre argumentele care se transmit constructorului acesteia o parte sunt utilizate pentru iniţializarea datelor membre ale clasei derivate, iar altă parte sunt transmise constructorilor claselor de bază. Argumentele necesare pentru iniţializarea claselor de bază sunt plasate în definiţia (nu în declaraţia) constructorului clasei derivate. Un exemplu simplu, în care constructorul clasei derivate D transferă constructorului clasei de bază B un număr de k argumente arată astfel:

class B{ //…………………. public: B(tip1 arg1,…,tipk argk); }; class D:public B { //…………………. public: D(tip1 arg1, …,tipk argk,…,tipn argn); }; D::D(tip1 arg1, …,tipk argk, ….…,tipn argn) :B(arg1, arg2, ……,argk); { // initialzare date membre clasa derivata }

Sintaxa generalã pentru transmiterea de argumente din clasa derivatã cãtre clasa de bazã: <construct_cls_derivata>(<L1>) : <construct_cls_baza>(<L2>) { // corpul constructorului clasei derivate } <L1> reprezintã lista de argumente ale constructorului clasei derivate, iar <L2> lista de argumente ale constructorului clasei de bazã. Lista <L1> include lista <L2>. Observatii:

Constructorii si destructorul clasei de bazã nu se mostenesc.

44

Page 45: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

45

Constructorul clasei de bazã se va executa înaintea constructorului clasei derivate, iar destructorul clasei derivate se va executa înaintea destructorului clasei de bazã.

3.3 Controlul accesului la membrii clasei de bază Accesul la membrii clasei de bază moşteniţi în clasa derivată este controlat de specificatorul de acces (public, protected, private) din declaraţia clasei derivate. O regulă generală este că, indiferent de specificatorul de acces declarat la derivare, datele de tip private în clasa de bază nu pot fi accesate dintr-o clasă derivată. O altă regulă generală este că, prin derivare, nu se modifică tipul datelor în clasa de bază. Un membru protected într-o clasă se comportă ca un membru private, adică poate fi accesat numai de membrii acelei clase şi de funcţiile de tip friend ale clasei. Diferenţa între tipul private şi tipul protected apare în mecanismul de derivare: un membru protected al unei clase moştenită ca public într-o clasă derivată devine tot protected în clasa derivată, adică poate fi accesat numai de funcţiile membre şi friend ale clasei derivate şi poate fi transmis mai departe, la o nouă derivare, ca tip protected. Moştenirea de tip public a clasei de bază Dacă specificatorul de acces din declaraţia unei clase derivate este public, atunci:

Datele de tip public ale clasei de bază sunt moştenite ca date de tip public în clasa derivată şi deci pot fi accesate din orice punct al domeniului de definiţie al clasei derivate.

Datele de tip protected în clasa de bază sunt moştenite protected în clasa derivată, deci pot fi accesate numai de funcţiile membre şi friend ale clasei derivate.

În programul urmator P3.2 sunt prezentate şi comentate câteva din situaţiile de acces la membrii clasei de bază din clasa derivată atunci când specificatorul de acces este public.

class Base { inta; protected: int b; public: int c; void seta(int x){a = x; cout << "seta din baza\n";} void setb(int y){b = y; cout << "setb din baza\n";} void setc(int z){c = z; cout << "setc din baza\n";} }; class Derived : public Base { int d; public: void seta(int x) { a = x; // eroare, a este private } void setb(int y) {

Page 46: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

b = y; cout << "setb din derivata\n"; } void setc(int z) { c = z; cout << "setc din derivata\n"; } }; void fb(){ Derived obd; obd.a = 1; // eroare, a este private in baza obd.seta(2); // corect, se apelează baza::seta obd.b = 3; // eroare, b este protected obd.Base::setb(5);// corect, Base::setb este public obd.setb(4); // corect, Derived::setb este public obd.c = 6; // corect, c este public obd.Base::setc(7);// corect, Base::setc este public obd.setc(8); // corect, Derived::setc este public }

Dacă se comentează liniile de program care provoacă erori şi se execută funcţia fb(), se obţin următoarele mesaje la consolă:

seta din baza setb din baza setb din derivata setc din baza setc din derivata

Moştenirea de tip protected a clasei de bază Dacă specificatorul de acces din declaraţia clasei derivate este protected, atunci toţi membrii de tip public şi protected din clasa de bază devin membri protected în clasa derivată. Bineînţeles, membrii de tip private în clasa de bază nu pot fi accesaţi din clasa derivată. Se reiau clasele din exemplul precedent cu moştenire protected:

class Derived : protected Base { // acelasi corp al clasei };

În această situaţie, în funcţia fb() sunt anunţate ca erori de compilare toate apelurile de funcţii ale clasei de bază pentru un obiect derivat, precum şi accesul la variabila c a clasei de bază:

void fb(){ Derived obd; obd.a = 1; // eroare, a este private in baza obd.seta(2); // eroare, Base::seta()este protected obd.b = 3; // eroare, b este protected

46

Page 47: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

obd.Base::setb(5); // eroare, Base::setb este prot. obd.setb(4); // corect, Derived::setb este public obd.c = 6; // eroare, c este protected obd.Base::setc(7); // eroare, Base::setc este prot. obd.setc(8); // corect, Derived::setc este public }

Dacă se comentează toate liniile din funcţia fb() care produc erori, la execuţia acesteia se afişează următoarele rezultate:

setb din derivata setc din derivata

Din acest exemplu reiese pregnant faptul că în moştenirea protected a unei clase de bază nu mai pot fi accesaţi din afara clasei derivate nici unul dintre membrii clasei de bază. Moştenirea de tip private a clasei de bază Dacă specificatorul de acces din declaraţia clasei derivate este private, atunci toţi membrii de tip public şi protected din clasa de bază devin membri de tip private în clasa derivată şi pot fi accesaţi numai din funcţiile membre şi friend ale clasei derivate. Din nou trebuie reamintit că membrii de tip private în clasa de bază nu pot fi accesaţi din clasa derivată. Din punct de vedere al clasei derivate, moştenirea de tip private este echivalentă cu moştenirea de tip protected. Într-adevăr, dacă modificăm clasa derivata din Exemplul 5.2 astfel:

class Derived : private Base { // acelasi corp al clasei };

mesajele de erori de compilare şi de execuţie ale funcţiei fb() sunt aceleaşi ca şi în moştenirea protected. Ceea ce diferenţiază moştenirea de tip private faţă de moştenirea de tip protected este modul cum vor fi trasmişi mai departe, într-o nouă derivare, membrii clasei de bază. Toţi membrii clasei de bază fiind moşteniţi de tip private, o nouă clasă derivată (care moşteneşte indirect clasa de bază) nu va mai putea accesa nici unul din membrii clasei de bază. 3.4 Mostenirea multipla Relatia de derivare conduce la generarea unor ierarhii de clase. În astfel de ierarhii poate apare atât mostenirea singularã cât si cea multiplã. Un exemplu de ierarhie de clasa este reprezentat in figura 3.4

Persoana

ElevStudent Angajat

Profesor 47

Medic

Page 48: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Fig. 3.4 Ierarhie de clase

Clasa de bazã a ierarhiei de clase este clasa Persoana. Din aceastã clasã sunt derivate direct clasele Elev, Student si Salariat. La rândul ei clasa Salariat este clasã de bazã pentru clasele Medic si Profesor. Programul P3.3 prezintã un exemplu de implementare a claselor Persoana, Salariat, Arhitect, Inginer si Medic. // fisierul sursa p3_5.cpp

#include <iostream.h> #include <string.h> #include <assert.h> // definitia clasei de baza Persoana class Persoana {

char* nume; char* pren; public: Persoana(char*, char*); ~Persoana(); void afisare(); };

Persoana::Persoana(char* N, char* P) { nume = new char[strlen(N)+1]; strcpy(nume, N); pren = new char[strlen(P)+1]; strcpy(pren, P); } Persoana::~Persoana() { delete nume; delete pren; } void Persoana::afisare() { cout << "\n Nume: "<< nume << " Prenume: " << pren; } // definitia clasei Salariat derivata din clasa Persoana class Salariat : public Persoana { float salariu; public: Salariat(char *n, char *p, float s=0); void seteazaSalariu(float s); void afisare(); };

48

Page 49: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Salariat::Salariat(char* n, char* p, float S) :Persoana(n, p) { seteazaSalariu(s); } void Salariat::seteazaSalariu(float S) { salariu = s; } void Salariat::afisare() { cout << "\n Salariat:"; Persoana::afisare(); cout << "\n Salariu:" << salariu; } // definitia clasei Inginer derivata din clasa Salariat class Inginer : public Salariat { char* domeniu; public: Inginer(char* n, char* p, float s, char* d); void seteazaDomeniu(char *d); void afisare(); }; Inginer::Inginer(char* n, char* p, float s, char * d) :Salariat(n, p, s) { domeniu = new char[strlen(d)+1]; strcpy(domeniu, d); } void Inginer::seteazaDomeniu(char* d) { strcpy(domeniu, d); } void Inginer::afisare() { cout << "\n Inginer "; Salariat::afisare(); cout << "\n\t Domeniu de lucru este " << domeniu; } class Profesor : public Salariat { char* tip; public: Profesor(char* n, char* p, float s, char* t); void seteazaTip(char *t); void afisare(); }; Profesor::Profesor(char* n, char* p, float s, char* t) :Salariat(n, p, s) {

49

Page 50: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

tip = new char[strlen(t)+1]; strcpy(tip_inginer, t); } void Profesor::seteazaTip(char* t) { strcpy(tip_inginer, T); } void Profesor::afisare() { cout << "\n Profesor "; Salariat::afisare(); cout << "\n\t Tip profesor " << tip; } void main() { Persoana P1=Persoana("Popescu", "Ana"); Salariat S1=Salariat("Ion", "Alexandru", 2300); Inginer I1=Inginer("Syan", "Maria", 3400, "inginer civile"); Profesor PR1=Inginer("Pop", "Cristi", 2200, "informatica"); P1.afisare(); S1.afisare(); I1.afisare(); PR1.afisare(); }

Mostenirea utilizatã este cea publicã. Functia afisare() a fost redefinitã în toate clasele derivate, ea apelând varianta din clasa imediat urmãtoare pe nivelul superior din ierarhia de clase. De exemplu, functia afisare() din clasa Salariat apeleazã functia afisare() din clasa Persoana astfel,

Persoana::afisare();

Functia afisare() din clasele Inginer si Profesor apeleazã functia afisare() din clasa Salariat: Salariat::afisare();

3.4 Clase de bază virtuale Într-o moştenire multiplă este posibil ca o clasă să fie moştenită indirect de mai multe ori, prin intermediul unor clase care moştenesc, fiecare în parte, clasa de bază. De exemplu:

class L { public: int x;}; class A : public L { /* */}; class B : public L { /* */}; class D : public A, public B { /* */};

Acestă moştenire se poate reprezenta printr-un graf aciclic direcţionat care indică relaţiile dintre subobiectele unui obiect din clasa D. Din graful de reprezentare a moştenirilor, se poate observa faptul că baza L este replicată în clasa D.

50

Page 51: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Un obiect din clasa D va conţine membrii clasei L de două ori, o dată prin clasa A (A::L) şi o dată prin clasa B (B::L). In acest caz ser creaza o ambiguitate in situatia urmatoare :

D ob; ob.x = 2; // eroare D::x este ambiguu; poate fi în baza L a clasei A sau în baza L a clasei B

Aceste ambiguitati se pot elimina prin urmatoarele doua metode: - prin calificarea variabilei cu domeniul clasei căreia îi aparţine:

ob.A::x = 2; // corect, x din A ob.B::x = 3; // corect, x din B

- crearea unei singure copii a clasei de bază în clasa derivată. Pentru aceasta este necesar ca acea clasă care ar putea produce copii multiple prin moştenire indirectă (clasa L, în exemplul de mai sus) să fie declarată clasă de bază de tip virtual în clasele care o introduc în clasa cu moştenire multipă. De exemplu:

class L { public: int x; }; class A : virtual public L { /* */ }; class B : virtual public L { /* */ }; class D : public A, public B { /* */ };

O clasă de bază virtuală este moştenită o singură dată şi creează o singură copie în clasa derivată. Graful de reprezentare a moştenirilor din aceste declaraţile de mai sus poate fi ilustrat asfel:

3.6 Funcţii virtuale şi polimorfism O funcţie virtuală este o funcţie care este declarată de tip virtual în clasa de bază şi redefinită într-o clasă derivată. Redefinirea unei funcţii virtuale într-o clasă derivată domină definiţia funcţiei în

51

Page 52: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

clasa de bază. Funcţia declarată virtual în clasa de bază acţionează ca o descriere generică prin care se defineşte interfaţa comună, iar funcţiile redefinite în clasele derivate precizează acţiunile specifice fiecărei clase derivate. Mecanismul de virtualitate asigură selecţia funcţiei redefinite în clasa derivată numai la apelul funcţiei pentru un obiect cunoscut printr-un pointer. În apelul ca funcţie membră a unui obiect dat cu numele lui, funcţiile virtuale se comportă normal, ca funcţii redefinite.

În limbajele de programare, un obiect polimorfic este o entitate, ca de exemplu, o variabilã sau argumentul unei functii, cãreia i se permite sã pãstreze valori de tipuri diferite în timpul executiei programului. Functiile polimorfice sunt acele functii care au argumente polimorfice. În limbajele de programare orientate pe obiecte, polimorfismul împreunã cu legarea dinamicã reprezintã una din caracteristicile extrem de utile care conduc la cresterea calitãtii programelor. Implementarea obiectelor polimorfice se realizeazã prin intermediul functiilor virtuale. Sintaxa declarãrii unei functii virtuale:

virtual <tip_functie> <nume_functie> ([<lp>]); <tip_functie> reprezintã tipul întors de functie, <lp> este

Când un pointer al clasei de bazã puncteazã la o functie virtualã din clasa derivatã si aceasta este apelatã prin intermediul acestui pointer, compilatorul determinã care versiune a functiei trebuie apelatã, tinând cont de tipul obiectului la care puncteazã acel pointer. Astfel, tipul obiectului la care puncteazã determinã versiunea functiei virtuale care va fi executatã. In programul P3.4 se consideră o clasă de bază B şi două clase derivate D1 şi D2. În clasa de bază sunt definite două funcţii: funcţia normală f()şi funcţia virtuală g(). În fiecare din clasele derivate se redefinesc cele două funcţii f() şi g(). În funcţia main() se creează trei obiecte: un obiect din clasa de bază B indicat prin pointerul B* pb şi două obiecte din clasele derivate D1 şi D2. Fiecare dintre obiectele derivate poate fi indicat printr-un pointer la clasa derivată respectivă (D1* pd1, respectiv D2* pd2), precum şi printr-un pointer la bază corespunzător (B* pb1 = pd1, respectiv B* pb2 = pd2).

class B { public: void f() { cout << "f() din B\n"; } virtual void g(){ cout << "g() din B\n"; } }; class D1:public B { public: void f() { cout << "f() din D1\n"; } void g() { cout << "g() din D1\n"; } }; class D2:public B { public: void f() { cout << "f() din D2\n"; }

52

Page 53: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

void g() { cout << "g() din D2\n"; } }; void fv1 { B* pb = new B; D1* pd1 = new D1; D2* pd2 = new D2; B* pb1 = pd1; B* pb2 = pd2; // f() este functie normala,g() este functie virtuala // Obiect B, pointer B* pb->f(); // f() din B pb->g(); // g() din B // Obiecte D1, D2 , pointeri D1*, D2* pd1->f(); // f() din D1 pd2->f(); // f() din D2 pd1->g(); // g() din D1 pd2->g(); // g() din D2 // Obiecte D1, D2 , pointeri B*, B* pb1->f(); // f() din B pb2->f(); // f() din B pb1->g(); // g() din D1 pb2->g(); // g() din D2 delete pb; delete pd1; delete pd2; };

În primele situaţii, când pointerul este pointer la tipul obiectului, nu se manifestă nici o deosebire între comportarea unei funcţii virtuale faţă de comportarea unei funcţii normale: se selectează funcţia corespunzătoare tipului pointerului şi obiectului. Diferenţa de comportare se manifestă în ultima situaţie, atunci când este apelată o funcţie pentru un obiect de tip clasă derivată printr-un pointer la o clasă de bază a acesteia. Pentru funcţia normală f() se selectează varianta depinzând de tipul pointerului. Pentru funcţia virtuală g() se selectează varianta în funcţie de tipul obiectului, chiar dacă este accesat prin pointer de tip bază. Polimorfismul, adică apelul unei funcţii dintr-o clasă derivată prin pointer de tip clasă de bază, este posibil numai prin utilizarea pointerilor la obiecte. Obiectele însele determină varianta funcţiei apelate, deci nu se pot selecta alte funcţii decât cele ale obiectului de tipul respectiv. De exemplu, pentru aceleaşi clase definite ca mai sus, se consideră funcţia fv2():

void fv2(){ B obB; D1 obD1; D2 obD2; obB.f(); // f() din B obB.g(); // g() din B obD1.f(); // f() din D1 obD1.g(); // g() din D1

53

Page 54: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

obD2.f(); // f() din D2 obD2.g(); // g() din D2 }

Observati Constructorii nu pot fi functii virtuale. În schimb, destructorii pot fi functii virtuale. Functiile inline nu pot fi virtuale. Functiile virtuale sunt întotdeauna functii membru nestatice ale unei clase.

3.7 Clase abstracte

De cele mai multe ori, o funcţie declarată de tip virtual în clasa de bază nu defineşte o acţiune semnificativă şi este neapărat necesar ca ea să fie redefinită în fiecare din clasele derivate. Pentru ca programatorul să fie obligat să redefinească o funcţie virtuală în toate clasele derivate în care este folosită această funcţie, se declară funcţia respectivă virtuală pură. O funcţie virtuală pură este o funcţie care nu are definiţie în clasa de bază, iar declaraţia ei arată în felul următor:

virtual tip_returnat nume_functie(lista_argumente) = 0;

O clasă care conţine cel puţin o funcţie virtuală pură se numeşte clasă abstractă. Deoarece o clasă abstractă conţine una sau mai multe funcţii pentru care nu există definiţii, nu pot fi create instanţe din acea clasă, dar pot fi creaţi pointeri şi referinţe la astfel de clase abstracte. O clasă abstractă este folosită în general ca o clasă fundamentală, din care se construiesc alte clase prin derivare. Orice clasă derivată dintr-o clasă abstractă este, la rândul ei clasă abstractă (şi deci nu se pot crea instanţe ale acesteia) dacă nu se redefinesc toate funcţiile virtuale pure moştenite. Dacă o clasă redefineşte toate funcţiile virtuale pure ale claselor ei de bază, devine clasă normală şi pot fi create instanţe ale acesteia. Exemplul următor (5.6) evidenţiază caracteristicile claselor abstracte şi ale funcţiilor virtuale pure. Programul P3.5 se realizeaza conversia unor date dintr-o valoare de intrare într-o valoare de ieşire; de exemplu, din grade Farenheit în grade Celsius, din inch în centimetri, etc.

class Convert{ protected: double x; // valoare intrare double y; // valoare iesire public: Convert(double i){x = i;} double getx(){return x;} double gety(){return y;} virtual void conv() = 0; }; // clasa FC de conversie grade Farenheit in grade Celsius class FC: public Convert{ public: FC(double i):Convert(i){} void conv(){y = (x-32)/1.8;}

54

Page 55: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

}; // clasa IC de conversie inch in centimetri class IC: public Convert{ public: IC(double i):Convert(i){} void conv(){y = 2.54*x;} } void main (){ Convert* p = 0; // pointer la baza cout<<"Introduceti valoarea si tipul conversiei: "; double v; char ch; cin >> v >> ch; switch (ch){ case 'i': //conversie inch -> cm (clasa IC) p = new IC(v); break; case 'f': //conv. Farenheit -> Celsius (clasa FC) p = new FC(v); break; } if (p){ p->conv(); cout << p->getx() << "---> " << p->gety()<< endl; delete p; } }

Clasa de bază abstractă Convert este folosită pentru crearea prin derivare a unei clase specifice fiecărui tip de conversie de date dorit. Această clasă defineşte datele comune, necesare oricărui tip de conversie preconizat, de la o valoare de intrare x la o valoare de ieşire y. Funcţia de conversie conv() nu se poate defini în clasa de bază, ea fiind specifică fiecărui tip de conversie în parte; de aceea funcţia conv() se declară funcţie virtuală pură şi trebuie să fie redefinită în fiecare clasă derivată. În funcţia main() se execută o conversie a unei valori introduse de la consolă, folosind un tip de conversie (o clasă derivată) care se selectează pe baza unui caracter introdus la consolă. Acesta este un exemplu în care este destul de pregnantă necesitatea funcţiilor virtuale: deoarece nu se cunoaşte în momentul compilării tipul de conversie care se va efectua, se foloseşte un pointer la clasa de bază pentru orice operaţie (crearea unui obiect de conversie nou, apelul funcţiei conv(), afişarea rezultatelor, distrugerea obiectului la terminarea programului). Singura diferenţiere care permite selecţia corectă a funcţiilor, este tipul obiectului creat, care depinde de tipul conversiei cerute de la consolă.

55

Page 56: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

3.8 Polimorfism

Polimorfismul permite unei entitãti (de exemplu, variabilã, functie, obiect) sã aibã o varietate de reprezentãri. El este furnizat atât la momentul compilãrii (legare timpurie), prin folosirea operatorilor si a functiilor redefinite, cât si la momentul executiei (legare târzie), prin utilizarea functiilor virtuale. Conceptul de legare dinamicã permite unei variabile sã aibã tipuri diferite în functie de continutul ei la un moment dat. Aceastã abilitate a variabilei se numeste polimorfism, iar variabila se numeste variabilã polimorficã. În limbajul C++ variabilele polimorfice apar doar prin utilizarea pointerilor sau referintelor. În cazul în care un pointer al clasei de bazã puncteazã cãtre o functie virtualã, programul va determina la momentul executiei la care tip de obiect puncteaza pointerul si apoi va selecta versiunea corespunzãtoare functiei redefinite. Programul P4.4 defineste clasa de bazã Persoana si douã clase derivate, Student si Salariat. Clasa de bazã Persoana este o clasã abstractã având declaratã o functie virtualã purã, venit(), definitã în clasele derivate. De asemenea, clasa Persoana mai contine o altã functie virtualã, afisare(), care este redefinitã în clasele derivate. În programul principal sunt create douã obiecte S1 si T1 din clasele Student si respectiv Salariat. Pentru fiecare din cele douã obiecte se executã o secventã de patru instructiuni: primele douã instructiuni ilustreazã legarea staticã, prin apelul celor douã functii afisare() si venit(), corespunzãtoare obiectului referit prin nume; ultimele douã instructiuni ilustreazã legarea dinamicã apelând cele douã functii afisare() si venit() prin intermediul a douã functii, Ref_Pointer(.), Ref_Referinta(.), care utilizeazã un pointer, respectiv o referintã cãtre clasa de bazã Persoana. Aceste ultime douã instructiuni genereazã un comportament polimorfic, la momentul executiei programului. // fisierul sursa p4_4.cpp

#include <iostream.h> #include <string.h> #include <assert.h> // clasa de baza Persoana, clasa abstracta class Persoana { char* prenume; char* nume; public: Persoana(char*, char*); ~Persoana(); char* preiaPrenume(); char* preiaNume(); // functie virtuala pura virtual double venit() = 0; virtual void afisare(); }; Persoana::Persoana(char* P, char* N) { prenume = new char[strlen(P)+1];

56

Page 57: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

strcpy(prenume, P); nume = new char[strlen(N)+1]; strcpy(nume, N); } Persoana::~Persoana() { delete prenume; delete nume; } char* Persoana::preiaPrenume() { return prenume; } char* Persoana::preiaNume() { return nume; } void Persoana::afisare() { cout << prenume << " " << nume << "\n"; } // definim clasa Student derivata din clasa Persoana class Student : public Persoana { double bursa; double media; public: Student(char*, char*, double = 0.0, double = 0.0); void seteazaBursa(double); void seteazaMedia(double); virtual double venit(); virtual void afisare(); }; Student::Student(char* P, char* N, double B, double M) :Persoana(P,N) { M>=8.50?seteazaBursa(B):seteazaBursa(0.0); seteazaMedia(M); } void Student::seteazaBursa(double B) { bursa = B>0?B:0; } void Student::seteazaMedia(double M) { media = M>0?M:0.0; } double Student::venit() { return bursa;

57

Page 58: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

} void Student::afisare() { cout << "\n Student:"; Persoana::afisare(); cout << "\t Media = " << media << "\n"; } // definim clasa Salariat derivata din clasa Persoana class Salariat:public Persoana { double salariu; double venit_ora; int nr_ore; public: Salariat(char*, char*, double = 0.0, double = 0.0, int = 0); void seteazaSalariu(double); void seteazaVenitOra(double); void seteazaNrOre(int); virtual double venit(); virtual void afisare(); }; Salariat::Salariat(char* P,char* N,double S,double V,int nr) :Persoana(P, N) { seteazaSalariu(S); seteazaVenitOra(V); seteazaNrOre(nr); } void Salariat::seteazaSalariu(double S) { salariu = S>0 ? S : 0; } void Salariat::seteazaVenitOra(double V) { venit_ora = V>0.0 ? V : 0.0; } void Salariat::seteazaNrOre(int nr) { nr_ore = nr>0 ? nr : 0; } double Salariat::venit() { return salariu + nr_ore*venit_ora; } void Salariat::afisare() { cout << "\n Salariat:"; Persoana::afisare(); } // functie care apeleaza functiile virtuale prin legare

58

Page 59: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

// dinamica, in cazul unui pointer al clasei de baza // referire prin pointer void Ref_Pointer(Persoana *Ptr) { Ptr->afisare(); cout << " venit (lei) " << Ptr->venit(); } // functie care apeleaza functiile virtuale prin legare // dinamica, in cazul unei referinte la clasa de baza – // referire prin referinta void Ref_Referinta(Persoana &Ptr) { Ptr.afisare(); cout << " venit (lei) " << Ptr.venit(); } void main() { Student S1("Alexandra", "Stoica", 1500000, 10); cout << "\n Legare statica:\n"; S1.afisare(); cout << " venit (lei) "<<S1.venit(); cout << "\n Legare dinamica:\n"; Ref_Pointer(&S1); Ref_Referinta(S1); Salariat T1("Silvan", "Manole", 50000000, 100000, 30); cout << "\n Legare statica:\n"; T1.afisare(); cout<<" venit (lei) "<<T1.venit(); cout << "\n Legare dinamica: \n"; Ref_Pointer(&T1); Ref_Referinta(T1); }

Observatii Principiul care stã la baza polimorfismului este “o singurã interfatã, metode multiple”.

Practic, polimorfismul reprezintã abilitatea obiectelor din clase diferite ale unei ierarhii de clase de a rãspunde diferit la acelasi mesaj (adicã,la apelul unei functii membru).

Implementarea polimorfismului este realizatã prin intermediul functiilor virtuale.

În cazul în care o functie membru ne-virtualã este definitã într-o clasã de bazã si redefinitã într-o clasã derivatã, comportamentul este ne-polimorfic. Astfel, dacã functia membru este apelatã printr-un pointer al clasei de bazã la obiectul clasei derivate, se utilizeazã versiunea clasei de bazã. Dacã functia membru este apelatã printr-un pointer al clasei derivate, se utilizeazã versiunea clasei derivate.

59

Page 60: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Teste de autocontrol 3.1 Definiti relatia de derivare. 3.2 Definiti mostenirea. 3.3 Dacã din clasa X se genereazã o clasã Z cum se numesc cele douã clase?

(a) X clasã derivatã, Z clasã de bazã (b) X superclasã, Z subclasã (c) X clasã copil, Z clasã pãrinte (d) X clasã de bazã, Z clasã derivatã

3.4 Care dintre afirmatiile urmãtoare sunt adevãrate si care sunt false? (a) Obiectele unei clase derivate au acces la membrii privati ai clasei sale de bazã. (b) Relatia de mostenire este tranzitivã. (c) Functiile friend ale clasei de bazã se mostenesc de cãtre clasa derivatã. (d) Constructorul si destructorul clasei de bazã se mostenesc în clasa derivatã.

3.5 Selectati rãspunsul corect referitor la ordinea de apelare a constructorilor si a destructorilor în cazul claselor derivate dintr-o clasã de bazã. Ordinea de apelare este urmãtoarea:

(a) constructorul clasei derivate constructorul clasei de bazã destructorul clasei derivate destructorul clasei de bazã (b) constructorul clasei de bazã constructorul clasei derivate destructorul clasei derivate destructorul clasei de bazã (c) constructorul clasei derivate constructorul clasei de bazã destructorul clasei de bazã destructorul clasei derivate (d) constructorul clasei de bazã constructorul clasei derivate destructorul clasei de bazã

3.6 Care este avantajul principal oferit de mecanismul mostenirii? 3.7 Utilizarea mostenirii si a polimorfismului permite eliminarea unei anumite instructiuni. Care este aceastã instructiune în limbajul C++? 3.8 Cum este specificatã o functie virtualã purã? 3.9 Cum se numeste o clasã care contine una sau mai multe functii virtuale pure? 3.10 Dacã apelul unei functii este rezolvat la momentul executiei legarea este

(a) staticã (b) timpurie (c) nulã (d) dinamicã

60

Page 61: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

3.11 Care dintre afirmatiile urmãtoare sunt adevãrate? (a) Constructorii pot fi functii virtuale. (b) Destructorul poate fi functie virtualã. (c) Orice functie membru staticã este functie virtualã. (d) Functiile inline nu pot fi functii virtuale.

3.12 Care este diferenta între o functie virtualã si o functie virtualã purã? 3.13 Ce se întelege prin polimorfism pur? Comparati cu notiunea de supraîncãrcare. 3.14 Cum pot fi definite variabilele polimorfice în limbajul C++? 3.15 Explicati pe scurt notiunea de polimorfism în cazul programãrii orientate pe obiecte. 3.16 Care este rolul claselor abstracte? 3.17 Scrieti un program care defineste clasa de bazã Aparat si clasa derivatã Radio. Printre functiile membru includeti si o functie de afisare a datelor membru ale clasei. În programul principal se vor crea douã obiecte ob1 al clasei Aparat si ob2 al clasei Radio si se vor apela functiile de afisare. 3.18 Redefiniti functia de afisare a clasei Aparat în clasa Radio si rescrieti programul de la exercitiul 3.17. 3.19 Rescrieti programul de la exercitiul T3.2 realizând supraîncãrcarea constructorului clasei derivate Radio. 3.20 Care sunt erorile din urmãtoarea secventã de program? // …

class A { int a,b; public: A(int , int, double); void afisare(); // … protected: double t; }; // … class B::public A { double w; public: B(int, int, double); void afisare(); void setValori(int x, int y, double z) { a=x; b=y; t=z;} }; // …

61

Page 62: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

3.21 Fiind date următoarele tipuri de clase:

class B { /* instructiuni */ }; class D1: virtual B { /* instructiuni */ }; class D2: virtual B { /* instructiuni */ }; class D3: B { /* instructiuni */ }; class D4: private B { /* instructiuni */ }; class D5: virtual public B { /* instructiuni */ }; class M1: D1, public D2, D3, private D4, virtual D5 { /* instructiuni */ }; class M2: D1, D2, virtual D3, virtual D4, virtual D5 { /* instructiuni */ };

spuneţi de câte ori este moştenită clasa B în clasa M1. Dar în clasa M2 ? Justificaţi. 3.22 Spuneţi care dintre declaraţiile funcţiei main() sunt corecte în programul de mai jos. Justificaţi. #include <iostream.h>

class B1 { public: int x; }; class B2 { int y; }; class B3 { public: int z; }; class B4 { public: int t; }; class D: private B1, protected B2, public B3, B4 { int u; }; int main() { D d; cout<<d.u; cout<<d.x; cout<<d.y; cout<<d.z; cout<<d.t; return 0; }

3. 23 Spuneţi dacă programul de mai jos este corect. În caz afirmativ, precizaţi exact constructorii care se execută şi în ce ordine. În caz negativ spuneţi de ce nu este corect. #include<iostream.h>

class cls1 { public: int x; cls1(int i=13) { x=i; } }; class cls2: virtual public cls1 { public: cls2(int i=15) { x=i; } }; class cls3: virtual public cls1 { public: cls3(int i=17) { x=i; } };

62

Page 63: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

class cls4: public cls1 { public: cls4(int i=19) { x=i; } }; class cls5: public cls2, public cls3, public cls4 { public: int y; cls5(int i,int j):cls4(i),cls2(j){ y=i+j; } cls5(cls5& ob) ){ y=-ob.y; }}; int main() { cls5 ob1(-9,3), ob2=ob1; cout<<ob2.y; return 0; }

3.24 Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează, în caz negativ spuneţi de ce nu este corect. #include <iostream.h>

class B { int a; public: B(int i=0) { a=i; } int get_a(){ return a; } }; class D: private B { public: D(int x=0): B(x) {} int get_a() { return B::get_a(); } }; int main() { D d(-89); cout<<d.get_a(); return 0; }

63

Page 64: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Cap. 4 Facilitãti ale limbajului C++ Obiective · Redefinirea operatorilor · Definirea template-urilor · Însusirea modului de lucru cu fisiere în limbajul C++ 4.1 Supraincarcarea operatorilor Limbajul C++ permite programatorului sã defineascã diverse operatii cu obiecte ale claselor, utilizând simbolurile operatorilor standard. Pentru tipurile fundamentale ale limbajului sunt definite seturi de operatori care permit operaţii de bază executate într-un mod convenabil. Dar, după cum este cunoscut, în limbaj sunt definite prea puţine tipuri de date ca date fundamentale, iar pentru reprezentarea altor tipuri care sunt necesare în diferite domenii (cum ar fi aritmetica numerelor complexe, algebra matricilor, etc.), se definesc clase care conţin funcţii ce pot opera asupra acestor tipuri. Limbajul C++ nu permite crearea de noi operatori, în schimb permite redefinirea majoritãtii operatorilor existenti astfel încât atunci când acesti operatori sunt aplicati obiectelor unor clase sã aibã semnificatia corespunzãtoare noilor tipuri de date. Principalele avantaje ale redefinirii operatorilor sunt claritatea si usurinta cu care se exprimã anumite operatii specifice unei clase. Solutia alternativã ar fi definirea unor functii si apelul functiilor în cadrul unor expresii. Funcţiile operator pentru o anumită clasă pot să fie sau nu funcţii membre ale clasei. Dacă nu sunt funcţii membre ele sunt, totuşi, funcţii friend ale clasei şi trebuie să aibă ca argument cel puţin un obiect din clasa respectivă sau o referinţă la aceasta. Funcţii operator membre ale claselor Forma generală pentru funcţiile operator membre ale clasei este următoarea:

tip_returnat operator # (lista_argumente){ // operaţii }

Semnul # reprezintă oricare dintre operanzii care pot fi supraîncărcaţi. Programul P 4.1 prezinta clasa Point care descrie un vector într-un plan bidimensional prin două numere de tip float, x şi y. Valorile x şi y reprezintă coordonatele punctului de

64

Page 65: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

extremitate al vectorului. Pentru această clasă se pot defini mai multe operaţii cu vectori, ca de exemplu:

• Suma a doi vectori • Diferenţa a doi vectori • Produsul scalar a doi vectori • Multiplicarea unui vector cu o constantă (scalare)

Aceste operaţii se pot implementa prin supraîncărcarea corespunzătoare a operatorilor. Pentru început se vor defini funcţiile operator+() şi operator–() pentru calculul sumei, respectiv a diferenţei a doi vectori.

class Point{ float x; float y; public: Point(){ x = 0; y = 0; } Point(double a, double b){ x = a; y = b; } void Display() { cout << x << " " << y << endl; } Point operator+(Point op2); // suma a doi vectori Point operator-(Point op2); // diferenţa a doi vect double operator*(Point op2);// produs scalar Point& operator*(double v); // multipl. cu o const. }; Point Point::operator+(Point op2){ point temp; temp.x = x + op2.x; temp.y = y + op2.y; return temp; } Point Point::operator-(Point op2){ point temp; temp.x = x + op2.x; temp.y = y + op2.y; return temp; } double Point::operator*(Point op2){ return x*op2.y + y*op2.x; }

65

Page 66: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Point& Point::operator*(double v){ x *=v; y *=v; return *this; } void f1(){ Punct pct1(10,20); Punct pct2(30,40); Punct pct3; pct1.Display(); // afiseaza 10 20 pct2.Display(); // afiseaza 30 40 pct3 = pct1 + pct2; pct3.Display(); // afiseaza 40 60 pct3 = pct2 – pct1; pct3.Display(); // afiseaza 20 20 }

Funcţia operator+() are un singur argument, chiar dacă ea supraîncarcă un operator binar pentru ca argumentul transmis funcţiei este operandul din dreapta operaţiei, iar operandul din stânga este chiar obiectul pentru care se apelează funcţia operator. Pentru acelaşi operator se pot defini mai multe funcţii supraîncărcate, cu condiţia ca selecţia uneia dintre ele în funcţie de numărul şi tipul argumentelor să nu fie ambiguă. În clasa Point s-a supraîncărcat operatorul * cu două funcţii: prima pentru calculul produsului scalar a doi vectori, cealaltă pentru multiplicarea vectorului cu o constantă. În implementarea prezentată, funcţia operator+() crează un obiect temporar, care este distrus după returnare. În acest fel, ea nu modifică nici unul dintre operanzi, aşa cum nici operatorul + pentru tipurile predefinite nu modifică operanzii. În general, un operator binar poate fi supraîncărcat fie printr-o funcţie membră nestatică cu un argument, fie printr-o funcţie nemembră cu două argumente. Un operator unar poate fi supraîncărcat fie printr-o funcţie membră nestatică fără nici un argument, fie printr-o funcţie nemembră cu un argument. La supraîncărcarea operatorilor de incrementare sau decrementare (++, --) se poate diferenţia un operator prefix de un operator postfix folosind două versiuni ale funcţiei operator. În continuare sunt prezentate câteva funcţii operator ale clasei Point pentru operatori unari.

class Point{ //…………… public: Point operator!(); Point operator++(); Point operator—(); Point operator++(int x); Point operator—(int x); };

66

Page 67: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Point operator!(){ x = -x; y = -y; return *this; } Point Point::operator++(){ x++; y++; return *this; } Point Point::operator--(){ x--; y--; return *this; } Point Point::operator ++(int x){ ++x; ++y; return *this; } Point Point::operator --(int x){ --x; --y; return *this; }

Dacă ++ precede operandul, este apelată funcţia operator++(); dacă ++ urmează operandului, atunci este apelată funcţia operator++(int x), iar x are valoarea 0. La supraîncărcarea unui operator folosind o funcţie care nu este membră a clasei este necesar să fie transmişi toţi operanzii necesari, deoarece nu mai există un obiect al cărui pointer (this) să fie transferat implicit funcţiei. Din această cauză, funcţiile operator binar necesită două argumente de tip clasă sau referinţă la clasă, iar funcţiile operator unar necesită un argument de tip clasă sau referinţă la clasă. În cazul operatorilor binari, primul argument transmis este operandul stânga, iar al doilea argument este operandul dreapta In programul P4.2 o parte din funcţiile operator ale clasei Point sunt implementate ca funcţii friend ale clasei.

class Point { int x; int y; public: //………………………. friend Point operator+(Point op1, Point op2); friend Point operator-(Point op1, Point op2);

67

Page 68: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Point operator+(Point op1, Point op2){ Point temp; temp.x = op1.x + op2.x; temp.y = op1.y + op2.y; return temp; } Point operator-(Point op1, Point op2){ Point temp; temp.x = op1.x - op2.x; temp.y = op1.y - op2.y; return temp; } void f2(){ Punct pct1(10,20); Punct pct2(30,40); Punct pct3; pct1.Display(); // afiseaza 10 20 pct2.Display(); // afiseaza 30 40 pct3 = pct1 + pct2; pct3.Display(); // afiseaza 40 60 pct3 = pct2 – pct1; }

Observatii

Dacã functia operator este implementatã ca o functie membru, operandul cel mai din stânga (eventual, unicul operand) trebuie sã fie un obiect al clasei sau o referintã cãtre un obiect al clasei. Implementarea sub formã de functie nemembru este indicatã în cazul în care cel mai din stânga operand este un obiect al unei clase diferite sau al unui tip predefinit.

O functie operator ne-membru trebuie declaratã functie friend dacã functia respectivã trebuie sã acceseze direct membrii privati sau protejati ai clasei respective.

4. 2 Template-uri O altã facilitate importantã a limbajului C++ este datã de posibilitatea definirii unor sabloane numite template-uri. Un template reprezintã o modalitate de parametrizare a unei clase sau a unei functii prin utilizarea unui tip în acelasi mod în care parametrii unei functii furnizeazã o modalitate de a defini un algoritm abstract fãrã identificarea valorilor specifice. O clasă template specifică modul în care pot fi construite clase individuale, diferenţiate prin tipul sau tipurile de date asupra cărore se operează. Sintaxa definirii unui template:

template <class <parametru> > class <nume_clasã>

68

Page 69: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

{ // definitia clasei sablon };

Programul P4.3 prezintã un exemplu de definire a unei functii template, afisare_vector(.). Aplicarea functiei template celor trei variabile T1, T2 si respectiv T3 va determina afisarea unui vector de numere întregi, a unui vector de numere reale, respectiv a unui vector de caractere.

// fisierul sursa P4.3.cpp #include <iostream.h> template <class T> void afisare_vector(const T *t, const int nr) { for (int i=0;i<nr;i++) cout<<t[i]<<" "; } void main() { const int nr1 = 5, nr2 = 7, nr3 = 4; int T1[nr1] = {1,2,3,4,5}; double T2[nr2] = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7}; char T3[nr3] = "Ana"; cout<<"\n Vectorul contine: "; // functia template pentru vector de numere intregi afisare_vector(T1, nr1); cout<<"\n Vectorul contine: "; // functia template pentru vector de numere reale afisare_vector(T2, nr2); cout<<"\n Vectorul contine: "; // functia template pentru vector de caractere afisare_vector(T3, nr3); }

Observatie

Template-urile furnizeazã o altã formã de polimorfism. 4.3 Stream-uri de I/E Bibliotecile standard ale limbajului C++ furnizeazã un set extins de facilitãti pentru operatii de intrare/iesire (I/E). Dacã o functie de I/E este definitã corespunzãtor unui anumit tip de date, ea va putea fi apelatã pentru a lucra cu acel tip de date. Se pot specifica atât tipuri standard, cât si tipuri definite de utilizatori. Funcţii de I/O pentru tipurile predefinite Operaţiile de I/O din C++ se efectuează folosind funcţiile operator de inserţie << şi operator de extragere >>.

69

Page 70: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Funcţiile de operare asupra streamurilor specifică modul în care se execută conversia între un şir de caractere din stream şi o variabilă de un anumit tip. Aceste funcţii operator sunt definite în clasa ostream, respectiv istream, pentru toate tipurile predefinite ale limbajului, iar pentru tipurile definite de utilizator ele pot fi supraîncărcate. Funcţia de citire de la consolă a unei secvenţe de numere întregi separate prin spaţii albe (whitespace adică unul din caracterele blanc, tab, newline, carriage return, formfeed) poate arăta asfel:

void main(){ int size = 10; int array[10]; for(int i=0;i<size;i++){ if (cin >> array[i]) cout << array[i] << " "; else { cout << "eroare non-int"; break; } } }

O intrare diferită de întreg va cauza eroare în operaţia de citire şi deci oprirea buclei for. De exemplu, din intrarea: 1 2 3 4.7 5 6 7 8 9 0 11

se va citi primele patru numere, după care apare eroare în operaţia de intrare, citirea numerelor întregi se întrerupe şi pe ecran apare mesajul: 1 2 3 4 eroare non-int

Caracterul punct este lăsat în streamul de intrare, ca următor caracter de citit. O altă soluţie pentru citirea unei secvenţe de intrare este prin folosirea uneia din funcţiile get() definite în clasa iostream astfel:

class iostream : public virtual ios { // …… istream& get(char& c); istream& get(char* p, int n, char ch=’\n’); };

Aceste funcţii treatează spaţiile albe la fel ca pe toate celelalte caractere. Funcţia get(char& c) citeşte un singur caracter în argumentul c. De exemplu, o funcţie fg() de copiere caracter cu caracter de la intrare (streamul cin) la ieşire (streamul cout) poate arăta astfel:

void stream(){ char c; while(cin.get(c)) cout << c;

70

Page 71: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

}

Şi funcţia operator >> () are un echivalent ca funcţie de scriere la consolă fără supraîncărcarea operatorului >>, numită funcţia put(), astfel încât funcţia fg() se poate rescrie astfel:

void stream(){ char c; while(cin.get(c)) cout.put(c); }

Funcţii de I/O pentru tipuri definite de utilizator Funcţiile de I/O pentru tipuri definite de utilizator se obţin prin supraîncărcarea operatorilor de inserţie şi de extragere, care au următoarea formă generală:

ostream& operator<<(ostream& os,tip_clasa nume){ // corpul functiei return os; } istream& operator<<(istream& is,tip_clasa& nume){ // corpul functiei return is; }

Primul argument al funcţiei este o referinţă la streamul de ieşire, respectiv de intrare. Pentru funcţia operator de extragere << al doilea argument este dat printr-o referinţă la obiectul care trebuie să fie extras din stream; în această referinţă sunt înscrise datele extrase din streamul de intrare. Pentru funcţia operator de inserţie >> al doilea argument este dat prin tipul şi numele obiectului care trebuie să fie inserat, sau printr-o referinţă la acesta. Funcţiile operator de inserţie şi extracţie returnează o referinţă la streamul pentru care au fost apelate, astfel încât o altă operaţie de I/O poate fi adăugată acestuia. Funcţiile operator << sau >> nu sunt membre ale clasei pentru care au fost definite, dar pot (şi este recomandabil) să fie declarate funcţii friend în clasa respectivă. In programul P 4.4 se defineste clasa Complex care cuprinde functiile operator << si >>.

class Complex { double x, y; public: Complex(){x = 0; y = 0} Complex(double r, double i){ x = r; y = i; } ………….. friend ostrem& operator << (ostrem& os,Complex z);

71

Page 72: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

friend istream& operator >>(istream& is,Complex& z); }; ostream& operator<<(ostream& os, Complex& z){ os << ‘(‘ << z.x << ’,’<< z.y << ‘)’; return os; } istream& operator>>(istream& is, Complex z){ is >> z.x >> z.y; return is; } void main(){ Complex z; cout << "Introduceti x, y :"; cin >> z; cout << "z = " << z << '\n'; cin.get(); int size = 10;

Execuţia acestei funcţii produce următoarele mesaje la consolă: Introduceti x, y: 1.3 4.5 z = (1.3,4.5) Introduceti un sir:123456 123456

4. 4 Procesarea fisierelor Procesarea fisierelor în limbajul C++ necesitã includerea fisierelor antet <iostream.h> si <fstream.h>. Fisierul antet <fstream.h> cuprinde definitiile claselor streamului: ifstream (pentru intrãrile dintr-un fisier – operatii de citire din fisier), ofstream (pentru iesirile cãtre un fisier – operatii de scriere în fisier) si fstream (pentru intrãrile/iesirile de la/cãtre un fisier). Pentru utilizarea unui fişier pe disc acesta trebuie să fie asociat unui stream. Pentru aceasta se crează mai întâi un stream, iar apelul funcţiei open() a streamului execută asocierea acestuia cu un fişier ale cărui caracteristici se transmit ca argumente ale funcţiei open(). Funcţia open() este funcţie membră a fiecăreia dintre cele trei clase stream (ifstream, ofstream şi fstream) Implicit, fişierele se deschid în mod text. Valoarea ios::binary determină deschiderea în mod binar a fişierului. Pentru închiderea unui fişier se apelează funcţia close(), care funcţie membră a claselor stream (ifstream, ofstream şi fstream).

Pentru scrierea şi citirea dintr-un fişier de tip text se folosesc funcţiile operator << şi >> ale streamului asociat acelui fişier. Aceste funcţii operator supraîncărcate pentru un anumit tip de date pot fi folosite fără nici o modificare atât pentru a scrie sau citi de la consolă cât şi pentru a scrie sau citi dintr-un fişier pe disc.

72

Page 73: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Programul P4.5 crează un fişier de inventariere “inventar.txt” care conţine numele articolului, preţul unitar, numărul de bucăţi şi valoarea totală.

void finv(){ ofstream output("inventar.txt"); if (!output) cout << "Nu se poate deschide fisierul inventar.txt\n"; char buffer[80]; double pret,total; int buc; while(1){ cout << "\nArticol: "; cin.get(buffer,80); if (buffer[0] == 0) break; cout << "Pret unitar: "; cin >> pret; cout << "Nr. bucati: "; cin >> buc; cin.get(); total = buc*pret; output << buffer << endl; output << pret<< endl; output << buc << endl; output << total << endl; } output.close(); }

ν La citirea dintr-un fişier de tip text de pe disk folosind operatorul >> apar, la fel ca la citirea de la consolă, anumite modificări de caractere. Pentru a evita astfel de modificări se folosesc funcţiile de I/O binare care vor fi prezentate în secţiunea următoare.

73

Page 74: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Teste de autocontrol 4.1 Care este rolul redefinirii operatorilor în limbajul C++? 4.2 Care dintre afirmatiile urmãtoare sunt adevãrate?

(a) Precedenta unui operator poate fi modificatã prin redefinire. (b) Aritatea unui operator nu poate fi modificatã prin redefinire. (c) Asociativitatea unui operator poate fi modificata prin redefinire. (d) Semnificatia modului în care lucreazã un operator asupra obiectelor de tipuri predefinite nu poate fi schimbatã prin redefinire.

4.3 Care sunt clasele predefinite, dedicate procesãrii fisierelor C++? 4.4 Să se citească un fişier care conţine numere flotante, să se construiască numere complexe din perechi de câte două numere flotante şi să se afişeze la consolă numerele complexe. 4.4 Pentru clasa String să se definească următoarele operaţii de comparaţie folosind funcţii operator friend:

int operator ==( const String& s1, const String& s2 ); int operator ==( const String& s1, const char* s2 ); int operator ==( const char* s1, const String& s2 ); int operator !=( const String& s1, const String& s2 );

4.5 Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează, în caz negativ spuneţi de ce nu este corect. #include<iostream.h>

class B { protected: int x; B(int i=10) { x=i; } public: virtual B operator+(B ob) { B y(x+ob.x); return y;} }; class D: public B { public: D(int i=10) { x=i; } void operator=(B p) { x=p.x; } B operator+(B ob) { B y(x+ob.x+1); return y; } void afisare(){ cout<<x; } }; int main() { D p1(-59),p2(32),*p3=new D; *p3=p1+p2; p3->afisare(); return 0; }

74

Page 75: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

4.6 Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează, în caz negativ spuneţi de ce nu este corect. #include<iostream.h>

class cls { public: int sa; cls(int s=0) { sa=s; } operator int() { return sa; } int f(int c) { return (sa*(1+c/100)); } }; int main() { cls p(37); cout<<p.f(p); return 0; }

4.7 Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează, în caz negativ spuneţi de ce nu este corect. #include<iostream.h>

class B { public: int x; B(int i=0) { x=i; } virtual B aduna(B ob) { return(x+ob.x); } B minus() { return(1-x); } void afisare(){ cout<<x; } }; class D: public B { public: D(int i=0) { x=i; } B aduna(B ob) { return(x+ob.x+1); } }; int main() { B *p1, *p2; p1=new D(138); p2=new B(-37); *p2=p2->aduna(*p1); *p1=p2->minus(); p1->afisare(); return 0; }

75

Page 76: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

Teste recapitulative:

Specificaţi varianta corectă : 1. Clasa

class c { float a; void afisisare(); }

are membrii: a). publici; b). privaţi c). protected; d). date private şi metode pubice. 2. Fie secvenţa:

class c {....}; void main() { c e /* instructiuni */ }

În acest caz: a). c este un obiect şi e este clasă; b). c este o instanţa a clasei şi e este un obiect; c). c este o clasa şi e este un obiect; d). descrierea este eronată deoarece se foloseşte acelaşi identificator pentru clasă şi obiect.

3. Având declaraţia: class persoana { char nume[20]; int varsta; public: persoana(); int spune_varsta() { return varsta;} }; void main() {persoana p; cout<<p.nume<<p.varsta; }

În acest caz : a). programul afisează numele şi vârsta unei persoane; b). nu este permisă afisarea datelor unei persoane; c). descriere eronată pentru că funcţia membra nu este utilizată; d). afisează doar vârsta unei persoane. 4. Fie clasa :

class c { int a,b; public: float c (int, int) int det_a {return a;} c (); }

Declaraţia float c(int, int) ar putea corespunde unui constructor al clasei?

76

Page 77: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

a). da, fiind o supraincarcare a celui existent; b). nu., deoarece crează ambiguitate; c). nu, deoarece constructorul nu are tip returnat; d). nu, deoarece nu este de tip friend.

5. Fie declaraţia : class c1 {int a ;} class c2 :public c1 { public : int b ; void scrie_a() {cout<<a ;} } void main () { c2 ob ; ob.scie_a () ; }

Selectaţi afirmaţia corectă : a). funcţia scrie_a() nu are drept de acces asupra unui membru privat; b). programul afisează valoare lui a; c). derivarea publică este incorect relaizată; d). prin derivare publică, accesul la membrii moşteniti devine publică. 6. Fie declaraţia

class c1{/* instructiuni */} class c2:public c1 {/* ….*/}

Atunci clasa c2 faţa de clasa c1 este: a). derivată; b). de bază; c). friend d). virtuală. 7. In secvenţa urmatoare:

class cls { public:static int s;}; int cls::s=0; int main(){int i=7;cls::s=i;cout<<cls::s;}

Utilizarea lui s este: a). ilegală, deoarece nu există nici un obiect creat; b). ilegală, deoarece variabilele statice pot fi doar private; c). ilegală, deoarece s este dublu definit, în clasă şi în afara ei; d). corectă, deoarece membrii statici există înainte de a se crea obiecte din clasa. 8. Secvenţa urmatoare:

class persoana { int varsta, salariul; friend ostream & operator<<(ostream &out,persoana p) {out<<p.varsta<<” “<<p.salariul; return out;} public: persoana(int v){varsta=v;salariul=0;} persoana(){varsta=0;salariul=0;} } int main() {persoana p(1);cout<<p;}

Afisează: a). afisează 1 0; b). afisează 0 0

77

Page 78: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

c). afisează 1 1; d). afisează 0 1. 9. Secvenţa urmatoare:

class vector{int*pe,nr_c; public: operator int(){return nr_c;} vector(int n){ pe=new int[n];nr_c=n; while(n--) pe[n]=n;} void f(int i){cout<<i<<endl;} int main() {vector x(10); f(x)} Afisează:

a). 10 b). 9 c). numerele de la 1 la 10 d). numerele de la 0 la

10. Secventa urmatoare:

class vector{int*pe,nr_c; public: operator int(){return nr_c;} vector(int n){ pe=new int[n];nr_c=n; while(n--) pe[n]=n;} void f(int i){cout<<i<<endl;} int main() {vector x(10); f(x)}

Afiseaza: a). 10 b). 9 c). numerele de la 1 la 10 d). numerele de la 0 la 9 11. Secvenţa urmatoare afisează:

class persoana {int varsta; public: persoana(int v=18){varsta=18;} operator int(){return varsta;} persoana& operator++() {varsta++;return *this;} persoana operator ++(int) {persoana aux=*this;varsta++;return aux;} int main(){ persoana p(20); int x=p++,y=++p; cout<< x<< y ;}

Afiseaza : a).20 20; b).20 21 ; c).21 22; d).20 22.

78

Page 79: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

12 . Fie secvenţa :

class c { int a; public: c(); c(const c&); void operator=(c&);} int main() { c a; //instructiuni; c b=a;}

Linia c b=a; determină: a). execuţia constructorului de copiere; b). execuţia metodei prin care se supraincarcă operatorul =; c). execuţia atât a constructorului de copiere, cât şi a metodei operator =; d). execuţia contructorului implicit

13. Se considera urmatoarea secventa de program:

class complex { double real; double imag; public: complex(double x=1.0, double y=20.0){real=x; imag=y;} complex( const complex &u) { real=u.real; imag=u.imag; } .............. }

Precizati ˆın care situatie se creaza un obiect anonim: a) complex z1(3.42, -12.9); b) complex z2=z1; c) complex z3(z1); d) complex z1= complex(45.0,0.9); e) complex z(23.25); 14. Se considera urmatoarea secventa de program:

class complex { double real; double imag; public: complex(double x=10.0, double y=10.0){real=x; imag=y;} complex(const complex &u) { real=u.real; imag=u.imag; } .............. }

Precizati ın care situatie se realizeaza o copiere a unui obiect ın alt obiect:

79

Page 80: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

a) complex z1(3.42, -12.9); b) complex z2=z1; c) complex z3(1.0,-1.0); d) complex z(10.7,0.8); e) complex z(23.25); 15. Se considera urmatoarea secventa de program:

class complex { double real; double imag; public: complex(double x=-11.0, double y=-56.90){real=x; imag=y;} complex( const complex &u) { real=u.real; imag=u.imag; } .............. }

Precizai in care situatie se creaza un obiect anonim: a) complex z1(3.42, -12.9); b) complex z1= complex(0.0,0.9); c) complex z2=z1; d) complex z3(z1); e) complex z(23.25); 16. Se considera urmatoarea secventa de program:

class complex { double re; double im; public: complex(double x=-11.0, double y=-56.90){re=x; im=y;} complex( const complex &u) { real=u.re; imag=u.im; } ............ }

Precizati ın situatie se utilizeaza constructorul de copiere: a) complex z1(3.4, -12.9); b) complex z3(0.0,-10.9); c) complex z2(0.0,1.0); d) complex z3(z1); e) complex z(2.25); 17. Se considera urmatoarea secventa de program:

class complex { double real;

80

Page 81: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

double imag; public: complex(double x=-11.0, double y=-56.90){real=x; imag=y;} complex( const complex &u) { real=u.real; imag=u.imag; }

.............. }

Precizati situatia ın care nu era necesara folosirea unui constructor cu parametri care iau valori ın mod implicit: a) complex z2(3.42,-12.9); b) complex z3(z2); c) complex z=z2; d) complex z4(z); e) complex z5=z4; 18. Se da secventa de program:

class A { int a[3]; public: A(int i, int j, int k){a[0]=i; a[1]=j; a[2]=k;} int &operator[](int i){return a[i];} }; void main(void) { A ob(1,2,3); cout << ob[1]; ob[1]=25; cout<<ob[1]; }

Ce se poate afirma despre operator[]()? a) produce supraıncarcarea unei functii; b) produce supraıncarcarea unui operator unar; c) supraıncarca operatorul []; d) este o functie membru oarecare a clasei A care nu produce supraıncarcarea unui operator; e) reprezinta un operator ternar; 19. Supraıncarcarea unor operatori se poate realiza prin functii operator sau functii friend. Diferenta ıntre aceste doua posibilitati consta ın: a) lista de parametri; b) obiect returnat; c) precedent a operatorilor; d) n-aritatea operatorului; e) alte situatii. 20. In secventa de program:

................. int k=100;

81

Page 82: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

void fct() { int k; ........... k++; ........... } void gct() { int k=2; ........... ::k++; // (?) ........... }

In instructiunea marcata cu (?), k este o variabila: a) externa; b) statica; c) registru; d) globala; e) automatica; 21. Se considera secventa de program:

class B1 { class D1:public B1,public B2 { public: public: B1(){cout<<"(1)\n";} D1(){cout<<"(7)\n";} ~B1(){cout<<"(2)\n";} ~D1(){cout<<"(8)\n";} }; }; class B2 { class D2:public D1,public B3 { public: public: B2(){cout<<"(3)\n";} D2(){cout<<"(9)\n";} ~B2(){cout<<"(4)\n";} ~D2(){cout<<"(10)\n";} }; }; class B3 { public: B3(){cout<<"(5)\n";} ~B3(){cout<<"(6)\n";} }; void main(){ D1 ob1; D2 ob2; }

Care mesaj se va scrie? a) (1),(3),(7),(3),(5),(7),(9),(10),(6),(8),(4),(2),(2),(3),(2),(2); b) (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(9),(7),(2),(3),(2),(2); c) (1),(3),(7),(1),(3),(7),(5),(9),(10),(6),(8),(4),(2),(8),(4),(2); d) (1),(3),(5),(7),(9),(2),(4),(6),(6),(8),(8),(10),(2),(2),(4),(2); e) (1),(3),(7),(1),(3),(7),(9),(5),(10),(6),(4),(8),(2),(8),(4),(2); 22. Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează, în caz negativ spuneţi de ce nu este corect. #include <iostream.h>

template <class tip> class cls { tip z;

82

Page 83: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

public: cls(tip i) { z=i; } tip operator-(cls); }; template <class tip> tip cls<tip>::operator-(cls<tip> a) { return z-a.z; } template <class tip> tip dif(tip x, tip y) { return x-y; } int dif(int x, int y) { return x>=y?x-y:y-x; } int main() { cls<int> i=3; cls<float> j=4; cout<<dif(i,j); return 0; }

23. Descrieţi pe scurt cum puteţi prelua o dată prin incluziune şi a doua oară prin moştenire o clasă numar într-o clasă lista care descrie liste nevide de dimensiune variabilă de elemente de tip numar.

24. Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează, în caz negativ

spuneţi de ce nu este corect. #include<iostream.h> class cls { static int x; public: cls(int i=25) { x=i; } friend int& f(cls); }; int cls::x=-13; int& f(cls c) { return c.x; } int main() { cls d(15); cout<<f(d); return 0; }

25. Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează, în caz negativ spuneţi de ce nu este corect. #include<iostream.h> class cls { int v,nr; public: cls(int i) { nr=i; v=new int[i]; } friend int& operator[](int); friend ostream& operator<<(ostream&,cls); }; int& operator[](cls& x, int i) { return x.v[i]; } ostream& operator<<(ostream& o, cls x) { for(int i=0;i<x.nr;i++) cout<<x.v[i]<<” ”; return o; } int main() { cls x(10);

83

Page 84: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

x[5]=7; cout<<x; return 0; }

26. Descrieţi pe scurt metoda de identificare a tipului în timpul rulării (RTTI). 27. Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează, în caz negativ

spuneţi de ce nu este corect. #include<iostream.h> class cls { static int i; int j; public: cls(int x=7) { j=x; } static int imp(int k){ cls a; return i+k+a.j; } }; int cls::i; int main() { int k=5; cout<<cls::imp(k); return 0; }

28. Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează, în caz negativ spuneţi de ce nu este corect. #include<iostream.h>

class cls { int x; public: cls(int i=32) { x=i; } int f() const; }; int cls::f() const { return x++; } void main() { const cls d(-15); cout<<d.f(); }

29. Spuneţi dacă o variabilă constantă poate fi transmisă ca parametru al unei funcţii şi dacă da, în ce situaţii. Justificaţi. 30. Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează pentru o

valoare întreagă citită egală cu 7, în caz negativ spuneţi de ce nu este corect.

#include <iostream.h> float f(float f) { if (f) throw f; return f/2; } int main() { int x; try { cout<<”Da-mi un numar intreg: ”; cin>>x; if (x) f(x); else throw x;

84

Page 85: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

cout<<”Numarul ”<<x<<” e bun!”<<endl; } catch (int i) { cout<<”Numarul ”<<i<<” nu e bun!”<<endl; } return 0; }

31. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneŃi ce afisează, în caz negativ spuneti de ce nu este corect.

#include<iostream.h> class B { int x; public: B(int i=2):x(i){} int get_x() const { return x; } }; class D: public B { int *y; public: D(int i=2):B(i){ y=new int[i]; for(int j=0; j<i; j++) y[j]=1; } D(D& a){ y=new int[a.get_x()]; for(int i=0;i<a.get_x();i++) y[i]=a[i]; } int& operator[](int i) { return y[i]; } }; ostream& operator<<(ostream& o, const D& a) { for(int i=0;i<a.get_x();i++) o<<a[i]; return o; } int main() { D ob(5); cout<<ob; return 0; }

32. Descrieti trei metode de proiectare diferite prin care elementele unei clase se pot regăsi în dublu exemplar, sub diverse forme, în definitia altei clase. 33. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz negativ spuneŃi de ce nu este corect.

#include<iostream.h> class B { int x; public: B(int i=10) { x=i; } int get_x() { return x; } }; class D: public B { public: D(int i):B(i) {} D operator+(const D& a) {return x+a.x; } }; int main() { D ob1(7), ob2(-12); cout<<(ob1+ob2).get_x(); return 0; }

34. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz negativ spuneti de ce nu este corect.

#include<iostream.h> class B { public: int x; B(int i=16) { x=i; }

85

Page 86: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

B f(B ob) { return x+ob.x; } }; class D: public B { public: D(int i=25) { x=i; } B f(B ob) { return x+ob.x+1; } void afisare(){ cout<<x; } }; int main() { B *p1=new D, *p2=new B, *p3=new B(p1->f(*p2)); cout<<p3->x; return 0; }

35. Spuneti ce este obiectul implicit al unei metode si descrieti pe scurt proprietătile pe care le cunoasteti despre acesta. 36. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz negativ spuneti de ce nu este corect.

#include<iostream.h> class cls { int *v,nr; public: cls(int i) { nr=i; v=new int[i]; for (int j=1; j<nr; j++) v[j]=0; } int size() { return nr; } int& operator[](int i) { return *(v+i); } }; int main() { cls x(10); x[4]=-15; for (int i=0; i<x.size(); i++) cout<<x[i]; return 0; }

37. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz negativ spuneti de ce nu este corect.

#include<iostream.h> class cls { int x; public: cls(int i=-20) { x=i; } const int& f(){ return x; } }; int main() { cls a(14); int b=a.f()++; cout<<b; return 0; }

38. DescrieŃi pe scurt mostenirea virtuală si scopul în care este folosită. 39. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz negativ spuneti de ce nu este corect.

#include<iostream.h> class B { static int x; int i; public: B() { x++; i=1; } ~B() { x--; }

86

Page 87: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

static int get_x() { return x; } int get_i() { return i; } }; int B::x; class D: public B { public: D() { x++; } ~D() { x--; } }; int f(B *q) { return (q->get_i())+1; } int main() { B *p=new B; cout<<f(p); delete p; p=new D; cout<<f(p); delete p; cout<<D::get_x(); return 0; }

40. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz negativ spuneti de ce nu este corect.

#include<iostream.h> class B { int x; public: B(int i=17) { x=i; } int get_x() { return x; } operator int() { return x; } }; class D: public B { public: D(int i=-16):B(i) {} }; int main() { D a; cout<<27+a; return 0; }

41. EnumeraŃi 3 metode de implementare a polimorfismului de compilare. 42. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz negativ spuneti de ce nu este corect.

#include<iostream.h> class cls { static int x; public: cls (int i=1) { x=i; } cls f(cls a) { return x+a.x; } static int g() { return f()/2; } }; int cls::x=7; int main() { cls ob; cout<<cls::g(); return 0; }

87

Page 88: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

43. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz negativ spuneti de ce nu este corect.

#include<iostream.h> class cls { int *v,nr; public: cls(int i=0) { nr=i; v=new int[i]; for (int j=0; j<size(); j++) v[j]=3*j; } ~cls() { delete[] v; } int size() { return nr; } int& operator[](int i) { return v[i]; } cls operator+(cls); }; cls cls::operator+(cls y) { cls x(size()); for (int i=0; i<size(); i++) x[i]=v[i]+y[i]; return x; } int main() { cls x(10), y=x, z; x[3]=y[6]=-15; z=x+y; for (int i=0; i<x.size(); i++) cout<<z[i]; return 0; }

44. Descrieti pe scurt mecanismul de tratare a exceptiilor. 45. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz negativ spuneti de ce nu este corect.

#include<iostream.h> class B { int i; public: B() { i=1; } int get_i() { return i; } }; class D: public B { int j; public: D() { j=2; } int get_j() {return j; } }; int main() { B *p; int x=0; if (x) p=new B; else p=new D; if (typeid(p).name()=="D*") cout<<((D*)p)->get_j(); return 0; }

46. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz negativ spuneti de ce nu este corect.

#include <iostream.h> class cls { int x;

88

Page 89: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

public: cls(int i) { x=i; } int set_x(int i) { int y=x; x=i; return y; } int get_x(){ return x; } }; int main() { cls *p=new cls[10]; int i=0; for(;i<10;i++) p[i].set_x(i); for(i=0;i<10;i++) cout<<p[i].get_x(i); return 0; }

47. Descrieti pe scurt diferenta dintre un pointer si o referintă. 48. Spuneti dacă programul de mai jos este corect. În caz afirmativ, spuneti ce afisează, în caz negativ spuneti de ce nu este corect.

#include <iostream.h> template<class T> int f(T x, T y) { return x+y; } int f(int x, int y) { return x-y; } int main() { int a=5; float b=8.6; cout<<f(a,b); return 0; }

49. Spuneţi pe scurt prin ce se caracterizează un câmp static al unei clase. 50 . Spuneţi dacă programul de mai jos este corect. În caz afirmativ, spuneţi ce afişează, în caz negativ spuneţi de ce nu este corect. #include<iostream.h>

class cls1 { public: int a; cls1() { a=7; } }; class cls2 { public: int b; cls2(int i) { b=i; } cls2(cls1& x) { b=x.a; } }; int main() { cls1 x; cout<<x.a; cls2 y(x); cout<<y.b; return 0; }

89

Page 90: Programare orientata pe obiecte C++ Dascalescu Ana Cristina

90

Bibliografie

1. Oprea M., Programare orientatã pe obiecte - Exemple în limbajul C++, Editura Matrix Rom.

2. Dr. Kris Jamasa, Totul despre C si C++, Editura Teora.

3. Ion Smeureanu , Programare orientatã pe obiecte in Limbajul C++, Editura CISON, Bucuresti 2005.

4.Liviu Negrescu, Limbajul C++, Editura ALBASTRA , Cluj 2000. 5.Luminita Duta, Programarea calculatoarelor in limbajul C++ , Editura Cetatea de Scaun 2006.