Sunteți pe pagina 1din 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
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
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
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
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
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
@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
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
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
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
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”;

11
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
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
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
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
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
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
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.

18
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
(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
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
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 “->”.
9 Operatorul “.” acceseazã o structurã sau un membru al unei clase prin numele variabilei
pentru obiect sau printr-o referintã la obiect.
9 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
{
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
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
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:
9 constructori, responsabili cu crearea obiectelor;
9 destructor, reponsabil cu distrugere obiectelor si eliberarea memoriei ocupate de acesta;
9 functii de acces, care mediaza legatura obiectului cu exteriorul;
9 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
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
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
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
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
#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
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
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
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
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
// 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
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
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
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
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
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
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
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
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
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
¾ 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) {

45
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
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

Student Angajat Elev

Medic Profesor
47
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
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
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
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
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
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
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
};
// 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
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
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
}
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
// 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
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
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
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
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
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
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
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
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
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
{
// 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
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
}

Ş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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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.

90