Sunteți pe pagina 1din 230

Cuprins

CUPRINS

PARTEA I

CAPITOLUL 1 Noţiuni introductive 3.2. Implementarea structurii de decizie . . . 42


1.1. Structura generală a unui sistem de 3.3. Implementarea structurilor repetitive
calcul . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 (ciclice) . . . . . . . . . . . . . . . . . . . . . . . . . 45
1.2. Algoritmi . . . . . . . . . . . . . . . . . . . . . . . . 11 3.3.1. Implementarea structurilor
1.2.1. Noţiuni generale . . . . . . . . . . . . . . 11 ciclice cu test iniţial . . . . . . . . . . 45
1.2.2. Definiţii şi caracteristici . . . . . . . . 12 3.3.2. Implementarea structurilor
1.2.3. Reprezentarea algorimilor . . . . . . . 12 ciclice cu test final . . . . . . . . . . . 46
1.2.3.1. Reprezentarea prin 3.4. Facilităţi de întrerupere a unei secvenţe 50
scheme logice . . . . . . . . . 13 Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . 51
1.2.3.2. Reprezentarea prin
pseudocod . . . . . . . . . . . . 14 CAPITOLUL 4 Tablouri
1.3. Teoria rezolvării problemelor . . . . . . . . 16 4.1. Declararea tablourilor . . . . . . . . . . . . . . . 53
Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . 18 4.2. Tablouri unidimensionale . . . . . . . . . . . . 53
4.3. Tablouri bidimensionale . . . . . . . . . . . . . 55
CAPITOLUL 2 Date, operatori şi expresii 4.4. Şiruri de caractere . . . . . . . . . . . . . . . . . . 57
2.1. Limbajele C şi C++ . . . . . . . . . . . . . . . . 19 Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . 60
2.2. Programe în limbajul C/C++ . . . . . . . . . 19
2.3. Preprocesorul . . . . . . . . . . . . . . . . . . . . . 21 CAPITOLUL 5 Pointeri
2.4. Elemente de bază ale limbajului . . . . . . 22 5.1.Variabile pointer . . . . . . . . . . . . . . . . . . . 61
2.4.1. Vocabularul . . . . . . . . . . . . . . . . . . 22 5.1.1. Declararea variabilelor pointer . . . 61
2.4.2. Unităţile lexicale . . . . . . . . . . . . . . 22 5.1.2. Iniţializarea variabilelor pointer . . 62
2.5. Date în limbajul C/C++ . . . . . . . . . . . . . 23 5.1.3. Pointeri generici . . . . . . . . . . . . . 63
2.5.1. Tipuri de date . . . . . . . . . . . . . . . . 23 5.2. Operaţii cu pointeri . . . . . . . . . . . . . . . . . 63
2.5.2. Constante . . . . . . . . . . . . . . . . . . . 25 5.3. Pointeri şi tablouri . . . . . . . . . . . . . . . . . 64
.
2.5.2.1. Constante întregi . . . . . . . 25 5.3.1. Pointeri şi şiruri de caractere . . . . 64
2.5.2.2. Constante numerice, reale. 25 5.3.2.Pointeri şi tablouri bidimensionale. 66
2.5.2.3. Constante caracter . . . . . . 27 5.4. Tablouri de pointeri . . . . . . . . . . . . . . . . 67
2.5.2.4. Constante şir de caractere 28 5.5. Pointeri la pointeri . . . . . . . . . . . . . . . . . 68
2.5.3. Variabile . . . . . . . . . . . . . . . . . . . . 29 5.6. Modificatorul const în declararea
2.5.3.1. Declararea variabilelor . . 29 pointerilor . . . . . . . . . . . . . . . . . . . . . . . . 69
2.5.3.2. Iniţializarea variabilelor în Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . 69
declaraţii . . . . . . . . . . . . . . 29
2.5.3.3. Operaţii de intrare/ieşire . 30 CAPITOLUL 6 Funcţii
2.6. Operatori şi expresii . . . . . . . . . . . . . . . . 31 6.1. Structura unei funcţii . . . . . . . . . . . . . . . 71
2.6.1. Operatori . . . . . . . . . . . . . . . . . . . . 31 6.2. Apelul şi prototipul unei funcţii . . . . . . . 72
2.6.2. Expresii . . . . . . . . . . . . . . . . . . . . . 39 6.3. Transferul parametrilor unei funcţii . . . . 74
2.6.3. Conversii de tip . . . . . . . . . . . . . . . 39 6.3.1. Transferul parametrilor prin
Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . 39 valoare . . . . . . . . . . . . . . . . . . . . . 75
6.3.2. Transferul prin pointeri . . . . . . . . 75
CAPITOLUL 3 Implementarea 6.3.2.1. Funcţii care returnează
structurilor de control pointeri . . . . . . . . . . . . . . 77
3.1. Implementarea structurii secvenţiale . . . 41 6.3.3. Transferul prin referinţă . . . . . . . 77

5
Cuprins

PARTEA a II a

6.3.4. Transferul parametrilor către CAPITOLUL 9 Concepte de bază ale


funcţia main . . . . . . . . . . . . . . . . 81 programării orientate obiect
6.4. Tablouri ca parametri . . . . . . . . . . . . . . . 81 9.1. Introducere . . . . . . . . . . . . . . . . . . . . . . . 129
6.5. Funcţii cu parametri impliciţi . . . . . . . . . 84 9.2. Abstractizarea datelor . . . . . . . . . . . . . . 129
6.6. Funcţii cu număr variabil de parametri . 84 9.3. Moştenirea . . . . . . . . . . . . . . . . . . . . . . . 130
6.7. Funcţii predefinite . . . . . . . . . . . . . . . . . 85 9.3.1. Moştenirea unică . . . . . . . . . . . . . 130
6.7.1. Funcţii matematice . . . . . . . . . . . . 85 9.3.1. Moştenirea multiplă . . . . . . . . . . . 130
6.7.2. Funcţii de clasificare (testare) a 9.4. Încapsularea informaţiei . . . . . . . . . . . . . 131
caracterelor . . . . . . . . . . . . . . . . . 85 9.5. Legarea dinamică (târzie) . . . . . . . . . . . . 132
6.7.3. Funcţii de conversie a 9.6. Alte aspecte . . . . . . . . . . . . . . . . . . . . . . 132
caracterelor . . . . . . . . . . . . . . . . . 87
6.7.4. Funcţii de conversie din şir în CAPITOLUL 10 Clase şi obiecte
număr . . . . . . . . . . . . . . . . . . . . . . 87 10.1. Definiţia claselor şi accesul la
6.7.5. Funcţii de terminare a unui proces Membri . . . . . . . . . . . . . . . . . . . . . . . . . 133
(program) . . . . . . . . . . . . . . . . . . . 87 10.1.1. Legătura clasă-structură-
6.7.6. Funcţii de intrare/ieşire . . . . . . . . 88 Uniune . . . . . . . . . . . . . . . . . . . 133
6.8. Clase de memorare . . . . . . . . . . . . . . . . . 90 10.1.2. Declararea claselor . . . . . . . . . 133
6.9. Moduri de alocare a memoriei . . . . . . . 92 10.1.3. Obiecte . . . . . . . . . . . . . . . . . . 136
6.10.Funcţii recursive . . . . . . . . . . . . . . . . . . 95 10.1.4. Membrii unei clase . . . . . . . . . 136
6.11.Pointeri către funcţii . . . . . . . . . . . . . . . 100 10.1.5. Pointerul this . . . . . . . . . . . . . . 139
Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . 101 10.1.6. Domeniul unui nume,
vizibilitate şi timp de viaţă . . . 139
CAPITOLUL 7 Tipuri de date definite 10.2. Funcţii inline . . . . . . . . . . . . . . . . . . . . 140
de utilizator 10.3. Constructori şi destructori . . . . . . . . . . 142
7.1. Tipuri definite de utilizator . . . . . . . . . . 103 10.3.1. Iniţializarea datelor . . . . . . . . . 142
7.2. Structuri . . . . . . . . . . . . . . . . . . . . . . . . . 103 10.3.2. Constructori . . . . . . . . . . . . . . 143
7.3. Câmpuri de biţi . . . . . . . . . . . . . . . . . . . . 106 10.3.1.1. Constructori cu
7.4. Declaraţii typedef . . . . . . . . . . . . . . . . . . 107 liste de iniţializare. . 144
7.5. Uniuni . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 10.3.1.2. Constructori de
7.6. Enumerări . . . . . . . . . . . . . . . . . . . . . . . . 108 copiere . . . . . . . . . . 145
Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . 111 10.3.3. Destructori . . . . . . . . . . . . . . . 145
10.3.4. Tablouri de obiecte . . . . . . . . . 150
CAPITOLUL 8 Fişiere 10.4. Funcţii prietene (friend) . . . . . . . . . . . . 151
8.1. Caracteristicile generale ale fişierelor. . . 113 Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . 151
8.2. Deschiderea unui fişier . . . . . . . . . . . . . . 114
8.3. Închiderea unui fişier . . . . . . . . . . . . . . . 116 CAPITOLUL 11 Supraîncărcarea
8.4. Prelucrarea fişierelor text . . . . . . . . . . . . 116 operatorilor
8.4.1. Prelucrarea la nivel de caracter . . 116 11.1. Moduri de supraîncărcare a
8.4.2. Prelucrarea la nivel de cuvânt. . . . 117 operatorilor . . . . . . . . . . . . . . . . . . . . . 153
8.4.3. Prelucrarea la nivel de şir de 11.1.1. Supraîncărcarea prin funcţii
caractere . . . . . . . . . . . . . . . . . . . 118 membre . . . . . . . . . . . . . . . . . . 153
8.4.4. Intrări/ieşiri formatate . . . . . . . . . 119 11.1.2. Supraîncărcarea prin funcţii
8.5. Intrări/ieşiri binare . . . . . . . . . . . . . . . . . 119 prietene . . . . . . . . . . . . . . . . . . 154
8.6. Poziţionarea într-un fişier . . . . . . . . . . . . 120 11.2. Restricţii la supraîncărcarea
8.7. Funcţii utilitare pentru lucrul cu fişiere. . 121 operatorilor . . . . . . . . . . . . . . . . . . . . . 155
8.8. Alte operaţii cu fişiere . . . . . . . . . . . . . . 122 11.3. Supraîncărcarea operatorilor unari . . . 156
Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . 126 11.4. Membrii constanţi ai unei clase . . . . . 156

6
Cuprins

11.5. Supraîncărcarea operatorilor 12.4. Moştenirea simplă . . . . . . . . . . . . . . . 179


insertor şi extractor . . . . . . . . . . . . . . . 156 12.5. Moştenirea multiplă . . . . . . . . . . . . . . 186
11.6. Supraîncărcarea operatorului de 12.6. Redefinirea membrilor unei clase de
atribuire = . . . . . . . . . . . . . . . . . . . . . . . 157 bază în clasa derivată . . . . . . . . . . . . . 187
11.7. Supraîncărcarea operatorului de 12.7. Metode virtuale . . . . . . . . . . . . . . . . . 191
indexare [ ] . . . . . . . . . . . . . . . . . . . . . . 158 Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . 195
11.8. Supraîncărcarea operatorilor new şi
delete. . . . . . . . . . . . . . . . . . . . . . . . . . 164 CAPITOLUL 13 Intrări/ieşiri
11.9. Supraîncărcarea operatorului ( ) . . . . . 166 13.1. Principiile de bază ale sistemului de
11.10. Supraîncărcarea operatorului -> . . . . . 166 I/O din limbajul C++ . . . . . . . . . . . . . 197
11.11. Conversii . . . . . . . . . . . . . . . . . . . . . . 172 13.2. Testarea şi modificarea stării unui
11.11.1. Conversii din tip flux . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
predefinit1 în tip predefinit2. 172 13.3. Formatarea unui flux . . . . . . . . . . . . . 201
11.11.2. Conversii din tip predefinit 13.3.1. Formatarea prin
în clasă . . . . . . . . . . . . . . . . . 172 manipulatori . . . . . . . . . . . . . 202
11.11.3. Conversii din clasă în tip 13.3.1.2. Manipulatori fără
predefinit . . . . . . . . . . . . . . . 173 parametri . . . . . . . . 202
11.11.4. Conversii din clasă1 în 13.3.1.2. Manipulatori cu
clasă2 . . . . . . . . . . . . . . . . . . 173 parametri . . . . . . . . 202
Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . 176 13.3.2. Formatarea prin metode . . . . . 203
13.4. Metodele clasei istream . . . . . . . . . . . 204
CAPITOLUL 12 Crearea ierarhiilor de 13.5. Metodele clasei ostream . . . . . . . . . . 205
clase 13.6. Manipulatori creaţi de utilizator . . . . 206
12.1. Mecanismul moştenirii . . . . . . . . . . . . . 176 13.7. Fluxuri pentru fişiere . . . . . . . . . . . . . 207
12.2. Modul de declarare a claselor derivate. 177 13.8. Fişiere binare . . . . . . . . . . . . . . . . . . . 210
12.3. Constructorii claselor derivate . . . . . . . 178 Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . .

7
INTRODUCERE

Limbajele C şi C++ sunt limbaje de programare de nivel înalt.

Limbajul C a apărut în anii 1970 şi a fost creat de Dennis Ritchie în laboratoarele AT&T Bell. Limbajul C
face parte din familia de limbaje concepute pe principiile programării structurate, la care ideea centrală este
”structurează pentru a stăpâni o aplicaţie”. Popularitatea limbajului a crescut rapid datorită eleganţei şi a
multiplelor posibilităţi oferite programatorului.
Limbajul C++ apare la începutul anilor ’80 şi îl are ca autor pe Bjarne Stroustrup. El este o variantă de
limbaj C îmbunătăţit, mai riguroasă şi mai puternică, completată cu construcţiile necesare aplicării principiilor
programării orientate pe obiecte (POO). Limbajul C++ păstrează toate elementele limbajului C, beneficiind de
eficienţa şi flexibilitatea acestuia. Limbajul C++ este un superset al limbajului C. Incompatibilităţile sunt
minore, de aceea, modulele C pot fi încorporate în proiecte C++ cu un efort minim.

Lucrarea cuprinde două părţi.

Prima parte se adresează programatorilor începători. Ea prezintă elementele de bază şi construcţiile limbajului
C, completate cu extensiile limbajului C++. Acestea permit rezolvarea problemelor prin metoda programării
structurate.

Partea a doua se adresează cunoscătorilor limbajului C dornici să-şi modernizeze stilul şi concepţia abordării
proiectelor informatice. Elementele prezentate sunt specifice limbajului C++ şi permit stiluri de programare
impracticabile în C: programarea prin abstractizarea datelor şi programarea orientată obiect. Citându-l chiar
pe Bjarne Stroustrup, ”C++ este un limbaj de programare general, conceput astfel încât să-i facă pe
programatorii serioşi să programeze într-o manieră cât mai plăcută”.

Şi cum cea mai bună metodă de învăţare este practica, prezentarea aspectelor teoretice este însoţită de multe
exemple şi probleme rezolvate. Deasemenea, întrebările teoretice şi problemele propuse spre rezolvare, de la
sfârşitul fiecărui capitol, permit cititorului să-şi verifice cunoştinţele dobândite. Un aspect foarte important îl
constituie implementarea şi testarea pe calculator a exemplelor şi a problemelor rezolvate sau propuse, găsirea
unor soluţii proprii.

Sperăm ca acest material să constituie un sprijin real pentru cei care doresc să pătrundă în tainele limbajelor
C/C++, cât şi un punct de plecare în activitatea de programare.

Dorim să mulţumim şi pe acestă cale domnului prof. Severin BUMBARU şi colegilor pentru observaţiile şi
sfaturile care au condus la forma actuală a cărţii.

Sugestiile cititorilor sunt aşteptate la adresele: Diana.Stefanescu@ugal.ro, Cristina.Segal@ugal.ro.

AUTORII
CAPITOLUL 1 Noţiuni introductive

NOŢIUNI INTRODUCTIVE 1
1.1. Structura generală a unui sistem de calcul 1.2.2. Definiţii şi caracteristici
1.2. Algoritmi 1.2.3. Reprezentarea algorimilor
1.2.1. Noţiuni generale 1.3. Teoria rezolvării problemelor

1.1. STRUCTURA GENERALĂ A UNUI SISTEM DE CALCUL

Calculatorul reprezintă un sistem electronic (ansamblu de dispozitive şi circuite diverse) complex care
prelucrează datele introduse într-o formă prestabilită, efectuează diverse operaţii asupra acestora şi furnizează
rezultatele obţinute (figura 1.1.).

PROGRAM (şir de acţiuni ,


prelucrări, algoritm)
Date de intrare
Date de ieşire (rezultatele
(datele iniţiale ale
obţinute)
problemei)

Figura 1.1. Calculatorul - sistem automat de prelucrare a datelor


Principalele avantaje ale folosirii calculatorului constau în:
 viteza mare de efectuare a operaţiilor;
 capacitatea extinsă de prelucrare şi memorare a informaţiei.

Deşi construcţia unui calculator - determinată de tehnologia existentă la un moment dat, de domeniul de
aplicaţie, de costul echipamentului şi de performanţele cerute - a evoluat rapid în ultimii ani, sistemele de
calcul, indiferent de model, serie sau generaţie, au o serie de caracteristici comune. Cunoaşterea acestor
caracteristici uşurează procesul de înţelegere şi învăţare a modului de funcţionare şi de utilizare a
calculatorului.

În orice sistem de calcul vom găsi două părţi distincte şi la fel de importante: hardware-ul şi software-ul.
 Hardware-ul este reprezentat de totalitatea echipamentelor şi dispozitivelor fizice;
 Software-ul este reprezentat prin totalitatea programelor care ajută utilizatorul în rezolvarea problemelor
sale (figura 1.2.).

Software-ul are două componente principale:


 Sistemul de operare (de exploatare) care coordonează întreaga activitate a echipamentului de calcul.
Sistemul de operare intră în funcţiune la pornirea calculatorului şi asigură, în principal, trei funcţii:
 Gestiunea echitabilă şi eficientă a resurselor din cadrul sistemului de calcul;
 Realizarea interfeţei cu utilizatorul;
 Furnizarea suportului pentru dezvoltarea şi execuţia aplicaţiilor.
Exemple de sisteme de operare: RSX11, CP/M, MS-DOS, LINUX, WINDOWS NT, UNIX.
 Sistemul de aplicaţii (de programare): medii de programare, editoare de texte, compilatoare, programe
aplicative din diverse domenii (economic, ştiinţific, financiar, divertisment).

Componentele unui sistem de calcul pot fi grupate în unităţi cu funcţii complexe, dar bine precizate, numite
unităţi funcţionale. Modelul din figura SISTEM OPERARE
1.3. face o prezentare simplificată a structurii unui calculator,
facilitând înţelegerea unor noţiuni şi concepte de bază privind funcţionarea şi utilizarea acestuia. Denumirea
fiecărei unităţi indică funcţia ei, iar săgeţile - modul de transfer al informaţiei. UTILIZATOR
HARDWARE

SOFTWARE DE
APLICAŢIE9
SOFTWARE

Figura 1.2. Echipamentul de calcul ca un sistem hardware-software


CAPITOLUL 1 Noţiuni introductive

Vom utiliza în continuare termenii de citire pentru operaţia de introducere (de intrare) de la tastatură a datelor
iniţiale ale unei probleme, şi scriere pentru operaţia de afişare (de ieşire) a rezultatelor obţinute. În cazul în
care utilizatorul doreşte să rezolve o problemă cu ajutorul calculatorului, informaţia de intrare (furnizată
calculatorului de către utilizator) va consta din datele iniţiale ale problemei de rezolvat şi dintr-un program
(numit program sursă). În programul sursă utilizatorul implementează (traduce) într-un limbaj de programare
un algoritm (acţiunile executate asupra datelor de intrare pentru a obţine rezultatele). Această informaţie de
intrare este prezentată într-o forma externă, accesibilă omului (numere, text, grafică) şi va fi transformată de
către calculator într-o forma internă, binară.

Unitatea de intrare (cu funcţia de citire) realizează această conversie a informaţiei din format extern în cel
intern. Din punct de vedere logic, fluxul (informaţia) de intrare este un şir de caractere, din exterior către
memoria calculatorului. Din punct de vedere fizic, unitatea de intrare standard este tastatura calculatorului.
Tot ca unităţi de intrare, pot fi enumerate: mouse-ul, joystick-ul, scanner-ul (pentru introducerea informaţiilor
grafice).

Unitatea de ieşire (cu funcţia de scriere, afişare) realizează conversia inversă, din formatul intern în cel
extern, accesibil omului. Din punct de vedere fizic, unitatea de ieşire standard este monitorul calculatorului.
Ca unităţi de ieşire într-un sistem de calcul, mai putem enumera: imprimanta, plotter-ul, etc.

Informaţia este înregistrată în memorie.


Memoria internă (memoria RAM - Random Acces Memory) se prezintă ca o succesiune de octeţi (octet sau
byte sau locaţie de memorie). Un octet are 8 biţi. Bit-ul reprezintă unitatea elementară de informaţie şi poate
avea una din valorile: 0 sau 1.

Capacitatea unei memorii este dată de numărul de locaţii pe care aceasta le conţine şi se măsoară în
multiplii de 1024 (2 10 ). De exemplu, 1 Mbyte=1024Kbytes; 1Kbyte=1024bytes.
Numărul de ordine al unui octet în memorie se poate specifica printr-un cod, numit adresă. Ordinea în
care sunt adresate locaţiile de memorie nu este impusă, memoria fiind un dispozitiv cu acces aleator la
informaţie.

În memorie se înregistrează două categorii de informaţii:


 Date - informaţii de prelucrat;
 Programe - conţin descrierea (implementarea într-un limbaj de programare) a acţiunilor care vor fi
executate asupra datelor, în vederea prelucrării acestora.

În memoria internă este păstrată doar informaţia prelucrată la un moment dat. Memoria internă are capacitate
redusă; accesul la informaţia pastrată în aceasta este extrem de rapid, iar datele nu sunt păstrate după
terminarea prelucrării (au un caracter temporar).
Unitatea centrală prelucrează datele din memoria internă şi coordonează activitatea tuturor componentelor
fizice ale unui sistem de calcul. Ea înglobează:
 Microprocesorul- circuit integrat complex cu următoarele componente de bază:
 Unitatea de execuţie (realizează operaţii logice şi matematice);
 Unitatea de interfaţă a magistralei (transferă datele la/de la microprocesor).

10
CAPITOLUL 1 Noţiuni introductive
 Coprocesorul matematic – circuit integrat destinat realizării cu viteză sporită a operaţiilor cu
numere reale.

Unitate de intrare (flux de Unitate de ieşire (flux de


Memorie internă
intrare - istream în C++) ieşire - ostream în C++)

Unitate centrală

Memorie externă

Figura 1.3. Unităţile funcţionale ale unui sistem de calcul


În funcţie de numărul de biţi transferaţi simultan pe magistrala de date, microprocesoarele pot fi clasificate
astfel: microprocesoare pe 8 biţi (Z80, 8080); microprocesoare pe 16 biţi (8086, 8088, 80286) cu
coprocesoarele corespunzătoare (8087, 80287); familii de procesoare pe 32 biţi (80386DX, 80486,
PENTIUM) cu coprocesoarele corespunzătoare (începând de la 486, coprocesoare sunt încorporate
microprocesoarelor).

Memoria externă este reprezentată, fizic, prin unităţile de discuri (discuri dure-hard disk, discuri flexibile-
floppy disk, discuri de pe care informaţia poate fi doar citită-CDROM, DVDROM, etc). Spre deosebire de
memoria internă, memoria externă are capacitate mult mai mare, datele înregistrate au caracter permanent, în
dezavantajul timpului de acces la informaţie.

1.2. ALGORITMI

1.2.1. NOŢIUNI GENERALE

Algoritmul este conceptul fundamental al informaticii. Orice echipament de calcul poate fi considerat o
maşină algoritmică. Într-o definiţie aproximativă algoritmul este un set de paşi care defineşte modul în care
poate fi dusă la îndeplinire o anumită sarcină. Exemplu de algoritm: algoritmul de interpretare a unei bucăţi
muzicale (descris în partitură). Pentru ca o maşină de calcul să poată rezolva o anumită problemă,
programatorul trebuie mai întâi să stabilească un algoritm care să conducă la efectuarea la sarcinii respective.

Exemplu:
Algoritmul lui Euclid pentru determinarea celui mai mare divizor comun (cmmdc) a 2 numere întregi
pozitive.
Date de intrare: cele 2 numere întregi
Date de iesire: cmmdc
1. Se notează cu A şi B- cea mai mare, respectiv cea mai mică, dintre datele de intrare
2. Se împarte A la B şi se notează cu R restul împărţirii
3. a. Dacă R diferit de 0, se atribuie lui A valoarea lui B şi lui B valoarea lui R. Se revine la
pasul 2.
b. Dacă R este 0, atunci cmmdc este B.

Probleme legate de algoritmi

Descoperirea unui algoritm care să rezolve o problemă echivalează în esenţă cu descoperirea unei soluţii a
problemei. După descoperirea algoritmului, pasul următor este ca algoritmul respectiv să fie reprezentat într-o
formă în care să poată fi comunicat unei maşini de calcul. Algoritmul trebuie transcris din forma conceptuală
într-un set clar de instrucţiuni. Aceste instrucţiuni trebuie reprezentate într-un mod lipsit de ambiguitate. În
acest domeniu, studiile se bazează pe cunoştinţele privitoare la gramatică şi limbaj şi au dus la o mare
varietate de scheme de reprezentare a algoritmilor (numite limbaje de programare), bazate pe diverse abordări
ale procesului de programare (numite paradigme de programare).

11
CAPITOLUL 1 Noţiuni introductive

Căutarea unor algoritmi pentru rezolvarea unor probleme din ce în ce mai complexe a avut ca urmare apariţia
unor întrebări legate de limitele proceselor algoritmice, cum ar fi:
 Ce probleme pot fi rezolvate prin intermediul proceselor algoritmice?
 Cum trebuie procedat pentru descoperirea algoritmilor?
 Cum pot fi îmbunătăţite tehnicile de reprezentare şi comunicare a algoritmilor?
 Cum pot fi aplicate cunoştinţele dobândite în vederea obţinerii unor maşini algoritmice mai performante?
 Cum pot fi analizate şi comparate caracteristicile diverşilor algoritmi?

1.2.2. DEFINIŢII ŞI CARACTERISTICI

Definiţii:
Algoritmul unei prelucrări constă într-o secvenţă de primitive care descrie prelucrarea.
Algoritmul este un set ordonat de paşi executabili, descrişi fără echivoc, care definesc un proces finit.

Proprietăţile fundamentale ale algoritmilor:


 Caracterul finit: orice algoritm bine proiectat se termină într-un număr finit de paşi;
 Caracterul unic şi universal: orice algoritm trebuie să rezolve toate problemele dintr-o clasă de probleme;
 Realizabilitatea: orice algoritm trebuie să poată fi codificat într-un limbaj de programare;
 Caracterul discret: fiecare acţiune se execută la un moment dat de timp;
 Caracterul determinist: ordinea acţiunilor în execuţie este determinată în mod unic de rezultatele obţinute
la fiecare moment de timp.
Nerespectarea acestor caracteristici generale conduce la obţinerea de algoritmi neperformanţi, posibil infiniţi
sau nerealizabili.

1.2.3. REPREZENTAREA ALGORITMILOR

Reprezentarea (descrierea) unui algoritm nu se poate face în absenţa unui limbaj comun celor care vor să îl
înţeleagă. De aceea s-a stabilit o mulţime bine definită de primitive (blocuri elementare care stau la baza
reprezentării algoritmilor). Fiecare primitivă se caracterizează prin sintaxă şi semantică. Sintaxa se referă la
reprezentarea simbolică a primitivei; semantica se referă la semnificaţia primitivei. Exemplu de primitivă: aer-
din punct de vedere sintactic este un cuvânt format din trei simboluri (litere); din punct de vedere semantic este
o substanţă gazoasă care înconjoară globul pământesc.

Algoritmii se reprezintă prin:


 scheme logice;
 pseudocod.

1.2.3.1. Reprezentarea algoritmilor prin scheme logice

Primitivele utilizate în schemele logice sunt simboluri grafice, cu funcţiuni (reprezentând procese de calcul)
bine precizate. Aceste simboluri sunt unite prin arce orientate care indică ordinea de execuţie a proceselor de
calcul.
Categorii de simboluri:

 Simboluri de început şi sfârşit


Simbolul START desemnează începutul unui
program sau al unui subprogram.
START STOP Simbolul STOP desemnează sfârşitul unui
program sau al unui subprogram. Prezenţa lor
este obligatorie.
 Simbolul paralelogram

Semnifică procese (operaţii) de


CITEŞTE a, b AFIŞEAZĂ a, b intrare/ieşire (citirea sau scrierea)
12
CAPITOLUL 1 Noţiuni introductive

 Simbolul dreptunghi
Semnifică o atribuire (modificarea
a ←34
valorii unei date).

 Simbolul romb

Simbolul romb este utilizat pentru decizii


(figura 1.4.). Se testează îndeplinirea
NU Condiţie DA condiţiei din blocul de decizie. Dacă
îndeplinită această condiţie este îndeplinită, se
?
execută ACŢIUNE1. Dacă nu, se execută
ACŢIUNE2. La un moment dat, se execută
ACŢIUNE2 ACŢIUNE1
sau ACŢIUNE1, sau ACŢIUNE2.

Figura 1.4. Structura de decizie


Cu ajutorul acestor simboluri grafice se poate reprezenta orice algoritm.
Repetarea unei secvenţe se realizează prin combinarea simbolurilor de decizie şi de atribuire.
Structurile repetitive obţinute pot fi: cu test iniţial sau cu test final.

Structuri repetitive cu test initial

Se evaluează condiţia de test (figura 1.5.).


Dacă aceasta este îndeplinită, se execută
ACŢIUNE1. Se revine apoi şi se testează iar
condiţia. Dacă este îndeplinită, se execută (se
NU Condiţie DA repetă) ACŢIUNE1, ş.a.m.d. Abia în momentul
îndeplinită în care condiţia nu mai este îndeplinită, se trece
? la execuţia ACŢIUNE2.
Astfel, cât timp condiţia este îndeplinită, se
ACŢIUNE2 ACŢIUNE1 repetă ACŢIUNE1. În cazul în care, la prima
testare a condiţiei, aceasta nu este îndeplinită,
se execută ACŢIUNE2. Astfel, este posibil ca
ExisăFigura 1.5. în
şi situaţii Structură
care se repetitivă
ştie de la cu de câte oriACŢIUNE1
test iniţial
început se va repetasă onuanumită
fie executată niciodată.
acţiune. În aceste cazuri se
foloseşte tot o structură de control repetitivă cu test iniţial. Se utilizează un contor (numeric) pentru a ţine o
evidenţă a numărului de execuţii ale acţiunii. De câte ori se execută acţiunea, contorul este incrementat.

contorvaloare_iniţială

NU DA Se atribuie contorului valoarea iniţială


valoare_contor<= (figura 1.6.). Cât timp condiţia (valoarea
valoare_finală contorului este mai mică sau egală cu
valoarea finală) este îndeplinită, se repetă:
ACŢIUNE
ACŢIUNE incrementare contor (se adună 1 la valoarea
anterioară a contorului).

valoare_contor valoare_contor + 1

Figura 1.6. Structură repetitivă cu test iniţial, cu


număr cunoscut de paşi
13
CAPITOLUL 1 Noţiuni introductive

Structură repetitivă cu test final:

ACŢIUNE 1 Se execută mai întâi ACŢIUNE1. Se


testează apoi condiţia (figura 1.7.). Se
repetă ACŢIUNE1 cât timp condiţia este
îndeplinită.
Condiţie DA În acest caz, corpul ciclului (ACŢIUNE1)
îndeplinită
? este executat cel puţin o dată.

NU
ACŢIUNE 2

Figura 1.7. Structură repetitivă cu test final

1.2.3.2. Reprezentarea algoritmilor prin pseudocod

Pseudocodul este inspirat din limbajele de programare, nefiind însă atât de formalizat ca acestea. Pseudocodul
reprezintă o punte de legătură între limbajul natural şi limbajele de programare. Nu există un standard pentru
regulile lexicale. Limbajul pseudocod permite comunicarea între oameni, şi nu comunicarea om-maşina
(precum limbajele de programare). Pseudocodul utilizează cuvinte cheie (scrise cu majuscule subliniate) cu
următoarele semnificaţii:

Sfârşit algoritm: SFÂRŞIT


Început algoritm: ÎNCEPUT
Citire (introducere) date: CITEŞTE lista
Scriere (afişare) date: SCRIE lista
Atribuire: <-
Structura de decizie (alternativă): DACĂ condiţie
ATUNCI acţiune1
ALTFEL acţiune2
Structuri repetitive cu test iniţial: CÂT TIMP condiţie
REPETĂ acţiune
PENTRU contor=val_iniţ LA val_fin [PAS]
REPETĂ acţiune;
Structuri repetitive cu test final:
REPETĂ acţiune CÂT TIMP condiţie
sau:
REPETĂ acţiune PÂNĂ CÂND condiţie

ALGORITM
Pe lângă cuvintele cheie, în reprezentarea algoritmilor în pseudocod aflare_arie_drept
pot apare şi propoziţii nestandard a caror
detaliere va fi realizată ulterior. INCEPUT
START CITEŞTE
În cazul în care se realizează un algoritm modularizat, pot apare cuvintele cheie: L,l
SUBALGORITM nume (lista_intrări) aria <- L*l
AFIŞEAZA aria
CHEAMĂ nume (lista_valori_efective_de_intrare)
SFARŞIT
CITEŞTE L, l
Exemple:
Se vor reprezinta în continuare algoritmii de rezolvare pentru câteva probleme simple (pentru primele 2
probleme se va exemplifica şi modul de implementare a acestor algoritmi în limbajul C++).
#include <iostream.h>
1. Se citesc 2aria
valori numerice
<- L *l Implementare:
reale, care reprezintă dimensiunile (lungimea şi lăţimea unui dreptunghi). Să se
void main( )
calculeze şi să se afişeze aria dreptunghiului. { double L, l;
cout<<"Lungime="; cin>>L;
AFIŞEAZĂ aria cout<<"Laţime="; cin>>l;
double aria = L * l;
14 cout << "Aria="<< aria;
}
STOP
CAPITOLUL 1 Noţiuni introductive

2. Se citesc 2 valori reale. Să se afiseze valoarea maximului dintre cele 2 numere.

ALGORITM max_2_nr ALGORITM max_2_nr


Sau:
INCEPUT ÎNCEPUT
CITESTE a, b CITEŞTE a, b
DACA a >= b DACA a >= b
ATUNCI max<-a ATUNCI AFISEAZA a
ALTFEL max<-b ALTFEL AFISEAZA b
AFISEAZA max SFARŞIT
SFARŞIT
Implementare în limbajul C++:
#include <iostream.h> #include <iostream.h>
void main( ) void main( )
{ float a, b, max; { float a, b;
cout<<"a="; cin>>a; cout<<"a=";cin>>a;
cout<<"b="; cin>>b; cout<<"b="; cin>>b;
if (a >= b) if (a >= b)
max = a; cout<<"Maximul este:"<<a;
else max = b; else
cout<<"Maximul este:"<<max;} cout<<"Maximul
3. Să se citească câte 2 numere întregi, până la întâlnirea perechii de numere 0, este:"<<b; } pereche de
0. Pentru fiecare
numere citite, să se afişeze maximul.
Algoritm care utilizează structură repetitivă cu test iniţial:
ALGORITM max_perechi1 ALGORITM max_perechi2
INCEPUT INCEPUT
CITESTE a,b a ← 3
CAT TIMP(a#0sau b#0)REPETA CAT TIMP (a#0 sau b#0) REPETA
INCEPUT INCEPUT
DACA (a>=b) CITESTE a, b
ATUNCI AFISEAZA a DACA (a>=b)
ALTFEL AFISEAZA b ATUNCI AFISEAZA a
CITESTE a,b ALTFEL AFISEAZA b
SFARSIT SFARSIT
SFARSIT SFARSIT

Algoritm care utilizează structură repetitivă cu test final:


ALGORITM max_perechi3
INCEPUT
REPETA
INCEPUT
CITEŞTE a,b
DACA (a>=b)
ATUNCI AFIŞEAZA a
ALTFEL AFIŞEAZA
15 b
SFARŞIT
CAT TIMP (a#0 sau b#0)
SFARŞIT
CAPITOLUL 1 Noţiuni introductive

1.3. TEORIA REZOLVĂRII PROBLEMELOR

Creşterea complexităţii problemelor supuse rezolvării automate (cu ajutorul calculatorului) a determinat ca
activitatea de programare să devină, de fapt, un complex de activităţi.

Pentru rezolvarea unei probleme trebuie parcurse următoarele etape:


 Analiza problemei (înţelegerea problemei şi specificarea cerinţelor acesteia). Se stabileste ce trebuie să
facă aplicaţia, şi nu cum. Se stabilesc datele de intrare (identificarea mediului iniţial) şi se stabilesc
obiectivele (identificarea mediului final, a rezultatelor);
 Proiectarea (conceperea unei metode de rezolvare a problemei printr-o metodă algoritmică);
 Implementarea (codificarea algoritmului ales într-un limbaj de programare);
 Testarea aplicaţiei obţinute (verificarea corectitudinii programului);
 Exploatarea şi întreţinerea (mentenanţa, activitatea de modificare a aplicaţiei la cererea beneficiarului sau
în urma unor deficienţe constatate pe parcursul utilizării aplicaţiei).

În acest context, activitatea de programare a devenit o activitate organizată, definindu-se metode formale de
dezvoltare a fiecărei etape. Etapele descrise anterior alcătuiesc ciclul de viaţă al unui produs software şi
constituie obiectul de studiu al disciplinei numite ingineria sistemulor de programe (software engineering).
Teoreticienii ingineriei programării consideră că rezolvarea unei probleme se poate face pe 3 direcţii:
 Rezolvarea orientată pe algoritm (pe acţiune), în care organizarea datelor este neesenţială;
 Rezolvarea orientată pe date, acţiunile fiind determinate doar de organizarea datelor;
 Rezolvarea orientată obiect, care combină tendinţele primelor două abordări.

Abordarea aleasă determină modelarea problemei de rezolvat.


Dintre metodele de proiectare orientate pe algoritm amintim: metoda programării structurate şi metoda
rafinării succesive. Ambele au ca punct de plecare metoda de proiectare top-down, considerată ca fiind o
metodă clasică de formalizare a procesului de dezvoltare a unui produs software.
La baza metodei top-down stă descompunerea funcţională a problemei P, adica găsirea unui număr de
subprobleme P 1 , P 2 , ... P n , cu următoarele proprietăţi:
 Fiecare subproblemă P i (1<=i<=n) poate fi rezolvată independent. Dacă nu constituie o problemă
elementară, poate fi, la randul ei, descompusă;
 Fiecare subproblemă P i este mai simplă decât problema P;
 Soluţia problemei P se obţine prin reuniunea soluţiilor subproblemelor P i ;
 Procesul de descompunere se opreşte în momentul în care toate subproblemele P i obţinute sunt
elementare, deci pot fi implementate;

Comunicarea între aceste subprobleme se realizează prin intermediul parametrilor. Implementarea metodei
top-down într-un limbaj de programare se face cu ajutorul modulelor de program (funcţii sau proceduri în
limbajul Pascal, funcţii în limbajul C).

P
P Descompunerea funcţională a unui
program P constă în identificarea
funcţiilor (task-urilor, sarcinilor)
P1 P principale ale programului (P, P, P),
P2
fiecare dintre aceste funcţii
reprezentând un subprogram (figura
1.8.). Problemele de pe acelaşi nivel i
P P sunt independente unele faţă de altele.

16
Figura 1.8. Descompunerea funcţională
CAPITOLUL 1 Noţiuni introductive

1.3.1. Etapele rezolvării unei probleme cu ajutorul calculatorului

Să detaliem în continuare etapa de implementare. După analiza problemei şi stabilirea algoritmului, acesta
trebuie tradus (implementat) într-un limbaj de programare.

 Srierea (editarea) programului sursă.


Programele sursă sunt fişiere text care conţin instrucţiuni (cu sintactica şi semantica proprii limbajului
utilizat). Programul (fişierul) sursă este creat cu ajutorul unui editor de texte şi va fi salvat pe disc
(programele sursă C primesc, de obicei, extensia .c, iar cele C++, extensia .cpp).
Pentru a putea fi executat, programul sursă trebuie compilat şi linkeditat.

 Compilarea
Procesul de compilare este realizat cu ajutorul compilatorului, care translatează codul sursă în cod obiect
(cod maşină), pentru ca programul să poată fi înţeles de calculator. În cazul limbajului C, în prima fază a
compilării este invocat preprocesorul. Acesta recunoaşte şi analizează mai întâi o serie de instrucţiuni
speciale, numite directive procesor. Verifică apoi codul sursă pentru a constata dacă acesta respectă
sintaxa şi semantica limbajului. Dacă există erori, acestea sunt semnalate utilizatorului. Utilizatorul
trebuie să corecteze erorile (modificând programul sursă). Abia apoi codul sursă este translatat în cod de
asamblare, iar în final, în cod maşină, binar, propriu calculatorului. Acest cod binar este numit cod obiect
şi de obicei este memorat într-un alt fişier, numit fişier obiect. Fişierul obiect va avea, de obicei, acelaşi
nume cu fişierul sursă şi extensia .obj.

 Linkeditarea
Dupa ce programul sursă a fost translatat în program obiect, el este va fi supus operaţiei de linkeditare.
Scopul fazei de linkeditare este acela de a obţine o formă finală a programului, în vederea execuţiei
acestuia. Linkeditorul “leagă” modulele obiect, rezolvă referinţele către funcţiile externe şi rutinele din
biblioteci şi produce cod executabil, memorat într-un alt fisier, numit fişier executabil (acelaşi nume,
extensia .exe)

 Execuţia
Lansarea în execuţie constă în încărcarea programului executabil în memorie şi startarea execuţiei sale.

(Preprocesor) Cod
Cod sursă Cod obiect Linkeditor
Compilator executabil

Figura 1.9. Etapele necesare obţinerii fişierului executabil

Observaţii:
1. Mediile de programare integrate (BORLANDC, TURBOC) înglobează editorul, compilatorul, linkeditorul
şi depanatorul (utilizat în situaţiile în care apar erori la execuţie);
2. Dacă nu se utilizează un mediu integrat, programatorul va apela în mod explicit (în linie de comandă) un
editor de texte, compilatorul, linkeditorul. Lansarea în execuţie se va face tot din linie de comandă.
3. Extensiile specificate pentru fişierele sursă, obiect şi executabile sunt

ÎNTREBĂRI ŞI EXERCIŢII

Chestiuni teoretice

17
CAPITOLUL 1 Noţiuni introductive
1. Enumeraţi unităţile funcţionale componente ale 4. Care sunt proprietăţile fundamentale ale
unui sistem de calcul. algoritmilor?
2. Care sunt diferenţele între soft-ul de aplicaţie 5. Care sunt modalităţile de reprezentare a
şi sistemul de operare? algoritmilor?
3. Care este deosebirea între algoritm şi
program?

Chestiuni practice

1. Reprezentaţi algoritmul lui Euclid (pentru calculul celui mai mare divizor comun a 2 numere întregi) prin
schema logică.
2. Proiectaţi un algoritm care să rezolve o ecuaţie de gradul I (de forma ax + b = 0), unde a,b sunt numere
reale. Discuţie după coeficienţi.
3. Proiectaţi un algoritm care să rezolve o ecuaţie de gradul II (de forma ax 2 + bx + c = 0), unde a,b,c sunt
numere reale. Discuţie după coeficienţi.
4. Proiectaţi un algoritm care să testeze dacă un număr întreg dat este număr prim.
5. Proiectaţi un algoritm care să afişeze toţi divizorii unui număr întreg introdus de la tastatură.
6. Proiectaţi un algoritm care să afişeze toţi divizorii primi ai unui număr întreg introdus de la tastatură.
7. Proiectaţi un algoritm care calculează factorialul unui număr natural dat. (Prin definiţie 0!=1)

18
CAPITOLUL 2 Date, operatori şi expresii

DATE, OPERATORI ŞI EXPRESII 2


2.1. Limbajele C şi C++ 2.5.1. Tipuri de date
2.2. Programe în limbajul C/C++ 2.5.2. Constante
2.3. Preprocesorul 2.5.3. Variabile
2.4. Elemente de bază ale limbajului 2.6. Operatori şi expresii
2.4.1. Vocabularul 2.6.1. Operatori
2.4.2. Unităţile lexicale 2.6.2. Expresii
2.5. Date în limbajul C/C++ 2.7. Conversii de tip

2.1. LIMBAJELE C ŞI C++

Aşa cum comunicarea dintre două persoane se realizează prin intermediul limbajului natural, comunicarea
dintre om şi calculator este mijlocită de un limbaj de programare. Limbajele C şi C++ sunt limbaje de
programare de nivel înalt.

Limbajul C a apărut în anii 1970 şi a fost creat de Dennis Ritchie în laboratoarele AT&T Bell. Limbajul C
face parte din familia de limbaje concepute pe principiile programării structurate, la care ideea centrală este
”structurează pentru a stăpâni o aplicaţie”. Popularitatea limbajului a crescut rapid datorită eleganţei şi a
multiplelor posibilităţi oferite programatorului (puterea şi flexibilitatea unui limbaj de asamblare); ca urmare,
au apărut numeroase alte implementări. De aceea, în anii ’80 se impune necesitatea standardizării acestui
limbaj. În perioada 1983-1990, un comitet desemnat de ANSI (American National Standards Institute) a
elaborat un compilator ANSI C, care permite scrierea unor programe care pot fi portate fără modificări, pe
orice sistem.

Limbajul C++ apare la începutul anilor ’80 şi îl are ca autor pe Bjarne Stroustrup. El este o variantă de
limbaj C îmbunătăţit, mai riguroasă şi mai puternică, completată cu construcţiile necesare aplicării
principiilor programării orientate pe obiecte (POO). Limbajul C++ păstrează toate elementele limbajului C,
beneficiind de eficienţa şi flexibilitatea acestuia. Limbajul C++ este un superset al limbajului C.
Incompatibilităţile sunt minore, de aceea, modulele C pot fi încorporate în proiecte C++ cu un efort minim.

2.2. PROGRAME ÎN LIMBAJUL C/C++

Un program scris în limbajul C (sau C++) este compus din unul sau mai multe fişiere sursă. Un fişier sursă
este un fişier text care conţine codul sursă (în limbajul C) al unui program. Fiecare fişier sursă conţine una
sau mai multe funcţii şi eventual, referinţe către unul sau mai multe fişiere header (figura 2.1.).

Funcţia principală a unui program este numită main. Execuţia programului începe cu execuţia acestei funcţii,
care poate apela, la rândul ei, alte funcţii. Toate funcţiile folosite în program trebuie descrise în fişierele sursă
(cele scrise de către programator), în fişiere header (funcţiile predefinite, existente în limbaj), sau în
biblioteci de funcţii.

Un fişier header este un fişier aflat în sistem sau creat de către programator, care conţine declaraţii şi definiţii
de funcţii şi variabile.

Acţiunile din fiecare funcţie sunt codificate prin instrucţiuni (figura 2.2.a.). Există mai multe tipuri de
instrucţiuni, care vor fi discutate în capitolul următor. O instrucţiune este orice expresie validă (de obicei, o
asignare sau un apel de funcţie), urmată de simbolul ;. În figura 2.2.b. este dat un exemplu de instrucţiune

19
CAPITOLUL 2 Date, operatori şi expresii
simplă. Uneori, ca instrucţiune poate apare instrucţiunea nulă (doar ;), sau instrucţiunea compusă (privită ca
o succesiune de instrucţiuni simple, încadrate între acoladele delimitatoare {}.
Program Fişiere header

Fişier sursă

Biblioteci C++
Funcţii main

Funcţii din bibliotecă

Figura 2.1. Structura unui program în limbajul C

O expresie este o structură corectă sintactic, formată din operanzi şi operatori (figura 2.2.c.).

FUNCŢII INSTRUCŢIUNI EXPRESII


Operanzi
Instrucţiunea1
Instrucţiunea2 Expresie;
Instrucţiunea3
.
.
.
.
Operatori
2.2.a. 2.2.b. 2.2.c.
Figura 2.2. Funcţie, instrucţiune, expresie

Pentru a înţelege mai bine noţiunile prezentate, să considerăm un exemplu foarte simplu. Programul următor
afişează pe ecran un mesaj (mesajul Primul meu program). Informaţia de prelucrat (de intrare) este însuşi
mesajul (o constantă şir), iar prelucrarea ei constă în afişarea pe ecran.
Exemplu:
#include <iostream.h> // linia 1
void main() // linia 2 - antetul funcţiei main
{ /* linia 3 - începutul corpului funcţiei, a unei intrucţiuni
compuse */
cout<<”Primul meu program in limbajul C++\n”; // linia 5
} // linia6-sfârşitul corpului funcţiei

Prima linie este o directivă preprocesor (indicată de simbolul #) care determină includerea în fişierul sursă a
fişierului header cu numele iostream.h. Acest header permite realizarea afişării pe monitor.
Programul conţine o singură funcţie, funcţia principală, numită main, al cărui antet (linia 2) indică:
- tipul valorii returnate de funcţie (void, ceea ce înseamnă că funcţia nu returnează nici o valoare)
- numele funcţiei (main)

20
CAPITOLUL 2 Date, operatori şi expresii
- lista argumentelor primite de funcţie, încadrată de cele 2 paranteze rotunde.
Funcţiile comunică între ele prin argumente. Aceste argumente reprezintă datele de intrare ale funcţiei. În
cazul nostru, nu avem nici un argument în acea listă, deci puteam să scriem antetul funcţiei şi astfel:
void main(void)
Ceea ce urmează după simbolul //, până la sfărşitul liniei, este un comentariu, care va fi ignorat de către
compilator. Comentariul poate conţine un text explicativ; informaţii lămuritoare la anumite aspecte ale
problemei sau observaţii. Dacă vrem să folosim un comentariu care cuprinde mai multe linii, vom delimita
începutul acestuia indicat prin simbolulurile /*, iar sfârşitul - prin */ (vezi liniile 3, 4). Introducerea
comentariilor în programele sursă uşurează înţelegerea acestora. În general, se recomandă introducerea unor
comentarii după antetul unei funcţiei, pentru a preciza prelucrările efectuate în funcţie, anumite limite
impuse datelor de intrare, etc.
Începutul şi sfârşitul corpului funcţiei main sunt indicate de cele două acoalade { (linia3) şi }(linia 6).
Corpul funcţiei (linia 5) este format dintr-o singură instrucţiune, care implementează o operaţie de scriere.
Cuvantul cout este un cuvânt predefinit al limbajului C++ - console output - care desemnează dispozitivul
logic de iesire; simbolul << este operatorul de transfer a informaţiei. Folosite astfel, se deschide un canal de
comunicaţie a datelor către dispozitivul de ieşire, în cazul acesta, monitorul. După operator se specifică
informaţiile care vor fi afişate (în acest exemplu, un şir de caractere constant). Faptul că este un şir constant
de caractere este indicat de ghilimelele care îl încadrează. Pe ecran va fi afişat fiecare caracter din acest şir,
cu excepţia grupului \n. Deşi grupul este format din două caractere, acesta va fi interpretat ca un singur
caracter - numit caracter escape - care determină poziţionarea cursorului la începutul următoarei linii. O
secvenţă escape (cum este \n) furnizează un mecanism general şi extensibil pentru reprezentarea
caracterelor invizibile sau greu de obţinut. La sfârşitul instrucţiunii care implementează operaţia de scriere,
apare ; .

2.3. PREPROCESORUL

Aşa cum am menţionat în capitolul 1.3., în faza de compilare a fişierului sursă este invocat întâi
preprocesorul. Acesta tratează directivele speciale - numite directive preprocesor - pe care le găseşte în
fişierul sursă. Directivele preprocesor sunt identificate prin simbolul #, care trebuie să fie primul caracter,
diferit de spaţiu, dintr-o linie. Directivele preprocesor sunt utilizate la includerea fişierelor header, la
definirea numelor constantelor simbolice, la definirea macro-urilor, sau la realizarea altor funcţii (de
exemplu, compilarea condiţionată), aşa cum ilustrează exemplele următoare:

 Includerea fişierelor header în codul sursă:


Exemplul1:
#include <stdio.h>
Când procesorul întâlneşte această linie, datorită simbolului #, o recunoaşte ca fiind o directivă
preprocesor, localizează fişierul header indicat (parantezele unghiulare < > indică faptul că este
vorba de un fişier header sistem).
Exemplul 2:
#include "headerul_meu.h"
Numele fişierului header inclus între ghilimele, indică faptul că headerul_meu.h este un fişier
header creat de utilizator. Preprocesorul va căuta să localizeze acest fişier în directorul curent de
lucru al utilizatorului. În cazul în care fişierul header nu se află în directorul curent, se va indica şi
calea către acesta.
Exemplul 3:
#include "c:\\bc\\head\\headerul_meu.h"
În acest exemplu, pentru interpretarea corectă a caracterului backslash \, a fost necesară "dublarea"
acestuia, din motive pe care le vom prezenta în paragraful 2.5.2.4.

 Asignarea de nume simbolice constantelor:


Exemplu:
#define TRUE 1
#define FALSE 0

21
CAPITOLUL 2 Date, operatori şi expresii
Tratarea acestor directive preprocesor are ca efect asignarea (atribuirea) valorii întregi 1 numelui
(constantei simbolice) TRUE, şi a valorii 0 numelui simbolic FALSE. Ca urmare, înaintea compilării
propriu-zise, în programul sursă, apariţiile numelor TRUE şi FALSE vor fi înlocuite cu valorile 1,
respectiv 0.

 Macrodefiniţii:
Directiva #define este folosită şi în macrodefiniţii. Macrodefiniţiile permit folosirea unor nume
simbolice pentru expresiile indicate în directivă.
Exemplu:
#define NEGATIV(x) -(x)
Între numele macrodefiniţiei şi paranteza stângă ( NEGATIV(…) ) nu sunt permise spaţii albe. La
întalnirea în programul sursă a macrodefiniţiei NEGATIV, preprocesorul subtituie argumentul
acesteia cu expresia (negativarea argumentului). Macrodefiniţia din exemplu poate fi folosită în
programul sursă astfel: NEGATIV(a+b). Când preprocesorul întâlneşte numele expresiei, subtituie
literalii din paranteză, a+b, cu argumentul din macrodefiniţie, x, obţinându-se -(a+b).
Dacă macrodefiniţia ar fi fost de forma:
#define NEGATIV(x) -x
NEGATIV(a+b) ar fi fost tratată ca -a+b.

2.4. ELEMENTE DE BAZĂ ALE LIMBAJULUI

2.4.1. VOCABULARUL

În scrierea programelor în limbajul C/C++ pot fi folosite doar anumite simboluri care alcătuiesc alfabetul
limbajului. Acesta cuprinde:
 Literele mari sau mici de la A la Z (a-z);
 Caracterul subliniere ( _ underscore), folosit, de obicei, ca element de legătura între cuvintele compuse;
 Cifrele zecimale (0-9);
 Simboluri speciale:
 Caractere:
 operatori (Exemple: +, *, !=);
 delimitatori (Exemple: blank (spaţiu), tab \t, newline \n, cu rolul de a separa cuvintele);
 Grupuri (perechi de caractere).

Grupurile de caractere, numire adesea separatori, pot fi:


 ( ) - Încadrează lista de argumente ale unei funcţii sau sunt folosite în expresii pentru
schimbarea ordinii de efectuare a operaţiilor (în ultimul caz, fiind operator);
 { } - Încadrează instrucţiunile compuse;
 // - Indică începutul unui comentariu care se poate întinde până la sfârşitul liniei;
 /* */ - Indică începutul şi sfârşitul unui comentariu care poate cuprinde mai multe linii;
 " " - Încadrează o constantă şir (un şir de caractere);
 ' ' - Încadrează o constantă caracter (un caracter imprimabil sau o secvenţă escape).

2.4.2. UNITĂŢILE LEXICALE

Unităţile lexicale (cuvintele) limbajului C/C++ reprezintă grupuri de caractere cu o semnificaţie de sine
stătătoare. Acestea sunt:
 Identificatori;
 Cuvinte cheie ale limbajului;

Identificatorii reprezintă numele unor date (constante sau variabile), sau ale unor funcţii. Identificatorul este
format dintr-un şir de litere, cifre sau caracterul de subliniere (underscore), trebuie să înceapă cu o literă sau
cu caracterul de subliniere şi să fie sugestivi.

22
CAPITOLUL 2 Date, operatori şi expresii

Exemple: viteză, greutate_netă, Viteza, Viteza1, GreutateNetă


Identificatorii pot conţine litere mici sau mari, dar limbajul C++ este senzitiv la majuscule şi minuscule
(case-sensitive). Astfel, identificatorii viteza şi Viteza sunt diferiţi.
Nu pot fi folosiţi ca identificatori cuvintele cheie. Identificatorii pot fi standard (ca de exemplu numele unor
funcţii predefinite: scanf, clear, etc.) sau aleşi de utilizator.

Cuvintele cheie sunt cuvinte ale limbajului, împrumutate din limba engleză, cărora programatorul nu le
poate da o altă utilizare. Cuvintele cheie se scriu cu litere mici şi pot reprezenta:
 Tipuri de date (Exemple: int, char, double);
 Clase de memorare (Exemple: extern, static, register);
 Instrucţiuni (Exemple: if, for, while);
 Operatori (Exemplu: sizeof).
Sensul cuvintelor cheie va fi explicat pe masură ce vor fi prezentate construcţiile în care acestea apar.

2.5. DATE ÎN LIMBAJUL C/C++

Aşa cum s-a văzut în capitolul 1, un program realizează o prelucrare de informaţie. Termenul de prelucrare
trebuie să fie considerat într-un sens foarte general (de exemplu, în programul prezentat în paragraful 2.2.,
prelucrarea se referea la un text şi consta în afişarea lui). În program datele apar fie sub forma unor
constante (valori cunoscute anticipat, care nu se modifică), fie sub forma de variabile. Constantele şi
variabilele sunt obiectele informaţionale de bază manipulate într-un program.

Fiecare categorie de date este caracterizată de atributele:


 Nume;
 Valoare;
 Tip;
 Clasa de memorare.
De primele trei tipuri de atribute ne vom ocupa în continuare, urmând ca de atributul clasă de memorare să
ne ocupăm în paragraful 6.8.

Numele unei date


Numele unei date este un identificator şi, ca urmare, trebuie să respecte regulile specifice identificatorilor.
Deasemenea, numărul de caractere care intră în compunerea unui identificator este nelimitat, însă, implicit,
numai primele 32 de caractere sunt luate în considerare. Aceasta înseamnă că doi identificatori care au
primele 32 de caractere identice, diferenţiindu-se prin caracterul 33, vor fi consideraţi identici.

2.5.1. TIPURI DE DATE

Tipul unei date constă într-o mulţime de valori pentru care s-a adoptat un anumit mod de reprezentare în
memoria calculatorului şi o mulţime de operatori care pot fi aplicaţi acestor valori. Tipul unei date determină
lungimea zonei de memorie ocupată de acea dată. În general, lungimea zonei de memorare este dependentă
de calculatorul pe care s-a implementat compilatorul. Tabelul 2.1. prezintă lungimea zonei de memorie
ocupată de fiecare tip de dată pentru compilatoarele sub MS-DOS şi UNIX/LINUX.
Tipurile de bază sunt:
 char un singur octet (1 byte=8 biţi), capabil să conţină codul unui caracter din setul
local de caractere;
 int număr întreg, reflectă în mod tipic mărimea naturală din calculatorul utilizat;
 float număr real, în virgulă mobilă, simplă precizie;
 double număr real, în virgulă mobilă, dublă precizie.

În completare există un număr de calificatori, care se pot aplica tipurilor de bază char, int, float sau
double: short, long, signed şi unsigned. Astfel, se obţin tipurile derivate de date. Short şi long
se referă la mărimea diferită a întregilor, iar datele de tip unsigned int sunt întotdeauna pozitive. S-a

23
CAPITOLUL 2 Date, operatori şi expresii
intenţionat ca short şi long să furnizeze diferite lungimi de întregi, int reflectând mărimea cea mai
"naturală" pentru un anumit calculator. Fiecare compilator este liber să interpreteze short şi long în mod
adecvat propriului hardware; în nici un caz, însă, short nu este mai lung decât long. Toţi aceşti calificatori
pot aplicaţi tipului int. Calificatorii signed (cel implicit) şi unsigned se aplică tipului char. Calificatorul
long se aplică tipului double. Dacă într-o declaraţie se omite tipul de bază, implicit, acesta va fi int.

Tabelul 2.1.
Tip Lungimea zonei Descriere
de memorie
ocupate (în biţi)
MS- UNIX
DOS LINUX
char 8 8 Valoarea unui singur caracter; poate fi întâlnit în expresii cu
extensie de semn
unsigned char 8 8 Aceeaşi ca la char, fară extensie de semn
signed char 8 8 Aceeaşi ca la char, cu extensie de semn obligatorie
int 16 32 Valoare întreagă
long 32 64 Valoare întreagă cu precizie mare
(long int)
long long int 32 64 Valoare întreagă cu precizie mare
short int 16 32 Valoare întreagă cu precizie mică
unsigned int 16 32 Valoare întreagă, fără semn
unsigned long 32 64 Valoare întreagă, fără semn
int
float 32 32 Valoare numerică cu zecimale, simplă precizie (6 )
double 64 64 Valoare numerică cu zecimale, dublă precizie (10 )
long double 80 128 Valoare numerică cu zecimale, dublă precizie

Să considerăm, de exmplu, tipul int, folosit pentru date întregi (pozitive sau negative). Evident că mulţimea
valorilor pentru acest tip va fi, de fapt, o submulţime finită de numere întregi. Dacă pentru memorarea unei
1
date de tip int se folosesc 2 octeţi de memorie, atunci valoarea maximă pentru aceasta va fi × 2 16 - 1, deci
2
1
2 15 - 1 (32767), iar valoarea minimă va fi - × 2 16 , deci -2 15 (-32768). Încercarea de a calcula o expresie
2
de tip int a cărei valoare se situează în afara acestui domeniu va conduce la o eroare de execuţie.
Mulţimea valorilor pentru o dată de tip unsigned int (întreg fără semn) va fi formată din numerele
întregi situate în intervalul [0, 2 16 - 1].

În header-ul <values.h> sunt definite constantele simbolice (cum ar fi: MAXINT, MAXSHORT, MAXLONG,
MINDOUBLE, MINFLOAT, etc.) care au ca valoare limitele inferioară şi superioară ale intervalului de valori
pentru tipurile de date enumerate. (de exemplu MAXINT reprezintă valoarea întregului maxim care se poate
memora, etc. )

Fără a detalia foarte mult modul de reprezentare a datelor reale (de tip float sau double), vom sublinia
faptul că, pentru acestea, este importantă şi precizia de reprezentare. Deoarece calculatorul poate reprezenta
doar o submulţime finită de valori reale, în anumite cazuri, pot apare erori importante.
Numerele reale pot fi scrise sub forma: N = mantisa × baza exponent
unde:baza reprezintă baza sistemului de numeraţie; mantisa (coeficientul) este un număr fracţionar
normalizat ( în faţa virgulei se află 0, iar prima cifră de după virgulă este diferită de zero); exponentul este
un număr întreg. Deoarece forma internă de reprezentare este binară, baza=2. În memorie vor fi reprezentate
doar mantisa şi exponentul. Numărul de cifre de după virgulă determină precizia de exprimare a numărului.
Ce alte cuvinte, pe un calculator cu o precizie de 6 cifre semnificative, două valori reale care diferă la a 7-a

24
CAPITOLUL 2 Date, operatori şi expresii
cifră zecimală, vor avea aceeaşi reprezentare. Pentru datele de tip float, precizia de reprezentare este 6;
pentru cele de tip double, precizia este 14, iar pentru cele de tip long double, precizia este 20.
Lungimea zonei de memorie ocupate de o dată de un anumit tip (pe câţi octeţi este memorată data) poate fi
aflată cu ajutorul operatorului sizeof.
Exemplu:
cout<<"Un int este memorat pe "<<sizeof(int)<<"octeti.\n";
Instrucţiunea are ca efect afişarea pe monitor a mesajului: Un int este memorat pe 2 octeţi.

2.5.2. CONSTANTE

O constantă este un literal (o formă externă de reprezentare) numeric, caracter sau şir de caractere. Numele
şi valoarea unei constante sunt identice. Valoarea unei constante nu poate fi schimbată în timpul execuţiei
programului în care a fost utilizată. Tipul şi valoarea ei sunt determinate în mod automat, de către compilator,
pe baza caracterelor care compun literalul.

2.5.2.1. Constante întregi

Constantele întregi sunt literali numerici (compuşi din cifre), fără punct zecimal.
 Constante întregi în baza 10, 8 sau 16
 Constante întregi în baza 10
Exemple:
45
-78 // constante întregi decimale (în baza 10), tip int
 Constante întregi octale
Dacă în faţa numărului apare cifra zero (0), acest lucru indică faptul că acea constantă este de tipul
int, in baza opt (constantă octală).
Exemple:
056
077 // constante întregi octale, tip int
 Constante întregi hexagesimale
Dacă în faţa numărului apar caracterele zero (0) şi x (sau X), acest lucru indică faptul că acea
constantă este de tipul int, în baza 16 (constantă hexagesimală). Amintim că în baza 16 cifrele sunt:
0-9, A (sau a) cu valoare 10, B (sau b) cu valoare 11, C (sau c) cu valoare 12, D (sau d) cu valoare
13, E (sau e) cu valoare 14, F (sau f) cu valoare 15.
Exemple:
0x45
0x3A
0Xbc // constante întregi hexagesimale, tip int
 Constante întregi, de tipuri derivate
 Dacă secvenţa de cifre este urmată de L sau l, tipul constantei este long int.
Exemple:
145677L
897655l // tip decimal long int
 Dacă secvenţa de cifre este urmată de U sau u, tipul constantei este unsigned int.
Exemple:
65555u
 Dacă secvenţa de cifre este urmată de U (u) şi L (l), tipul constantei este unsigned long int.
Exemple: 7899UL //tip decimal unsigned long int

2.5.2.2. Constante numerice, reale

 Dacă o constantă numerică conţine punctul zecimal, ea este de tipul double.


Exemplu:
3.1459 //tip double

25
CAPITOLUL 2 Date, operatori şi expresii
 Dacă numărul este urmat de F sau f, constante este de tip float.
 Dacă numărul este urmat de L sau l, este de tip long double.
Exemplu:
0.45f //tip float
9.788L //tip long double
 Constante reale în format ştiinţific
Numărul poate fi urmat de caracterul e sau E şi de un număr întreg, cu sau fără semn. În acest caz,
constanta este în notaţie ştiinţifică. În această formă externă de reprezentare, numărul din faţa literei E
reprezintă mantisa, iar numărul întreg care urmează caracterului E reprezintă exponentul. In forma
externă de reprezentare, baza de numeraţie este 10, deci valoarea constantei va fi dată de mantisa × 10
exp onent .
Exemplu:
1.5e-2 //tip double, în notaţie ştiinţifică, valoare 1.5 × 10 − 2

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia.


#include <iostream.h>
#include <values.h>
#define PI 3.14359
int main()
{
cout<<"Tipul int memorat pe: "<<sizeof(int)<<" octeti\n";
cout<<"Tipul int memorat pe: "<<sizeof(23)<<" octeti\n"; //23-const. zecimala int
cout<<"Int maxim="<<MAXINT<<’\n’;
//const. simbolice MAXINT, MAXLONG, etc. - definite in <values.h>
cout<<"Const. octala 077 are val decimala:"<<077<<’\n;
cout<<"Const. hexagesimala d3 are val decimala:"<<0xd3<<’\n’;
cout<<"Tipul unsigned int memorat pe:"<<sizeof(unsigned int)<<" octeti\n";
cout<<"Tipul unsigned int memorat pe: "<<sizeof(23U)<<" octeti\n";
cout<<"Tipul unsigned int memorat pe: "<<sizeof(23u)<<" octeti\n";
cout<<"Tipul long int memorat pe: "<<sizeof(long int)<<" octeti\n";
cout<<"Tipul long int memorat pe: "<<sizeof(23L)<<" octeti\n";
cout<<"Tipul long int memorat pe: "<<sizeof(23l)<<" octeti\n";
//23L sau 23l-const. decimala long int
cout<<"Long int maxim="<<MAXLONG<<’\n’;
cout<<"Tipul unsigned long memorat pe:";
cout<<sizeof(unsigned long int)<<" octeti\n";
cout<<"Tipul unsigned long memorat pe: "<<sizeof(23UL)<<" octeti\n";
cout<<"Tipul unsigned long memorat pe: "<<sizeof(23ul)<<" octeti\n";
//23UL sau 23ul-const. decimala unsigned long int
cout<<"Tipul long long int memorat pe: ";
cout<<sizeof(long long int)<<" octeti\n";
cout<<"Tipul long long int memorat pe: "<<sizeof(d)<<" octeti\n";
cout<<"Tipul short int memorat pe: "<<sizeof(short int)<<" octeti\n";
cout<<"Short int maxim="<<MAXSHORT<<’\n’;
cout<<"Tipul float memorat pe: "<<sizeof(float)<<" octeti\n";
cout<<"Tipul float memorat pe: "<<sizeof(23.7f)<<" octeti\n";
//23.7f-const. decimala float
cout<<"Float maxim="<<MAXFLOAT<<’\n’;
cout<<"Float minim="<<MINFLOAT<<’\n’;
cout<<"Tipul double memorat pe: "<<sizeof(double)<<" octeti\n";
cout<<"Tipul double memorat pe: "<<sizeof(23.7)<<" octeti\n";
//23.7-const. decimala double
cout<<"Const. decim. doubla in notatie stiintifica:"<<23.7e-5<<’\n’;
cout<<”Const. PI este:”<<PI<<’\n’;
cout<<”Constanta PI este memorata pe:”<<sizeof(PI)<<”octeti\n”:
cout<<"Double maxim="<<MAXDOUBLE<<’\n’<<"Double minim="<<MINDOUBLE<<’\n’;
cout<<"Tipul long double memorat pe: "<<sizeof(long double)<<" octeti\n";
cout<<"Tipul long double memorat pe: "<<sizeof(23.7L)<<" octeti\n";
//23.7L-const. decimala long double

26
CAPITOLUL 2 Date, operatori şi expresii
cout<<"Cifra A din HEXA are val.:"<<0xA<<"\n";
cout<<"Cifra B din HEXA are val.:"<<0XB<<"\n";
cout<<"Cifra C din HEXA are val.:"<<0xc<<"\n";
cout<<" Cifra D din HEXA are val.:"<<0xD<<"\n";
cout<<" Cifra E din HEXA are val.:"<<0XE<<"\n";
cout<<" Cifra F din HEXA are val.:"<<0xf<<"\n";
cout<<"Val. const. hexa 0x7ac1e este: "<<0x7ac1e<<'\n';
cout<<"Val. const. octale 171 este: "<<0171<<'\n';
cout<<"O const. octala se memoreaza pe "<<sizeof(011)<<" octeti\n";
cout<<"O const.oct.long se mem pe ";cout<<sizeof(011L)<<" octeti\n";}

2.5.2.3. Constante caracter

Constantele caracter sunt încadrate între apostroafe.


Exemplu:
'a' //tip char
O constantă caracter are ca valoare codul ASCII al caracterului pe care îl reprezintă.
Acest set de caractere are următoarele proprietăţi:
 Fiecărui caracter îi corespunde o valoare întreagă distinctă (ordinală);
 Valorile ordinale ale literelor mari sunt ordonate şi consecutive ('A' are codul ASCII 65, 'B' - codul 66,
'C' - codul 67, etc.);
 Valorile ordinale ale literelor mici sunt ordonate şi consecutive ('a' are codul ASCII 97, 'b' - codul 98, 'c' -
codul 99, etc.);
 Valorile ordinale ale cifrelor sunt ordonate şi consecutive ('0' are codul ASCII 48, '1' - codul 49, '2' -
codul 50, etc.).

 Constante caracter corespunzătoare caracterelor imprimabile


O constantă caracter corespunzătoare unui caracter imprimabil se reprezintă prin caracterul respectiv
inclus între apostroafe.
Exemplu:
Constantă caracter Valoare
‘A’ 65
‘a’ 97
‘0’ 48
‘*’ 42
Excepţii de la regula de mai sus le constituie caracterele imprimabile apostrof (') şi backslash (\).
Caracterul backslash se reprezintă: '\\'. Caracterul apostrof se reprezintă: '\''.

 Constante caracter corespunzătoare caracterelor neimprimabile


Pentru caracterele neimprimabile, se folosesc secvenţe escape. O secvenţă escape furnizează un
mecanism general şi extensibil pentru reprezentarea caracterelor invizibile sau greu de obţinut. În
tabelul 2.2. sunt prezentate câteva caractere escape utilizate frecvent.

Tabelul 2.2.
Constantă Valoare Denumirea Utilizare
caracter (Cod ASCII) caracterului
‘\n’ 10 LF rând nou (Line Feed)
‘\t’ 9 HT tabulator orizontal
‘\r’ 13 CR poziţionează cursorul în coloana 1 din rândul curent
‘\f’ 12 FF salt de pagină la imprimantă (Form Feed)
‘\a’ 7 BEL activare sunet
O constantă caracter pentru o secvenţă escape poate apare însă, şi sub o formă în care se indică codul ASCII,
în octal, al caracterului dorit:
’\ddd’ unde d este o cifră octală.
Exemple:
’\11’ (pentru ’\t’)

27
CAPITOLUL 2 Date, operatori şi expresii
reprezintă constanta caracter backspace, cu codul 9 în baza 10, deci codul 11 în baza 8.
’\15’ (pentru ’\r’)
reprezintă constanta caracter CR, cu codul 13 în baza 10, deci codul 11 în baza 8.

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia.


#include <iostream.h>
void main(void)
{
cout<<"Un caracter este memorat pe "<<sizeof(char)<<" octet\n";
cout<<"Caracterul escape \\n este memorat pe ";
cout<<sizeof('\n')<<" octet\n";
cout<<"Caracterul escape '\\n\' este memorat pe "<<sizeof('\n');
cout<<" octet\n";
cout<<"Caracterul '9' este memorat pe "<<sizeof('9')<<" octet\n";
cout<<'B';cout<<' ';cout<<'c';cout<<'\t';
cout<<'\t';cout<<'9';cout<<'\b';cout<<'\a';
cout<<'L';cout<<'\v';cout<<'L';
cout<<'\'';cout<<'\t';cout<<'\"';cout<<'\\';cout<<'\n';
cout<<'\a';cout<<'\7';
}

2.5.2.4. Constante şir de caractere

Constanta şir este o succesiune de zero sau mai multe caractere, încadrate de ghilimele. În componenţa unui
şir de caractere, poate intra orice caracter, deci şi caracterele escape. Lungimea unui şir este practic
nelimitată. Dacă se doreşte continuarea unui şir pe rândul următor, se foloseşte caracterul backslash.

Caracterele componente ale unui şir sunt memorate într-o zonă continuă de memorie (la adrese succesive).
Pentru fiecare caracter se memorează codul ASCII al acestuia. După ultimul caracter al şirului, compilatorul
plasează automat caracterul NULL (\0), caracter care reprezintă marcatorul sfârşitului de şir. Numărul de
octeţi pe care este memorat un şir va fi, deci, mai mare cu 1 decât numărul de caractere din şir.
Exemple:
”Acesta este un şir de caractere” //constantă şir memorată pe 32 octeţi
”Şir de caractere continuat\”
pe rândul următor!” //constantă şir memorată pe 45 octeţi
”Şir \t cu secvenţe escape\n” //constantă şir memorată pe 26 octeţi
’\n’ //constantă caracter memorată pe un octet
”\n” //constanta şir memorată pe 2 octeţi (codul caracterului escape şi terminatorul de şir)
”a\a4” /*Şir memorat pe 4 octeţi:
Pe primul octet: codul ASCII al caracterului a
Pe al doilea octet: codul ASCII al caracterului escape \a
Pe al treilea octet: codul ASCII al caracterului 4
Pe al patrulea octet: terminatorul de şir NULL, cod ASCII 0 */
”\\ASCII\\” /*Şir memorat pe 8 octeţi:
Pe primul octet: codul ASCII al caracterului backslah
Pe al doilea octet: codul ASCII al caracterului A
Pe al treilea octet: codul ASCII al caracterului S
Pe al patrulea octet: codul ASCII al caracterului S
Pe al 6-lea octet: codul ASCII al caracterului I
Pe al 7-lea octet: codul ASCII al caracterului I
Pe al 8-lea octet: codul ASCII al caracterului backslah
Pe al 9-ea octet: terminatorul de şir NULL, de cod ASCII 0 */
”1\175a” /*Şir memorat pe 4 octeţi:
Primul octet: Codul ASCII al caracterul 1
Al 2-lea octet: codul ASCII 125 (175 in octal) al caracterului }
Al 3-lea octet: codul ASCII al caracterului a

28
CAPITOLUL 2 Date, operatori şi expresii
Al 4-lea octet: codul ASCII 0 pentru terminatorul şirului */
Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia.
#include <iostream.h>
void main()
{ cout<<"Şirul \"Ab9d\" este memorat pe:"<<sizeof("Ab9d")<<" octeţi\n";
cout<<"Şirul \"Abcd\\t\" este memorat pe:"<<sizeof("Abcd\t")<<" octeţi\n";
cout<<"Şirul \"\n\" este memorat pe "<<sizeof("\n")<<" octeţi\n";
cout<<"Şirul \"\\n\" este memorat pe "<<sizeof("\n")<<" octeţi\n";
cout<<"Şirul \"ABCDE\" se memorează pe "<<sizeof("ABCDE")<<" octeţi\n";}

2.5.3. VARIABILE

Spre deosebire de constante, variabilele sunt date (obiecte informaţionale) ale căror valori se pot modifica în
timpul execuţiei programului. Şi variabilele sunt caracterizate de atributele nume, tip, valoare şi clasă de
memorare. Variabilele sunt nume simbolice utilizate pentru memorarea valorilor introduse pentru datele de
intrare sau a rezultatelor. Dacă la o constantă ne puteam referi folosind caracterele componente, la o variabilă
ne vom referi prin numele ei. Numele unei variabile ne permite accesul la valoarea ei, sau schimbarea valorii
sale, dacă este necesar acest lucru. Numele unei variabile este un identificator ales de programator. Ca
urmare, trebuie respectate regulile enumerate în secţiunea identificatori.

Dacă o dată nu are legături cu alte date (de exemplu, relaţia de ordine), vom spune că este o dată izolată. O
dată izolată este o variabilă simplă. Dacă datele se grupează într-un anumit mod (în tablouri - vectori,
matrici - sau structuri), variabilele sunt compuse (structurate).

În cazul constantelor, în funcţie de componenţa literalului, compilatorul stabilea, automat, tipul constantei. În
cazul variabilelor este necesară specificarea tipului fiecăreia, la declararea acesteia. Toate variabilele care vor
fi folosite în program, trebuie declarate înainte de utilizare.

2.5.3.1. Declararea variabilelor

Modul general de declarare a variabilelor este:


tip_variabile listă_nume_variabile;
Se specifică tipul variabilei(lor) şi o listă formată din unul sau mai mulţi identificatori ai variabilelor de tipul
respectiv. Într-un program în limbajul C++, declaraţiile de variabile pot apare în orice loc în programul sursă.
La declararea variabilelor, se rezervă în memorie un număr de octeţi corespunzător tipului variabilei, urmând
ca ulterior, în acea zonă de memorie, să fie depusă (memorată, înregistrată) o anumită valoare.
Exemple:
int i, j;/*declararea var. simple i, j, de tip int. Se rezervă pentru i şi j câte 16 biţi (2octeţi)*/
char c; /* declararea variabilei simple c, de tip char. Se rezervă un octet. */
float lungime; /* declararea variabilei simple lungime; se rezervă 4 octeţi */

2.5.3.2. Iniţializarea variabilelor în declaraţii

În momentul declarării unei variabile, acesteia i se poate da (asigna, atribui) o anumită valoare. În acest caz,
în memorie se rezervă numărul de locaţii corespunzător tipului variabilei respective, iar valoarea va fi depusă
(memorată) în acele locaţii.
Forma unei declaraţii de variabile cu atribuire este:
tip_variabilă nume_variabilă=expresie;
Se evaluează expresia, iar rezultatul acesteia este asignat variabilei specificate.
Exemple:
char backslash=’\\’; //declararea şi iniţializarea variabilei simple backslash
int a=7*9+2; /* declararea variabilei simple a, de tip int şi iniţializarea ei cu valoarea 65*/
float radiani, pi=3.14;/*declararea variabilei radiani;declararea şi iniţializarea var. pi*/

29
CAPITOLUL 2 Date, operatori şi expresii
short int z=3; //declararea şi iniţializarea variabilei simple z
char d=’\011’;
char LinieNoua=’\n’;
double x=9.8, y=0;

Compilatorul C++ furnizează mecanisme care permit programatorului să influenţeze codul generat la
compilare, prin aşa-numiţii calificatori.
Aceştia sunt:
 const;
 volatile.

Calificatorul const asociat unei variabile, nu va permite modificarea ulterioară a valorii acesteia, prin
program (printr-o atribuire). Calificatorul volatile (cel implicit) are efect invers calificatorului const. Dacă
după calificator nu este specificat tipul datei, acesta este considerat tipul implicit, adică int.
Exemple:
const float b=8.8;
volatile char terminator;terminator=’@’;terminator=’*’; //permis
b=4/5; //nepermisa modificarea valorii variabilei b
const w; volatile g; //w, g de tip int, implicit

2.5.3.3. Operaţii de intrare/ieşire

Limbajele C/C++ nu posedă instrucţiuni de intrare/ieşire, deci de citire/scriere (ca limbajul PASCAL, de
exemplu). În limbajul C aceste operaţii se realizează cu ajutorul unor funcţii (de exemplu, printf şi scanf), iar
în limbajul C++ prin supraîncărcarea operatorilor (definirea unor noi proprietăţi ale unor operatori existenţi,
fără ca proprietăţile anterioare să dispară), mai precis a operatorilor >> şi << . Vom folosi în continuare
abordarea limbajului C++, fiind, în momentul de faţă, mai simplă. În limbajul C++ sunt predefinite
următoarele dispozitive logice de intrare/ieşire:
cin - console input - dispozitivul de intrare (tastatura);
cout - console output - dispozitivul de ieşire (monitorul).
Aşa cum se va vedea în capitolul 9, cin şi cout sunt, de fapt, obiecte (predefinite). Transferul informaţiei se
realizează cu operatorul >> pentru intrare şi operatorul << pentru ieşire. Utilizarea dispozitivelor de
intrare/ieşire cu operatorii corespunzători determină deschiderea unui canal de comunicaţie a datelor către
dispozitivul respectiv. După operator se specifică informaţiile care vor fi citite sau afişate.
Exemple:
cout << var; /* afişează valoarea variabilei var pe monitor*/
cin >> var; /* citeşte valoarea variabilei var de la tasatatură */

Sunt posibile operarţii multiple, de tipul:


Exemple:
cout << var1 << var2 << var3;
cin >> var1 >> var2 >> var3;
În acest caz, se efectuează succesiv, de la stânga la dreapta, scrierea, respectiv citirea valorilor variabilelor
var1, var2 şi var3.

Operatorul >> se numeşte operator extractor (extrage valori din fluxul datelor de intrare, conform tipului
acestora), iar operatorul << se numeşte operator insertor (inserează valori în fluxul datelor de ieşire,
conform tipului acestora). Tipurile de date citite de la tastatură pot fi toate tipurile numerice, caracter sau şir
de caractere. Tipurile de date transferate către ieşire pot fi: toate tipurile numerice, caracter sau şir de
caractere. Operanzii operatorului extractor (>>) pot fi doar nume de variabile. Operanzii operatorului
insertor (<<) pot fi nume de variabile (caz în care se afişează valoarea variabilei), constante sau expresii.
Utilizarea dispozitivelor şi operatorilor de intrare/ieşire în C++ impune includerea fişierului iostream.h.
Exemple:
char c;

30
CAPITOLUL 2 Date, operatori şi expresii
cout<<"Astept un caracter:"; //afişarea constantei şir de caractere, deci a mesajului
cin>>c; //citirea valorii variabilei c, de tip caracter
int a, b, e; double d;
cin>>a>>b>>e>>d; //citirea valorilor variabilelor a, b, e, d de tip int, int, int, double
cout<<"a="<<a<<"Valoarea expresiei a+b este:"<<a+b<<'\n';

2.6. OPERATORI ŞI EXPRESII

Datele (constante sau variabile) legate prin operatori, formează expresii (figura 2.4). Operatorii care pot fi
aplicaţi datelor (operanzilor) depind de tipul operanzilor, datorită faptului că tipul unei date constă într-o
mulţime de valori pentru care s-a adoptat un anumit mod de reprezentare în memoria calculatorului şi o
mulţime de operatori care pot fi aplicaţi acestor valori.

Operatorii pot fi:


 unari (necesită un singur operand);
 binari (necesită doi operanzi);
 ternari (trei operanzi).

O expresie este o combinaţie corectă din punct de vedere sintactic, formată din operanzi şi operatori.
Expresiile, ca şi operanzii, au tip şi valoare.

2.6.1. OPERATORI

 Operatorul unar adresă &, aplicat identificatorului unei variabile, furnizează adresa la care este
memorată aceasta. Poate fi aplicat oricărui tip de date şi se mai numeşte operator de referenţiere.
Exemplu:
int a;
cout<<"Adresa la care este memorata variabila a este:"<<&a;

 Operatorul de atribuire (de asignare) este un operator binar care se aplică tuturor tipurilor de variabile.
Este folosit sub formele următoare:
nume_variabilă=expresie;
sau: expresie1=expresie2;
Se evaluează expresia din membrul drept, iar valoarea acesteia este atribuită variabilei din membrul stâng.
Dacă tipurile membrilor stâng şi drept diferă, se pot realiza anumite conversii, prezentate în paragraful 2.7.
Exemplu:
float x; int a,b; x=9.18;
a=b=10;
int s; s=a+20*5; //rezultat: s=110
s=x+2; //rezultat s=11, deoarece s este int.

Aşa cum se observă în linia a 2-a din exemplul precedent, operatorul de atribuire poate fi utilizat de mai
multe ori în aceeaşi expresie. Asociativitatea operatorului are loc de la dreapta la stânga. Astfel, mai întâi
b=10, apoi a=b.

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia.


#include <iostream.h>
void main()
{
float x,y=4.25; char car=’A’; int a,b,c;
cout<<”Val. lui y este:”<<y<<’\n’; //Afişare: Val. lui y este:4.25
x=y; cout<<”Val. lui x este:”<<x<<’\n’; //Afişare: Val. lui x este:4.25
a=x;cout<<”Val.lui a este:”<<a<<’\n’; //Afişare:Val. lui a este:4, deoarece a de tip int!!!
c=b=a; cout<<”b=”<<b<<”\tc=”<<c<<’\n’; //Afişare: b=4 c=4

31
CAPITOLUL 2 Date, operatori şi expresii
cout<<”Introduceţi val. lui c:”; cin>>c; // citire val. pentru c
cout<<”Val. lui c este:”<<c<<’\n’; //Afişare: Val. lui c este:4
}

Operatorul poate fi aplicat tipurilor de date întregi, reale, caracter, şi chiar şiruri de caractere, aşa cum vom
vedea în capitolele următoare (exemplu: char şir [10]=”a5dfgthklj”).

 Operatori aritmetici unari:


Operator Semnificaţie Exemple
- Minus unar -a
++ Operator de incrementare a++ sau
(adună 1 la valoarea operandului) ++a
-- Operator de decrementare a-- sau
(scade 1 din valoarea operandului) --a

 Operatorul - unar schimbă semnul operandului.


Exemplu:
int a,b; cout<<”a=”<<-a<<’\n’; b=-a;
cout<<”b=”<<b<<’\n’;
Operatorul - unar poate fi aplicat datelor întregi, reale, caracter.

 Operatorii de incrementare şi decrementare pot fi aplicaţi datelor numerice sau caracter.


Ambii operatori pot fi folosiţi în formă prefixată, înaintea operandului, (++a, respectiv --a) sau
postfixată, după operand (a++, respectiv a--).
Operatorul de decrementare -- care poate fi folosit în formă prefixată (--a) sau postfixată (a--).

Utilizarea acestor operatori în expresii, în formă prefixată sau postfixată, determină evaluarea
acestora în moduri diferite, astfel:

y=++x este echivalent cu: x=x+1;


y=x;
y=x++ este echivalent cu: y=x;
x=x+1;
y=--x este echivalent cu: x=x-1;
y=x;
y=x-- este echivalent cu: y=x;
x=x-1;

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia.


#include <iostream.h>
void main()
{ int a=9; cout<<”a++=”<<a++<<’\n’; //Afişare: a++=9
cout<<”a=”<<a<<’\n’; //Afişare: a=10
a=9; //Revenire in situatia anterioara
cout<<”++a=”<<++a<<’\n’; //Afişare: ++a=10
cout<<”a=”<<a<<’\n’; //Afişare: a=10
a=9; cout<<”a--=”<<a--<<’\n’; //Afişare: a--=9
cout<<”a=”<<a<<’\n’; //Afişare: a=8
a=9; //Revenire in situaţia anterioara
cout<<”--a=”<<--a<<’\n’; //Afişare: --a=8
cout<<”a=”<<a<<’\n’; //Afişare: a=8
int z,x=3; z=x++-2;
cout<<”z=”<<z<<’\n’; //Afişare: z=1
cout<<"x=”<<x<<’\n’; //Afişare: x=4
x=3; z=++x-2; cout<<”z=”<<z<<’\n’; //Afişare: z=2

32
CAPITOLUL 2 Date, operatori şi expresii
cout<<"x=”<<x<<’\n’; //Afişare: x=4
}
 Operatori aritmetici binari:
Operator Semnificaţie Exemple
+ Adunarea celor doi operanzi a+b
- Scăderea celor doi operanzi a-b
* Înmulţirea celor doi operanzi a*b
/ Împărţirea celor doi operanzi a/b
% Operatorul modulo (operatorul rest) a%b
(furnizează restul împărţirii operatorului stâng la operatorul drept).

Operatorul modulo se aplică numai operanzilor întregi (de tip int sau char). Ceilalţi operatori aritmetici binari
pot fi aplicaţi datelor întregi sau reale.
Dacă într-o expresie cu 2 operanzi şi un operator binar aritmetic, ambii operanzi sunt întregi, rezultatul
expresiei va fi tot un număr întreg. De exemplu, la evaluarea expresiei 9/2, ambii operanzi fiind întregi,
rezultatul furnizat este numărul întreg 4.
Operatorii prezentaţi respectă o serie de reguli de precedenţă (prioritate) şi asociativitate, care determină
precis modul în care va fi evaluată expresia în care aceştia apar. În tabelul 2.3 sunt prezentaţi operatorii
anteriori, în ordinea descrescătoare a priorităţii. Precedenţa operatorilor poate fi schimbată cu ajutorul
parantezelor.

Tabelul 2.3.
Clasă de operatori Operatori Asociativitate
Unari - (unar) ++ -- de la dreapta la stânga
Multiplicativi * / % de la stânga la dreapta
Aditivi + - de la stânga la dreapta
Atribuire = de la dreapta la stânga

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia.


#include <iostream.h>
void main()
{
int rezult, a=20,b=2,c=25,d=4; rezult=a-b;
cout<<”a-b=”<<rezult<<’\n’; // Afişare: a-b=18
rezult=a+b; cout<<”a+b=”<<rezult<<’\n’; // Afişare: a+b=22
rezult=a*b;cout<<”c*b=”<<rezult<<’\n’; // Afişare: c*b=50
rezult=a/d; cout<<”a/d=”<<rezult<<’\n’; // Afişare: a/d=5
rezult=c%b; cout<<”c%b=”<<rezult<<’\n’; // Afişare: c%b=1
rezult=c/b*d; cout<<”c/b*d=”<<rezult<<’\n’; // Afişare: c/b*d=48
rezult= -b+a; cout<<”-b+a=”<<rezult<<’\n’; // Afişare: -b+a=18
rezult= -(b+a); cout<<”-(b+a)=”<<rezult<<’\n’; // Afişare: -(b+a)=-22
rezult=b+c*d;cout<<”b+c*d=”<<rezult<<’\n’; // Afişare: b+c*d=102
rezult=(b+c)*d;cout<<”(b+c)*d=”<<rezult<<’\n’; // Afişare: (b+c)*d=108
}

 Operatori aritmetici binari compuşi


Operator Semnificaţie Exemple
+= a=a+b a+=b
-= a=a+b a-=b
*= a=a*b a*=b
/= a=a/b a/=b
%= a=a%b a%=b
Aceşti operatori se obţin prin combinarea operatorilor aritmetici binari cu operatorul de atribuire şi sunt
folosiţi sub forma următoare:
expresie1 operator= expresie2;

33
CAPITOLUL 2 Date, operatori şi expresii
Rezultatul obţinut este acelaşi cu rezultatul obţinut prin:
expresie1 = expresie1 operator expresie2;
Toţi aceşti operatorii modifică valoarea operandului stâng prin adunarea, scăderea, înmulţirea sau împărţirea
acestuia prin valoarea operandului drept.
Construcţia x+=1 generează acelaşi rezultat ca expresia x=x+1.
Observaţiile referitoare la operatorii aritmetici binari sunt valabile şi pentru operatorii aritmetici binari
compuşi. Operatorii aritmetici binari compuşi au aceeaşi prioritate şi asociativitate ca şi operatorul de
atribuire.

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia.


#include <iostream.h>
void main()
{
int a,b; float c=9.3; a=3; b=8;
cout<<”a=”<<a<<’\n’; //Afişare a=3
a+=b; cout<<”a=”<<a<<’\n’; //Afişare a=11
a-=b; cout<<”a=”<<a<<’\n’; //Afişare a=-5
a*=b; cout<<”a=”<<a<<’\n’; //Afişare a=24
a/=b; cout<<”a=”<<a<<’\n’; //Afişare a=0
a%=b; cout<<”a=”<<a<<’\n’; //Afisare a=3
}

 Operatori relaţionali binari


Operator Semnificaţie Exemple
== Egal cu a==b
!= Diferit de a!=b
< Mai mic decât a<b
<= Mai mic sau egal a<=b
> Mai mare decât a>b
>= Mai mare sau egal a>=b
Primii doi operatori mai sunt numiţi operatori de egalitate. Operatorii relaţionali servesc la compararea
valorilor celor doi operanzi şi nu modifică valorile operanzilor. Rezultatul unei expresii în care apare unul
din operatorii relaţionali binari este întreg şi are valoarea zero (0) dacă relaţia este falsă, sau valoarea unu (1)
(sau diferită de 0 în cazul compilatoarelor sub UNIX), dacă relaţia este adevărată. Aceşti operatorii pot fi
aplicaţi datelor de tip întreg, real sau char.
Regulile de precedenţă şi asociativitate ale acestor operatori sunt prezentate în tabelul 2.4.

Tabelul 2.4.
Clasă de operatori Operatori Asociativitate
Unari - (unar) ++ -- de la dreapta la stânga
Multiplicativi * / % de la stânga la dreapta
Aditivi + - de la stânga la dreapta
Atribuire = de la dreapta la stânga
Relaţionali < <= > >= de la stânga la dreapta
De egalitate == != de la stânga la dreapta
Atribuire şi aritmetici binari = *= /= %= += -= de la dreapta la stânga

Observaţie: Deosebirea dintre operatorii == (relaţional, de egalitate) şi = (de atribuire) constă în faptul că
primul nu modifică valoarea nici unuia dintre operanzii săi, pe când cel de-al doilea modifică valoarea
operandului stâng (vezi exemplul următor)

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia.


#include <iostream.h>
void main()
{
int a=1, b=20, lim=100; int rezult; rezult=a<b;

34
CAPITOLUL 2 Date, operatori şi expresii
cout<<”a<b=”<<rezult<<’\n’;
// Afişare: a<b=1 (sau o altă valoare diferită de zero pentru alte compilatoare)
rezult=a<=b;
//operatorul realţional <= are prioritate mai mare decât cel de atribuire
cout<<”a<=b=”<<rezult<<’\n’;
// Afisare: a<b=1 (sau o alta valoare diferită de zero pentru alte compilatoare)
rezult=a>b; cout<<”a>b=”<<rezult<<’\n’; // Afişare: a<b=0
rezult=a+10>=lim; cout<<”a+10>=lim=”<<rezult<<’\n’;
/* Operatorul + are prioritate mai mare decât operatorul >= . Afişare: a+10>=lim=0 */
rezult=a+(10>=lim); cout<<”a+(10>=lim)=”<<rezult<<’\n’;
/* Schimbarea prioritatii operatorilor prin folosirea parantezelor; Afişare: a+(10>=lim)=1 */
rezult=a==b;
cout<<”a==b=”<<rezult<<’\n’; // Afişare: a==b=0
cout<<”a=”<<a<<’\n’; // Afişare: a=1
cout<<”b=”<<b<<’\n’; // Afişare: b=20
rezult=a=b; cout<<”a=b=”<<rezult<<’\n’; // Afişare: a=b=20
cout<<”a=”<<a<<’\n’; // Afişare: a=20
cout<<”b=”<<b<<’\n’; // Afişare: b=20
rezult=5>b>10;cout<<”b=”<<b<<’\n’; // Afişare: b=20
cout<<”5>b>10=”<<rezult<<’\n’; //Echivalent cu (5>b)>10 Afişare: 5>b>10=0
}

 Operatori logici pe cuvânt


Operator Semnificaţie Exemple
! Not (negaţie logică) !(a==b)
&& And (conjuncţie, şi logic) (a>b) && (b>c)
|| Or (disjuncţie, sau logic) (a>b) || (b>c)
Aceşti operatori pot fi aplicaţi datelor de tip întreg, real sau caracter. Evaluarea unei expresii în care intervin
operatorii logici se face conform tabelului 2.5.

Tabelul 2.5.
x y !x x&&y x||y
adevărat (1) adevărat (1) fals (0) adevărat (1) adevărat (1)
adevărat (1) fals (0) fals (0) fals (0) adevărat (1)
fals (0) adevărat (1) adevărat (1) fals (0) adevărat (1)
fals (0) fals (0) adevărat (1) fals (0) fals (0)
Expresia !expresie are valoarea 0 (fals) dacă expresia-operand are o valoare diferită de zero şi valoarea
unu (adevărat) dacă expresia-operand are valoarea zero.
Expresia expresie1||expresie2 are valoarea diferită de 0 (true) dacă FIE expresie1, FIE expresie2
au valori diferite de zero.
Expresia expresie1 && expresie2 are valoarea diferită de 0 (true) dacă AMBELE expresii-
operand ( expresie1 şi expresie2) au valori diferite de zero.

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia.


#include <iostream.h>
void main()
{ int a=0, b=10, c=100, d=200; int rezult; rezult=a&&b;
cout<<”a&&b=”<<rezult<<’\n’; //Afişare a&&b=0
rezult=a||b; cout<<”a||b=”<<rezult<<’\n’;//Afişare a||b=1 (sau valoare nenula)
rezult=!a;cout<<”!a=”<<rezult<<’\n’; //Afişare !a=1 (sau valoare nenula)
rezult=!b; cout<<”!b=”<<rezult<<’\n’; //Afişare !b=0
rezult=(a>b) || (b>c);cout<<”(a>b) || (b>c)=”<<rezult<<’\n’;
//Afişare (a>b) || (b>c) =1(sau valoare nenula)
rezult=!(c<d);cout<<”!(c<d)=”<<rezult<<’\n’;//Afişare !(c>d)=0
rezult=(a-b)&&1;cout<<”(a-b)&&1=”<<rezult<<’\n’;
//Afişare (a-b)&&1 =1(sau valoare nenula)

35
CAPITOLUL 2 Date, operatori şi expresii
rezult=d||b&&a;cout<<”d||b&&a=”<<rezult<<’\n’;//Afişare d||b&&a =1
}// În evaluarea expresiilor din exemplu, s-au aplicat priorităţile operatorilor, indicate în tabelul. 2.6.
Tabelul 2.6.
Clasă de operatori Operatori Asociativitate
Unari ! - (unar) ++ -- de la dreapta la stânga
Multiplicativi * / % de la stânga la dreapta
Aditivi + - de la stânga la dreapta
Atribuire = de la dreapta la stânga
relaţionali < <= > >= de la stânga la dreapta
de egalitate == != de la stânga la dreapta
logici && de la stânga la dreapta
logici || de la stânga la dreapta
atribuire şi aritmetici binari = *= /= %= += -= de la dreapta la stânga

Exerciţiu: Să se scrie un program care citeşte un număr real şi afişează 1 dacă numărul citit aparţine unui
interval ale cărui limite sunt introduse tot de la tastatură, sau 0 în caz contrar.
#include <iostream.h>
void main()
{
double lmin, lmax, nr;cout<<"Numar=";cin>>nr;
cout<<”Limita inferioară a intervalului:”; cin>>lmin;
cout<<”Limita superioară a intervalului:”; cin>>lmax;
cout<<(nr>=lmin && nr<=lmax); }

 Operatori logici pe bit


Operator Semnificaţie Exemple
~ Negaţie (cod complementar faţă de unu) ~a
& AND (Conjuncţie, şi logic pe bit a & 0377
| OR (Disjuncţie, sau logic pe bit) a | 0377
^ XOR (Sau exclusiv logic pe bit) a^b
<< Deplasare stânga 0377 << 2
>> Deplasare dreapta 0377 >> 2

Aceşti operatori nu se aplică numerelor reale, ci numai datelor de tip întreg sau caracter. Primul operator este
unar, ceilalţi binari. Operatorii acţionează la nivel de bit, la nivelul reprezentării interne (în binar), conform
tabelelului 2.7.

Tabelul 2.7.
x y x&y x|y x^y ~x
1 1 1 1 0 0
1 0 0 1 1 0
0 1 0 1 1 1
0 0 0 0 0 1

Operatorul ~ are aceeaşi prioritate ca şi ceilalţi operatori unari. El furnizează complementul faţă de unu al
unui întreg, adică va schimba fiecare bit de pe 1 în zero şi invers. Operatorii de deplasare pe bit (<< şi >>)
efectuează deplasarea la stânga sau la dreapta a operandului stâng, cu numărul de biţi indicaţi de operandul
drept. Astfel, x<<2 deplasează biţii din x la stânga, cu două poziţii, introducând zero pe poziţiile rămase
vacante.
Exemple:
int a=3; //Reprezentare internă a lui a (pe 2 octeţi): 0000000000000011
int b=5; //Reprezentare internă a lui b (pe 2 octeţi): 0000000000000101
int rez=~a;
cout<<"~"<<a<<'='<<rez<<'\n'; //~3= -4
//Complementul faţă de unu este: 1111111111111100 (în octal: 0177777774 (!a= - 4)

36
CAPITOLUL 2 Date, operatori şi expresii
rez=a & b; cout<<a<<'&'<<b<<'='<<rez<<'\n'; //3&5=1
//a&b=0000000000000001 =1
rez=a^b; cout<<a<<'^'<<b<<'='<<rez; // 3^5= 6
//a ^b = 0000000000000110
rez=a|b; cout<<a<<'|'<<b<<'='<<rez; //3|5= 7
//a | b = 0000000000000111
rez=a<<2; cout<<a<<"<<"<<3<<'='<<rez; //3<<2=16=2*2 3
//a<<2= 0000000001100000
rez=5>>2; cout<<b<<">>"<<2<<'='<<rez; //5>>2=1=5/2 2
//b>>2= 0000000000000001

Operatorul binar ^ îşi găseşte o utilizare tipică în expresii ca: x&^077, care maschează ultimii 6 biţi ai lui x
pe zero.
Operatorul & este adesea utilizat în expresii ca x&0177, unde setează toţi biţii pe zero, cu excepţia celor de
ordin inferior din x.
Operatorul | este utilizat în expresii ca: x&MASK , unde setează pe unu biţii care în x şi masca MASK sunt
setaţi pe unu.
Operatorii logici pe bit & şi | sunt diferiţi de operatorii logici && şi || (pe cuvânt).
Deplasarea la stânga a unei date cu n poziţii este echivalentă cu înmulţirea valorii acesteia cu 2 n . Deplasarea
la dreapta a unei date fără semn cu n poziţii este echivalentă cu împărţirea valorii acesteia cu 2 n .
Combinând operatorii logici pe bit cu operatorul de atribuire, se obţin operatorii:
&=, ^=, |=, <<=, >>=.

 Operatorul condiţional
Este un operator ternar (necesită 3 operanzi), utilizat în construcţii de forma:
expresie1?expresie2:expresie3

Se evaluează expresia1. Dacă aceasta are o valoare diferită de zero, atunci tipul şi valoarea întregii expresii
vor fi aceleaşi cu tipul şi valoarea expresiei2. Altfel (dacă expresie1 are valoarea zero), tipul şi valoarea
întregii expresii vor fi aceleaşi cu tipul şi valoarea expresiei3. Deci operatorul condiţional este folosit pentru
a atribui întregii expresii tipul şi valoarea expresiei2 sau a expresiei3, în funcţie de o anumită condiţie. Acest
lucru este echivalent cu:
Dacă expresie1 diferită de zero
Atunci evaluează expresie2
Altfel evaluează expresie3
Exemplu:
int semn=(x<0)?-1:1
Dacă x<0, atunci semn=-1, altfel semn=1.

 Operatorul virgulă
Este utilizat în construcţii de forma:
expresie1 , expresie2
Operatorul virgulă forţează evaluarea unei expresii de la stânga la dreapta. Tipul şi valoarea întregii expresii
este dată de tipul şi valoarea expresiei2. Operatorul virgulă este folosit în instrucţiunea for. Operatorul
virgulă are cea mai mică prioritate.
Exemplu:
int x, c, y;
cout<<”Astept val. ptr. y:”; cin>>y;
x=(c=y, c<=5); /* c va primi valoarea lui y (citită); se verifică dacă c este mai mic sau
egal cu 5. Daca nu, x=0; daca da, x=1 sau x=valoare diferită de zero)*/
x++, y--; //întâi este incrementat x, apoi este decrementat y
 Operatorul sizeof()
Este un operator unar, care are ca rezultat numărul de octeţi pe care este memorată o dată de un
anumit tip. Operandul este un tip sau o dată (constantă sau variabilă) de un anumit tip.
Exemple:

37
CAPITOLUL 2 Date, operatori şi expresii
cout<<sizeof(int); // afişează numărul de octeţi pe care este memorat un întreg (2)
cout<<sizeof(”ab6*”);// afişează 5, nr. de octeţi pe care este memorată constanta şir ”ab6*”

 Operatorul (tip)
Este un operator unar care apare în construcţii numite ”cast” şi converteşte tipul operandului său la
tipul specificat între paranteze.
Exemple:
int a; (float) a; // converteşte operandul a (care era de tip întreg) în float

În afara operatorilor prezentaţi, există şi alţii, pe care îi vom enumera în continuare. Despre aceşti operatori
vom discuta în capitolele viitoare, când cunoştinţele acumulate vor permite acest lucru.

 Operatorul unar *
Este operator unar, numit şi operator de deferenţiere. Se aplică unei expresii de tip pointer şi este
folosit pentru a accesa conţinutul unei zone de memorie spre care pointează operatorul. Operatorii &
(adresă) şi * sunt complementari.
Exemplu: Expresia *a este înlocuită cu valoarea de la adresa conţinută în variabila pointer a.

 Operatorii paranteză
Parantezele rotunde ( ) se utilizează în expresii, pentru schimbarea ordinii de efectuare a
operaţiilor, sau la apelul funcţiilor. La apelul funcţiilor, parantezele rotunde încadrează lista
parametrilor efectivi. Din acest motiv, parantezele rotunde sunt numite şi operatori de apel de
funcţie.
Exemplu:
double sum(double a, double b);
/*declar. funcţiei sum, care primeşte 2 argumente reale(double) şi returnează o valoare tip double */
void main()
{
. . .
double a=sum(89.9, 56.6); //apelul funcţiei sum, cu parametri efectivi 89.9 şi 56.6
int s0=6; double s1=(s0+9)/a; //folosirea parantezelor în expresii
. . .
}

 Operatorii de indexare
Operatorii de indexare sunt parantezele pătrate []. Acestea includ expresii întregi care reprezintă
indici ai unui tablou.

 Operatori de acces la membri structurilor


Operatorii ::, ., ->, .* şi ->* permit accesul la componentele unei structuri. Ei vor fi studiaţi în
capitolul 7.

În tabelul 2.8. sunt prezentaţi toţi operatorii, grupaţi pe categorii, cu priorităţile lor şi regulile de
asociativitate. Operatorii dintr-o categorie au aceeaşi prioritate.

Tabelul 2.8.
Nr. Clasă de operatori Operatori Asociativitate
1. Primari () [] . -> :: de la stânga la dreapta
2. Unari ! ~ ++ -- sizeof (tip) de la stânga la dreapta
-(unar) *(deferenţiere) &(referenţiere)
3. Multiplicativi * / % de la stânga la dreapta
4. Aditivi + - de la stânga la dreapta
5. Deplasare pe bit << >> de la stânga la dreapta
6. Relaţionali < <= > >= de la stânga la dreapta
7. De egalitate == != de la stânga la dreapta

38
CAPITOLUL 2 Date, operatori şi expresii
8. & (ŞI logic pe bit) de la stânga la dreapta
9. ^ (XOR pe bit) de la stânga la dreapta
10. | (SAU logic pe bit) de la stânga la dreapta
11. && de la stânga la dreapta
12. || de la stânga la dreapta
13. Condiţional ?: de la dreapta la stânga
14. De atribuire = += -= *= %= de la dreapta la stânga
&= ^= |= <<= >>=
15. Virgulă , de la stânga la dreapta

2.6.2. EXPRESII
Prin combinarea operanzilor şi a operatorilor se obţin expresii. Tipul unei expresii este dat de tipul
rezultatului obţinut în urma evaluării acesteia. La evaluarea unei expresii se aplică regulile de prioritate şi
asociativitate a operatorilor din expresie. Ordinea de aplicare a operatorilor poate fi schimbată prin folosirea
parantezelor. La alcătuirea expresiilor, este indicată evitarea expresiilor în care un operand apare de mai
multe ori.

2.6.3. CONVERSII DE TIP


La evaluarea expresiilor, se realizează conversii ale tipului operanzilor. Conversiile sunt:
 Automate;
 Cerute de evaluarea expresiilor;
 Cerute de programator (prin construcţiile cast), explicite.

Conversiile automate sunt realizate de către compilator:


char, short int -> int
Ele sunt realizate de fiecare dată când într-o expresie apar operanzi de tipul char sau short int.
Conversiile cerute de evaluarea expresiilor sunt efectuate în cazurile în care în expresii apar operanzi de
tipuri diferite. Înaintea aplicării operatorilor, se realizează conversia unuia sau a ambilor operanzi:
 Dacă un operand este de tip long int, celălalt este convertit la acelaşi tip; tipul expresiei este long int.
 Dacă un operand este de tipul double, celălalt este convertit la acelaşi tip; tipul expresiei este double.
 Dacă un operand este de tipul float, celălalt este convertit la acelaşi tip; tipul expresiei este float.
Conversiile explicite (cerute de programator) se realizează cu ajutorul construcţiilor cast.
Exemplu:
int x=3; float y; y=(float)x/2;
Înainte de a se efectua împărţirea celor 2 operanzi, operandul x (întreg) este convertit în număr real simplă
precizie. După atribuire, valoarea lui y va fi 1.5. Dacă nu ar fi fost folosit operatorul de conversie în expresia
y=x / 2, operanzii x şi 2 fiind întregi, rezultatul împărţirii este întreg, deci y ar fi avut valoarea 1.

ÎNTREBĂRI ŞI EXERCIŢII
Chestiuni teoretice

1. Ce reprezintă datele şi care sunt atributele lor? 11. Ce loc ocupă declararea varibilelor în cadrul
2. Care sunt diferenţele între constante şi unui program sursă scris în limbajul C++?
variabile? 12. Ce conţin fişierele header?
3. Cine determină tipul unei constante? 13. Ce tipuri de variabile se utilizează pentru
4. Ce sunt identificatorii? datele numerice?
5. Ce sunt directivele preprocesor? 14. Care sunt calificatorii folosiţi alături de
6. Ce reprezinta variabilele? tipurile de bază pentru obţinerea tipurilor
7. Ce sunt constantele? derivate de date?
8. Enumeraţi tipurile simple de variabile. 15. Ce semnifică parantezele unghiulare < > care
9. Câte tipuri de directive preprocesor încadrează numele unui fişier header?
cunoasteţi? Exemple. 16. Care este diferenţa între constantele 35.2e-1 şi
10. Care este modalitatea de a interzice 3.52 ? Dar între "\t" şi '\t'?
modificarea valorii unei variabile? 17. Ce tip are constanta 6.44 ?

39
CAPITOLUL 2 Date, operatori şi expresii
18. Care este diferenţa între operatorii = şi = = ? 25. Constante reale.
19. Ce reprezintă caracterele "escape"? 26. Ce operatori ternari cunoasteţi?
20. Constante întregi. 27. Operatorul virgulă.
21. Constante caracter. 28. Operatorul sizeof.
22. Ce tipuri de conversii cunoaşteţi? 29. Operatori aritmetici binari compuşi.
23. Care sunt conversiile realizate în mod 30. Operatorul de referenţiere.
automat, de către compilator? 31. Operatori relaţionali binari.
24. Constante şir de caractere.

Chestiuni aplicative

1. Să se scrie declaraţiile pentru definirea constantelor simbolice: pi, g (acceleraţia gravitaţională),


unghi_drept, dimensiune_MAX.
2. Care va fi rezultatul afişat pe ecran în urma execuţiei următoarelor secvenţe de instrucţiuni:
 double a=9/2; cout<<a*5<<’\n’;
 double a=9.7, b=5.6; cout<<(a+6<b)<<’\n’;
 double a=9/4; cout<<a*6<<’\n’;
 double x=3;int y=++x+5;cout<<y<<’\n’;
 int a=7; cout<<(!a)<<’\n’;
 int a=10.5; cout<<a++<<’\n’; cout<<a<<’\n’;
 int a=7; cout<<++a<<’\n’; cout<<a<<’\n’;
 int a=10; cout<<a++<<’\n’; cout<<a<<’\n’;
 double a=7/2; cout<<a<<’\n’;
 int x=3; int y=x++-2; cout<<y<<’\n’;
 int x=3; int y=++x+5; cout<<y<<’\n’;
 double a=5.6, b=7.45; cout<<(a>b)<<’\n’;
3. Să se verifice corectitudinea următoarelor secvenţe. Pentru cele incorecte, explicaţi sursa erorilor.
 double a=9.7, b=5.2; int c=(a+6<b)++; cout<<c<<’\n’;
 double a=7/5; double c=a*5++; cout<<c<<’\n’;
 double a=9.7, b=5.6; int c=(a%6<b)++; cout<<c<<’\n’;
 double a=5.6, b=7.45; cout<<++(a+5>b)<<’\n’;
 double a=9.8; double b=9.7; cout<<a%b<<’\n’;
 cout<<&(a+8)<<'\n';
 int I=8; cout<<(I+10)++<<'\n';
 double a=8.7; A=(a+8)/56; cout<<A<<'\n';
 int x=3/5; int y=x++; char x='J'; cout<<"y="<<y<<'\n';
 char a='X'; const int b=89; b+=8; cout<<"b="<<b<<" a="<<a<<'\n';
4. Să se scrie un program care afişează următoarele mesaje:
 Sirul "este dupa-amiaza" este memorat pe .... octeti.
 O marime intreaga este memorata pe ... octeti.
 O marime reala, in simpla precizie este memorata pe ... octeti!
 O marime reala, in dubla precizie este memorata pe ... byti!
 Constanta caracter 'Q' memorata pe ... octeti!
 Sirul "a\n\n" este memorat pe ... octei!
 Sirul "\n" este memorat pe ... biti!
 Caracterul '\' este memorat pe .... biti.
5. Să se evalueze expresiile, ştiind că: int i=1;int j=2;int k=-7;double x=0;double y=2.3;
 -i - 5 * j >= k + 1
 3 < j < 5
 i + j + k == -2 * j
 x && i || j - 3
6. Ce operaţie logică şi ce mască trebuie să folosiţi pentru a converti codurile ASCII ale literelor mici în
litere mari? Dar pentru conversia inversă?
7. O deplasare la dreapta cu 3 biţi este echivalentă cu o rotaţie la stânga cu câţi biţi?
8. Să se seteze pe 1 toţi biţii dintr-un octet, cu excepţia bitului cel mai semnificativ.
9. Să se scrie un program care citeşte o valoare întreagă. Să se afişeze un mesaj care să indice dacă numărul
citit este par sau impar.
10. Să se citeasca două valori întregi. Să se calculeze şi să se afişeze restul împărţirii celor două numere.

40
CAPITOLUL 3 Implementarea structurilor de
control

IMPLEMENTAREA STRUCTURILOR
DE CONTROL
3
3.1. Implementarea structurii secvenţiale 3.3. Implementarea structurilor repetitive
3.2. Implementarea structurii de decizie 3.4. Facilităţi de întrerupere a unei secvenţe

Algoritmul proiectat pentru rezolvarea unei anumite probleme trebuie implementat într-un limbaj de
programare; prelucrarea datelor se realizează cu ajutorul instrucţiunilor. Instrucţiunea descrie un proces de
prelucrare pe care un calculator îl poate executa. O instrucţiune este o construcţie validă (care respectă sintaxa
limbajului) urmată de ; . Ordinea în care se execută instrucţiunile unui program defineşte aşa-numita
structură de control a programului.

Limbajele moderne sunt alcătuite pe principiile programării structurate. Conform lui C. Bohm şi G. Jacobini,
orice algoritm poate fi realizat prin combinarea a trei structuri fundamentale:
 structura secvenţială;
 structura alternativă (de decizie, de selecţie);
 structura repetitivă (ciclică).

3.1. IMPLEMENTAREA STRUCTURII SECVENŢIALE

Structura secvenţială este o înşiruire de secvenţe de prelucrare (instrucţiuni), plasate una după alta, în ordinea
în care se doreşte execuţia acestora.

Reprezentarea structurii secvenţiale cu


ajutorul schemei logice ( figura 3.1.): Reprezentarea structurii secvenţiale cu
ajutorul pseudocodului:
S1 instr1;
instr2;
. . . . .
S2

Sn

Figura 3.1. Schema logică pentru structura

secvenţială
Implementarea structurii secvenţiale se realizează cu ajutorul instrucţiunilor:
 Instrucţiunea vidă
Sintaxa: ;
Instrucţiunea vidă nu are nici un efect. Se utilizează în construcţii în care se cere prezenţa unei instrucţiuni,
dar nu se execută nimic (de obicei, în instrucţiunile repetitive).
Exemple:
int a;
. . . . . .
int j;
;
for (;;)
{

41
CAPITOLUL 3 Implementarea structurilor de
control
. . . .
}
 Instrucţiunea expresie
Sintaxa: expresie;
sau: apel_funcţie;

Exemple:
int b, a=9;
double c;
b=a+9;
cout<<a;
c=sqrt(a);
clrcsr();//apelul funcţiei predefinite care şterge ecranul; prototipul în headerul conio.h

 Instrucţiunea compusă (instrucţiunea bloc)


Sintaxa: {
declaratii;
instr1;
instr2;
. . . .
}
Într-un bloc se pot declara şi variabile care pot fi accesate doar în corpul blocului. Instrucţiunea bloc este
utilizată în locurile în care este necesară prezenţa unei singure instrucţiuni, însă procesul de calcul este mai
complex, deci trebuie descris în mai multe secvenţe.

3.2. IMPLEMENTAREA STRUCTURII DE DECIZIE (ALTERNATIVE, DE SELECŢIE)

Reprezentarea prin schemă logică şi prin pseudocod a structurilor de decizie şi repetitive sunt descrise în
capitolul 1. Se vor prezenta în continure doar instrucţiunile care le implementează.

 Instrucţiunea if:
Sintaxa:
if (expresie)
instrucţiune1;
[ else
instrucţiune2; ]

Ramura else este opţională.


La întâlnirea instrucţiunii if, se evaluează expresie (care reprezintă o condiţie) din paranteze. Dacă
valoarea expresiei este 1 (condiţia este îndeplinită) se execută instrucţiune1; dacă valoarea expresiei este
0 (condiţia nu este îndeplinită), se execută instrucţiune2. Deci, la un moment dat, se execută doar una
dintre cele două instrucţiuni: fie instrucţiune1, fie instrucţiune2. După execuţia instrucţiunii if se trece la
execuţia instrucţiunii care urmează acesteia.

Observaţii:
1. Instrucţiune1 şi instrucţiune2 pot fi instrucţiuni compuse (blocuri), sau chiar alte instrucţiuni if
(if-uri imbricate).
2. Deoarece instrucţiunea if testează valoarea numerică a expresiei (condiţiei), este posibilă prescurtarea: if
(expresie), în loc de if (expresie != 0).
3. Deoarece ramura else a instrucţiunii if este opţională, în cazul în care aceasta este omisă din secvenţele
if-else imbricate, se produce o ambiguitate. De obicei, ramura else se asociază ultimei instrucţiuni if.
Exemplu:
if (n>0)
if (a>b)
z=a;
else z=b;

42
CAPITOLUL 3 Implementarea structurilor de
control
4. Pentru claritatea programelor sursă se recomandă alinierea instrucţiunilor prin utilizarea tabulatorului
orizontal.
5. Deseori, apare construcţia: Aceeaşi construcţie poate fi scrisă şi
if (expresie1) astfel:
instrucţiune1; if (expresie1)
else instrucţiune1;
if (expresie2) else if (expresie2)
instrucţiune2; instrucţiune2;
else else if (expresie3)
if (expresie3) instrucţiune3;
instrucţiune3; . . . . .. . . . . .
. . . . . . . . . else
else instrucţiune_n;
instrucţiune_n;

Expresiile sunt evaluate în ordine; dacă una dintre expresii are valoarea 1, se execută instrucţiunea
corespunzătoare şi se termină întreaga înlănţuire. Ultima parte a lui else furnizează cazul când nici una
dintre expresiile 1,2,. . ., n-1 nu are valoarea 1.
6. În cazul în care instrucţiunile din cadrul if-else sunt simple, se poate folosi operatorul condiţional.

Exerciţii:
1. Să se citească de la tastatură un număr real. Daca acesta se află în intervalul [-1000, 1000], să se afiseze
1, dacă nu, să se afiseze -1.
#include <iostream.h>
void main()
{
double nr; cout<<”Astept numar:”; cin>>nr;
int afis = (nr>= -1000 && nr <= 1000 ? 1 : -1); cout<<afis;
/* int afis;
if (nr >= -1000 && nr <= 10000)
afis = 1;
else afis= -1;
cout<<afis; */
}

2. Să se calculeze valoarea funcţiei f(x), ştiind că x este un număr real introdus de la tastatură:
- 6x + 20 , dacă x ∈ [- ∞ , -7 ]
f(x) = x + 30 , dacă x ∈ (-7, 0]
x , dacă x>0

#include <iostream.h> Sau: #include <iostream.h>


#include <math.h> #include <math.h>
void main() void main()
{ {
double x,f;cout<<”x=”;cin>>x; double x,f;cout<<”x=”;cin>>x;
if (x <= -7) if (x <= 7)
f= -x* 6 +20; f= -x* 6 +20;
else if (x>=-7 && x<=0 )
if ( x<=0 ) f= x+30;
f= x+30; if (x>0) f=sqrt(x);
else f=sqrt(x); cout<<”f=”<<f<<’\n’;
cout<<”f=”<<f<<’\n’; }
}

Uneori, construcţia if-else este utilizată pentru a compara valoarea unei variabile cu diferite valori constante,
ca în programul următor:

43
CAPITOLUL 3 Implementarea structurilor de
control
3. Se citeşte un caracter reprezentând un operator aritmetic binar simplu. În funcţie de caracterul citit, se
afişează numele operaţiei pe care acesta o poate realiza.
#include <iostream.h>
void main()
{
char oper;
cout<<”Introdu operator aritmetic, simplu, binar:”; cin>>oper;
if (oper == ’+’)
cout<<”Operatorul de adunare!\n”;
else if (oper==’-’ )
cout<<”Operatorul de scadere!\n”;
else if (oper==’*’ )
cout<<”Operatorul de inmultire!\n”;
else if (oper==’/’ )
cout<<”Operatorul de impartire!\n”;
else if (oper==’%’ )
cout<<”Operatorul rest!\n”;
else cout<<”Operator ilegal!!!\n”;
}

 Instrucţiunea switch
În unele cazuri este necesară o decizie multiplă specială. Instrucţiunea switch permite acest lucru.
Reprezentare prin schema logică (figura 3.2):
Reprezentare prin pseudocod:

test_expresie
Dacă expresie=expr_const_1
instrucţiune1;
instrucţiune1 [ieşire;]
break Altfel dacă expresie=expr_const_2
instrucţiune2;
[ieşire;]
instrucţiune2
break
Altfel dacă expresie=expr_const_n-1
instrucţiune_n-1;
[ieşire;]
instrucţiune_n Altfel instrucţiune_n;

Figura 3.2. Decizia multiplă


Se testează dacă valoarea pentru expresie este una dintre constantele specificate (expr_const_1,
expr_const_2, etc.) şi se execută instrucţiunea de pe ramura corespunzătoare. În schema logică
test_expresie este una din condiţiile: expresie=expr_const_1, expresie=expr_const_2, etc.
Sintaxa:
switch (expresie)
{
case expresie_const_1: instructiune_1;
[break;]
case expresie_const_2: instructiune_2;
[break;]
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
case expresie_const_n-1: instructiune_n-1;
[break;]

44
CAPITOLUL 3 Implementarea structurilor de
control
[ default: instructiune_n; ]
}
Este evaluată expresie (expresie aritmetică), iar valoarea ei este comparată cu valoarea expresiilor
constante 1, 2, etc. (expresii constante=expresii care nu conţin variabile). În situaţia în care valoarea
expresie este egală cu valoarea expr_const_k, se execută instrucţiunea corespunzătoare acelei ramuri
(instrucţiune_k). Dacă se întâlneşte instrucţiunea break, parcurgerea este întreruptă, deci se va trece
la execuţia primei instrucţiuni de după switch. Dacă nu este întâlnită instrucţiunea break, parcurgerea
continuă. Break-ul cauzează deci, ieşirea imediată din switch.
În cazul în care valoarea expresiei nu este găsită printre valorile expresiilor constante, se execută cazul marcat
cu eticheta default (când acesta există). Expresiile expresie, expresie_const_1,
expresie_const_2,etc., trebuie să fie întregi. În exemplul următor, ele sunt de tip char, dar o dată de
tip char este convertită automat în tipul int.

Exerciţiu: Să rescriem programul pentru problema 3, utilizând instrucţiunea switch.


#include <iostream.h>
void main()
{
char oper;
cout<<”Introdu operator aritmetic, simplu, binar:”;
cin>>oper;
switch (oper)
{
case (’+’):
cout<<”Operatorul de adunare!\n”;
break;
case (’-’):
cout<<”Operatorul de scadere!\n”;
break;
case (’*’):
cout<<” Operatorul de inmultire!\n”;
break;
case (’/’):
cout<<”Operatorul de impartire!\n”;
break;
case (’%’):
cout<<”Operatorul rest!\n”;
break;
default:
cout<<”Operator ilegal!\n”;
}
}

3.3. IMPLEMENTAREA STRUCTURILOR REPETITIVE (CICLICE)

Există două categorii de instrucţiuni ciclice: cu test iniţial şi cu test final.

3.3.1. Implementarea structurilor ciclice cu test iniţial

Structura ciclică cu test iniţial este implementată prin instrucţiunile while şi for.
 Instrucţiunea while
Sintaxa:
while (expresie)
instructiune;

La întâlnirea acestei instrucţiuni, se evaluează expresie. Dacă aceasta are valoarea 1 - sau diferită de 0 -
(condiţie îndeplinită), se execută instrucţiune. Se revine apoi în punctul în care se evaluează din nou

45
CAPITOLUL 3 Implementarea structurilor de
control
valoarea expresiei. Dacă ea este tot 1, se repetă instrucţiune, ş.a.m.d. Astfel, instrucţiunea (corpul
ciclului) se repetă atât timp cât expresie are valoarea 1. În momentul în care expresie ia valoarea 0
(condiţie neîndeplinită), se iese din ciclu şi se trece la următoarea instrucţiune de după while.

Observaţii:
1. În cazul în care la prima evaluare a expresiei, aceasta are valoarea zero, corpul instrucţiunii while nu va fi
executat niciodată.
2. Instrucţiune din corpul ciclului while poate fi compusă (un bloc), sau o altă instrucţiune ciclică.
3. Este de dorit ca instrucţiunea din corpul ciclului while să modifice valoarea expresiei. Dacă nu se
realizează acest lucru, corpul instrucţiunii while se repetă de un număr infinit de ori.
Exemplu:
int a=7;
while (a==7)
cout<<”Buna ziua!\n”; // ciclu infinit; se repetă la infinit afişarea mesajului

 Instrucţiunea for
În majoritatea limbajelor de programare de nivel înalt, instrucţiunea for implementează structura ciclică cu
număr cunoscut de paşi (vezi reprezentarea prin schema logică şi pseudocod din capitolul 1). În limbajul C
instrucţiunea for poate fi utilizată într-un mod mult mai flexibil.

Reprezentare prin schema logică (figura 3.3.):


Reprezentare în pseudocod:

evaluare expresie1 (particular iniţializare contor) evaluare expresie1


CÂT TIMP expresie2
0 REPETĂ
expresie2 ÎNCEPUT
instrucţiune
1 evaluare expresie3
instrucţiune SFÂRŞIT

evaluare expresie3 (particular


incrementare contor)

Figura 3.3. Structura ciclică cu test iniţial


Sintaxa:
for (expresie1; expresie2; expresie3)
instructiune;

Nu este obligatorie prezenţa expresiilor, ci doar a instrucţiunilor vide.


Exemplu:
for ( ; expresie2; ) sau: for ( ; ; )
instructiune; instructiune;

3.3.2. Implementarea structurilor ciclice cu test final

 Instrucţiunea do-while
Sintaxa:
do instructiune;
while(expresie);

Se execută instrucţiune. Se evaluează apoi expresie. Dacă aceasta are valoarea 1, se execută
instrucţiune. Se testează din nou valoarea expresiei. Se repetă instrucţiune cât timp valoarea

46
CAPITOLUL 3 Implementarea structurilor de
control
expresiei este 1 (condiţia este îndeplinită). În cazul instrucţiunii do-while, corpul ciclului se execută cel puţin o
dată.

Exerciţii:
1. Se citeşte câte un caracter, până la întâlnirea caracterului @. Pentru fiecare caracter citit, să se afişeze un
mesaj care să indice dacă s-a citit o literă mare, o literă mică, o cifră sau un alt caracter. Să se afişeze câte
litere mari au fost introduse, câte litere mici, câte cifre şi câte alte caractere. Se prezintă trei modalităţi de
implementare (cu instrucţiunea while, cu instrucţiunea for şi cu instrucţiunea do-while).
#include <iostream.h> Observaţii legate de implementare
#include <conio.h> Variabila c (tip char) memorează caracterul
void main() introdus la un moment dat, de la tastatură.
{ char c; clrscr();
Variabilele întregi lmic, lmare, lcif şi altcar
int lmic=0, lmare=0, lcif=0;
int altcar=0;
sunt utilizate pe post de contor pentru litere
cout<<"Aştept car.:"; cin>>c; mari, mici, cifre, respectiv alte caractere.
while (c!='@'){ Acţiunea care se repetă cât timp caracterul
if (c>='A' && c<='Z') { citit este diferit de constanta caracter '@'
cout<<"Lit. mare!\n"; constă din mai multe acţiuni simple: citirea
lmare++; } unui caracter (cu afişarea în prealabil a
else if (c>='a' && c<='z') { mesajului "Aştept car.:"; testarea
cout<<"Lit. mică!\n"; caracterului citit (operatorii relaţionali pot fi
lmica++; } aplicaţi datelor de tip char).
else if (c>='0' && c<='9') { Ca urmare, acţiunea din corpul instructiunii
cout<<"Cifră!\n"; while a fost implementată printr-o
lcif++; } instrucţiune bloc.
else { Tot instrucţiuni bloc au fost utilizate pe
cout<<"Alt car.!\n"; fiecare ramură a instrucţiunii if (afişare
altcar++; } mesaj referitor la caracter şi incrementare
cout<<"Aştept car.:";cin>>c; contor).
}
cout<<"Aţi introdus \n";
cout<<lmare<<" litere mari, ";
cout<<lmic<<" litere mici\n";
cout<<lcif<<" cifre şi \n";

#include <iostream.h> Pentru implementarea aceluiaşi algoritm


#include <conio.h> se poate utiliza instrucţiunea for. În
void main() cadrul acesteia, expresie1 şi expresie3
{ char c;clrscr(); lipsesc, însă prezenţa instrucţiunilor vide
intlmic=0,lmare=0,lcif=0;int altcar=0; este obligatorie.
cout<<"Aştept caract.:"; cin>>c;
for( ; c!='@'; ){
// corp identic
}
cout<<"Aţi introdus \n";
cout<<lmare<<" litere mari, ";
cout<<lmic<<" litere mici\n";
cout<<lcif<<" cifre şi \n";
cout<<altcar<<" alte carctere\n";

O altă variantă de implementare poate fi următoarea, în care şi iniţializarea variabilelor contor se realizează
în cadrul expresiei expresie1.

int lmic, lmare, lcif, altcar;


for(lmare=0, lmic=0, lcif=0, altcar=0; c!='@'; ){
// corp identic
}

47
CAPITOLUL 3 Implementarea structurilor de
control
Variantă de implementare care utilizează instrucţiunea do-while:
int lmic=0, lmare=0, lcif=0;
int altcar=0;
cout<<"Aştept caract.:";cin>>c;
do {
//corp do-while
} while (c!='@');
cout<<"Aţi introdus \n";
//. . .
2. Să se calculeze suma şi produsul primelor n numere naturale, n fiind introdus de la tastatură. Se vor
exemplifica modalităţile de implementare cu ajutorul instrucţiunilor do-while, while, şi for. (Se
n n
observă că: S = ∑k , P =
k =1
∏ k ).
k =1

cout<<"n="; int n; cin>>n; cout<<"n="; int n; cin>>n;


int S=0, P=1, k=1; int S=0, P=1, k=1;
while (k <= n){ do{
S+=k; P*=k; S+=k; P*=k;
k++; k++;
} } while (k <= n);
cout<<"P="<<P<<"\tS="<<S<<'\n'; cout<<"P="<<P<<"\tS="<<S<<'\n';
Pentru a ilustra multiplele posibilităţi oferite de instrucţiunea for, prezentăm variantele

// varianta1 // varianta2
int S=0, P=1, k; int S=0, P=1;
for (k=1; k<=n; k++){ for (int k=1; k<=n; k++){
S+=k; P*=k; S+=k; P*=k;
} }
cout<<"P="<<P<<"\tS="; cout<<"P="<<P<<"\tS=";
cout<<S<<'\n'; cout<<S<<'\n';

// varianta3
for (int S=0, P=1, k=1; k<=n; k++){
S+=k; P*=k;
}
cout<<"P="<<P<<"\tS="<<cout<<S<<'\n';

// varianta4
for (int S=0, P=1, k=1; k<=n; S+=k, P*=k, k++)
;
cout<<"P="<<P<<"\tS="<<cout<<S<<'\n';
3. Să se citească un şir de numere reale, până la întâlnirea numărului 900. Să se afişeze maximul numerelor
citite.
#include <iostream.h>
void main() Se presupune că primul element din şirul de
{double n; numere are valoarea maximă. Se memorează
cout<<"Introdu nr:"; cin>>n; valoarea sa în variabila max. Se parcurge apoi
double max=n; şirul, comparându-se valoarea fiecărui element
while (n!=900)
cu valoarea variabilei max. În cazul în care se
{ if (n>=max)
găseşte un element cu o valoare mai mare decât
max=n;
cout<<"Introdu nr:"; a variabilei max, se reţine noua valoare
cin>>n; (max=n).
}
cout<<"Max şir este:"<<max<<'\n';
4. Să} se afişeze literele mari ale alfabetului şi codurile aferente acestora în ordine crescătoare, iar literele
mici şi codurile aferente în ordine descrescătoare. Afişarea se va face cu pauză după fiecare ecran.

48
CAPITOLUL 3 Implementarea structurilor de
control

#include <iostream.h>
#include <conio.h>
#define DIM_PAG 22 //dimensiunea paginii (numarul de randuri de pe o pagina)
void main()
{clrscr();
cout<<"LITERELE MARI:\n";int nr_lin=0; // nr_lin este contorul de linii de pe un
ecran
for (char LitMare='A'; LitMare<='Z'; LitMare++){
if (nr_lin==DIM_PAG){
cout<<"Apasa o tasta...."; getch(); clrscr(); nr_lin=0;}
cout<<"Litera "<<LitMare<<" cu codul ASCII "<<(int)LitMare<<'\n';
// conversia explicita (int)LitMare permite afisarea codului ASCII al caracterului
nr_lin++;
}
cout<<"LITERELE MICI:\n";
for (char LitMica='z'; LitMica>='a'; LitMica--){
if (nr_lin==DIM_PAG){
cout<<"Apasa o tasta...."; getch(); clrscr(); nr_lin=0;}
cout<<"Litera "<<LitMica<<" cu codul ASCII "<<(int)LitMica<<'\n';
nr_lin++;
}
}

5. Să se scrie un program care realizează conversia numărului N întreg, din baza 10 într-o altă bază de
numeraţie, b<10 (N şi b citite de la tastatură). Conversia unui număr întreg din baza 10 în baza b se
realizează prin împărţiri succesive la b şi memorarea resturilor, în ordine inversă. De exemplu:
547:8=68 rest 3; 68:8=8 rest 4; 8:8=1 rest 0; 1:8=0 rest 1 547 10 = 1043 8

#include <iostream.h>
void main()
{ int nrcif=0,N,b,rest,Nv,p=1;
long Nnou=0;
cout<<"\nIntroduceti baza<10, b=";cin>>b;
cout<<"Introduceti numarul in baza 10, nr=";cin>>N;
Nv=N;
while(N!=0)
{
rest=N%b; N/=b; cout<<"nr="<<N<<'\n'; cout<<"rest="<<rest<<'\n';
nrcif++; Nnou+=rest*p; p*=10; cout<<"Nr. nou="<<Nnou<<'\n';
}
cout<<"Numarul de cifre este "<<nrcif<<'\n'; cout<<"Nr. in baza 10 "<<Nv;
cout<<" convertit in baza "<<b<<" este "<<Nnou<<'\n'; }


xk
6. Să se calculeze seria următoare cu o eroare mai mică decât EPS (EPS introdus de la tastatură): 1+ ∑
k =1 k
, x ∈ [0,1], x citit de la tastatură. Vom aduna la sumă încă un termen cât timp diferenţa dintre suma
calculată la pasul curent şi cea calculată la pasul anterior este mai mare sau egală cu EPS.

#include <iostream.h>
#include <conio.h>
#include <math.h>
void main()
{ double T,S,S1;long k;k=1;T=1;S=T;double x; cout<<"x="; cin>>x;
// T= termenul general de la pasul curent; S=suma la pasul curent; S1=suma la pasul anterior
do {
S1=S;k=k+1;T=pow(x,k)/k; //funcţia pow(x, k), aflată în <math.h> calculează x k
S=S+T; // cout<<k<<" "<<T<<" "<<S<<'\n';getch();

49
CAPITOLUL 3 Implementarea structurilor de
control
} while ((S-S1)>=EPS);
cout<<"Nr termeni="<<k<<" T="<<T<<" S="<<S<<'\n'; }
3.4. FACILITĂŢI DE ÎNTRERUPERE A UNEI SECVENŢE

Pentru o mai mare flexibilitate (tratarea excepţiilor care pot apare în procesul de prelucrare), în limbajul C se
utilizează instrucţiunile break şi continue. Ambele instrucţiuni sunt utilizate în instrucţiunile ciclice. În
plus, instrucţiunea break poate fi folosită în instrucţiunea switch.

 Instrucţiunea break
Aşa cum se observă din figura 3.4., utilizată în cadrul instrucţiunilor ciclice, instrucţiunea break "forţează"
ieşirea din acestea. Fără a se mai testa valoarea expresiei (condiţia) care determină repetarea corpului
instrucţiunii ciclice, se continuă execuţia cu instructiunea care urmează instructiunii ciclice. Astfel, se
întrerupe repetarea corpului instrucţiunii ciclice, indiferent de valoarea condiţiei de test.
Utilizarea în cadrul instrucţiunii switch: În situaţia în care s-a ajuns la o valoare a unei expresiei constante
egală cu cea a expresiei aritmetice, se execută instrucţiunea corespunzătoare acelei ramuri. Dacă se întâlneşte
instrucţiunea break, parcurgerea este întreruptă (nu se mai compară valoarea expresiei aritmetice cu
următoarele constante), deci se va trece la execuţia primei instrucţiuni de după switch. Dacă nu este întâlnit
break, parcurgerea continuă. Instrucţiunea breakl cauzează deci, ieşirea imediată din switch.

 Instrucţiunea continue
Întâlnirea instrucţiunii continue (figura 3.4.) determină ignorarea instrucţiunilor care o urmează în corpul
instrucţiunii ciclice şi reluarea execuţiei cu testarea valorii expresiei care determină repetarea sau nu a
corpului ciclului.

Exemplu: Să revenim la programul realizat pentru problema 1, care foloseşte instrucţiunea dowhile. Dacă
primul caracter citit este chiar caracterul @, se realizează testarea acestuia; ca urmare, se afişează mesajul
"Alt car.!" şi se incrementează valoarea contorului altcar. Dacă nu se doreşte ca acest caracter să fie
testat şi numărat, în corpul instrucţiunii do while putem face un test suplimentar.
int lmic=0,lmare=0,lcif=0,altcar=0;cout<<"Aştept caract.:";cin>>c;
do {
if (c == '@') break; //ieşire din do while
//corp do-while
} while (c!='@');
cout<<"Aţi introdus \n";
//. . .
do{
while (expresie1){ instructiune1;
instructiune1; instructiune2;
instructiune2; if (expresie2)
if (expresie2) break;
break; else
else continue;
continue; instructiune3;
instructiune3; } while (expresie1);
}

for (expr1; expr2; expr3)){


instructiune1;
instructiune2;
if (expresie2)
break;
else
continue;
instructiune3;
}

Figura 3.4. Modul de utilizare a instrucţiunilor break şi continue


50
CAPITOLUL 3 Implementarea structurilor de
control
ÎNTREBĂRI ŞI EXERCIŢII

Chestiuni teoretice

1. Care sunt instrucţiunile care implementează în 4. Care sunt instrucţiunile care implementează în
limbajul C structura condiţională? limbajul C structura repetitivă cu test final?
2. Care sunt instrucţiunile care implementează în 5. Ce deosebiri sunt între instrucţiunea while şi
limbajul C structura secvenţială? instrucţiunea do-while?
3. Care sunt instrucţiunile care implementează în 6. Pornind de la sintaxa instrucţiunii for, stabiliţi
limbajul C structura repetitivă cu test iniţial? echivalenţa între aceasta şi instrucţiunile while
şi do-while.

Chestiuni practice

1. Să se implementeze programele cu exemplele prezentate.


2. Să se scrie programele pentru exerciţiile rezolvate care au fost prezentate.
3. Să se implementeze algoritmii proiectaţi pentru problemele 1-7 din capitolul 1.
4. Să se calculeze aria unui triunghi, cunoscându-se mărimea laturilor sale. Numerele care reprezintă
mărimile laturilor vor fi introduse de utilizator. Se va testa mai întâi dacă cele 3 numere reprezentând
mărimea laturilor pot forma un triunghi ( a <= b+c, b <= c+d, c <= a+b).
5. Să se rescrie următoarea secvenţă, folosind o singură instrucţiune if.
if (n<0)
if (n>=90)
if (x!=0)
int b= n/x;
6. Să se citească un numar natural n. Să se scrie un program care afişează dacă numărul n citit reprezintă
sau nu, un an bisect (anii bisecţi sunt multipli de 4, exceptând multiplii de 100, dar incluzând multiplii de
400).
7. Să se găsească toate numerele de două cifre care satisfac relaţia:

xy =( x +y ) 2
8. Să se citească un şir de numere reale, până la întâlnirea numarului 800 şi să se afişeze valoarea minimă
introdusă, suma şi produsul elementelor şirului.
9. Scrieţi un program care să verifice inegalitatea 1/(n+1) < ln[(n+1)/n] < 1/n, unde n este un număr natural
pozitiv, introdus de la tastatură.
10. Fie funcţia
e x −3 , x ∈ [0, 1)
Să se calculeze f(x), x citit de la tastatură.
f(x)= sinx+cosx , x ∈ [1, 2)
0,9ln(x+3) , x ∈ [2, 100]
11. Să se scrie un program care calculează şi afişează maximul a 3 numere reale (a, b şi c) citite de la
tastatură.
12. Să se scrie un program care calculează şi afişează minimul a 3 numere reale (a, b şi c) citite de la
tastatură.
13. Să se citească 2 caractere care reprezintă 2 litere mari. Să se afişeze caracterele citite în ordine alfabetică.
14. Să se citească 3 caractere care reprezintă 3 litere mici. Să se afişeze caracterele citite în ordine alfabetică.
15. Să se scrie un program care citeşte o cifră. În funcţie de valoarea ei, să se facă următorul calcul: dacă
cifra este 3, 5 sau 7 să se afişeze pătratul valorii numerice a cifrei; dacă cifra este 2, 4 sau 6 să se afişeze
cubul valorii numerice a cifrei; dacă cifra este 0 sau 1 să se afişeze mesajul "Valori mici"; altfel., să se
afişeze mesajul "Caz ignorat!".
16. Fie şirul lui Fibonacci, definit astfel:
f(0)=0, f(1)=1, f(n)=f(n-1)+f(n-2) în cazul în care n>1.
Să se scrie un program care implementează algoritmul de calcul al şirului Fibonacci.
17. Să se calculeze valoarea polinomului Cebîşev de ordin n într-un punct x dat, cunoscând relaţia:
T 0 (x)=1, T 1 (x)=x şi T k +1 (x) - 2xT k (x) + T k −1 (x) = 0
18. Să se citească câte 2 numere întregi, până la întâlnirea perechii (0, 0). Pentru fiecare pereche de numere,
să se calculeze şi să se afişeze cel mai mare divizor comun.

51
CAPITOLUL 3 Implementarea structurilor de
control
19. Se citesc câte 3 numere reale, până la întâlnirea numerelor 9, 9, 9. Pentru fiecare triplet de numere citit, să
se afişeze maximul.
20. Se citeşte câte un caracter până la întâlnirea caracterului @. Să se afişeze numărul literelor mari, numarul
literelor mici şi numărul cifrelor citite; care este cea mai mare (lexicografic) literă mare, literă mică şi
cifră introdusă.
21. Se citesc câte 2 numere întregi, până la întâlnirea perechii de numere 9, 9. Pentru fiecare pereche de
numere citite, să se afişeze cel mai mare divizor comun al acestora.
22. Să se calculeze suma seriei
1 + x 3 /3 - x 5 /5 + x 7 /7 - …
cu o eroare mai mică decât epsilon (epsilon citit de la tastatură). Să se afişeze şi numărul de termeni ai
sumei.
23. Să se citească un număr întreg format din 4 cifre (abcd). Să se calculeze şi să se afişeze valoarea expresiei
reale: 4*a + b/20 -c + 1/d.
24. Să se scrie un program care afişează literele mari ale alfabetului în ordine crescătoare, iar literele mici - în
ordine descrescătoare.
25. Să se scrie un program care generează toate numerele perfecte până la o limită dată, LIM. Un număr
perfect este egal cu suma divizorilor lui, inclusiv 1 (exemplu: 6=1+2+3).
26. Să se calculeze valoarea sumei urmatoare, cu o eroare EPS mai mică de 0.0001:
S=1+(x+1)/ 2! + (x+2)/ 3! + (x+3)/ 3! + ... , unde 0<=x<=1, x citit de la tastatură.
27. Să se genereze toate numerele naturale de 3 cifre pentru care cifra sutelor este egală cu suma cifrelor
zecilor şi unităţilor.
28. Să se citească câte un număr întreg, până la întâlnirea numărului 90. Pentru fiecare numar să se afişeze un
mesaj care indică dacă numărul este pozitiv sau negativ. Să se afişeze cel mai mic număr din şir.
29. Să se genereze toate numerele naturale de 3 cifre pentru care cifra zecilor este egală cu diferenţa cifrelor
sutelor şi unităţilor.
30. Să se calculeze suma:
(1 + 2!) / (2 + 3!) - (2+3!) / (3+4!) + (3+4!) / (4+5!) - .....

52
CAPITOLUL 4
4 Tablouri

TABLOURI

4.1. Declararea tablourilor 4.3. Tablouri bidimensionale


4.2. Tablouri unidimensionale 4.4. Şiruri de caractere

4.1. DECLARAREA TABOURILOR

Numim tablou o colecţie (grup, mulţime ordonată) de date, de acelaşi tip, situate într-o
zonă de memorie continuă (elementele tabloului se află la adrese succesive). Tablourile
sunt variabile compuse (structurate), deoarece grupează mai multe elemente. Variabilele tablou
au nume, iar tipul tabloului este dat de tipul elementelor sale. Elementele tabloului pot fi
referite prin numele tabloului şi indicii (numere întregi) care reprezintă poziţia
elementului în cadrul tabloului.

În funcţie de numărul indicilor utilizaţi pentru a referi elementele tabloului, putem


întâlni tablouri unidimensionale (vectorii) sau multidimensionale (matricile sunt tablouri bidimensionale).

Ca şi variabilele simple, variabilele tablou trebuie declarate înainte de utilizare.


Modul de declarare:
tip nume_tablou[dim_1][dim_2]…[dim_n];
unde:tip reprezintă tipul elementelor tabloului; dim_1,dim_2,...,dim_n sunt numere
întregi sau expresii constante întregi (a căror valoare este evaluată la compilare) care
reprezintă limitele superioare ale indicilor tabloului.
Exemple:
//1
int vect[20]; // declararea tabloului vect, de maximum 20 de elemente, de tipul int.
// Se rezervă 20*sizeof(int)=20 * 2 = 40 octeţi
//2
double p,q,tab[10];
// declararea variabilelor simple p, q şi a vectorului tab, de maximum 10
elemente, tip double
//3
#define MAX 10
char tabc[MAX]; /*declararea tabloului tabc, de maximum MAX (10) elemente de tip char*/
//4
double matrice[2][3]; // declararea tabloului matrice (bidimensional),
// maximum 2 linii şi maximum 3 coloane, tip double

4.2. TABLOURI UNIDIMENSIONALE

Tablourile unidimensionale sunt tablouri cu un singur indice (vectori). Dacă tabloul


conţine dim_1 elemente, indicii elementelor au valori întregi din intervalul [0, dim_1-1].
La întâlnirea declaraţiei unei variabile tablou, compilatorul alocă o zonă de memorie
continuă (dată de produsul dintre dimensiunea maximă şi numărul de octeţi
corespunzător tipului tabloului) pentru păstrarea valorilor elementelor sale. Numele
tabloului poate fi utilizat în diferite expresii şi valoarea lui este chiar adresa de început
a zonei de memorie care i-a fost alocată. Un element al unui tablou poate fi utilizat ca
orice altă variabilă (în exemplul următor, atribuirea de valori elementelor tabloului
vector). Se pot efectua operaţii asupra fiecărui element al tabloului, nu asupra întregului tablou.

53
vector

CAPITOLUL 4 Tablouri

Exemplu:
// Declararea tabloului vector
int vector[6];

// Iniţializarea elementelor tabloului


vector[0]=100;
vector[1]=101;
vector[2]=102;
vector[3]=103;
vector[4]=104;
vector[5]=105;

Exemplu:
double alpha[5], beta[5], gama[5];
int i=2;
alpha[2*i-1] = 5.78;
alpha[0]=2*beta[i]+3.5;
gama[i]=aplha[i]+beta[i]; //permis
gama=alpha+beta; //nepermis

Variabilele tablou pot fi iniţializate în momentul declarării:


declaraţie_tablou=listă_valori;
Valorile din lista de valori sunt separate prin virgulă, iar întreaga listă este inclusă între
acolade:
Exemple:
//1
int vector[6]={100,101,102,103,104,105};

[0] [5]
//2
double x=9.8;
double a[5]={1.2, 3.5, x, x-1, 7.5};

La declararea unui vector cu iniţializarea elementelor sale, numărul maxim de


elemente ale tabloului poate fi omis, caz în care compilatorul determină automat
mărimea tabloului, în funcţie de numărul elementelor iniţializate.
Exemplu:
char tab[]={ ’A’, ’C’, ’D’, ’C’};

[0] [3]

float data[5]={ 1.2, 2.3, 3.4 };

[0] [4]
Adresa elementului de indice i dintr-un tablou unidimensional poate fi calculată astfel:
adresa_elementului_i = adresa_de_bază + i ∗ lungime_element

Exerciţii:
//1 Citirea elementelor unui vector:
double a[5];
int i;
for (i=0; i<5; i++)

54
Q=

CAPITOLUL 4 Tablouri
{ cout<<”a["<<i<<”]=”; //afişarea unui mesaj
prealabil citirii fiecărui element
cin>>a[i]; //citirea valorii elementului de indice i
}
//Sau:
double a[20]; int i, n;
cout<<”Dim. Max. =”; cin>>n;
for (i=0; i<n; i++)
{ cout<<”a[“<<i<<”]=”;
cin>>a[i];
}
//2 Afişarea elementelor unui vector:
cout<<”Vectorul introdus este:\n”;
for (i=0; i<n i++)
cout<<a[i]<<’ ’;
//3 Afişarea elementelor unui vector în ordine inversă:
cout<<”Elementele vectorului în ordine inversă:\n”;
for (i=n-1; i>=0 i++)
cout<<a[i]<<’ ’;
//3 Vectorul sumă (c) a vectorilor a şi b, cu acelaşi număr de elemente:
for (i=0; i<n i++)
c[i]=a[i]+b[i];
//4 Vectorul diferenţă (c) a vectorilor a şi b, cu acelaşi număr de elemente:
for (i=0; i<n i++)
c[i]=a[i] - b[i];
//5 Produsul scalar (p) a vectorilor a şi b, cu acelaşi număr de elemente:
n −1

∑ a ∗b i i
p= i = 0 double p=0;
for (i=0; i<n i++)
p += a[i] ∗ b[i];

4.2. TABLOURI BIDIMENSIONALE

Din punct de vedere conceptual, elementele unui tablou bidimensional sunt plasate în
spaţiu pe două direcţii. Matricea reprezintă o aplicaţie naturală a tablourilor
bidimensionale.
În matematică:
q 11 q 12 q 13 ... q 1n
q 21 q 22 q 23 ... q 2n
Q= .......................... Q m× n
q m1 q m2 q m3 ... q mn

În limbajele C/C++ (indicii de linie şi de coloană pornesc de la 0):


q 00 q 01 q 02 ... q 0,n−1
q 10 q 11 q 12 ... q 1,n−1 Q m× n
............................
q m−1, 0 q m−1,1 q m−1, 2 . . . q m−1,n −1

Exemplu:
double q[3][2]; // declararea matricii q, cu maxim3 linii şi 2 coloane, tip double

55
CAPITOLUL 4 Tablouri

În memorie, elementele unei matrici sunt memorate pe linii:


q 00 q 01 q 10 q 11 q 20 q 21 ...
Dacă notăm cu k poziţia în memorie a unui element, valoarea lui k = i ∗ m + j (unde
m este numărul maxim de linii, i este indicele de linie, j este indicele de coloană).

Dacă se doreşte iniţializarea elementelor unei matrici în momentul declarării acesteia,


se poate proceda astfel:
int mat[4][3] = {
{10, -50, 3},
{32, 20, 1},
{-1, 1, -2},
{7, -8, 19} };
Prin această construcţie, elementele matricii mat se iniţializează în modul următor:
mat[0][0]=10, mat[0][1]=-50, mat[0][2]=3
mat[1][0]=32, mat[1][1]=20, mat[1][2]=1
mat[2][0]=-1, mat[2][1]=1, mat[2][2]=-2
mat[3][0]=7, mat[3][1]=-8, mat[3][2]=19

La declararea unei matrici şi iniţializarea elementelor sale, se poate omite numărul


maxim de linii, în schimb, datorită modului de memorare, trebuie specificat numărul
maxim de coloane:
int mat[][3] = {
{10, -5, 3},
{32, 20, 1},
{-1, 1, -2},
{7, -8, 9} };
Construcţia are acelaşi efect ca precedenta.
int mat[][3] = {
{1, 1},
{ -1},
{3, 2, 1}};
mat reprezintă o matrice 3 ∗ 3, ale cărei elemente se iniţializează astfel:
mat[0][0]=1, mat[0][1]=1, mat[1][0]=-1, mat[2][0]=3, mat[2][1]=2, mat[2][2]=1
Elementele mat[0][2], mat[1][1], mat[1][2] nu sunt initalizate. Ele au valoarea zero dacă tabloul
este global şi valori iniţiale nedefinite dacă tabloul este automatic.

Construcţiile utilizate la iniţializarea tablourilor bidimensionale se extind pentru


tablouri multidimensionale, cu mai mult de doi indici.
Exemplu:
int a[2][2][3]={
{ {10, 20}, {1, -1}, {3, 4}},
{ {20, 30}, {50, -40}, {11, 12}}
};
Exerciţiu: Să se citească de la tastatură elementele unei matrici de maxim 10 linii şi
10 coloane. Să se afişeze matricea citită.
#include <iostream.h>
void main(void)
{int A[10][10]; int nr_lin, nr_col; cout<<"Nr. linii:"; cin>>nr_lin;
cout<<"Nr. coloane:"; cin>>nr_col;int i, j;
//citirea elementelor unei matrici
for (i=0; i<nr_lin; i++)
for (j=0; j<nr_col; j++) {
cout<<"A["<<i<<","<<j<<"]="; //afişarea unui mesaj prealabil citirii
cin>>A[i][j];
}
//afişarea elementelor matricii

56
CAPITOLUL 4 Tablouri
for (i=0; i<nr_lin; i++) {
for (j=0; j<nr_col; j++)
cout<<A[i][j]<<'\t';
cout<<'\n'; // după afişarea elementelor unei linii, se trece pe linia
următoare
}
}

4.3. ŞIRURI DE CARACTERE

Şirurile de caractere sunt tablouri de caractere, care au ca ultim element un terminator de şir,
caracterul null (zero ASCII), ’\0’.
Exemplu:
char tc[5] = {’a’, ’b’, ’c’, ’d’, ’e’}; // tablou de caractere
char sc[5] = {’a’, ’b’, ’c’, ’d’, ’\0’}; // şirul de caractere cu elementele
abcd
Limbajul C/C++ permite iniţializarea unui tablou de caractere printr-o constantă şir (şir
între ghilimele), care include automat caracterul null. Deci ultima iniţializare este
echivalentă cu:
char sc[5] = ”abcd”; //sau cu
char sc[] = ”abcd”;

Exemplu:
char tc[5] = {’a’, ’b’, ’c’, ’d’, ’e’};
char sc[5] = {’a’, ’b’, ’c’, ’d’, ’\0’};
char sc1[5] = ”abcd”;
char s[10];
cout<<sc<<’\n’; //afişează abcd
cout<<tc<<’\n’;
//eroare: tabloul de caractere nu conţine terminatorul de şir, deci nu poate fi
afişat ca şir
cout<<s<<’\n’; // eroare: tablou neiniţializat
cout<<sc1[2]; // afişează al treilea element din şirul sc1
sc1[1]=’K’; // elementului din şir de indice 1 i se atribuie valoarea ‘K’;

4.3.1. FUNCŢII PENTRU OPERAŢII CU ŞIRURI DE CARACTERE

Funcţiile pentru operaţii cu şiruri se găsesc în header-ul <string.h>.

strlen (nume_şir)
Returnează un număr întreg ce reprezintă lungimea unui şir de caractere, fără a
număra terminatorul de şir.

strcmp (şir_1, şir_2)


Funcţia compară cele două şiruri date ca argument şi returnează o valoare întreagă
egală diferenţa dintre codurile ASCII ale primelor caractere care nu coincid.

strcpy (şir_destinaţie, şir_sursă)


Funcţia copie şirul sursă în şirul destinaţie. Pentru a fi posibilă copierea, lungimea
şirului destinaţie trebuie să fie mai mare sau egală cu cea a şirului sursă, altfel pot

57
CAPITOLUL 4 Tablouri
apare erori grave.

strcat (şir_destinaţie, şir_sursă)


Funcţia concatenează cele două şiruri: şirul sursă este adăugat la sfârşitul şirului
destinaţie. Tabloul care conţine şirul destinaţie trebuie să aibă suficiente elemente.

Exemplu:
#include <iostream.h>
#include <string.h>
void main()
{
char sir1[] = ”abcd”, sir2[] = ”abcde”, sir3 = "abcdef”, sir4 = "de”;
cout<<strcmp(sir1, sir2)<<’\n’; // afişare: -101
// ’e’ = 101, ’a’ = 97, ’d’ = 100
//’0’ - ’e’ = -101
cout<<strcmp(sir2, sir1)<<’\n’; //afişare: 101
cout<<strcmp(sir1, "")<<’ '; //compararea variabilei sir1 cu constanta
şir vid
char str1[20]=”hello”;
char str2[20]=”goodbye”;
char str3[20];
int difer, lungime;
cout<<”str1=”<<str1<<” str2=”<<str2<<’\n’;
difer=strcmp(str1, str2);
if (difer == 0)
cout<<”Siruri echivalente!\n”;
else if (difer>0)
cout<<str1<<” mai mare (lexicografic) decât “<<str2<<’\n’;
else
cout<<str1<<” mai mic (lexicografic) decât “<<str2<<’\n’;
cout<<”str1=”<<str1<<’\n’; cout<<”str3=”<<str3<<’\n’;
strcpy (str3, str1); cout<<”str1=”<<str1<<’\n’;
cout<<”str3=”<<str3<<’\n’;
strcat (str3, str1);
cout<<”str1=”<<str1<<’\n’;
cout<<”str3=”<<str3<<’\n’;
}

Exemplu: Să se citească elementele unui vector cu maxim 100 de elemente reale.


a) Să se interschimbe elementele vectorului în modul următor: primul cu ultimul, al
doilea cu penultimul, etc.
b) Să se ordoneze crescător elementele vectorului.
// a)
#define FALSE 0
#define TRUE 1
#include <iostream.h>
void main()
{ double vect[100];int n;//n-numarul de elemente ale vectorului
cout<<"Nr. elemente"; cin>>n; double aux;
// de completat exemplul cu secventa de citire a elementelor vectorului
for (int i=0; i<n/2; i++){
aux=vect[i];
vect[i]=vect[n-1-i];
vect[n-1-i]=aux;
}
// de completat exemplul cu secventa de afisare a vectorului
}
Pentru schimbarea elementelor vectorului s-a folosit variabila auxiliară aux (figura 4.2.). Fără

58
CAPITOLUL 4 Tablouri
această variabilă, la atribuirea vect[i]=vect[n-1-i], valoarea elementului vect[i] s-ar fi
pierdut. Trebuie observat, deasemenea, că variabila contor i ia valori între 0 şi n/2 (de
exemplu, dacă vectorul are 4 sau 5 elemente sunt necesare 2 interschimbări).
b) Pentru ordonarea elementelor vectorului, este prezentat un algoritmi de sortare. Metoda Bubble Sort
compară fiecare element al vectorului cu cel vecin, iar dacă este cazul, le schimbă
între ele.

ALGORITM Bubble_Sort
INCEPUT
gata ← false
CIT TIMP not gata REPETA
INCEPUT
gata = true
PENTRU i=0 LA n-2 REPETA
INCEPUT
DACA vect[i] > vect[i+1] ATUNCI
` INCEPUT
aux=vect[i]
vect[i]=vect[i+1]
vect[i+1]=aux
gata=fals
SFARSIT
SFARSIT
SFARSIT
SFARSIT

// implementarea metodei BubbleSort


int gata =FALSE;int i;
while (!gata){
gata=TRUE;
for (i=0; i<=n-2; i++)
if (vect[i]>vect[i+1]){
aux=vect[i];
vect[i]=vect[i+1];
vect[i+1]=aux;
gata=FALSE;}
}

Exemplu: Să se citească elementele matricilor A(MXN), B(NXP) şi C(MXN), unde M<=10,


N<=10 şi P<=10. Să se interschimbe liniile matricii A în modul următor: prima cu
T
ultima, a doua cu penultima, etc. Să se calculeze şi să se afişeze matricile: AT=A ,
SUM=A+C, PROD=AXB. Implementarea citirilor şi afişărilor se va completa conform
exemplului dat în capitolul 4.2.
#include <iostream.h>
void main()
{
double a[10][10], b[10][10], c[10][10];
int m,n,p,j;
cout<<"m="; cin>>m; cout<<"n="; cin>>n; cout<<"p="; cin>>p;
// de completat secvenţa de citire a elementelor matricii a, cu m linii şi n coloane
// de completat secvenţa de citire a elementelor matricii b, cu n linii şi p coloane
// de completat secvenţa de afişare a matricii a
//interschimbarea liniilor matricii A:
for (i=0; i<m/2; i++)
for (j=0; j<n; j++){
double aux=a[i][j];a[i][j]=a[m-1-i][j];a[m-1-i][j]=aux;
}
cout<<"Matricea A cu liniile interschimbate:\n";
// de completat secvenţa de afişare a matricii a

59
CAPITOLUL 4 Tablouri
T
// calculul matricii AT =A
double at[10][10]; // at este matricea transpusa
for (i=0; i<n; i++)
for (j=0; j<m; j++)
at[i][j]=a[j][i];
cout<<"A transpus=\n";
// de completat secvenţa de afişare a matricii at, cu n linii si m coloane
// de completat secvenţa de citire a elementelor matricii c, cu m linii şi n coloane
// calculul matricii SUM=A+C, SUM(MxN):
double sum[10][10]; // sum este matricea suma dintre a si c
for (i=0; i<m; i++)
for (j=0; j<n; j++)
sum[i][j]=a[i][j]+ c[i][j];
cout<<"Matricea SUM=A+C este:\n";
// de completat secvenţa de afişare a matricii sum
double prod[10][10]; // prod este matricea produs dintre a si b
for (i=0; i<m; i++)
for (j=0; j<p; j++){
prod[i][j]=0;
for (k=0; k<n; k++)
prod[i][j]+=a[i][k]*b[k][j];
}
cout<<"Matricea produs dintre A si B este:\n";
// de completat secvenţa de afişare a matricii prod, cu m linii si p coloane
}
Se observă că fiecare element din matricea produs PROD=AXB ( A(MXN), B(NXP) ),
n −1

∑a i ,k * bk , j
PROD(MXP) este de forma: prod i, j
= k =0 , unde i= 0, m − 1 şi j= 0, n − 1 .

ÎNTREBĂRI ŞI EXERCIŢII

Chestiuni teoretice

60
CAPITOLUL 4 Tablouri
1. Care este diferenţa dintre şirurile de structurate?
caractere şi vectorii de caractere? 4. Prin ce se referă elementele unui
2. Ce sunt tablourile? tablou?
3. De ce tablourile reprezintă date 5. Cine impune tipul unui tablou?

53
CAPITOLUL 4 Tablouri

Chestiuni aplicative

1. Să se implementeze programele cu exemplele prezentate.


2. Să se scrie programele pentru exerciţiile rezolvate care au fost prezentate.
3. Se citesc de la tastatura elementele unei matrici de caractere (nr. linii=nr. coloane), A(NXN), N<=10.
a) Să se afişeze matricea A;
b) Să se formeze şi să se afişeze cuvântul format din caracterele pe pe diagonala
principală a matricii A;
c) Să se calculeze şi să se afişeze numărul de litere mari, litere mici şi cifre din
matrice;
d) Să se afişeze cuvântul format din caracterele de pe diagonala secundară;
e) Să se afişeze procentul literelor mari, al literelor mici şi al cifrelor de pe cele 2
diagonale;
f) Să se afişeze caracterele comune aflate pe liniile p şi q (p, q < N, p şi q citite de la
tastatură);
g) Să se afişeze în ordine alfabetică, crescătoare, literele mari aflate pe coloanele
impare.

4. Se citesc de la tastatură elementele unei matrici cu elemente reale, B (N X N), N<=8.


a) Să se afişeze matricea B;
b) Să se calculeze şi să se afişeze produsul elementelor de pe coloanele impare;
T 2
c) Să se calculeze şi să se afişeze matricea A, unde: A=(B+ B ) ;
d) Să se formeze şi să se afişeze vectorul V, ale cărui elemente sunt elementele
pozitive din matricea A;
e) Să se calculeze şi să se afişeze sumele şi produsele elementelor matricii A, aflate în
triunghiurile haşurate:

f) Să se calculeze procentul elementelor pozitive aflate pe diagonala secundară;


T 2
g) Să se calculeze şi să se afişeze matricea C, unde: C= 3*B + B ;
2 3 4
h) Să se calculeze şi să se afişeze matricea D, unde: D=B +B +B +B ;
i) Să se interschimbe coloanele matricii A astfel: prima cu ultima, a doua cu
antipenultima, etc.

5. Se citesc de la tastatură elementele unei matrici de numere întregi C (N X N), N<=10.


a) Să se afişeze matricea C;
b) Să se calculeze şi să se afişeze procentul elementelor impare de pe liniile pare;
2
c) Să se calculeze şi să se afişeze matricea B, unde: B=C ;
T 2
d) Să se calculeze şi să se afişeze matricea E, unde: E = (C + C )
+ I, unde I
este matricea unitate;
e) Să se afle elementul minim din matricea C;
f) Să se înlocuiască elementul maxim din matricea C cu valoarea val, introdusă de la
tastatură;
g) Să se afişeze elementele matricii C care sunt numere prime;
h) Să se calculeze şi să se afişeze sumele şi produsele elementelor matricii A, aflate în
triunghiurile haşurate:

53
CAPITOLUL 5 Pointeri

POINTERI 5
5.1.Variabile pointer 5.3.1. Pointeri şi şiruri de caractere
5.1.1. Declararea variabilelor pointer 5.3.2. Pointeri şi tablouri bidimensionale
5.1.2. Iniţializarea variabilelor pointer 5.4. Tablouri de pointeri
5.1.3. Pointeri generici 5.5. Pointeri la pointeri
5.2. Operaţii cu pointeri 5.6. Modificatorul const în declararea
5.3. Pointeri şi tablouri pointerilor

5.1. VARIABILE POINTER

Pointerii sunt variabile care au ca valori sunt adresele altor variabile (obiecte). Variabila este un nume
simbolic utilizat pentru un grup de locaţii de memorie. Valoarea memorată într-o variabilă pointer este o
adresă.
Din punctul de vedere al conţinutului zonei de memorie adresate, se disting următoarele categorii de pointeri:
 pointeri de date (obiecte) - conţin adresa unei variabile din memorie;
 pointeri generici (numiţi şi pointeri void) - conţin adresa unui obiect oarecare, de tip neprecizat;
 pointeri de funcţii (prezentaţi în capitolul 6.11.)- conţin adresa codului executabil al unei funcţii.

Variabilă Variabilă pointer


x Nume variabilă ptrx

5 Valoare 1024

1024 adresă 1028


Figura 5.1. Variabile pointer
În figura 5.1, variabila x este memorată la adresa 1024 şi are valoarea 5. Variabila ptrx este memorată la
adresa de memorie 1028 şi are valoarea 1024 (adresa variabilei x). Vom spune că ptrx pointează către x,
deoarece valoarea variabilei ptrx este chiar adresa de memorie a variabilei x.

5.1.1. DECLARAREA VARIABILELOR POINTER

Sintaxa declaraţiei unui pointer de date este:


tip ∗ identificator_pointer;
Simbolul ∗ precizează că identificator_pointer este numele unei variabile pointer de date, iar tip
este tipul obiectelor a căror adresă o va conţine.
Exemplu:
int u, v, ∗ p, ∗ q; // ∗ p, ∗ q sunt pointeri de date (către int)
double a, b, ∗ p1, ∗ q1; // ∗ p1, ∗ q1 sunt pointeri către date de tip double

Pentru pointerii generici, se foloseşte declaraţia:


void ∗ identificator_pointer;
Exemplu:
void ∗ m;
Aceasta permite declararea unui pointer generic, care nu are asociat un tip de date precis. Din acest motiv, în
cazul unui pointer vid, dimensiunea zonei de memorie adresate şi interpretarea informaţiei nu sunt definite,
iar proprietăţile diferă de ale pointerilor de date.
5.1.2. INIŢIALIZAEA VARIABILELOR POINTER

61
CAPITOLUL 5 Pointeri

Există doi operatori unari care permit utilizarea variabilelor pointer:


 & - operatorul adresă (de referenţiere) - pentru aflarea adresei din memorie a unei variabile;
 ∗ - operatorul de indirectare (de deferenţiere) - care furnizează valoarea din zona de memorie spre care
pointează pointerul operand.

În exemplul prezentat în figura 5.1, pentru variabila întreagă x, expresia &x furnizează adresa variabilei x.
Pentru variabila pointer de obiecte int, numită ptr, expresia ∗ ptr înseamnă conţinutul locaţiei de memorie a
cărei adresă este memorată în variabila ptr. Expresia ∗ ptr poate fi folosită atât pentru aflarea valorii
obiectului spre care pointează ptr, cât şi pentru modificarea acesteia (printr-o operaţie de atribuire).

Exemplu:
int x, y, ∗ ptr;
// ptr- variabilă pointer către un int; x,y-variabile predefinite, simple, de tip int
x=5; cout<<”Adresa variabilei x este:”<<&x<<’\n’;
cout<<”Valoarea lui x:”<<x<<’\n’;
ptr=&x; // atribuire: variabila ptr conţine adresa variabilei x
cout<<”Variabila pointer ptr are valoarea:”<<ptr;
cout<<” si adreseaza obiectul:”<< ∗ ptr<<’\n’;
y= ∗ ptr; cout<<”y=”<<y<<’\n’; // y=5
x=4; cout<<”x=”<<x<<’\n’; cout<<” ∗ ptr=”<< ∗ ptr<<’\n’;
// x si ∗ ptr reprezinta acelasi obiect, un intreg cu valoarea 4
x=70; // echivalenta cu ∗ ptr=70;
y=x+10; // echivalenta cu y= ∗ ptr+10

În exemplul anterior, atribuirea ptr=&x se execută astfel: operatorul & furnizează adresa lui x;
operatorul = atribuie valoarea (care este o adresă) variabilei pointer ptr.
Atribuirea y= ∗ ptr se realizează astfel: operatorul ∗ accesează conţinutul locaţiei a cărei adresă este
conţinută în variabila ptr; operatorul = atribuie valoarea variabilei y.

Declaraţia int ∗ ptr; poate fi, deci, interpretată în două moduri, ambele corecte:
 ptr este de tipul int ∗ ( ptr este de tip pointer spre int)
 ∗ ptr este de tipul int (conţinutul locaţiei spre care pointează variabila ptr este de tipul int)
Construcţia tip ∗ este de tipul pointer către int.
Atribuirea x=8;este echivalentă cu ptr=&x; ∗ p=x;

Variabilele pointer, alături de operatorii de referenţiere şi de deferenţiere, pot apare în expresii.


Exemple:
int x, y, ∗ q; q=&x;
∗ q=8; // echivalentă cu x=8;
q=&5; // invalidă - constantele nu au adresă
∗ x=9; // invalidă - x nu este variabilă pointer
x=&y; //invalidă: x nu este variabilă pointer, deci nu poate fi folosită cu operatorul de indirectare
y= ∗ q + 3; // echivalentă cu y=x+3;
∗ q = 0; // setează x pe 0
∗ q += 1; // echivalentă cu ( ∗ q)++ sau cu x++
int ∗ r; r = q;
/* copiază conţinutul lui q (adresa lui x) în r, deci r va pointa tot către x (va conţine tot adresa lui x)*/
double w, ∗ r = &w, ∗ r1, ∗ r2; r1= &w; r2=r1;
cout<<”r1=”<<r1<<’\n’; //afişează valoarea pointerului r1 (adresa lui w)
cout<<”&r1=”<<&r1<<’\n’; // afişează adresa variabilei r1
cout<<” ∗ r1= ”<< ∗ r1<<’\n’;
double z= ∗ r1; // echivalentă cu z=w
cout<<”z=”<<z<<’\n’;
5.1.3. POINTERI GENERICI

62
CAPITOLUL 5 Pointeri

La declararea pointerilor generici ( void ∗ nume; ) nu se specifică un tip, deci unui pointer void i se pot
atribui adrese de memorie care pot conţine date de diferite tipuri: int, float, char, etc. Aceşti pointeri pot fi
folosiţi cu mai multe tipuri de date, de aceea este necesară folosirea conversiilor explicite prin expresii de tip
cast, pentru a preciza tipul datei spre care pointează la un moment dat pointerul generic.

Exemplu:
void ∗ v1, ∗ v2; int a, b, ∗ q1, ∗ q2;
q1 = &a; q2 = q1; v1 = q1;
q2 = v1; // eroare: unui pointer cu tip nu i se poate atribui un pointer generic
q2 = (int ∗ ) v1; double s, ∗ ps = &s;
int c, ∗ l; void ∗ sv;
l = (int ∗ ) sv; ps = (double ∗ ) sv;
∗ (char ∗ ) sv = 'a'; /*Interpretare: adresa la care se găseşte valoarea lui sv este
interpretată ca fiind adresa zonei de memorie care conţine o data de tip char. */

Pe baza exemplului anterior, se pot face observaţiile:


1. Conversia tipului pointer generic spre un tip concret înseamnă, de fapt, precizarea tipului de pointer pe
care îl are valoarea pointerului la care se aplică conversia respectivă.
2. Conversia tipului pointer generic asigură o flexibilitate mai mare în utilizarea pointerilor.
3. Utilizarea în mod abuziv a pointerilor generici poate constitui o sursă de erori.

5.2. OPERAŢII CU POINTERI

În afara operaţiei de atribuire (prezentată în paragraful 5.1.2.), asupra variabilelor pointer se pot realiza
operaţii de comparare, adunare şi scădere (inclusiv incrementare şi decrementare).

 Compararea valorilor variabilelor pointer


Valorile a doi pointeri pot fi comparate, folosind operatorii relaţionali, ca în exemplul:

Exemplu:
int ∗ p1, ∗ p2;
if (p1<p2)
cout<<”p1=”<<p1<<”<”<<”p2=”<<p2<<’\n’;
else cout<<”p1=”<<p1<<”>=”<<”p2=”<<p2<<’\n’;

O operaţie uzuală este compararea unui pointer cu valoarea nulă, pentru a verifica dacă acesta
adresează un obiect. Compararea se face cu constanta simbolică NULL (definită în header-ul stdio.h)
sau cu valoarea 0.

Exemplu:
if (!p1) // sau if (p1 != NULL)
. . . . . ; // pointer nul
else . . . . ; // pointer nenul

 Adunarea sau scăderea


Sunt permise operaţii de adunare sau scădere între un pointer de obiecte şi un întreg:
Astfel, dacă ptr este un pointer către tipul tip (tip ∗ ptr;), iar n este un întreg, expresiile
ptr + n şi ptr - n
au ca valoare, valoarea lui ptr la care se adaugă, respectiv, se scade n ∗ sizeof(tip).
Un caz particular al adunării sau scăderii dintre un pointer de date şi un întreg (n=1) îl reprezintă
incrementarea şi decrementarea unui pointer de date. În expresiile ptr++, respectiv ptr--, valoarea
variabilei ptr devine ptr+sizeof(tip), respectiv, ptr-sizeof(tip).
Este permisă scăderea a doi pointeri de obiecte de acelaşi tip, rezultatul fiind o valoare întreagă care
reprezintă diferenţa de adrese divizată prin dimensiunea tipului de bază.

63
CAPITOLUL 5 Pointeri

Exemplu:
int a, ∗ pa, ∗ pb;
cout<<”&a=”<<&a<<’\n’; pa=&a; cout<<”pa=”<<pa<<’\n’;
cout<<”pa+2”<<pa+2<<’\n’; pb=pa++; cout<<”pb=”<<pb<<’\n’;
int i=pa-pb; cout<<”i=”<<i<<’\n’;

5.3. POINTERI ŞI TABLOURI

În limbajele C/C++ există o strânsă legătură între pointeri şi tablouri, deoarece numele unui tablou este un
pointer (constant!) care are ca valoare adresa primului element din tablou. Diferenţa dintre numele unui
tablou şi o variabilă pointer este aceea că unei variabile de tip pointer i se pot atribui valori la execuţie, lucru
imposibil pentru numele unui tablou. Acesta are tot timpul, ca valoare, adresa primului său element. De
aceea, se spune că numele unui tablou este un pointer constant (valoarea lui nu poate fi schimbată). Numele
unui tablou este considerat ca fiind un rvalue (right value-valoare dreapta), deci nu poate apare decât în
partea dreaptă a unei expresii de atribuire. Numele unui pointer (în exemplul următor, ∗ ptr) este considerat
ca fiind un lvalue (left value-valoare stânga), deci poate fi folosit atât pentru a obţine valoarea obiectului, cât
şi pentru a o modifica printr-o operaţie de atribuire.

Exemplu:
int a[10], ∗ ptr; // a este definit ca &a[0]; a este pointer constant
a = a + 1; // ilegal
ptr = a ; // legal: ptr are aceeaşi valoare ca şi a, respectiv adresa elementului a[0]
// ptr este variabilă pointer, a este constantă pointer.
int x = a[0]; // echivalent cu x = ∗ ptr; se atribuie lui x valoarea lui a[0]

Deoarece numele tabloului a este sinonim pentru adresa elementului de indice zero din tablou, asignarea
ptr=&a[0] poate fi înlocuită, ca în exemplul anterior, cu ptr=a.

5.3.1. POINTERI ŞI ŞIRURI DE CARACTERE

Aşa cum s-a arătat în capitolul 4, un şir de caractere poate fi memorat într-un vector de caractere. Spre
deosebire de celelalte constante, constantele şir de caractere nu au o lungime fixă, deci numărul de octeţi
alocaţi la compilare pentru memorarea şirului, variază. Deoarece valoarea variabilelor pointer poate fi
schimbată în orice moment, cu multă uşurinţă, este preferabilă utilizarea acestora, în locul tablourilor de
caractere (vezi exemplul următor).

Exemplu:
char sir[10]; char ∗ psir;
sir = ”hello”; // ilegal
psir = ”hello”; // legal
Operaţia de indexare a elementelor unui tablou poate fi realizată cu ajutorul variabilelor pointer.

Exemplu:
int a[10], ∗ ptr; // a este pointer constant; ptr este variabilă pointer
ptr = a; // ptr este adresa lui a[0]
ptr+i înseamnă ptr+(i ∗ sizeof(int)), deci: ptr + i ⇔ & a[i]
Deoarece numele unui tablou este un pointer (constant), putem concluziona (figura 5.2):
a+i ⇔ & a[i]
a[i] ⇔ ∗ (a+i)
a[0] a[1] . . . . a[9]

a=&a[0] a+1=&a[1] . . . a+9=&a[9]


a=a[0] (a+1)=a[1] .64. . (a+9)=a[9]

ptr Figura 5.2. Legătura dintre pointeri şi tablouri


CAPITOLUL 5 Pointeri

Exerciţiu: Să se scrie următorul program (care ilustrează legătura dintre pointeri şi vectori) şi să se
urmărească rezultatele execuţiei acestuia.
#include <iostream.h>
void main(void)
{int a[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int ∗ pi1 = a ;
int ∗ pi2 = &a[0]; int ∗ pi3;
cout<<”a=”<<a<<”&a=”<<&a<<” ∗ a=”<< ∗ a<<’\n’;
cout<<”a+1=”<<(a+1)<< ” &a[1]=”<< &a[1]<<’\n’;
cout<<”a[1]=”<<a[1]<< ” ∗ (a+1)=”<< ∗ (a+1)<<’\n’;
cout<<”pi1=”<<pi1<<”pi2=”<<pi2<<’\n’; int x= ∗ pi1;
/* x primeşte valoarea locaţiei a carei adresă se află în variabila pointer pi1, deci valoarea lui a[0] */
cout<<”x=”<<x<<’\n’; x= ∗ pi1++; // echivalent cu ∗ (pi1++) x=1
cout<<”x=”<<x<<’\n’; x=( ∗ pi1)++;
/* x=0: întâi atribuirea, apoi incrementarea valorii spre care pointează pi1. În urma incrementării,
valoarea lui a[0] devine 1 */
cout<<”x=”<<x<<’\n’; cout<< ∗ pi1<<’\n’;x= ∗ ++pi1; //echivalent cu ∗ (++pi1)
cout<<”x=”<<x<<’\n’; x=++( ∗ pi1); cout<<”x=”<<x<<’\n’; pi1=a;
pi3=pi1+3;
cout<<”pi1=”<<pi1<<” ∗ pi1=”<< ∗ pi1<<”&pi1=”<<&pi1<<’\n’;
cout<<”pi3=”<<pi3<<” ∗ pi3=”<< ∗ pi3<<”&pi3=”<<&pi3<<’\n’;
cout<<”pi3-pi1=”<<(pi3-pi1)<<’\n’; //pi3-pi1=3
}

Exerciţiu: Să se scrie următorul program (legătura pointeri-şiruri de caractere) şi să se urmărească


rezultatele execuţiei acestuia.
#include <iostream.h>
void main(void)
{int a=-5, b=12, ∗ pi=&a; double u=7.13, v=-2.24, ∗ pd=&v;
char sir1[]=”sirul 1”, sir2[]=”sirul 2”, ∗ psir=sir1;
cout<<”a=”<<a<<” &a=”<<&a<<” b=”<<b<<” &b=”<<&b<<’\n’;
cout<<” ∗ pi=”<< ∗ pi<<”pi=”<<pi<<” &pi=”<<&pi<<’\n’;
cout<<” ∗ pd=”<< ∗ pd<<”pd=”<<pd<<” &pd=”<<&pd<<’\n’;
cout<<” ∗ sir1=”<< ∗ sir1<<” sir1=”<<sir1<<” &sir1=”<<&sir1<<’\n’;
// *sir1=s sir1=sirul 1 &sir1=0xffd6
cout<<” ∗ sir2=”<< ∗ sir2<<” sir2=”<<sir2<<” &sir2=”<<&sir2<<’\n’;
// *sir2=s sir2=sirul 2 &sir1=0xffce
cout<<” ∗ psir=”<< ∗ psir<<” psir=”<<psir<<” &psir=”<<&psir<<’\n’;
// *psir=s psir=sirul 1 &sir1=0xffcc
cout<<”sir1+2=”<<(sir1+2)<<” psir+2=”<<(psir+2)<<’\n’;
// sir1+2=rul 1 psir+2=rul 1
cout<<” ∗ (sir1+2)=”<< ∗ (sir1+2)<<’\n’;
// *(sir1+2)=r valoarea elementului de indice 2
void ∗ pv1, ∗ pv2;
pv1=psir; pv2=sir1;
cout<<”pv1=”<<pv1<<”&pv1=”<<&pv1<<’\n’;
cout<<”pv2=”<<pv2<<”&pv2=”<<&pv2<<’\n’;
pi=&b; pd=&v; psir=sir2;
cout<<” ∗ pi=”<< ∗ pi<<”pi=”<<pi<<” &pi=”<<&pi<<’\n’;
cout<<” ∗ pd=”<< ∗ pd<<”pd=”<<pd<<” &pd=”<<&pd<<’\n’;
cout<<” ∗ psir=”<< ∗ psir<<”psir=”<<psir<<” &psir=”<<&psir<<’\n’;

65
CAPITOLUL 5 Pointeri
}

Exerciţiu: Să se scrie un program care citeşte elementele unui vector de întregi, cu maxim 20 elemente şi
înlocuieşte elementul maxim din vector cu o valoare introdusă de la tastatură. Se va folosi aritmetica
pointerilor.
#include <iostream.h>
void main()
{ int a[20];
int n, max, indice; cout<<”Nr. elemente:”; cin>>n;
for (i=0; i<n; i++)
{ cout<<”a[“<<i<<”]=”; cin>> ∗ (a+i);}
// citirea elementelor vectorului
max= ∗ a; indice=0;
for (i=0; i<n; i++)
if (max<= ∗ (a+i))
{ max= ∗ (a+i); indice=i;}
// aflarea valorii elementului maxim din vector şi a poziţiei acestuia
int val;
cout<<”Valoare de inlocuire:”; cin >> val;
∗ (a+indice)=val;
// citirea valorii cu care se va înlocui elementul maxim
for (i=0; i<n; i++)
cout<< ∗ (a+i);<<'\t';
cout<<'\n';
// afişarea noului vector
/* în acest mod de implementare, în situaţia în care în vector există mai multe elemente a căror
valoare este egală cu valoarea elementului maxim, va fi înlocuit doar ultimul dintre acestea (cel de
indice maxim).*/
}

5.3.2. POINTERI ŞI TABLOURI MULTIDIMENSIONALE

Elementele unui tablou bidimensional sunt păstrate tot într-o zonă continuă de memorie, dar inconvenientul
constă în faptul că ne gândim la aceste elemente în termeni de rânduri (linii) şi coloane (figura 5.3). Un
tablou bidimensional este tratat ca un tablou unidimensional ale cărui elemente sunt tablouri
unidimensionale.
int M[4][3]={ {10, 5, -3}, {9, 18, 0}, {32, 20, 1}, {-1, 0, 8} };
Compilatorul tratează atât M, cât şi M[0], ca tablouri de mărimi diferite. Astfel:
cout<<”Marime M:”<<sizeof(M)<<’\n’; // 24 = 2octeţi ∗ 12elemente
cout<<”Marime M[0]”<<sizeof(M[0])<<’\n’; // 6 = 2octeţi ∗ 3elemente
cout<<”Marime M[0][0]”<<sizeof(M[0][0])<<’\n’; // 4 octeţi (sizeof(int))

Matricea M

M[0] 10 5 -3 Matricea M are 4 linii şi 3 coloane.


M[1] 9 18 0
Numele tabloului bidimensional, M, referă întregul tablou;
M[0] referă prima linie din tablou;
M[2] 32 20 1 M[0][0] referă primul element al tabloului.

M[3] -1 0 8

Figura 5.3. Matricea M


Aşa cum compilatorul evaluează referinţa către un tablou unidimensional ca un pointer, un tablou
bidimensional este referit într-o manieră similară. Numele tabloului bidimensional, M, reprezintă adresa
(pointer) către primul element din tabloul bidimensional, acesta fiind prima linie, M[0] (tablou

66
CAPITOLUL 5 Pointeri
unidimensional). M[0] este adresa primului element (M[0][0]) din linie (tablou unidimensional), deci M[0]
este un pointer către int: M = M[0] = &M[0][0]. Astfel, M şi M[linie] sunt pointeri constanţi.

Putem concluziona:
 M este un pointer către un tablou unidimensional (de întregi, în exemplul anterior).
 ∗ M este pointer către int (pentru că M[0] este pointer către int), şi ∗ M = ∗ (M + 0) ⇔ M[0].
 ∗ ∗ M este întreg; deoarece M[0][0] este int, ∗ ∗ M= ∗ ( ∗ M) ⇔ ∗ (M[0])= ∗ (M[0]+0) ⇔ M[0][0].

Exerciţiu: Să se testeze programul următor, urmărind cu atenţie rezultatele obţinute.


#include <iostream.h>
#include <conio.h>
void main()
{int a[3][3]={{5,6,7}, {55,66,77}, {555,666,777}};
clrscr();
cout<<"a="<<a<<" &a="<<&a<<" &a[0]="<<&a[0]<<'\n';
cout<<"Pointeri catre vectorii linii\n";
for (int i=0; i<3; i++){
cout<<" *(a+"<<i<<")="<<*(a+i);
cout<<" a["<<i<<"]="<<a[i]<<'\n';
}
// afişarea matricii
for (i=0; i<3; i++){
for (int j=0; j<3; j++)
cout<<*(*(a+i)+j)<<'\t'; //sau:
cout<<*(a[i]+j)<<'\t';
cout<<'\n';
}
}

5.4. TABLOURI DE POINTERI

Un tablou de pointeri este un tablou ale cărui elemente sunt pointeri. Modul general de declarare a unui
tablou de pointeri:
tip ∗ nume_tablou[dim];

Să considerăm exemplul în care se declară şi se iniţializează tabloul de pointeri str_ptr (figura 5.4.):
char ∗ str_ptr[3] = { ”Programarea”, ”este”, ”frumoasă!” };
str_ptr
Deoarece operatorul de indexare [ ] are
prioritate mai mare decât operatorul de
str_ptr[0]
”Programarea” deferenţiere , declaraţia charstr_ptr[3] este
str_ptr[1] echivalentă cu char(str_ptr[3]), care
”este” precizează că str_ptr este un vector de trei
str_ptr[2] elemente, fiecare element este pointer către
”frumoasă! ” caracter.
Figura 5.4. Tabloul de pointeri str_ptr

În ceea ce priveşte declaraţia: char ∗ (str_ptr[3]), se poate observa:


1. str_ptr[3] este de tipul char ∗ (fiecare dintre cele trei elemente ale vectorului str_ptr[3] este de
tipul pointer către char);
2. ∗ (str_ptr[3]) este de tip char (conţinutul locaţiei adresate de un element din str_ptr[3] este de
tip char).
Fiecare element (pointer) din str_ptr este iniţializat să pointeze către un şir de caractere constant. Fiecare
dintre aceste şiruri de caractere se găsesc în memorie la adresele memorate în elementele vectorului
str_ptr: str_ptr[0], str_ptr[1], etc.

67
CAPITOLUL 5 Pointeri

Să ne amintim de la pointeri către şiruri de caractere:


char ∗ p=”heLLO”;
∗ ( p+1) = ’e’ ⇔ p[1] = ’e’;
În mod analog:
str_ptr[1] = ”este”;
∗ ( str_ptr[1] + 1) = ’s’; ⇔ str_ptr[1][1]=’s’;

Putem conculziona:
 str_ptr este un pointer către un pointer de caractere.
 ∗ str_ptr este pointer către char. Este evident, deoarece str_ptr[0] este pointer către char, iar
∗ str_ptr = ∗ (str_ptr [0] + 0 ) ⇔ str_ptr[0].
 ∗ ∗ str_ptr este un de tip char. Este evident, deoarece str_ptr[0][0] este de tip char, iar
∗ ∗ str_ptr= ∗ ( ∗ str_ptr) ⇔ ∗ (str_ptr[0])= ∗ (str_ptr[0]+0) ⇔
str_ptr[0][0].

5.5. POINTERI LA POINTERI

Să revedem exemplul cu tabloul de pointeri str_ptr. Şirurile spre care pointează elementele tabloului pot
fi accesate prin str_ptr[index], însă deoarece str_ptr este un pointer constant, acestuia nu i se pot
aplica operatorii de incrementare şi decrementare. Este ilegală :
for (i=0;i<3;i++)
cout<<str_ptr++ ;

De aceea, putem declara o variabilă pointer ptr_ptr, care să pointeze către primul element din str_ptr.
Variabila ptr_ptr este pointer către pointer şi se declară astfel:
char ∗ ∗ ptr_ptr;

În exemplul următor este prezentat modul de utilizare a pointerului la pointer ptr_ptr (figura 5.5).

Exemplu:
char ∗ ∗ ptr_ptr;
char ∗ str_ptr[3] = { ”Programarea”, ”este”, ”frumoasă!” };
char ∗ ∗ ptr_ptr;
ptr_ptr = str_ptr;

str_ptr După atribuire, şi str_ptr şi ptr_ptr


ptr_ptr
pointează către aceeaşi locaţie de memorie
(primul element al tabloului str_str). În
”Programarea” timp ce fiecare element al lui str_str este un
pointer, ptr_ptr este un pointer către pointer.
”este” Deoarece ptr_ptr este un pointer variabil,
valoarea lui poate fi schimbată:
”frumoasă! ” for (i=0;i<3;i++)
cout<<ptr_ptr++ ;
Figura 5.5. Pointerul la pointer ptr_ptr

Referitor la declaraţia char ∗ ∗ ptr_ptr, putem concluziona:


 ptr_ptr este de tipul char ∗ ∗ (ptr_ptr este pointer la pointer către char);
 ∗ ptr_ptr este de tipul char ∗ (conţinutul locaţiei ptr_ptr este de tipul pointer către char);
 ∗ ∗ ptr_ptr este de tipul char ( ∗ ∗ ptr_ptr ⇔ ∗ ( ∗ ptr_ptr); conţinutul locaţiei ∗ ptr_ptr
este de tipul char).

5.6. MODIFICATORUL const ÎN DECLARAREA POINTERILOR

68
CAPITOLUL 5 Pointeri
Modificatorul const se utilizează frecvent în declararea pointerilor, având următoarele roluri:

 Declararea unui pointer spre o dată constantă


const *tip nume_pointer=dată_constantă;

Exemplu:
const char *sirul=”azi”;
//variabila sirul este pointer spre un şir constant de caractere
Atribuirile de forma:
*sirul=”coco”;
*(sirul+2)=’A’;
nu sunt acceptate, deoarece pointerul sirul pointează către o dată constantă (şir constant).

 Declararea unui pointer constant către o dată care nu este constantă


tip * const nume_pointer=dată_neconst;

Exemplu:
char * const psir=”abcd”; const char *sir=”un text”;
sir=”alt sir”; //incorect, sir pointează către dată constantă
psir=sir; //incorect, deoarece psir este pointer constant

 Declararea unui pointer constant către o dată constantă


const tip * const nume_pointer=dată_constantă;

Exemplu:
const char * const psir1="mnP";
*(psir1+2)='Z'; // incorect, data spre care pointeză psir1 este constantă
psir1++; // incorect, psir1 este pointer constant

ÎNTREBĂRI ŞI EXERCIŢII

Chestiuni teoretice

1. În ce constă operaţia de incrementare a


pointerilor? 6. Ce fel de variabile pot constitui operandul
2. Tablouri de pointeri. operatorului de deferenţiere?
3. Ce sunt pointerii generici? 7. Operatorul de referenţiere.
4. Ce operaţii se pot realiza asupra variabilelor 8. Unui pointer generic i se poate atribui
pointer? valoarea unui pointer cu tip?
5. De ce numele unui pointer este lvalue? 9. Care este legătura între tablouri şi pointeri?
10. De ce numele unui tablou este rvalue?

Chestiuni practice

1. Să se implementeze programele cu exemplele prezentate.


2. Să se scrie programele pentru exerciţiile rezolvate care au fost prezentate.
3. Analizaţi următoarele secvenţe de instrucţiuni. Identificaţi secvenţele incorecte (acolo unde este cazul) şi
sursele erorilor:
 int a,b,*c; a=7; b=90; c=a;
 double y, z, *x=&z; z=&y;
 char x, **p, *q; x = 'A'; q = &x; p = &q; cout<<”x=”<<x<<’\n’;
cout<<”**p=”<<**p<<’\n’; cout<<”*q=”<<*q<<’\n’;
cout<<”p=”<<p<<” q=”<<q<<”*p=”<<*p<<’\n’;
 char *p, x[3] = {'a', 'b', 'c'}; int i, *q, y[3] = {10, 20, 30};

69
CAPITOLUL 5 Pointeri
p = &x[0];
for (i = 0; i < 3; i++)
{
cout<<”*p=”<<*p<<” p=”<<p<<’\n’;
p++;
}
q = &y[0];
for (i = 0; i < 3; i++)
{
cout<<”*q=”<<*q<<”q=”<<q<<’\n’;
q++;
}
 const char *sirul=”să programăm”; *(sirul)++;
 double a, *s; s=&(a+89); cout<<”s=”s<<’\n’;
 double a1, *a2, *a3; a2=&a1; a2+=7.8; a3=a2; a3++;
 int m[10], *p;p=m;
for (int i=0; i<10; i++)
cout<<*m++;
 void *p1; int *p2; int x; p2=&x; p2=p1;
 char c=’A’; char *cc=&c; cout<<(*cc)++<<’\n’;

4. Rescrieţi programele pentru problemele din capitolul 4 (3.a.-3.g., 4.a.-4.i., 5.a.-5.h.), utilizând aritmetica
pointerilor.

70
CAPITOLUL 6
Funcţii

FUNCŢII 6
6.1. Structura unei funcţii 6.4. Tablouri ca parametri
6.2. Apelul şi prototipul unei funcţii 6.5. Funcţii cu parametri impliciţi
6.3. Transferul parametrilor unei funcţii 6.6. Funcţii cu număr variabil de parametri
6.3.1.Transferul parametrilor prin valoare 6.7. Funcţii predefinite
6.3.2.Transferul prin pointeri 6.8. Clase de memorare
6.3.3.Transferul prin referinţă 6.9. Moduri de alocare a memoriei
6.3.4.Transferul parametrilor către funcţia 6.10.Funcţii recursive
main 6.11.Pointeri către funcţii

6.1. STRUCTURA UNEI FUNCŢII

Un program scris în limbajul C/C++ este un ansamblu de funcţii, fiecare dintre acestea efectuând o activitate
bine definită. Din punct de vedere conceptual, funcţia reprezintă o aplicaţie definită pe o mulţime D
(D=mulţimea, domeniul de definiţie), cu valori în mulţimea C (C=mulţimea de valori, codomeniul), care
îndeplineşte condiţia că oricărui element din D îi corespunde un unic element din C.

Funcţiile comunică prin argumennte: ele primesc ca parametri (argumente) datele de intrare, efectuează
prelucrările descrise în corpul funcţiei asupra acestora şi pot returna o valoare (rezultatul, datele de ieşire).
Execuţia programului începe cu funcţia principală, numită main. Funcţiile pot fi descrise în cadrul aceluiaşi
fişier, sau în fişiere diferite, care sunt testate şi compilate separat, asamblarea lor realizându-se cu ajutorul
linkeditorului de legături.

O funcţie este formata din antet si corp:


antet_funcţie
{
corpul_funcţiei
}
Sau:
tip_val_return nume_func (lista_declaraţiilor_param_ formali)
{
declaraţii_variabile_locale
instrucţiuni
return valoare
}
Prima linie reprezintă antetul funcţiei, în care se indică: tipul funcţiei, numele acesteia şi lista declaraţiilor
parametrilor formali. La fel ca un operand sau o expresie, o funcţie are un tip, care este dat de tipul valorii
returnate de funcţie în funcţia apelantă. Dacă funcţia nu întoarce nici o valoare, în locul tip_vali_return se
specifică void. Dacă tip_val_return lipseşte, se consideră, implicit, că acesta este int. Nume_funcţie este un
identificator.
Lista_declaraţiilor_param_formali (încadrată între paranteze rotunde) constă într-o listă (enumerare) care
conţine tipul şi identificatorul fiecărui parametru de intrare, despărţite prin virgulă. Tipul unui parametru
poate fi oricare, chiar şi tipul pointer. Dacă lista parametrilor formali este vidă, în antet, după numele funcţiei,
apar doar parantezele ( ), sau (void).

Corpul funcţiei este un bloc, care implementează algoritmul de calcul folosit de către funcţie. În corpul
funcţiei apar (în orice ordine) declaraţii pentru variabilele locale şi instrucţiuni. Dacă funcţia întoarce o
valoare, se foloseşte instrucţiunea return valoare. La execuţie, la întâlnirea acestei instrucţiuni, se
revine în funcţia apelantă.
În limbajul C/C++ se utilizează declaraţii şi definiţii de funcţii.

71
CAPITOLUL 6
Funcţii
Declaraţia conţine antetul funcţiei şi informează compilatorul asupra tipului, numelui funcţiei şi a listei
parametrilor formali (în care se poate indica doar tipul parametrilor formali, nu şi numele acestora).
Declaraţiile de funcţii se numesc prototipuri, şi sunt constituite din antetul funcţiei, din care pot lipsi numele
parametrilor formali.
Definiţia conţine antetul funcţiei şi corpul acesteia. Nu este admisă definirea unei funcţii în corpul altei
funcţii.

O formă învechită a antetului unei funcţii este aceea de a specifica în lista parametrilor formali doar numele
acestora, nu şi tipul. Această libertate în omiterea tipului parametrilor constituie o sursă de erori.
tipul_valorii_returnate nume_funcţie (lista_parametrilor_ formali)
declararea_parametrilor_formali
{
declaraţii_variabile_locale
instrucţiuni
return valoare
}

6.2. APELUL ŞI PROTOTIPUL FUNCŢIILOR

O funcţie poate fi apelată printr-o construcţie urmată de punct şi virgulă, numită instrucţiune de apel, de
forma:
nume_funcţie (lista_parametrilor_efectivi);
Parametrii efectivi trebuie să corespundă cu cei formali ca ordine şi tip. La apel, se atribuie parametrilor
formali valorile parametrilor efectivi, după care se execută instrucţiunile din corpul funcţiei. La revenirea din
funcţie, controlul este redat funcţiei apelante, şi execuţia continuă cu instrucţiunea următoare instrucţiunii de
apel, din funcţia apelantă. O altă posibilitate de a apela o funcţie este aceea în care apelul funcţiei constituie
operandul unei expresii. Acest lucru este posibil doar în cazul în care funcţia returnează o valoare, folosită în
calculul expresiei.

Parametrii declaraţi în antetul unei funcţii sunt numiţi formali, pentru a sublinia faptul că ei nu reprezintă
valori concrete, ci numai ţin locul acestora pentru a putea exprima procesul de calcul realizat prin funcţie. Ei
se concretizează la execuţie prin apelurile funcţiei.
Parametrii folosiţi la apelul unei funcţii sunt parametri reali, efectivi, concreţi, iar valorile lor vor fi atribuite
parametrilor formali, la execuţie. Utilizarea parametrilor formali la implementarea funcţiilor şi atribuirea de
valori concrete pentru ei, la execuţie, reprezintă un prim nivel de abstractizare în programare. Acest mod de
programare se numeşte programare procedurală şi realizează un proces de abstractizare prin parametri.

Variabilele declarate în interiorul unei funcţii, cât şi parametrii formali ai acesteia nu pot fi accesaţi decât în
interiorul acesteia. Aceste variabile sunt numite variabile locale şi nu pot fi accesate din alte funcţii. Domeniul
de vizibilitate a unei variabile este porţiunea de cod la a cărei execuţie variabila respectivă este accesibilă.
Deci, domeniul de vizibilitate a unei variabile locale este funcţia în care ea a fost definită (vezi şi paragraful
6.8.).
Exemplu:
int f1(void)
{ double a,b; int c;
. . .
return c; // a, b, c - variabile locale, vizibile doar în corpul funcţiei
}
void main()
{ . . . . . . // variabile a şi b nu sunt accesibile în main()
}
Dacă în interiorul unei funcţii există instrucţiuni compuse (blocuri) care conţin declaraţii de variabile, aceste
variabile nu sunt vizibile în afara blocului.
Exemplu:
void main()
{ int a=1, b=2;

72
CAPITOLUL 6
Funcţii
cout << "a=”<<a<<” b=”<<b<<” c=”<<c’\n’; // a=1 b=2, c nedeclarat
. . . . . . . .
{ int a=5; b=6; int c=9;
cout << "a=”<<a<<” b=”<<b<<’\n’; // a=5 b=6 c=9
. . . . . . . .
}
cout << "a=”<<a<<” b=”<<b<<” c=”<<c’\n’; // a=1 b=6, c nedeclarat
. . . . . . . . . . . .
}

Exerciţiu: Să se scrie următorul program (pentru înţelegerea modului de apel al unei funcţii) şi să se
urmărească rezultatele execuţiei acestuia.
#include <iostream.h>
void f_afis(void)
{
cout<<”Se execută instrucţiunile din corpul funcţiei\n”;
double a=3, b=9.4; cout<<a<<”*”<<b<<”=”<<a*b<<’\n’;
cout<<”Ieşire din funcţie!\n”; }

void main()
{
cout<<”Intrare în funcţia principală\n”;
f_afis( ); //apelul funcţiei f_afis, printr-o instrucţiune de apel
cout<<”Terminat MAIN!\n”; }

Exerciţiu: Să se scrie un program care citeşte două numere şi afişează cele mai mare divizor comun al
acestora, folosind o funcţie care îl calculează.
#include <iostream.h>
int cmmdc(int x, int y)
{
if (x==0 || y==1 || x==1 || y==0) return 1;
if (x<0) x=-x;
if (y<0) y=-y;
while (x != 0){
if ( y > x )
{int z=x; x=y; y=z; }
x-=y; // sau: x%=y;
}
return y;}

void main()
{
int n1,n2; cout<<”n1=”;cin>>n1; cout<<”n2=”;cin>>n2;
int diviz=cmmdc(n1,n2);
cout<<”Cel mai mare divizor comun al nr-lor:”<<n1<<” şi ”;
cout<<n2<<” este:”<<diviz<<’\n’;
/* sau:
cout<<”Cel mai mare divizor comun al nr-lor:”<<n1<<” şi ”;
cout<<n2<<” este:”<<cmmdc(n1,n2)<<’\n’;*/ }

Exerciţiu: Să se calculeze valoarea lui y, u şi m fiind citite de la tastatură:


z=2 ω ( 2 ϕ (u) + 1, m) + ω (2 u 2 - 3, m + 1), unde:
n
ω (x,n) = ∑ sin(ix) cos(2ix) ,
i =1
ϕ (x) = 1 + e − x , ω : R x N → R,
2
ϕ: R → R

#include <iostream.h>
#include <math.h>
double omega(double x, int n)

73
CAPITOLUL 6
Funcţii
{ double s=0; int i;
for (i=1; i<=n; i++) s+=sin(i*x)*cos(i*x);
return s;
}

double psi( double x)


{ return sqrt( 1 + exp (- pow (x, 2)));}

void main()
{double u, z; int m; cout<<”u=”; cin>>u; cout<<”m=”; cin>>m;
z=2*omega(2* psi(u) + 1, m) + omega(2*pow(u,2) - 3, m+1);
cout<<”z=”<<z<<’\n’; }

În exemplele anterioare, înainte de apelul funcţiilor folosite, acestea au fost definite (antet+corp). Există cazuri
în care definirea unei funcţii nu poate fi făcută înaintea apelului acesteia (cazul funcţiilor care se apelează
unele pe altele). Să rezolvăm ultimul exerciţiu, folosind declaraţiile funcţiilor omega şi psi, şi nu definiţiile lor.

Exerciţiu:
#include <iostream.h>
#include <math.h>
double omega(double, int);
// prototipul funcţiei omega - antet din care lipsesc numele parametrilor formali
double psi(double); // prototipul funcţiei psi

void main()
{double u, z; int m; cout<<”u=”; cin>>u; cout<<”m=”; cin>>m;
z=2*omega(2* psi(u) + 1, m) + omega(2*pow(u,2) - 3, m+1);
cout<<”z=”<<z<<’\n’; }

double omega(double x, int i); // definiţia funcţiei omega


{ double s=0; int i;
for (i=1; i<=n; i++) s += sin (i*x) * cos (i*x);
return s; }

double psi( double x) // definiţia funcţiei psi


{ return sqrt( 1 + exp (- pow (x, 2))); }

Prototipurile funcţiilor din biblioteci (predefinite) se găsesc în headere. Utilizarea unei astfel de funcţii impune
doar includerea în program a headerului asociat, cu ajutorul directivei preprocesor #include.
Programatorul îşi poate crea propriile headere, care să conţină declaraţii de funcţii, tipuri globale,
macrodefiniţii, etc.

Similar cu declaraţia de variabilă, domeniul de valabilitate (vizibilitate) a unei funcţii este:


 fişierul sursă, dacă declaraţia funcţiei apare în afara oricărei funcţii (la nivel global);
 funcţia sau blocul în care apare declaraţia.

6.3. TRANSFERUL PARAMETRILOR UNEI FUNCŢII

Funcţiile comunică între ele prin argumente (parametrii).

Există următoarele moduri de transfer (transmitere) a parametrilor către funcţiile apelate:


 Transfer prin valoare;
 Transfer prin pointeri;
 Transfer prin referinţă.

74
CAPITOLUL 6
Funcţii
6.3.1. TRANFERUL PARAMETRILOR PRIN VALOARE

În exemplele anterioare, parametrii de la funcţia apelantă la funcţia apelată au fost transmişi prin valoare. De
la programul apelant către funcţia apelată, prin apel, se transmit valorile partametrilor efectivi, reali. Aceste
valori vor fi atribuite, la apel, parametrilor formali. Deci procedeul de transmitere a parametrilor prin valoare
constă în încărcarea valorii parametrilor efectivi în zona de memorie a parametrilor formali (în stivă). La
apelul unei funcţii, parametrii reali trebuie să corespundă - ca ordine şi tip - cu cei formali.

Exerciţiu: Să se scrie următorul program (care ilustrează legătura dintre pointeri şi vectori) şi să se
urmărească rezultatele execuţiei acestuia.
void f1(float intr,int nr)// intr, nr - parametri formali
data
{
for (int i=0; i<nr;i++) intr *= 2.0; 1.5
cout<<”Val. Param. intr=”<<intr<<’\n’;// intr=12
} Copiere valoare
void main()
{ 1.5
float data=1.5; f1(data,3);
intr
// apelul funcţiei f1; data, 3 sunt parametri efectivi
cout<<”data=”<<data<<’\n’; Figura 6.1. Transmiterea prin
// data=1.5 (nemodificat) valoare
}

Fiecare argument efectiv utilizat la apelul funcţiei este evaluat, iar valoarea este atribuită parametrului formal
corespunzător. În interiorul funcţiei, o copie locală a acestei valori va fi memorată în parametrul formal. O
modificare a valorii parametrului formal în interiorul funcţiei (printr-o operaţie din corpul funcţiei), nu va
modifica valoarea parametrului efectiv, ci doar valoarea parametrului formal, deci a copiei locale a
parametrului efectiv (figura 6.1.). Faptul că variabila din programul apelant (parametrul efectiv) şi parametrul
formal sunt obiecte distincte, poate constitui un mijloc util de protecţie. Astfel, în funcţia f1, valoarea
parametrului formal intr este modificată (alterată) prin instrucţiunea ciclică for. În schimb, valoarea
parametrului efectiv (data) din funcţia apelantă, rămâne nemodificată.
În cazul transmiterii parametrilor prin valoare, parametrii efectivi pot fi chiar expresii. Acestea sunt evaluate,
iar valoarea lor va iniţializa, la apel, parametrii formali.
Exemplu:
double psi(int a, double b)
{
if (a > 0) return a*b*2;
else return -a+3*b; }
void main()
{ int x=4; double y=12.6, z; z=psi ( 3*x+9, y-5) + 28;
cout<<”z=”<<z<<’\n’; }

Transferul valorii este însoţit de eventuale conversii de tip. Aceste conversii sunt realizate automat de
compilator, în urma verificării apelului de funcţie, pe baza informaţiilor despre funcţie, sau sunt conversii
explicite, specificate de programator, prin operatorul ”cast”.
Exemplu:
float f1(double, int);
void main()
{
int a, b; float g=f1(a, b); // conversie automată: int a -> double a
float h=f1( (double) a, b); // conversie explicită
}

Limbajul C este numit limbajul apelului prin valoare, deoarece, de fiecare dată când o funcţie transmite
argumente unei funcţii apelate, este transmisă, de fapt, o copie a parametrilor efectivi. În acest mod, dacă
valoarea parametrilor formali (iniţializaţi cu valorile parametrilor efectivi) se modifică în interiorul funcţiei
apelate, valorile parametrilor efectivi din funcţia apelantă nu vor fi afectate.

75
CAPITOLUL 6
Funcţii
6.3.2. TRANSFERUL PARAMETRILOR PRIN POINTERI

În unele cazuri, parametrii transmişi unei funcţii pot fi pointeri (variabile care conţin adrese). În aceste cazuri,
parametrii formali ai funcţiei apelate vor fi iniţializaţi cu valorile parametrilor efectivi, deci cu valorile unor
adrese. Astfel, funcţia apelată poate modifica conţinutul locaţiilor spre care pointează argumentele
(pointerii).

Exerciţiu: Să se citească 2 valori întregi şi să se interschimbe cele două valori. Se va folosi o funcţie de
interschimbare.
#include <iostream.h>
void schimbă(int *, int *); //prototipul functiei schimba
void main()
{
int x, y, *ptx, *pty; ptx=&x; pty=&y;
cout<<”x=”;cin>>x;cout<<”y=”;cin>>y;cout<<”x=”<<x;cout<<”y=”<<y<<’\n’;
cout<<"Adr. lui x:"<<&x<<" Val lui x:"<<x<<'\n';
cout<<"Adr.lui y:"<<&y<<" Val y:"<<y<<'\n'; cout<<"Val. lui ptx:"<<ptx;
cout<<" Cont. locaţiei spre care pointează ptx:”<<*ptx<<'\n';
cout<<"Val. lui pty:"<<pty;
cout<<"Cont. locaţiei spre care pointează pty:"<<*pty;
schimba(ptx, pty);
// SAU: schimba(&x, &y);
cout<<"Adr. lui x:"<<&x<<" %x Val lui x: %d\n”, &x, x);
cout<<"Adr. y:"<<&y<<" Val lui y:"<<y<<'\n';cout<<"Val. lui ptx:"<<ptx;
cout<<" Cont. locaţiei spre care pointează ptx:”<<*ptx<<'\n';
cout<<"Val. lui pty:"<<pty;
cout<<" Cont. locaţiei spre care pointează pty:"<<*pty<<'\n';
}
void schimbă( int *p1, int *p2)
{
cout<<"Val. lui p1:"<<p1;
cout<<" Cont. locaţiei spre care pointează p1:"<<*p1<<'\n';
cout<<"Val. lui p2:"<<p2;
cout<<" Cont. locaţiei spre care pointează p2:"<<*p2<<'\n';
int t = *p1; // int *t ; t=p1;
*p2=*p1; *p1=t;
cout<<"Val. lui p1:"<<p1;
cout<<" Cont. locaţiei spre care pointează p1:”<<*p1<<'\n';
cout<<"Val. lui p2:"<<p2;
cout<<" Cont. locaţiei spre care pointează p2:"<<*p2<<'\n';
}
Dacă parametrii funcţiei schimbă ar fi fost transmişi prin valoare, această funcţie ar fi interschimbat copiile
parametrilor formali, iar în funcţia main modificările asupra parametrilor transmişi nu s-ar fi păstrat. În
figura 6.2. sunt prezentate mecanismul de transmitere a parametrilor (prin pointeri) şi modificările efectuate
asupra lor de către funcţia schimbă.
x y
0x34 10 30 0x5A 30 10 Parametrii formali p1 şi p2, la apelul
ptx pty funcţiei schimbă, primesc valorile
parametrilor efectivi ptx şi pty, care
0x34 0x5A
reprezintă adresele variabilelor x şi y.
Astfel, variabilele pointer p1 şi ptx,
respectiv p2 şi pty pointează către x şi
y. Modificările asupra valorilor
variabilelor x şi y realizate în corpul
p1 p2 funcţiei schimbă, se păstrează şi în
funcţia main.
0x34 0x5A

Figura 6.2. Transmiterea parametrilor unei funcţii prin pointeri


76
CAPITOLUL 6
Funcţii

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia.


#include <iostream.h>
double omega (long *k)
{
cout<<"k=", k);
// k conţine adr. lui i
cout<<”*k=”;
cout<<k<<’\n’; i k
// *k = 35001 35001 (apoi 35017) 0x451 (adr.lui i)
double s=2+(*k)-3; 0x451 În funcţia omega
// s = 35000 În funcţia main
cout<<”s=”<<s<<’\n’;
*k+=17; // *k = 35017 w s
cout<<”*k=”<<*k; 35000 35000
cout<<’\n’;
return s; }
Figura 6.3. Transferul parametrilor prin pointeri
void main()
{
long i = 35001; double w;
cout<<"i="<<i;cout<<"Adr.lui i:"<<&i<<'\n';
w=omega(&i); cout<<”i=”<<i<< w=”<<w<<’\n’; // i = 350017 w = 35000
}

6.3.2.1. Funcţii care returnează pointeri

Valoarea returnată de o funcţie poate fi pointer, aşa cum se observă în exemplul următor:
Exemplu:
#include <iostream.h>
double *f (double *w, int k)
{ // w conţine adr. de început a vectorului a
cout<<"w="<<w<<" *w="<<*w<<'\n'; // w= adr. lui a ;*w = a[0]=10
return w+=k;
/*incrementeaza pointerului w cu 2(val. lui k); deci w pointează către elementul de indice 2 din
vectorul a*/
}
void main()
{double a[10]={10,1,2,3,4,5,6,7,8,9}; int i=2;
cout<<"Adr. lui a este:"<<a;
double *pa=a; // double *pa; pa=a;
cout<<"pa="<<pa<<'\n' // pointerul pa conţine adresa de început a tabloului a
// a[i] = * (a + i)
// &a[i] = a + i
pa=f(a,i); cout<<"pa="<<pa<<" *pa="<<*pa<<'\n';
// pa conţine adr. lui a[2], adica adr. a + 2 * sizeof(double);
*pa=2;
}

6.3.3. TRANSFERUL PARAMETRILOR PRIN REFERINŢĂ

În acest mod de transmitere a parametrilor, unui parametru formal i se poate asocia (atribui) chiar obiectul
parametrului efectiv. Astfel, parametrul efectiv poate fi modificat direct prin operaţiile din corpul funcţiei
apelate.

77
CAPITOLUL 6
Funcţii
În exemplul următor definim variabila br, variabilă referinţă către variabila b. Variabilele b şi br se găsesc,
în memorie, la aceeaşi adresă şi sunt variabile sinonime.

Exemplu:
#include <stdio.h> b, br 7 12
#include <iostream.h>
void main()
{ c 12
int b,c;
int &br=b; //br referinţă la altă variabilă (b)
br=7; Figura 6.4. Variabilele referinţă b, br
cout<<"b="<<b<<'\n'; //b=7
cout<<"br="<<br<<'\n'; //br=7
cout<<"Adr. br este:"<<&br; //Adr. br este:0xfff4
printf("Adr. b este:"<<&b<<'\n'; //Adr. b este:0xfff4
b=12; cout<<"br="<<br<<'\n'; //br=12
cout<<"b="<<b<<'\n'; //b=12
c=br; cout<<"c="<<c<<'\n'; //c=12
}

Exemplul devenit clasic pentru explicarea apelului prin referinţă este cel al funcţiei de permutare
(interschimbare) a două variabile.

Fie funcţia schimb definită astfel: Parametri funcţiei schimb sunt transmişi
void schimb (double x, double y) prin valoare: parametrilor formali x, y li se
{ double t=x; x=y; y=t; } atribuie (la apel) valorile parametrilor efectivi
a, b. Funcţia schimb permută valorile
void main() parametrilor formali x şi y, dar permutarea
{ double a=4.7, b=9.7;
nu are efect asupra parametrilor efectivi a şi
. . . . . . . . . . .
b.
schimb(a, b); // apel funcţie
. . . . . . . . . . . }

Pentru ca funcţia de interschimbare să poată permuta valorile parametrilor efectivi, în limbajul C/C++
parametrii formali trebuie să fie pointeri către valorile care trebuie interschimbate:

void pschimb(double *x, double *y)


{ int t=*x; *x=*y; *y=t; } Se atribuie pointerilor x şi y valorile
void main() pointerilor pa, pb, deci adresele
variabilelor a şi b. Funcţia pschimb
{ double a=4.7, b=9.7; permută valorile spre care pointează
. . . . . . . . . . . pointerii x şi y, deci valorile lui a şi b
pschimb(&a, &b); // apel funcţie (figura 6.5).
/* SAU:
double *pa, *pb;
pa=&a; pb=&b;
pschimb(pa, pb);*/
. . . . . . . . . . . }

pschimb main rschimb main


*x &a a 4.7 9.7 aux 4.7 x; a 4.7 9.7

*y &b b 9.7 4.7 9.7 4.7


y;b
aux 4.7

Figura 6.5. Transferul parametrilor Figura 6.6. Transferul parametrilor prin


prin pointeri referinţă
78
CAPITOLUL 6
Funcţii

În limbajul C++ acceaşi funcţie de permutare se poate defini cu parametri formali de tip referinţă.

void rschimb(double &x, double &y)


{ int t=x; x=y; y=t; }
void main()
{ double a=4.7, b=9.7;
. . . . . . . . . . . . . . .
rschimb(a, b); // apel funcţie
. . . . . . . . . . . . . . . }

În acest caz, x şi y sunt sinonime cu a şi b (nume diferite pentru aceleaşi grupuri de locaţii de memorie).
Interschimbarea valorilor variabilelor de x şi y înseamnă interschimbarea valorilor variabilelor a şi b (fig.
6.6.).

Comparând funcţiile pschimb şi rschimb, se observă că diferenţa dintre ele constă în modul de declarare a
parametrilor formali. În cazul funcţiei pschimb parametrii formali sunt pointeri (de tip *double), în cazul
funcţiei rschimb, parametrii formali sunt referinţe către date de tip double. În cazul transferului
parametrilor prin referinţă, parametrii formali ai funcţiei referă aceleaşi locaţii de memorie (sunt sinonime
pentru) parametrii efectivi.

Comparând cele trei moduri de transmitere a parametrilor către o funcţie, se poate observa:
1. La apelul prin valoare transferul datelor este unidirecţional, adică valorile se transferă numai de la funcţia
apelantă către cea apelată. La apelul prin referinţă transferul datelor este bidirecţional, deoarece o
modificare a parametrilor formali determină modificarea parametrilor efectivi, care sunt sinonime (au nume
diferite, dar referă aceleaşi locaţii de memorie).
2. La transmiterea parametrilor prin valoare, ca parametrii efectivi pot apare expresii sau nume de variabile.
La transmiterea parametrilor prin referinţă, ca parametri efectivi nu pot apare expresii, ci doar nume de
variabile. La transmiterea parametrilor prin pointeri, ca parametri efectivi pot apare expresii de pointeri.
3. Transmiterea parametrilor unei funcţii prin referinţă este specifică limbajului C++.
4. Limbajul C este numit limbajul apelului prin valoare. Apelul poate deveni, însă, apel prin referinţă în cazul
variabilelor simple, folosind pointeri, sau aşa cum vom vedea în paragraful 6.4., în cazul în care parametru
efectiv este un tablou.
5. În limbajul C++ se poate alege, pentru fiecare parametru, tipul de apel: prin valoare sau prin referinţă, aşa
cum ilustrează exemplele următoare:

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia.


#include <iostream.h>
#include <stdio.h>

double func(int a, double b, double *c, double &d)


{cout<<"*********************** func *****************\n";
cout<<"a="<<a<<" b="<<b; //a=7 (a=t prin val); b=21 (b=u prin val)
cout<<" c="<<c<<" *c="<<*c<<'\n'; // c=ffe(c=w=&u) *c=21
cout<<" d="<<d; //d=17
cout<<"Adr d="<<&d<<'\n'; //Adr d=ffe6 (adr d=adr v)
a+=2; cout<<"a="<<a<<'\n'; //a=9
d=2*a+b; cout<<"d="<<d<<'\n'; //d=39
/*c=500;
cout<<" c="<<c<<" *c="<<*c<<'\n'; // c=ffe(c=w=&u) *c=21*/
cout<<"*********************** func *****************\n";
return b+(*c);
}

void main()

79
CAPITOLUL 6
Funcţii
{cout<<"\n\n \n MAIN MAIN";
int t=7; double u=12, v=17, *w, z; cout<<"u="<<u<<'\n'; //u=12
w=&u; *w=21;
cout<<"t="<<t<<" u="<<u<<" v="<<v; //t=7 u=12 v=17 *w=21
cout<<" *w="<<*w<<" u="<<u<<'\n'; //*w=21 u=21
printf("w=%x Adr. u=%x\n",w,&u); //w=ffee Adr. u=ffee
printf("v=%f Adr v=%x\n",v,&v); //v=17.000 Adr v=ffe6
z=func(t,u,w, v);
cout<<"t="<<t<<"u="<<u<<"v="<<v; //t=7 u=21 (NESCHIMBATI) v=39 (v=d)
cout<<" *w="<<*w<<" z="<<z<<'\n'; //*w=21 w=ffee z=42
printf(" w=%x\n",w);
}

Exemplul ilustrează următoarele probleme:


La apelul funcţiei func, parametrii t şi u sunt transmişi prin valoare, deci valorile lor vor fi atribuite
parametrilor formali a şi b. Orice modificare a parametrilor formali a şi b, în funcţia func, nu va avea efect
asupra parametrilor efectivi t şi u. Al treilea parametru formal al funcţiei func este transmis prin pointeri,
deci c este de tip double * (pointer către un real), sau *c este de tip double.

La apelul funcţiei, valoarea pointerului w (adresa lui u : w=&u) este atribuită pointerului c. Deci pointerii w şi
c conţin aceeaşi adresă, pointând către un real. Dacă s-ar modifica valoarea spre care pointează c în func
(vezi instrucţiunile din comentariu *c=500), această modificare ar fi reflectată şi în funcţia apelantă, deoarece
pointerul w are acelaşi conţinut ca şi pointerul c, deci pointează către aceeaşi locaţie de memorie. Parametrul
formal d se transmite prin referinţă, deci, în momentul apelului, d şi v devin similare (d şi v sunt memorate la
aceeaşi adresă). Modificarea valorii variabilei d în func se reflectă, deci, şi asupra parametrului efectiv din
funcţia main.

Exerciţiu: Să se scrie următorul program (care ilustrează legătura dintre pointeri şi vectori) şi să se
urmărească rezultatele execuţiei acestuia.
#include <iostream.h>
#include <stdio.h>
double omega(long &k)
{printf("Adr k=%x Val k=%ld\n",&k,k); //Adr k=fff2 Val k=200001
double s=2+k-3;cout<<"s="<<s<<'\n'; //s=200000
k+=17;printf("Adr k=%x Val k=%ld\n",&k,k); //Adr k=fff2 Val k=200018
return s;
}
void main()
{long a=200001;
printf("Adr a=%x Val a=%ld\n",&a,a); //Adr a=fff2 Val a=200001
double w=omega(a); cout<<"w="<<w<<'\n'; //s=200000
}

Aşa cum s-a prezentat în paragrafele 2.5.3.2. şi 5.6., modificatorii sunt cuvinte cheie utilizaţi în declaraţii sau
definiţii de variabile sau funcţii. Modificatorul de acces const poate apare în:
 Declaraţia unei variabile (precede tipul variabilei) restricţionând modificarea valorii datei;
 La declararea variabilelor pointeri definind pointeri constanţi către date neconstante, pointeri neconstanţi
către date constante şi pointeri constanţi către date constante.
 În lista declaraţiilor parametrilor formali ai unei funcţii, conducând la imposibilitatea de a modifica
valoarea parametrului respectiv în corpul funcţiei, ca în exemplul următor:

Exemplu:
#include <iostream.h>
#include <stdio.h>
int func(const int &a)
{printf("Adr a=%x Val a=%d\n",&a,a);int b=2*a+1;
//modificarea valorii parametrului a nu este permisă

80
CAPITOLUL 6
Funcţii
cout<<"b="<<b<<'\n';return b;}
void main()
{const int c=33;int u;printf("Adr c=%x Val c=%d\n",&c,c);
u=func(c);cout<<"u="<<u<<'\n'; }

6.3.4. TRANSFERUL PARAMETRILOR CĂTRE FUNCŢIA main

În situaţiile în care se doreşte transmiterea a unor informaţii (opţiuni, date iniţiale, etc) către un program, la
lansarea în execuţie a acestuia, este necesară definirea unor parametri către funcţia main. Se utilizează trei
parametrii speciali: argc, argv şi env. Trebuie inclus headerul stdarg.h.
Prototipul funcţiei main cu parametri în linia de comandă este:
main (int argc, char *argv[ ], char *env[ ])

Dacă nu se lucrează cu un mediu de programare integrat, argumentele transmise către funcţia main trebuie
editate (specificate) în linia de comandă prin care se lansează în execuţie programul respectiv. Linia de
comandă tastată la lansarea în execuţie a programului este formată din grupuri de caractere delimitate de
spaţiu sau tab. Fiecare grup este memorat într-un şir de caractere. Dacă se lucrează cu un mediu integrat (de
exemplu, BorlandC), selecţia comanzii Arguments… din meniul Run determină afişarea unei casete de
dialog în care utilizatorul poate introduce argumentele funcţiei main.

 Adresele de început ale acestor şiruri sunt memorate în tabloul de pointeri argv[], în ordinea în care
apar în linia de comandă (argv[0] memorează adresa şirului care constituie numele programului,
argv[1] - adresa primului argument, etc.).
 Parametrul întreg argc memorează numărul de elemente din tabloul argv (argc>=1).
 Parametrul env[] este un tablou de pointeri către şiruri de caractere care pot specifica parametri ai
sistemului de operare.

Funcţia main poate returna o valoare întreagă. În acest caz în antetul funcţiei se specifică la tipul valorii
returnate int, sau nu se specifică nimic (implicit, tipul este int), iar în corpul funcţiei apare instrucţiunea
return valoare_intreagă;. Numărul returnat este transferat sistemului de operare (programul apelant) şi poate
fi tratat ca un cod de eroare sau de succes al încheierii execuţiei programului.

Exerciţiu: Să se implementeze un program care afişează argumentele transmise către funcţia main.
#include <iostream.h>
#include <stdarg.h>
void main(int argc, char *argv[], char *env[])
{cout<<"Nume program:"<<argv[0]<<'\n';//argv[0] contine numele programului
if(argc==1)
cout<<"Lipsa argumente!\n";
else
for (int i=1; i<argc; i++){
cout<<"Argumentul "<<i<<":"<<argv[i]<<'\n';
}

6.4. TABLOURI CA PARAMETRI

În limbajul C, cazul parametrilor tablou constituie o excepţie de la regula transferului parametrilor prin
valoare. Numele unui tablou reprezintă, de fapt, adresa tabloului, deci a primului element din tablou.

Exerciţiu: Să se afle elementul minim dintr-un vector de maxim 10 elemente. Se vor scrie două funcţii: de
citire a elementelor vectorului şi de aflare a elementului minim:
#include <iostream.h>
int min_tab(int a[], int nr_elem)
{int elm=a[0];

81
CAPITOLUL 6
Funcţii
for (int ind=0; ind<nr_elem; ind++)
if (elm>=a[ind]) elm=a[ind];
return elm;
}
void citireVector(int b[], int nr_el)
{ for (int ind=0; ind<nr_el; ind++){
cout<<"Elem "<<ind+1<<"="; cin>>b[ind];}
}
void main()
{
int a[10]; int i,j,n; cout<<"n="; cin>>n;
citireVector(a,n);
int min=min_tab(a,n); cout<<"Elem. min:"<<min<<'\n'; }

Aceleeaşi problemă poate fi implementată folosind aritmetica pointerilor:

#include <iostream.h>
void citireVector(int *b, int nr_el)
{ for (int ind=0; ind<nr_el; ind++){
cout<<"Elem "<<ind+1<<"="; cin>>*b(ind+ind);}
}
int min_tab(int *a, int nr_elem)
{int elm=*a;
for (int ind=0; ind<nr_elem; ind++)
if ( elm>=*(a+ind) ) elm=*(a+ind);
return elm;
}
void main()
{
int a[10]; int i,j,n; cout<<"n="; cin>>n;
citireVector(a, n);
int min=min_tab(a,n);
cout<<"Elem. min:"<<min<<'\n';
}

Din exemplele anterioare se poate observa:


1. Prototipul funcţiei min_tab poate fi unul dintre:
int min_tab(int a[], int nr_elem);
int min_tab(int *a, int nr_elem);
2. Echivalenţe:
int *a ⇔ int a[]
a[i] ⇔ *(a+i)
3. Apelul funcţiilor:
citireVector(a,n);
int min=min_tab(a,n);
4. Pentru tablourile unidimensionale, la apel, nu trebuie specificat numărul de elemente. Dimensiunea
tabloului trebuie să fie cunoscută în funcţia care îl primeşte ca parametru. De obicei, dimensiunea tabloului se
transferă ca parametru separat (nr_elem).

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia.


#include <iostream.h>
#include <stdio.h>
double omega(int j, double x, double t[], double *w)
{double s; cout<<"În funcţia omega:";
cout<<"j="<<j<<" t[j]="<<t[j]<<" t[j+1]="<<t[j+1]<<'\n';
//j=2 (=i din main)
//t[j]=-3.21 t[j+1]=7.44
cout<<"j="<<j<<" w[j]="<<w[j]<<" w[j+1]="<<w[j+1]<<'\n';
//j=2 (=i din main)

82
CAPITOLUL 6
Funcţii
//w[j]=-21.16 w[j+1]=92.2
t[j]=100; *(t+j+1)=200; w[j]=300; *(w+j+1)=400;
cout<<"După atribuiri:\n";
cout<<"j="<<j<<" t[j]="<< t[j]<<" t[j+1]="<<t[j+1]<<'\n';
//După atribuiri:
//j=2
//t[j]=100 t[j+1]=200
//w[j]=300 w[j+1]=400
cout<<"j="<<j<<" w[j]="<<w[j]<<" w[j+1]="<<w[j+1]<<'\n';
int i=2*j+1; x=x+2.29*i; s=x+2*t[0]-w[1];
cout<<"i="<<i<<" x="<<x<<" s="<<s<<'\n';
//i=5 x=1.123+2.29+5 s=x+2*1.32-(-15.34)
return s;
}
void switch1(double *x, double *y)
{double t=*x; *x=*y; *y=t;}
void switch2(double &x, double &y)
{double t; t=x;x=y;y=t;}
void main()
{double a=123, b=456, u=1.123;
int i=2;
double r[]={1.32, 2.15, -3.21, 7.44, -15.8};
double q[]={12.26, -15.34, -21.16, 92.2, 71.6};
cout<<"i="<<i<<" u="<<u<<'\n';
double y=omega(i,u,r,q);
cout<<"i="<<i<<" u="<<u<<'\n';
//i=2 u=....
cout<<"omega(i,u,r,q)=y="<<y<<'\n';
cout<<"r[i]="<<r[i]<<" r[i+1]="<<r[i+1]<<;
cout<<" q[i]="<<q[i]<<" q[i+1]="<<q[i]<<'\n';
//r[i]=100 r[i+1]=200 q[i]=300 q[i+1]=400
cout<<"a="<<a<<" b="<<b<<'\n'; //a=123 b=456
switch1(&a,&b);
cout<<"Rez. intersch. a="<<a<<" b="<<b<<'\n'; //a=456 b=123
switch2(a,b);
cout<<"Rez. intersch. a="<<a<<" b="<<b<<'\n'; //a=123 b=456
cout<<"r[i]="<<r[i]<<" r[i+1]="<<r[i+1]<<'\n';
switch1(r+i,r+i+1);
cout<<"Rez. intersch. r[i]="<<r[i]<<" r[i+1]="<<r[i+1]<<'\n';
switch2(r[i],r[i+1]);
//switch2(*(r+i),*(r+i+1));
cout<<"Rez. intersch. r[i]="<<r[i]<<" r[i+1]="<<r[i+1]<<'\n';
}

În exemplul anterior, parametrii formali i şi x din funcţia omega sunt transmişi prin valoare; parametrii t şi
w sunt parametri tablou, transmişi prin referinţă (referinţă şi pointeri). În funcţia switch1 parametrii sunt
transmişi prin pointeri. În funcţia switch2 parametrii sunt transmişi prin referinţă.

Pentru tablourile multidimensionale, pentru ca elementele tabloului să poată fi referite în funcţie,


compilatorul trebuie informat asupra modului de organizare a tabloului.
Pentru tablourile bidimensionale (vectori de vectori), poate fi omisă doar precizarea numărului de linii,
deoarece pentru a adresa elementul a[i][j], compilatorul utilizează relaţia: &mat[i][j]=&mat+(i*
N+j)*sizeof(tip), în care N reprezintă numărul de coloane, iar tip reprezintă tipul tabloului.

Exerciţiu: Fie o matrice de maxim 10 linii şi 10 coloane, ale cărei elemente se introduc de la tastatură. Să se
implementeze două funcţii care afişează matricea şi calculează elementul minim din matrice.

#include <iostream.h>

83
CAPITOLUL 6
Funcţii
int min_tab(int a[][10], int nr_lin, int nr_col)
{int elm=a[0][0];
for (int il=0; il<nr_lin; il++)
for (int ic=0; ic<nr_col; ic++)
if (elm>=a[il][ic]) elm=a[il][ic];
return elm;
}
void afisare(int a[][10], int nr_lin, int nr_col)
{
for (int i=0; i<nr_lin; i++)
{for (int j=0; j<nr_col; j++) cout<<a[i][j]<<'\t';
cout<<'\n';
}
}
void main()
{
int mat[10][10];int i, j, M, N;cout<<"Nr. linii:"; cin>>M;
cout<<"Nr. coloane:"; cin>>N;
for (i=0; i<M; i++)
for (j=0; j<N; j++)
{ cout<<"mat["<<i<<","<<j<<"]="; cin>>mat[i][j];}
afisare(mat, M, N);
int min=min_tab(mat, M, N);
cout<<"Elem. min:"<<min<<'\n';
}

Valoarea returnată de o funcţie poate să fie transmisă şi prin referinţă, cum ilustrează exemplul următor:

Exemplu:
#include <iostream.h>
#include <stdio.h>
double &func(double &a, double b)
{ printf("În funcţie:\n");
printf("Val a=%f Adr a=%x\n", a, &a); //Val a=1.20 Adr a=fffe
cout<<"b="<<b<<'\n'; //b=2.2
a=2*b+1; printf(" După atrib: val a=%f Adr a=%x\n", a, &a);
//Val a=5.40 Adr a=fffe
return a;
}
void main()
{double c=1.2;cout<<"***************MAIN****************\n";
printf("Val c=%f Adr c=%x\n",c, &c); //Val c=1.20 Adr c=fffe
double d; printf("Adr. d=%x\n", &d); //Adr. d=ffe6
d=func(c,2.2);
printf("Val d=%f Adr d=%x\n", d, &d); //Val d=5.40 Adr d=ffe6
}

6.5. FUNCŢII CU PARAMETRI IMPLICIŢI

Spre deosebire de limbajul C, în limbajul C++ se pot face iniţializări ale parametrilor formali. Parametrii
formali iniţializaţi se numesc parametri impliciţi. De exemplu, antetul funcţiei cmmdc (care calculează şi
returnează cel mai mare divizor comun al numerelor întregi primite ca argumente) poate avea aceasta formă:
int cmmdc(int x, int y=1);

Parametrul formal y este iniţializat cu valoarea 1 şi este parametru implicit. La apelul funcţiilor cu parametri
impliciţi, unui parametru implicit, poate să-i corespundă sau nu, un parametru efectiv. Dacă la apel nu îi
corespunde un parametru efectiv, atunci parametrul formal va primi valoarea prin care a fost iniţializat
(valoarea implicită). Dacă la apel îi corespunde un parametru efectiv, parametrul formal va fi iniţializat cu

84
CAPITOLUL 6
Funcţii
valoarea acestuia, negijându-se astfel valoarea implicită a parametrului formal. În exemplul anterior, la apelul:
int div=cmmdc(9);
x va lua valoarea 9, iar y va lua valoarea 1 (implicită).

Dacă în lista de parametri formali ai unei funcţii există şi parametri impliciţi şi parametri neiniţializaţi,
parametrii impliciţi trebuie să ocupe ultimele poziţii în listă, nefiind permisă intercalarea acestora printre
parametrii neiniţializaţi.

6.6. FUNCŢII CU NUMĂR VARIABIL DE PARAMETRI

În limbajele C şi C++ se pot defini funcţii cu un număr variabil de parametri. Parametrii care trebuie să fie
prezenţi la orice apel al funcţiei se numesc parametri ficşi, ceilalţi se numesc parametri variabili. Parametrii
ficşi preced parametrii variabili. Prezenţa parametrilor variabili se indică în antetul funcţiei prin trei puncte
care se scriu după ultimul parametru fix al funcţiei.

De exemplu, fie antetul funcţiei numite vârf:


void vârf (int n, double a, . . . )
Funcţia vârf are doi parametri ficşi (n şi a ) şi parametri variabili, pentru care nu se precizează în
prealabil numărul şi tipul; numărul şi tipul parametrilor variabili diferă de la un apel la altul.

Funcţiile cu un număr variabil de parametri sunt, de obicei, funcţii de bibliotecă (ex: printf, scanf) şi se
definesc folosind nişte macrouri speciale care permit accesul la parametrii variabili şi se găsesc în headerul
stdarg.h.

6.7. FUNCŢII PREDEFINITE

Orice mediu de programare este prevăzut cu una sau mai multe biblioteci de funcţii predefinite. Orice
bibliotecă este formată din:
 fişierele header (conţine prototipurile funcţiilor, declaraţiile de variabile);
 biblioteca (arhiva) propriu-zisă (conţine definiţii de funcţii).

Pentru ca funcţiile predefinite să poată fi utilizate, fişierele header în care se găsesc prototipurile acestora
trebuie inclus în funcţia (programul) apelant printr-o directivă preprocesor (exemplu #include
<stdio.h>). Deasemenea, utilizatorul îşi poate crea propriile headere proprii. Pentru a putea utiliza funcţiile
proprii, el trebuie să includă aceste headere în programul apelant (exemplu #include "my_header.h").

Pentru funcţiile predefinite, au fost create fişiere header orientate pe anumite numite tipuri de aplicaţii. De
exemplu, funcţiile matematice se găsesc în headerul <math.h>. Headerul <stdlib.h> care conţine funcţii
standard. Headerul <values.h> defineşte o serie de constante simbolice (exemplu MAXINT, MAXLONG) care
reprezintă, în principal, valorile maxime şi minime ale diferitelor tipuri de date.

6.7.1. Funcţii matematice (headerul <math.h>)

Funcţii aritmetice
Valori absolute
int abs(int x);
Returnează un întreg care reprezintă valoarea absolută a argumentului.
long int labs(long int x);
Analog cu funcţia abs, cu deosebirea că argumentul şi valoarea returnată sunt de tip long int.
double fabs(double x);
Returnează un real care reprezintă valoarea absolută a argumentului real.

Funcţii de rotunjire
double floor(double x);

85
CAPITOLUL 6
Funcţii
Returnează un real care reprezintă cel mai apropiat număr, fără zecimale, mai mic sau egal cu
x (rotunjire prin lipsă).
double ceil(double x);
Returnează un real care reprezintă cel mai apropiat număr, fără zecimale, mai mare sau egal
cu x (rotunjire prin adaos).

Funcţii trigonometrice
double sin(double x);
Returnează valoarea lui sin(x), unde x este dat în radiani. Numărul real returnat se află în
intervalul [-1, 1].
double cos(double x);
Returnează valoarea lui cos(x), unde x este dat în radiani. Numărul real returnat se află în
intervalul [-1, 1].
double tan(double x);
Returnează valoarea lui tg(x), unde x este dat în radiani.

Funcţii trigonometrice inverse


double asin(double x);
Returnează valoarea lui arcsin(x), unde x se află în intervalul [-1, 1]. Numărul real returnat
(în radiani) se află în intervalul [-pi/2, pi/2].
double acos(double x);
Returnează valoarea lui arccos(x), unde x se află în intervalul [-1, 1]. Numărul real returnat
se află în intervalul [0, pi].
double atan(double x);
Returnează valoarea lui arctg(x), unde x este dat în radiani. Numărul real returnat se află în
intervalul [0, pi].
double atan2(double y, double x);
Returnează valoarea lui tg(y/x), cu excepţia faptului ca semnele argumentelor x şi y permit
stabilirea cadranului şi x poate fi zero. Valoarea returnată se află în intervalul [-pi,pi]. Dacă x
şi y sunt coordonatele unui punct în plan, funcţia returnează valoarea unghiului format de
dreapta care uneşte originea axelor carteziene cu punctul, faţă de axa absciselor. Funcţia
foloseşte, deasemenea, la transformarea coordonatelor cartezine în coordonate polare.

Funcţii exponenţiale şi logaritmice


double exp(double x);
long double exp(long double x);
Returnează valoarea e x .
double log(double x);
Returnează logaritmul natural al argumentului ( ln(x) ).
double log10(double x);
Returnează logaritmul zecimal al argumentului (lg (x) ).
double pow(double baza, double exponent);
Returnează un real care reprezintă rezultatul ridicării bazei la exponent ( baza exp onent ).
double sqrt(double x);
Returnează rădăcina pătrată a argumentului x .
double hypot(double x, double y);
Funcţia distanţei euclidiene - returnează x 2 + y 2 , deci lungimea ipotenuzei unui triunghi
dreptunghic, sau distanţa punctului P(x, y) faţă de origine.

Funcţii de generare a numerelor aleatoare


int rand(void) <stdlib.h>
Generează un număr aleator în intervalul [0, RAND_MAX].

6.7.2. Funcţii de clasificare (testare) a caracterelor

86
CAPITOLUL 6
Funcţii
Au prototipul în headerul <ctype.h>. Toate aceste funcţii primesc ca argument un caracter şi returnează
un număr întreg care este pozitiv dacă argumentul îndeplineşte o anumită condiţie, sau valoarea zero dacă
argumentul nu îndeplineşte condiţia.

int isalnum(int c);


Returnează valoare întreagă pozitivă daca argumentul este literă sau cifră. Echivalentă cu:
isalpha(c)||isdigit(c)
int isalpha(int c);
Testează dacă argumentul este literă mare sau mică. Echivalentă cu isupper(c)||
islower(c).
int iscntrl(int c);
Testează dacă argumentul este caracter de control (neimprimabil).
int isdigit(int c);
Testează dacă argumentul este cifră.
int isxdigit(int c);
Testează dacă argumentul este cifră hexagesimală (0-9, a-f, A-F).
int islower(int c);
Testează dacă argumentul este literă mică.
int isupper(int c);
Testează dacă argumentul este literă mare.
int ispunct(int c);
Testează dacă argumentul este caracter de punctuaţie (caracter imprimabil, dar nu literă sau
spaţiu).
int isspace(int c);
Testează dacă argumentul este spaţiu alb (' ', '\n', '\t', '\v', '\r')
int isprint(int c);
Testează dacă argumentul este caracter imprimabil, inclusiv blancul.

6.7.3. Funcţii de conversie a caracterelor (prototip în <ctype.h>)


int tolower(int c);
Funcţia schimbă caracterul primit ca argument din literă mare, în literă mică şi returnează
codul ASCII al literei mici. Dacă argumentul nu este literă mare, codul returnat este chiar
codul argumentului.
int toupper(int c);
Funcţia schimbă caracterul primit ca argument din literă mică, în literă mare şi returnează
codul acesteia. Dacă argumentul nu este literă mică, codul returnat este chiar codul
argumentului.

6.7.4. Funcţii de conversie din şir în număr (de citire a unui număr dintr-un şir)
(prototip în <stdlib.h>)
long int atol(const char *npr);
Funcţia converteşte şirul transmis ca argument (spre care pointează npr) într-un număr cu
semn, care este returnat ca o valoare de tipul long int. Şirul poate conţine caracterele '+' sau
'-'. Se consideră că numărul este în baza 10 şi funcţia nu semnalizează eventualele erori de
depăşire care pot apare la conversia din şir în număr.
int atoi(const char *sir);
Converteste şirul spre care pointeaza sir într-un număr întreg.
double atof(const char *sir);
Funcţia converteste şirul transmis ca argument într-un număr real cu semn (returnează
valoare de tipul double). În secvenţa de cifre din şir poate apare litera 'e' sau 'E'
(exponentul), urmată de caracterul '+' sau '-' şi o altă secvenţă de cifre. Funcţia nu
semnalează eventualele erori de depăşire care pot apare.

6.7.5. Funcţii de terminare a unui proces (program)


(prototip în <process.h>)
void exit(int status);

87
CAPITOLUL 6
Funcţii
Termină execuţia unui program. Codul returnat de terminarea corectă este memorat în
constanta simbolică EXIT_SUCCES, iar codul de eroare - în EXIT_FAILURE.
void abort();
Termină forţat execuţia unui program.
int system(const char *comanda); prototip în <system.h>
Permite execuţia unei comenzi DOS, specificate prin şirul de caractere transmis ca parametru.

6.7.6. Funcţii de intrare/ieşire (prototip în <stdio.h>)


Streamurile (fluxurile de date) implicite sunt: stdin (fişierul, dispozitivul standard de intrare), stdout
(fişierul, dispozitivul standard de ieşire), stderr (fişier standard pentru erori), stdprn (fişier standard
pentru imprimantă) şi stdaux (dispozitivul auxiliar standard). De câte ori este executat un program,
streamurile implicite sunt deschise automat de către sistem. În headerul <stdio.h> sunt definite şi
constantele NULL (definită ca 0) şi EOF (sfârşit de fişier, definită ca -1, CTRL/Z).
int getchar(void);
Citeşte un caracter (cu ecou) din fişierul standard de intrare (tastatură).
int putchar(int c);
Afişează caracterul primit ca argument în fişierul standard de ieşire (monitor).
char *gets(char *sir);
Citeşte un şir de caractere din fişierul standard de intrare (până la primul blank întâlnit sau
linie nouă). Returnează pointerul către şirul citit.
int puts(const char *sir);
Afişează şirul argument în fişierul standard de ieşire şi adaugă terminatorul de şir. Returnează
codul ultimului caracter al şirului (caracterul care precede NULL) sau -1 în caz de eroare.
int printf(const char *format, ... );
Funcţia permite scrierea în fişierul standard de ieşire (pe monitor) a datelor, într-un anumit format.
Funcţia returnează numărul de octeţi (caractere) afişaţi, sau –1 în cazul unei erori.
1. Parametrul fix al funcţiei conţine:
 Succesiuni de caractere afişate ca atare
Exemplu:
printf("\n Buna ziua!\n\n”); // afişare: Buna ziua!
 Specificatori de format care definesc conversiile care vor fi realizate asupra datelor de ieşire,
din formatul intern, în cel extren (de afişare).
2. Parametrii variabili ai funcţiei sunt expresii. Valorile obţinute în urma evaluării acestora sunt
afişate corespunzător specificatorilor de format care apar în parametrul fix. De obicei, parametrul
fix conţine atât specificatori de format, cât şi alte caractere. Numărul şi tipul parametrilor
variabili trebuie să corespundă specificatorului de format.

Un specificator de format care apare în parametrul fix poate avea următoarea formă:
% [-|c|][sir_cifre_eventual_punct_zecimal] una_sau_doua_litere
- Implicit, datele se cadrează (aliniază) la dreapta câmpului în care se scriu. Prezenţa caracterului –
determină cadrarea la stânga.
Şirul de cifre defineşte dimensiunea câmpului în care se scrie data. Dacă scrierea datei necesită un
câmp de lungime mai mare, lungimea indicată în specificator este ignorată. Dacă scrierea datei
necesită un câmp de lungime mai mică, data se va scrie în câmp, cadrată la dreapta sau la stânga
(dacă apare semnul - ), completându-se restul câmpului cu caracterele nesemnificative implicite, adica
spaţii. Şirul de cifre aflate dupa punct definesc precizia (numarul de zecimale cu care este afişat un
numar real - implicit sunt afişate 6 zecimale).
Literele definesc tipul conversiei aplicat datei afişate:
c – Afişează un caracter
s – Afişează un şir de caractere
d– Afişează date întregi; cele negative sunt precedate de semnul -.
o – Afişează date de tip int sau unsigned int în octal.
x sau X - Afişează date de tip int sau unsigned int în hexagesimal.
f–Afişează date de tip float sau double în forma: parte_întreagă.parte_fract
e sau E-Afişează date de tip float sau double în forma:

88
CAPITOLUL 6
Funcţii
parte_întreagă.parte_fractionară exponent
Exponentul începe cu e sau E şi defineşte o putere a lui zece care înmulţită cu restul numărului dă
valoarea reală a acestuia.
g sau G–Afişează o dată reală fie ca în cazul specificatorului terminat cu f, fie ca în cazul
specificatorului terminat cu e. Criteriul de afisare se alege automat, astfel încât afişarea să ocupe un
număr minim de poziţii în câmpul de afişare.
l – Precede una din literele d, o, x, X, u. La afişare se fac conversii din tipul long sau unsigned long.
L – Precede una din literele f, e, E, g, G. La afişare se fac conversii din tipul long double.

int scanf(const char *format, ... );


Funcţia citeşte din fişierul standard de intrare valorile unor variabile şi le depune în memorie, la
adresele specificate. Funcţia returnează numărul câmpurilor citite corect.
1. Parametrul fix al funcţiei conţine:
Specificatorii de format care definesc conversiile aplicate datelor de intrare, din formatul extern,
în cel intren (în care sunt memorate). Specificatorii de format sunt asemanători celor folosiţi de
funcţia printf: c, s, d, o, x sau X, u, f, l, L.
2. Parametrii varaibili reprezintă o listă de adrese ale variabilelor care vor fi citite, deci în această
listă, numele unei varaibile simple va fi precedată de operatorul adresă &.

int sprintf(char *sir_cu_format, const char *format, ... );


Funcţia permite scrierea unor date în şirul transmis ca prim argument, într-un anumit format. Valoarea
returnată reprezintă numărul de octeţi (caractere) scrise în şir, sau –1 în cazul unei erori.

int sscanf(char *sir_cu_format, const char *format, ... );


Funcţia citeşte valorile unor variabile din şirul transmis ca prim argument şi le depune în memorie, la
adresele specificate. Returnează numărul câmpurilor citite corect.

Exemplu: Să se scrie următorul program (care ilustrează modalităţile de folosire a funcţiilor predefinite) şi să
se urmărească rezultatele execuţiei acestuia.
#include <iostream.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#include <stdio.h>
void main()
{ int x=-34; int a=abs(x); cout<<"a="<<a<<'\n';
long int y=-566666;
cout<<"labs(y)="<<labs(y)<<"fabs(-45.67)="<<fabs(-45.67)<<'\n';
cout<<"fabs(45.67)="<<fabs(45.67)<<'\n';
cout<<floor(78.99)<<'\n'; //78
cout<<floor(78.45)<<'\n'; //78
cout<<floor(-78.45)<<'\n'; //-79
cout<<ceil(78.99)<<'\n'; //79
cout<<ceil(78.45)<<'\n'; //79
cout<<ceil(-78.45)<<'\n'; //-78
cout<<isalpha('8')<<'\n'; //0
cout<<isalpha('f')<<'\n'; //val diferita de zero
cout<<isalpha('%')<<'\n'; //0
cout<<tolower('D')<<'\n'; //100 (codul caracterului 'd')
cout<<toupper('a')<<'\n'; //65 (codul caracterului 'A')
char s1[]="-56.234 h mk"; cout<<atol(s1)<<'\n'; //-56
cout<<atoi(s1)<<'\n'; //-56
cout<<atof(s1)<<'\n'; //-56.234
cout<<atof("45E+3 n")<<'\n'; //45000
cout<<"EXECUTIA COMENZII DOS DIR\n"; int cod_ret=system("dir");
cout<<"Val. cod retur="<<cod_ret<<'\n';

89
CAPITOLUL 6
Funcţii
int c;cout<<"Astept car:"; c=getchar(); //Presupunem caracter introdus: e
cout<<"Caracterul citit este:"<<putchar(c);//Caracterul citit este: 101
// 101=codul carcterului e
cout<<'\n';puts(s1);cout<<'\n'; printf("Afisarea unui mesaj\n");
int intreg=-45;
printf("VALOAREA VARIABILEI INTREG ESTE:%d\n", intreg);
printf("VALOAREA VARIABILEI INTREG ESTE:%10d\n", intreg);
printf("VALOAREA VARIABILEI INTREG ESTE:%-10d\n", intreg);
double real=2.45;
printf("VALOAREA VARIABILEI real ESTE:%f\n", real);
printf("VALOAREA VARIABILEI real ESTE:%10.3f\n", real);
printf("VALOAREA VARIABILEI real ESTE:%10.5f\n", real);
printf("VALOAREA VARIABILEI real ESTE:%e\n", real);
printf("VAL VAR real:%f si\neste mem. la adr.%x\n",real,&real );
printf("astept sir:");scanf("%s",s1);
printf("Sirul citit este: %s \n", s1);
char sir_f[100];
sprintf(sir_f,"Codul caracterului %c este:%d",c, (int)c);
puts(sir_f);
}

6.8. CLASE DE MEMORARE

Definiţii:
Variabilele declarate în afara oricărei funcţii sunt variabilele globale.
Variabilele declarate în interiorul unui bloc sunt variabilele locale.
Porţiunea de cod în care o variabilă este accesibilă reprezintă scopul (domeniul de vizibilitate) al
variabilei respective.

Parametrii formali ai unei funcţii sunt variabile locale ale funcţiei respective.
Domeniul de vizibilitate al unei variabile locale este blocul în care variabila respectivă este definită.
În situaţia în care numele unei variabile globale coincide cu numele unei variabile locale, variabila locală o
"maschează" pe cea globală, ca în exemplul următor: în interiorul blocului din funcţia main s-a redefinit
variabila a, care este variabilă locală în interiorul blocului. Variabila locală a maschează variablila globală
numită tot a.

Exemplu:
#include <stdio.h>
void main()
{ int a,b; a=1; b=2;
printf("În afara blocului a=%d b=%d\n", a, b);
{int a=5; b=6;
printf("În interiorul blocului a=%d b=%d\n",a,b);
}
printf("În afara blocului a=%d b=%d\n", a, b);
}

În cazul variabilelor locale, compilatorul alocă memorie în momentul execuţiei blocului sau funcţiei în care
acestea sunt definite. Când execuţia funcţiei sau blocului se termină, se eliberează memoria pentru acestea şi
valorile pentru variabilele locale se pierd.

Definiţii:
Timpul de viaţă a unei variabile locale este durata de execuţie a blocului (sau a funcţiei) în care
aceasta este definită.
Timpul de viaţă a unei variabile globale este durata de execuţie a programului.

90
CAPITOLUL 6
Funcţii
În exemplul următor, variabila întreagă x este vizibilă atât în funcţia main, cât şi în funcţia func1 (x este
variabila globală, fiind definită în exteriorul oricărei funcţii). Variabilele a şi b sunt variabile locale în funcţia
main (vizibile doar în main). Variabilele c şi d sunt variabile locale în funcţia func1 (vizibile doar în func1).
Varabila y este variabilă externă şi este vizibilă din punctul în care a fost definită, până la sfârşitul fişierului
sursă (în acest caz, în funcţia func1).

Exemplu:
int x;
void main()
{int a,b;
//- - - - - - - - -
}
int y;
void func1(void)
{int c,d;
//- - - - - - - -
}

Clase de memorare

O variabilă se caracterizează prin: nume, tip, valoare şi clasă de memorare.


Clasa de memorare se specifică la declararea variabilei, prin unul din următoarele cuvinte cheie:
 auto;
 register;
 extern;
 static.
Clasa de memorare determină timpul de viaţă şi domeniul de vizibilitate (scopul) unei variabile (tabelul 6.1).
Exemplu:
auto int a;
static int x;
extern double y;
register char c;

 Clasa de memorare auto


Dacă o variabilă locală este declarată fără a se indica în mod explicit o clasă de memorare, clasa de
memorare considerată implicit este auto. Pentru acea variabilă se alocă memorie automat, la intrarea în
blocul sau în funcţia în care ea este declarată. Deci domeniul de vizibilitate al variabilei este blocul sau
funcţia în care aceasta a fost definită. Timpul de viaţă este durata de execuţie a blocului sau a funcţiei.

 Clasa de memorare register


Variabilele din clasa register au acelaşi domeniu de vizibilitate şi timp de viaţă ca şi cele din clasa auto.
Deosebirea faţă de variabilele din clasa auto constă în faptul că pentru memorarea variabilelor register,
compilatorul utilizează regiştrii interni (ceea ce conduce la creşterea eficienţei). Unei variabile pentru care
se specifică drept clasă de memorare register, nu i se poate aplica operatorul de referenţiere.

 Clasa de memorare extern


O variabilă globală declarată fără specificarea unei clase de memorare, este considerată ca având clasa
de memorare extern. Domeniul de vizibilitate este din momentul declarării până la sfârşitul fişierului
sursă. Timpul de viaţă este durata execuţiei fişierului. O variabilă din clasa extern este iniţializată automat
cu valoarea 0.

 Clasa de memorare static


Clasa de memorare static are două utilizări distincte:
 Variabilele locale statice au ca domeniu de vizibilitate blocul sau funcţia în care sunt definite, iar ca
timp de viaţă - durata de execuţie a programului. Se iniţializează automat cu 0.

91
CAPITOLUL 6
Funcţii
 Variabilele globale statice au ca domeniu de vizibilitate punctul în care au fost definite până la
sfârşitul fişierului sursă, iar ca timp de viaţă - durata execuţiei programului.

Tabelul 6.1.
Clasa de Variabila Domeniu vizibilitate Timp de viaţă
memorare
auto locală (internă) Blocul sau funcţia Durara de execuţie a blocului
(register) sau a funcţiei
extern globală  Din punctul definirii, până la Durara de execuţie a blocului
sfârşitul fişierului (ROF) sau a programului
 Alte fişiere
static globală ROF -"-
locală Bloc sau funcţie -"-
nespecificat globală Vezi extern Vezi extern
locală Vezi auto Vezi auto

6.9. MODURI DE ALOCARE A MEMORIEI

Alocarea memoriei se poate realiza în următoarele moduri:


 alocare statică;
 alocare dinamică;
 alocare pe stivă.
 Se alocă static memorie în următoarele cazuri:
 pentru instrucţiunile de control propriu-zise;
 pentru variabilele globale şi variabilele locale declarate în mod explicit static.
 Se alocă memorie pe stivă pentru variabilele locale.
 Se aloca dinamic memorie în mod explicit, cu ajutorul funcţiilor de alocare dinamica, aflate în
headerul <alloc.h>.
Exemplu:
int a,b; double x;
double f1(int c, double v)
{int b;
static double z;
}
double w;
int f1(int w)
{double a;
}
void main()
{double b, c; int k;
b=f1(k,c);
}

6.9.1. Alocarea memoriei în mod dinamic

Pentru toate tipurile de date (simple sau structurate), la declararea acestora, compilatorul alocă automat un
număr de locaţii de memorie (corespunzător tipului datei). Dimensiunea zonei de memorie necesară pentru
păstrarea valorilor datelor este fixată înaintea lansării în execuţie a programului. În cazul declarării unui
tablou de întregi cu maximum 100 de elemente vor fi alocaţi 100*sizeof(int) locaţii de memorie
succesive. În situaţia în care la un moment dat tabloul are doar 20 de elemente, pentru a aloca doar atâta
memorie cât este necesară în momentul respectiv, se va aloca memorie în mod dinamic.

Este de dorit ca în cazul datelor a căror dimensiune nu este cunoscută a priori sau variază în limite largi, să se
utilizeze o altă abordare: alocarea memoriei în mod dinamic. În mod dinamic, memoria nu mai este alocată în

92
CAPITOLUL 6
Funcţii
momentul compilării, ci în momentul execuţiei. Alocarea dinamică elimină necesitatea definirii complete a
tuturor cerinţelor de memorie în momentul compilării. În limbajul C, alocarea memoriei în mod dinamic se
face cu ajutorul funcţiilor malloc, calloc, realloc; eliberarea zonei de memorie se face cu ajutorul
funcţiei free. Funcţiile de alocare/dezalocare a memoriei au prototipurile în header-ele <stdlib.h> şi
<alloc.h>:

void *malloc(size_t nr_octei_de_alocat);


Funcţia malloc necesită un singur argument (numărul de octeţi care vor fi alocaţi) şi returnează un pointer
generic către zona de memorie alocată (pointerul conţine adresa primului octet al zonei de memorie rezervate).
void *calloc(size_t nr_elemente, size_t mărimea_în_octeţi_
a_unui_elem);
Funcţia calloc lucrează în mod similar cu malloc; alocă memorie pentru un tablou de nr_elemente, numărul
de octeţi pe care este memorat un element este mărimea_în_octeţi_a_unui_elem şi returnează un pointer către
zona de memorie alocată.
void *realloc(void *ptr, size_t mărime);
Funcţia realloc permite modificarea zonei de memorie alocată dinamic cu ajutorul funcţiilor malloc sau
calloc.
Observaţie:
În cazul în care nu se reuşeşte alocarea dinamică a memoriei (memorie insuficientă), funcţiile malloc, calloc şi
realloc returnează un pointer null. Deoarece funcţiile malloc, calloc, realloc returnează un pointer generic,
rezultatul poate fi atribuit oricărui tip de pointer. La atribuire, este indicat să se utilizeze operatorul de
conversie explicită (vezi exemplu).

Eliberarea memoriei (alocate dinamic cu una dintre funcţiile malloc, calloc sau realloc) se realizează cu
ajutorul funcţiei free.
void free(void *ptr);

Exemplu: Să se aloce dinamic memorie pentru 20 de valori întregi.


int *p;
p=(int*)malloc(20*sizeof(int));
//p=(int*)calloc(20, sizeof(int));

Exerciţiu: Să se scrie un program care implementează funcţia numită introd_val. Funcţia trebuie să
permită introducerea unui număr de valori reale, pentru care se alocă memorie dinamic. Valorile citite cu
ajutorul funcţiei introd_val sunt prelucrate în funcţia main, apoi memoria este eliberată.
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
float *introd_val()
/* pentru a putea realiza eliberarea memoriei în funcţia main, funcţia introd_val trebuie să returneze
adresa de început a zonei de memorie alocate dinamic */
{double *p; int nr;printf("Număr valori:"); scanf("%d", nr);
if (!(p=(float*)malloc(nr*sizeof(float))) ){
printf("Memorie insuficientă!\n");return NULL;
}
for (int i=0; i<nr; i++){
printf("Val %d=", i+1); scanf("%lf", p+i); return p;}
}
void main()
{float *pt; pt=introd_val();
// prelucrare tablou
free(pt);
}

Exerciţiu: Să se scrie un program care citeşte numele angajaţilor unei întreprinderi. Numărul angajaţilor este
transmis ca argument către funcţia main. Alocarea memoriei pentru cei nr_ang angajaţi, cât şi pentru numele
fiecăruia dintre aceştia se va face în mod dinamic.
#include <stdlib.h>

93
CAPITOLUL 6
Funcţii
#include <stdio.h>
#include <stdarg.h>
void main(int argc, char *argv[])
{char **ang_ptr;
char *nume;
int nr_ang, i;
if (argc==2){
nr_ang=atoi(argv[1]);/* numărul angajaţilor este transmis ca argument către funcţia
main. El este convertit din şir de caractere în număr */
ang_ptr=(char**)calloc(nr_ang, sizeof(char*));
if ((ang_ptr==0){
printf("Memorie insuficientă!\n");exit(1);}
nume=(char*)calloc(30, sizeof(char));
for (i=0; i<nr_ang; ++i){
printf("Nume angajat:");
scanf("%s",nume);
ang_ptr[i]=(char*)calloc(strlen(nume)+1, sizeof(char));
strcpy(ang_ptr[i], nume);
}
free(nume);
printf("\n");
for (i=0; i<nr_ang; i++)
printf("Angajat nr %d: %s\n", i+1, ang_ptr[i]);
}
else
printf("Lansare în execuţie: %s număr_de_angajaţi\n", argv[0]);
}

În limbajul C++ alocarea dinamică a memoriei şi eliberarea ei se pot realiza cu operatorii new şi delete.
Folosirea acestor operatori reprezintă o metodă superioară, adaptată programării orientate obiect.
Operatorul new este un operator unar care returnează un pointer la zona de memorie alocată dinamic. În
situaţia în care nu există suficientă memorie şi alocarea nu reuşeşte, operatorul new returnează pointerul
NULL. Operatorul delete eliberează zona de memorie spre care pointează argumentul său.

Sintaxa:
tipdata_pointer = new tipdata;
tipdata_pointer = new tipdata(val_iniţializare);
//pentru iniţializarea datei pentru care se alocă memorie dinamic
tipdata_pointer = new tipdata[nr_elem]; //alocarea memoriei pentru un tablou

delete tipdata_pointer;
delete [nr_elem] tipdata_pointer; //eliberarea memoriei pentru
tablouri

Tipdata reprezintă tipul datei (predefinit sau obiect) pentru care se alocă dinamic memorie, iar
tipdata_pointer este o variabilă pointer către tipul tipdata.

Pentru a putea afla memoria RAM disponibilă la un moment dat, se poate utiliza funcţia coreleft:
unsigned coreleft(void);

Exerciţiu: Să se aloce dinamic memorie pentru o dată de tip întreg:


int *pint;
pint=new int;
//Sau:
int &i=*new int;
i=100; //i permite referirea la întregul păstrat în zona de memorie alocată dinamic

Exerciţiu: Să se aloce dinamic memorie pentru o dată reală, dublă precizie, iniţializând-o cu valoarea -7.2.

94
CAPITOLUL 6
Funcţii
double *p;
p=new double(-7.2);
//Sau:
double &p=* new double(-7.2);

Exerciţiu: Să se aloce dinamic memorie pentru un vector de m elemente reale.


double *vector; vector=new double[m];
Exemplu: Să se urmărească rezultatele execuţiei următorului program, care utilizează funcţia coreleft.
#include <iostream.h>
#include <alloc.h>
#include <conio.h>
void main()
{ int *a,*b; clrscr();
cout<<"Mem. libera inainte de alocare:"<<coreleft()<<'\n';
cout<<"Adr. pointerilor a si b:"<<&a<<" "<<&b<<'\n';
cout<<"Valorile pointeri a si b inainte de alocare:"<<a<<" "<<b<<'\n';
a=new int; b=new int[10];
cout<<"Mem. libera dupa alocare:"<<coreleft()<<'\n';
cout<<"Valorile pointerilor a si b dupa alocare:"<<a<<" "<<b<<'\n';
cout<<"Continutul memoriei alocate:\n"<<"*a="<<*a<<"\n*b="<<*b<<'\n';
for (int k=0;k<10;k++) cout<<"\nb["<<k<<"]="<<b[k]; cout<<'\n';
getch();
*a=1732;
for (int u=0;u<10;u++) b[u]=2*u+1;
cout<<"Cont. zonelor alocate dupa atribuire:"<<"\n*a="<<*a<<"\nb=";
for (u=0;u<10;u++) cout<<"\nb["<<u<<"]="<<b[u];
delete a; delete b;
cout<<"Mem. libera dupa eliberare:"<<coreleft()<<'\n';
cout<<"Valorile pointerilor a si b dupa eliberare:"<<a<<" "<<b<<'\n';
cout<<"Continutul memoriei eliberate:\n"<<"*a="<<*a<<"\n*b="<<*b<<'\n';
for (k=0;k<10;k++) cout<<"\nb["<<k<<"]="<<b[k]; cout<<'\n'; cout<<b[3];
getch();
}

6.10. FUNCŢII RECURSIVE

O funcţie este numită funcţie recursivă dacă ea se autoapelează, fie direct (în definiţia ei se face apel la ea
însăşi), fie indirect (prin apelul altor funcţii). Limbajele C/C++ dispun de mecanisme speciale care permit
suspendarea execuţiei unei funcţii, salvarea datelor şi reactivarea execuţiei la momentul potrivit. Pentru fiecare
apel al funcţiei, parametrii şi variabilele automatice se memorează pe stivă, având valori distincte. Variabilele
statice ocupă tot timpul aceeaşi zonă de memorie (figurează într-un singur exemplar) şi îşi păstrează valoarea
de la un apel la altul. Orice apel al unei funcţii conduce la o revenire în funcţia respectivă, în punctul următor
instrucţiunii de apel. La revenirea dintr-o funcţie, stiva este curăţată (stiva revine la starea dinaintea apelului).

Un exemplu de funcţie recursivă este funcţia de calcul a factorialului, definită astfel:


fact(n)=1, dacă n=0;
fact(n)=n*fact(n-1), dacă n>0;

Exemplu: Să se implementeze recursiv funcţia care calculează n!, unde n este introdus de la tastatură:
#include <iostream.h>
int fact(int n)
{if (n<0){
cout<<"Argument negativ!\n";
exit(2);
}
else if (n==0) return 1;
else return n*fact(n-1);
}

95
CAPITOLUL 6
Funcţii
void main()
{int nr, f; cout<<"nr="; cin>>nr;
f=fact(nr); cout<<nr<<"!="<<f<<'\n';
}

Se observă că în corpul funcţiei fact se apelează însăşi funcţia fact. Presupunem că nr=4 (iniţial, funcţia
fact este apelată pentru a calcula 4!). Să urmărim diagramele din figurile 6.7. şi 6.8. La apelul funcţiei fact,
valoarea parametrului de apel nr (nr=4) iniţializează parametrul formal n. Pe stivă se memorează adresa de
revenire în funcţia apelantă (adr1) şi valoarea lui n (n=4) (figura 6.7.a.). Deoarece n>0, se execută
intrucţiunea de pe ramura else (return n*fact(n-1)). Funcţia fact se autoapelează direct. Se memorează pe stivă
noua adresă de revenire şi noua valoare a parametrului n (n=3) (figura 6.7.b.).
La noul reapel al funcţiei fact, se execută din nou intrucţiunea de pe ramura else (return n*fact(n-1)). Se
memorează pe stivă adresa de revenire şi noua valoare a parametrului n (n=2) (figura 6.7.c.). La noul reapel al
funcţiei fact, se execută din nou intrucţiunea de pe ramura else (return n*fact(n-1)). Se memorează pe stivă
adresa de revenire şi noua valoare a parametrului n (n=1) (figura 6.7.d.). La noul reapel al funcţiei fact, se
execută din nou intrucţiunea de pe ramura else (return n*fact(n-1)). Se memorează pe stivă adresa de revenire
şi noua valoare a parametrului n (n=0) (figura 6.7.e.).
În acest moment n=0 şi se revine din funcţie cu valoarea 1 (1*fact(0)=1*1), la configuraţia stivei din figura
6.7.d.) (se curăţă stiva şi se ajunge la configuraţia din figura 6.7.d.). În acest moment n=1 şi se revine cu
valoarea 2*fact(1)=2*1=2, se curaţă stiva şi se ajunge la configuraţia stivei din figura 6.7.c. În acest moment
n=2 şi se revine cu valoarea 3*fact(2)=3*2=6, se curaţă stiva şi se ajunge la configuraţia stivei din figura
6.7.b. Se curăţă stiva şi se ajunge la configuraţia stivei din figura 6.7.a.. În acest moment n=3 şi se revine cu
valoarea 4*fact(3)=4*6=24.
O funcţie recursivă poate fi realizată şi iterativ. Modul de implementare trebuie ales în funcţie de problemă.
Deşi implementarea recursivă a unui algoritm permite o descriere clară şi compactă, recursivitatea nu conduce
la economie de memorie şi nici la execuţia mai rapidă a programelor. În general, se recomandă utilizarea
funcţiilor recursive în anumite tehnici de programare, cum ar fi unele metode de căutare (backtracking).

adr1 adr2 adr2 adr2 adr2 4 24


n=4 n=3 n=2 n=1 n=0 0!=1
adr2 adr2 fact
adr1 adr2
n=3 n=2 3 6
n=4 n=1
adr1 adr2 adr2 fact
n=4 n=3 n=2 2
(a) 2
adr1 adr2 fact
n=4 n=3
(b) adr1 1 1
(c) fact
n=4
(d) 0 1
(e) fact
Figura 6.7. Configuraţia stivei
Figura 6.8. Parametri funcţiei fact
Exerciţiu: Fie şirul lui Fibonacci, definit astfel: f(0)=0, f(1)=1, f(n)=f(n-1)+f(n-2), dacă n>1. Să se
scrie un program care implementează algoritmul de calcul al şirului Fibonacci atât recursiv, cât şi iterativ. Să
se compare timpul de execuţie în cele două situaţii.
#include <iostream.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <stdio.h>

long int iterativ_fib(long int n) //varianta de implementare iterativă


{if (n==0) return 0;
if (n==1) return 1;
int i; long int a, b, c; a=0; b=1;
for (i=2; i<=n; i++){
c=b; b+=a; a=c;}
return b;

96
CAPITOLUL 6
Funcţii
}

long int recursiv_fib(long int n) //varianta de implementare recursivă


{if (n==0) return 0;
if (n==1) return 1;
long int i1=recursiv_fib(n-1);
long int i2=recursiv_fib(n-2);
return i1+i2;
}

void main()
{int n; clrscr();
cout<<MAXLONG<<'\n';
for (n=10; n<=40; n++) {
clock_t t1, t2, t3;
cout<<CLK_TCK<<'\n';
t1=clock(); long int f1=iterativ_fib(n);
t2=clock(); long f2=recursiv_fib(n); t3=clock();
double timp1=(double)(t2-t1)/CLK_TCK;
double timp2=(double)(t3-t2)/CLK_TCK;
printf("ITERATIV: %10ld t=%20.10lf\n",f1,timp1);
printf("RECURSIV: %10ld t=%20.10lf\n",f2,timp2);
cout<<"Apasa o tasta....\n"; getch();
} }

În exemplul anterior, pentru măsurarea timpului de execuţie s-a utilizat funcţia clock, al cărei prototip se află
în header-ul time.h. Variabilele t1, t2 şi t3 sunt de tipul clock_t, tip definit în acelaşi header. Constanta
simbolică CLK_TCK defineşte numărul de bătăi ale ceasului, pe secundă.

În general, orice algoritm care poate fi implementat iterativ, poate fi implementat şi recursiv. Timpul de
execuţie a unei recursii este semnificativ mai mare decât cel necesar execuţiei iteraţiei echivalente.

Exerciţiu: Să se implementeze şi să se testeze un program care:


a) Generează aleator şi afişează elementele unui vector ;
b) Sortează aceste elemente, crescător, aplicând metodele de sortare BubbleSort, InsertSort, şi
QuickSort;
c) Să se compare viteza de sortare pentru vectori de diverse dimensiuni (10,30,50,100 elemete).

 Metoda BubbleSort a fost prezentată în capitolul 4.

 Metoda QuickSort reprezintă o altă metodă de sortare a elementelor unui vector. Algoritmul este
recursiv: se împarte vectorul în două partiţii, faţă de un element pivot (de obicei, elementul din "mijlocul
vectorului"). Partiţia stângă începe de la indexul i (la primul apel i=0), iar partiţia dreaptă se termină cu
indexul j (la primul apel j=n -1) (figura 6.9.).

... ....
i pivot j 0 n-1
0 n-1
j i

Figura 6.9. Sortare prin metoda QuickSort


Partiţia stângă este extinsă la dreapta (i incrementat) până când se găseşte un element mai mare decât pivotul;
partiţia dreaptă este extinsă la stânga (j decrementat) până când se găseşte un element mai mic decât pivotul.
Cele două elemente găsite, vect[i] şi vect[j], sunt interschimbate.
Se reia ciclic extinderea partiţiilor până când i şi j se "încrucişează" (i devine mai mare ca j). În final,
partiţia stângă va conţine elementele mai mici decât pivotul, iar partiţia dreaptă - elementele mai mari decât
pivotul, dar nesortate.

97
CAPITOLUL 6
Funcţii
Algoritmul este reluat prin recursie pentru partiţia stângă (cu limitele între 0 şi j ), apoi pentru partiţia dreaptă
(cu limitele între i şi n-1 ). Recursia pentru partea stângă se opreşte atunci când j atinge limita stângă (devine
0), iar recursia pentru partiţia dreaptă se opreşte când i atinge limita dreaptă (devine n-1).

SUBALGORITM QuickSort (vect[ ], stg, drt) //la primul apel stg = 0 si drt = n - 1
ÎNCEPUT SUBALGORITM
i ← stg
j ← drt
DACĂ i < j ATUNCI
ÎNCEPUT
pivot=vect[(stg+drt)/2]
CÂT TIMP i <= j REPETĂ
//extinderea partiţiilor stânga şi dreapta până când i se încrucişează cu j
ÎNCEPUT
CÂT TIMP i<drt si vect[i]<pivot REPETĂ
i = i + 1
CÂT TIMP j<stg si vect[j] >pivot REPETĂ
j = j - 1
DACĂ i<=j ATUNCI
ÎNCEPUT //interschimbă elementele vect[i] şi vect[j]
aux ← vect[i]
vect[i] ← vect[j]
vect[j] ← aux
i ← i+1
j ← j-1
SFÂRŞIT
SFÂRŞIT
DACĂ j > stg ATUNCI // partiţia stângă s-a extins la maxim, apel qiuckSort pentru ea
CHEAMĂ QuickSort(vect, stg, j)
DACĂ i < drt ATUNCI // partiţia dreaptă s-a extins la maxim, apel qiuckSort pentru ea
CHEAMĂ QuickSort(vect, i, drt)
SFÂRŞIT
SFÂRŞIT SUBALGORITM

 Metoda InsertSort (metoda inserţiei)


Metoda identifică cel mai mic element al vectorului şi îl schimbă cu primul element. Se reia procedura pentru
vectorul iniţial, fără primul element şi se caută minimul în acest nou vector, etc.

SUBALGORITM InsertSort (vect[ ], nr_elem)


ÎNCEPUT SUBALGORITM
CÂT TIMP i< nr_elem REPETĂ
ÎNCEPUT
pozMin ← cautMinim(vect, i) // se apelează algoritmul cautMinim
aux ← vect[i]
vect[i] ← vect[pozMin]
vect[pozMin] ← aux
i ← i+1
SFÂRŞIT
SFÂRŞIT SUBALGORITM

Funcţia cautMin(vect[ ], indexIni, nr_elem) caută elementul minim al unui vector, începând de la
poziţia indexIni şi returnează poziţia minimului găsit.

Mod de implementare (Se va completa programul cu instrucţiunile care obţin şi afişează timpului necesar
ordonării prin fiecare metodă. Se vor compara rezultatele pentru un vector de 10, 30, 50, 100 elemente):

98
CAPITOLUL 6
Funcţii
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#define TRUE 1
#define FALSE 0
void gener(double v[], int n)
//functia de generare aleatoare a elementelor vectorului v, cu n elemente
{for (int i=0; i<n; i++)
v[i]=1.0*rand()/100000;
}
void afis(double v[], int n)
//functia de afisare a vectorului
{for (int i=0; i<n; i++)
printf("%10.2f",v[i]);
printf("\n");
}
void copie_vect(double v1[], double v[], int n)
//functie de "duplicare "a unui vector; copie vectorul v in vectorul v1
{for (int i=0; i<n; i++)
v1[i]=v[i];
}
void bubbleSort(double v[], int n)
{int gata; gata=FALSE;
while (!gata){
gata=TRUE;
for (int i=0; i<n-1; i++)
if (v[i]>=v[i+1]){
double aux=v[i];
v[i]=v[i+1];
v[i+1]=aux;
// printf("Interschimbare element %d cu %d",i,i+1);
// afis(v,n);
gata=FALSE;}
}
}
int cautMin(double v[], int indexIni, int n)
// cauta elementul minim, incepând de la pozitia indexIni, inclusiv
{ double min=v[indexIni];
int pozMin=indexIni;
for (int i=indexIni; i<n; i++)
if (v[i]<=min){
min=v[i]; pozMin=i;
}
return pozMin;
}
void insertSort(double v[], int n)
{ int i;
for (i=0; i<n; i++){
int poz=cautMin(v, i, n);
double aux=v[i];
v[i]=v[poz];
v[poz]=aux;
}
}
void quickSort(double v[], int stg, int drt)
{int i,j; i=stg; j=drt; double pivot, aux;
if (i<j){
pivot=v[(stg+drt)/2];
while (i<=j){ //extindere partitie st si dr pana i se incrucis cu j
while (i<drt && v[i]<pivot) i++;

99
CAPITOLUL 6
Funcţii
while (j>stg && v[j]>pivot) j--;
if (i<=j){
aux=v[i];v[i]=v[j];v[j]=aux; //interschimbare elemente
i++; j--;
}
}
if (j>stg) quickSort(v, stg, j);
if (i<drt) quickSort(v, i, drt);
}
}
void main()
{
clock_t ti,tf; int n; //n = nr elemente vector
printf("Nr componente vector:"); scanf("%d", &n);
double v[200], v1[200], v2[200], v3[200];
gener(v, n);
copie_vect(v1,v,n);
printf("\nInainte de ordonare: v1="); afis(v1, n); ti=clock();
bubbleSort(v1,n); tf=clock(); printf("\nDupa ordonare : v1=");afis(v1, n);
printf("%10.7f", dif_b);
printf("\n\n****** INSERT SORT ******\n");
copie_vect(v2,v,n);
printf("\nInainte de ordonare INSERT: v2="); afis(v2, n);
insertSort(v2,n); printf("\nDupa ordonare INSERT: v2=");afis(v2, n);
int st=0; int dr=n-1; copie_vect(v3, v, n);
printf("\n\n****** QUICK SORT ******\n");
printf("\nInainte ordonare QUICK: v3="); afis(v3, n);
quickSort(v3, st, dr); printf("\nDupa ordonare QUICK: v3="); afis(v3, n);
}

6.11. POINTERI CĂTRE FUNCŢII

Aşa cum s-a evidenţiat în capitolul 5, exista trei categorii de variabilele pointer:
 Pointeri cu tip;
 Pointeri generici (void);
 Pointeri către funcţii.

Pointerii către funcţii sunt variabile pointer care conţin adresa de început a codului executabil al unei funcţii.
Pointerii către funcţii permit:
 Transferul ca parametru al adresei unei funcţii;
 Apelul funcţiei cu ajutorul pointerului.

Declaraţia unui pointer către funcţie are următoarea formă:


tip_val_intoarse (*nume_point)(lista_declar_param_formali); ,
unde:
nume_point este un pointer de tipul “funcţie cu rezultatul tipul_valorii_întoarse”. În declaraţia anterioară
trebuie remarcat rolul parantezelor, pentru a putea face distincţie între declaraţia unei funcţii care întoarce un
pointer şi declaraţia unui pointer de funcţie:
tip_val_intoarse * nume_point (lista_declar_param_formali);
tip_val_intoarse (* nume_point)(lista_declar_param_formali);
Exemplu:
int f(double u, int v); //prototipul funcţiei f
int (*pf)(double, int); //pointer către funcţia f
int i, j; double d;
pf=f; //atribuie adresa codului executabil al funcţiei f pointerului pf
j=*pf(d, i); //apelul funcţiei f, folosind pf

100
CAPITOLUL 6
Funcţii
Exerciţiu: Să se implementeze un program care calculează o funcţie a cărei valoare este integrala altei funcţii.
Pentru calculul integralei se va folosi metoda trapezelor.

Relaţia pentru calculul integralei prin metoda f(x)


b

trapezelor pentru ∫ f ( x)dx


a
este:

n −1
I = (f(a)+f(b))/2 + ∑ f ( a + k * h)
k =1
a x x x . . . . .x x b
h
| x|
b
0.2 + e 2 Figura 6.9. Calculul integralei prin metoda trapezelor
Să se calculeze
∫1+
a 0.3 + ln(1 + x 4 )
dx,

cu o eroare mai mică decât eps (valoarea erorii introdusă de la tastatură).

#include <conio.h>
#include <math.h>
#include <iostream.h>
double functie(double x)
{return sqrt(0.1+exp(0.5*fabs(x)))/(1+sqrt(0.3+log(1+pow(x,4))));}

double intrap(double a, double b, long int n, double (*f)(double))


{double h,s=0; long k;
if (a>=b) return 0;
if (n<=0) n=1;
h=(b-a)/n;
for (k=1; k<n; k++) s+=f(a+k*h);
return ((f(a)+f(b))/2+s)*h;
}

void main()
{long int j; double p,q; double eps, d2;double dif;
cout<<"Marg. inf:";cin>>p; cout<<"Marg. sup:";cin>>q;
cout<<"Eroare:";cin>>eps; j=1;
double d1=intrap(p, q, j, functie);
do{
j*=2;
if (j>MAXLONG || j<0) break;
d2=intrap(p, q, j, functie);
dif=fabs(d1-d2); d1=d2;
cout<<"Nr.intervale "<<j<<" Val.integralei "<<d2<<'\n';
}while (dif>eps);
cout<<"\n\n-----------------------------------------------\n";
cout<<"Val. integralei: "<<d2<<" cu eroare de:"<<eps<<'\n';
}

ÎNTREBĂRI ŞI EXERCIŢII

Chestiuni teoretice
1. Asemănări între transferul parametrilor unei 5. Care este diferenţa între antetul unei funcţii şi
funcţii prin pointeri şi prin referinţă. prototipul acesteia?
2. Caracteristicile modului de transfer a 6. Care sunt modurile de alocare a memoriei?
parametrilor unei funcţii prin pointeri. 7. Care sunt modurile de transfer a parametrilor
3. Caracteristicile variabilelor globale. unei funcţii?
4. Caracteristicile variabilelor locale. 8. Care sunt operatorii din C++ care permit
alocarea/dezalocarea dinamică a memoriei?

101
CAPITOLUL 6
Funcţii
9. Ce clase de memorare cunoasteţi? 23. Diferenţe între modurile de transfer a
10. Ce este domeniul de vizibilitate a unei parametrilor prin valoare şi prin referinţă.
variabile? 24. Diferenţe între modurile de transfer a
11. Ce este prototipul unei funcţii? parametrilor unei funcţii prin pointeri şi prin
12. Ce este timpul de viaţă a unei variabile? referinţă.
13. Ce loc ocupă declaraţiile variabilelor locale în 25. Din apelul funcţiei printf se poate omite
corpul unei funcţii? specificatorul de format?
14. Ce reprezintă antetul unei funcţii? 26. Din ce este formată o funcţie?
15. Ce rol are declararea funcţiilor? 27. În ce zonă de memorie se rezervă spaţiu pentru
16. Ce se indică în specificatorul de format al variabilele globale?
funcţiei printf ? 28. O funcţie poate fi declarată în corpul altei
17. Ce sunt funcţiile cu număr variabil de funcţii?
parametri? Exemple. 29. O funcţie poate fi definită în corpul unei alte
18. Ce sunt funcţiile cu parametri impliciţi? funcţii?
19. Ce sunt pointerii către funcţii? 30. Parametrii formali ai unei funcţii sunt
20. Ce sunt variabilele referinţă? variabile locale sau globale?
21. Cine determină timpul de viaţă şi domeniul de 31. Transferul parametrilor prin valoare.
vizibilitate ale unei variabile? 32. Ce rol au parametrii formali ai unei funcţii?
22. Comparaţie între declararea şi definirea
funcţiilor.
Chestiuni practice

1. Să se implementeze programele cu exemplele prezentate.


2. Să se scrie programele pentru exerciţiile rezolvate care au fost prezentate.
3. Să se modularizeze programele din capitolul 4 (3.a.-3.g., 4.a.-4.i, 5.a.-5.h.), prin implementarea unor
funcţii (funcţii pentru: citirea elementelor unui vector, afişarea vectorului, calculul sumei a doi vectori,
calculul produsului scalar a doi vectori, aflarea elementului minim din vector, citire a unei matrici, afişare
a matricii, calculul transpusei unei matrici, calculul sumei a două matrici, calculul produsului a două
matrici, calculul produsului elementelor din triunghiul haşurat, etc.).
4. Să se rescrie programele care rezolvă exerciţiile din capitolul 3, folosind funcţii (pentru calculul
factorialului, aflarea celui mai mare divizor comun, ordonarea lexicografică a caracterelor, etc). Utilizaţi
funcţiile de intrare/ieşire printf şi scanf.
5. Să se scrie un program care citeşte câte două numere, până la întâlnirea perechii de numere 0, 0 şi
afişează, de fiecare dată, cel mai mare divizor comun al acestora, folosind o funcţie care îl calculează.
6. Se introduce de la tastatura un număr întreg. Să se afişeze toţi divizorii numărului introdus. Se va folosi o
funcţie de calcul a celui mai mare divizor comun a 2 numere.
7. Secvenţele următoare sunt corecte din punct de vedere sintactic? Dacă nu, identificaţi sursele erorilor.
 void a(int x, y) {cout<<"x="<<x<<" y="<<y<<'\n'; }
void main( ) { int b=9; a(6, 7); }
 void main( ) { int x=8; double y=f(x); cout<<"y="<<y<<'\n';}
int f(int z) {return z+z*z;}
8. Scrieţi o funcţie găseşte_cifra care returnează valoarea cifrei aflate pe poziţia k în cadrul numărului
n, începând de la dreapta (n şi k vor fi argumentele funcţiei).
9. Implementaţi propriile versiuni ale funcţiile de lucru cu şiruri de caractere (din paragraful 4.4).
10. Să se calculeze valoarea funcţiei g, cu o eroare EPS (a, b, EPS citite de la tastatură):
b b

g(x)= ∫
a
( x 2 + x + 1) *ln x + a dx + ∫ x * arctg (b (b + x)) dx
a

11. Implementaţi funcţii iterative şi recursive pentru calculul valorilor polinoamelor Hermite H n (y), ştiind că:
H 0 (y)=1, H 1 (y)=2y, H n (x)=2yH n −1 (y)-2H n − 2 (y) dacă n>1. Comparaţi timpul de execuţie al
celor două funcţii.
12. Să se scrie un program care generează toate numerele palindrom, mai mici decât o valoare dată, LIM. Un
număr palindrom are cifrele simetrice egale (prima cu ultima, a doua cu penultima, etc). Se va folosi o
funcţie care testează dacă un număr este palindrom.
13. Fie matricea C (NXN), N<=10, ale cărei elemente sunt date de relaţia:

102
CAPITOLUL 6
Funcţii
j
j! + ∑ sin(kx) ,
k =0
dacă i<j

C i, j = x i , dacă i=j , unde x ∈ [0,1], x introdus de la tastatură

i! + i ∑ cos(kx) , dacă i>j


k =0
a) Să se implementeze următoarele funcţii: de calcul a elementelor matricii; de afişare a matricii; de
calcul şi de afişare a procentului elementelor negative de pe coloanele impare (de indice 1, 3, etc).
b) Să se calculeze şi să se afişeze matricea B, unde: B=C - C 2 + C 3 - C 4 + C 5 .
14. Să se creeze o bibliotecă de funcţii pentru lucrul cu matrici, care să conţină funcţiile utilizate frecvent
(citirea elementelor, afisarea matricii, adunare a două matrici, etc). Să se folosească această bibliotecă.
15. Să se creeze o bibliotecă de funcţii pentru lucrul cu vectori, care să conţină funcţiile utilizate frecvent. Să
se folosească această bibliotecă.

103
CAPITOLUL 7 Tipuri de date definite de utilizator

TIPURI DE DATE DEFINITE DE UTILIZATOR 7


7.1. Tipuri definite de utilizator 7.5. Declaraţii typedef
7.2. Structuri 7.4. Uniuni
7.3. Câmpuri de biţi 7.6. Enumerări

7.1. TIPURI DEFINITE DE UTILIZATOR

Limbajele de programare de nivel înalt oferă utilizatorului facilităţi de a prelucra atât datele singulare
(izolate), cât şi pe cele grupate. Un exemplu de grupare a datelor - de acelaşi tip - îl constituie tablourile.
Datele predefinite şi tablourile (prezentate în capitolele anterioare) nu sunt însă suficiente. Informaţia
prelucrată în programe este organizată, în general în ansambluri de date, de diferite tipuri. Pentru a putea
descrie aceste ansambluri (structuri) de date, limbajele de programare de nivel înalt permit programatorului
să-şi definească propriile tipuri de date.

Limbajul C oferă posibilităţi de definire a unor tipurilor de date, cu ajutorul:


 structurilor - permit gruparea unor obiecte (date) de tipuri diferite, referite printr-un nume comun;
 câmpurilor de biţi - membri ai unei structuri pentru care se alocă un grup de biţi, în interiorul unui
cuvânt de memorie;
 uniunilor - permit utilizarea în comun a unei zone de memorie de către mai multe obiecte de diferite
tipuri;
 declaraţiilor typedef - asociază nume tipurilor noi de date;
 enumerărilor - sunt liste de identificatori cu valori constante, întregi.

7.2. STRUCTURI

Structurile grupează date de tipuri diferite, constituind definiţii ale unor noi tipuri de date. Componentele
unei structuri se numesc membrii (câmpurile) structurii. La declararea unei structuri se pot preciza tipurile,
identificatorii elementelor componente şi numele structurii.
Forma generală de declarare a unei structuri:

struct identificator_tip_structura {
lista_de_declaratii_membrii;
} lista_identificatori_variabile; în care:

struct este un cuvânt cheie (obligatoriu)


identificator_tip_structura reprezintă numele noului tip (poate lipsi)
lista_de_declaratii_membri este o listă în care apar tipurile şi identificatorii membrilor structurii
lista_identificatori_variabile este o listă cu identificatorii variabilelor de tipul declarat.

Membrii unei structuri pot fi de orice tip, cu excepţia tipului structură care se declară. Se admit însă, pointeri
către tipul structură. Identificator_tip_structura poate lipsi din declaraţie, însă în acest caz, în
lista_identificatori_variabile trebuie să fie prezent cel puţin un identificator_varabila.
Lista_identificatori_variabile poate lipsi, însă, în acest caz, este obigatorie prezenţa unui
identificator_tip_structura.
Exemplu: Se defineşte noul tip de date numit data, cu membrii zi, luna, an. Identificatorii variabilelor de
tipul data sunt data_naşterii, data_angajării.
struct data {
int zi;

103
CAPITOLUL 7 Tipuri de date definite de utilizator
char luna[11];
int an;
} data_naşterii, data_angajării;

Declaraţia de mai sus poate apare sub forma:


struct data {
int zi;
char luna[11];
int an;
};
struct data data_nasterii, data_angajarii;
/*Variabilele data_nasterii şi data_angajarii sunt date de tipul data */

Se poate omite numele noului tip de date:


struct {
int zi;
char luna[11];
int an;
} data_naşterii, data_angajării;

Iniţializarea variabilelor de tip nou, definit prin structură, se poate realiza prin enumerarea valorilor
membrilor, în ordinea în care aceştia apar în declaraţia structurii. Referirea unui membru al structurii se
realizează cu ajutorul unui operator de bază, numit operator de selecţie, simbolizat prin . .Operatorul are
prioritate maximă. Membrul stâng al operatorului de selecţie precizează numele variabilei de tipul introdus
prin structură, iar membrul drept-numele membrului structurii, ca în exemplul următor:
Exemplu:
struct angajat{
char nume[20], prenume[20];
int nr_copii;
double salariu;
char loc_nastere[20];
};
struct angajat a1= {"Popescu", "Vlad", 2, 2900200, "Galati"};
a1.nr_copii = 3;
strcpy(a1.nume, "Popesco");

Variabilele de acelaşi tip pot apare ca operanzi ai operatorului de atribuire. În acest caz atribuirile se fac
membru cu membru. În exemplul anterior am declarat şi iniţializat variabila a1, de tip angajat. Declarăm şi
variabila a2, de acelaşi tip. Dacă dorim ca membrii variabilei a2 să conţină aceleaşi valori ca membrii
variabilei a1 (a1 si a2 de tip angajat), putem folosi operatorul de atribuire, ca în exemplul următor:
struct angajat a2;
a2=a1;

Aşa cum s-a observat din exemplul anterior, structurile pot avea ca membri tablouri (structura angajat are
ca membrii tablourile de caractere loc_naştere[20], nume[20], prenume[20]). Deasemenea, variabilele
de tip definit prin structură pot fi grupate în tablouri.
Exemplu:
struct persoana{
char nume[20], prenume[20];
int nr_copii;
double salariu;
char loc_nastere[20];
}angajati[100];
/* S-au declarat noul tip numit persoana şi variabila numită angajati, care este un vector (cu maxim
100 de elemente), ale cărui elemente sunt de tipul persoana */
//Iniţializarea elementelor vectorului angajaţi[100]
for (int i=0; i<100; i++){
cout<<"Intruduceti datele pentru angajatul "<<i+1<<'\n';

104
CAPITOLUL 7 Tipuri de date definite de utilizator
cout<<"Numele :"; cin>>angajati[i].nume;
cout<<"Prenumele :"; cin>>angajaţi[i].prenume;
cout<<"Nr. copii:"; cin>> angajaţi[i].nr_copii;
cout<<"Locul naşterii:"; cin>> angajaţi[i].loc_naştere;
}
Limbajul C permite definirea de structuri ale căror membri sunt tot structuri:
Exemplu:
struct data{
int zi;
char luna[11];
int an;
};
struct persoana{
char nume[20], prenume[20];
int nr_copii;
double salariu;
char loc_naştere[20];
struct data data_naşterii;
};
struct persoana
p1={"Popescu","Vasile",1,4000000,"Galati",{22,"Mai",1978}};
//Modificarea membrului data_naşterii pentru variabila p1 de tip persoana:
p1.data_naşteri.zi=23;
strcpy(p1.data_naşteri.luna, "Februarie");
p1.data_nasteri.an=1980;

Dacă se doreşte transmiterea ca parametri ai unor funcţii a datelor de tip definit de utilizator prin structuri,
acest lucru se realizează numai cu ajutorul pointerilor spre noul tipi.
De exemplu, este necesar ca variabila p1, de tip persoana, să fie prelucrată în funcţia f, În acest caz,
funcţia va primi ca parametru un pointer spre tipul persoana. Funcţia va avea prototipul:
void f(struct persoana *q);
Apelul funcţiei se realizează astfel: f(&p1);
În corpul funcţiei f, accesul la membrii varibilei q, de tip persoana, se realizează astfel:
(*q).nume;
(*q).prenume;
(*q).data_naşterii.an; , etc.
Pentru a simplifica construcţiile anterioare, se foloseste operatorul de selecţie indirectă (->):
q->nume;
q->prenume;
q->data_naşterii.an , etc.

Structurile sunt utilizate în mod frecvent la definirea unor tipuri de date recursive (în implementarea listelor,
arborilor, etc.). Un tip de date este direct recursiv dacă are cel puţin un membru care este de tip pointer spre
el însuşi.
Exemplu:
struct nod{
char nume[100];
int an;
struct nod *urmator;
};

Exerciţiu: Să se citească informaţiile despre angajaţii unei întreprinderi, folosind o funcţie de citire. Să se
afişeze apoi informaţiile despre angajaţi.
#include <stdio.h>
#include <conio.h>
struct persoana{
char nume[20];int varsta;int salariu;
};
void cit_pers(struct persoana *ptr_pers)
{printf("Nume angajat:"); scanf("%s",ptr_pers->nume);

105
CAPITOLUL 7 Tipuri de date definite de utilizator
printf("Varsta angajat:"); scanf("%d", &ptr_pers->varsta);
printf("Salariu angajat:"); scanf("%d", &ptr_pers->salariu);
}
void main()
{struct persoana *p; //pointer catre date de tip persoana
int nr_ang; clrscr();
printf("Nr. angajati:");scanf("%d", &nr_ang);
p=new persoana[nr_ang]; //alocare dinamica a memoriei pentru cei nr_ang angajati
for (int i=0; i<nr_ang; i++)
cit_pers(&p[i]);
printf("\n\n Datele despre angajati:\n\n");
for (i=0; i<nr_ang; i++){
printf("Angajatul %d\n NUME: %s\n VARSTA: %d\n \ //continuare sir
SALARIUL: %.d\n", i+1,p[i].nume,p[i].varsta, p[i].salariu);
printf("\n\n Apasa o tasta....\n"); getch();
}
}
Aşa cum se observă din exemplu, funcţia cit_pers primeşte ca parametru pointerul ptr_pers, către tipul
persoana. Pentru a acesa membri structurii, în corpul funcţiei, se foloseşte operatorul de selecţie indirectă (
→ ). În funcţia main, se alocă memorie dinamic (cu ajutorul operatorului new). La afişare, în funcţia printf,
şirul specificator de format se continuă pe rândul următor (folosirea caracterului \ pentru continuare).

7.3. CÂMPURI DE BIŢI

Limbajul C oferă posibilitatea de prelucrare a datelor la nivel de bit. De multe ori se utilizează date care pot
avea doar 2 valori (0 sau 1), cum ar fi datele pentru controlul unor dispozitive periferice, sau datele de valori
mici. Declarând aceste date de tip int sau short int, în memorie se rezervă 16 biţi. Alocarea unui număr atât
de mare de locaţii de memorie nu este justificată, de aceea, limbajul C oferă posibilitatea declarării unor date
pentru care să se aloce un număr specificat de biţi (alocare pe biţi).

Definiţie:
Un şir de biţi adiacenţi formeaza un câmp de biţi.

Câmpurile de biţi se pot declara ca membri ai unei structuri, astfel:


struct identificator_tip_struct {
tip_elem_1 identificator_elem_1:lungime1;
tip_elem_2 identificator_elem_2:lungime2;
. . .
tip_elem_3 identificator_elem_3:lungime3;
} lista_identif_var_struct;

Lungime1, lungime2, etc. reprezintă lungimea fiecărui câmp de biţi, rezervat pentru memorarea membrilor.
Câmpurile se alocă de la biţii de ordin inferior ai unui cuvânt (2 octeţi), către cei de ordin superior (figura
7.1).
Exemplu:
struct { . . .
int a: 2;
unsigned int b: 1; . . . c b a
int c: 3;
} x, y; Figura 7.1. Câmpurile de biţi a, b, c

Câmpurile se referă ca orice membru al unei structuri, prin nume calificate:


Exemplu:
x.a = -1; x.b = 3; x.c = 4;

Utilizarea câmpurilor de biţi impune următoarele restricţii:


 Tipul membrilor poate fi int sau unsigened int.

106
CAPITOLUL 7 Tipuri de date definite de utilizator
 Lungime este o constantă întreagă din intervalul [0, 31];
 Un câmp de biţi nu poate fi operandul unui operator de referenţiere.
 Nu se pot organiza tablouri de câmpuri de biţi.

Datorită restricţiilor pe care le impune folosirea câmpurilor de biţi, cât şi datorită faptului că aplicaţiile care
folosesc astfel de structuri de date au o portabilitate extrem de redusă (organizarea memoriei depinzând de
sistemul de calcul), se recomandă folosirea câmpurilor de biţi cu precauţie, doar în situaţiile în care se face o
economie substanţială de memorie.

7.4. DECLARAŢII DE TIP

Limbajul C permite atribuirea unui nume pentru un tip (predefinit sau utilizator) de date. Pentru aceasta se
folosesc delcaraţiile de tip. Forma generală a acestora este:
typedef tip nume_tip;
Nume_tip poate fi folosit la declararea datelor în mod similar cuvintelor cheie pentru tipurile predefinite.
Exemplu:
//1
typedef int INTREG;
INTREG x, y;
INTREG z=4;
//2
typedef struct{
double parte_reală;
double parte_imaginară;
} COMPLEX;
COMPLEX x, y;

7.5. UNIUNI

Aceeaşi zonă de memorie poate fi utilizată pentru păstrarea unor obiecte (date) de diferite tipuri, prin
declararea uniunilor. Uniunile sunt similare cu structurile, singura diferenţă constând în modul de memorare.
Declararea uniunilor:
union identificator_tip_uniune {
lista de declaratii_membrii;
} lista_identificatori_variabile;

Spaţiul de memorie alocat corespunde tipului membrului de dimensiune maximă. Tipul uniune foloseşte
aceeaşi zonă de memorie, care va conţine informaţii organizate în mai multe moduri, corespunzător tipurilor
membrilor.
Exemplu:
union numeric{
int i; num.i
float f; num.f 20 5.80
double d; num.d num
} num;
num.i = 20;
Figura 7.2. Modul de alocare a memoriei
num.f = 5.80;
pentru variabila num (uniune) - 8 octeţi
cout<<sizeof(num)<<'\n'; //8

Pentru variabile num se rezervă 8 octeţi de memorie, dimensiunea maximă a zonei de memorie alocate
membrilor (pentru int s-ar fi rezervat 2 octeţi, pentru float 4, iar pentru double 8). În exemplul anterior,
în aceeaşi zonă de memorie se păstrează fie o valoare întreagă (num.i=20), fie o valoare reală, dublă
precizie (num.f=5.80).

107
CAPITOLUL 7 Tipuri de date definite de utilizator
Dacă pentru definirea tipului numeric s-ar fi folosit o structură, modul de alocare a memoriei ar fi fost cel din
figura 7.3.

struct numeric{ num.d


int i;
float f; num.f 5.80
double d;
} num; num.i 20
num
num.i = 20;
num.f = 5.80; Figura 7.3. Modul de alocare a memoriei
cout<<sizeof(num)<<'\n'; //14 pentru variabila num (structură) - 14 octeţi

7.6. ENUMERĂRI

Tipul enumerare asociază fiecărui identificator o consatantă întreagă. Sintaxa declaraţiei:


enum identificator_tip_enumerare {
identif_elem1 = const1, . . .
} lista_identif_variabile;

Din declaraţie pot lipsi fie identificator_tip_enumerare, fie lista_identif_variabile. Pentru


fiecare element al enumerării, constanta poate fi asociată în mod explicit (ca în declaraţia anterioară), fie
implicit. În modul implicit nu se specifică nici o constantă, iar valoarea implicită este 0 pentru primul
element, iar pentru restul elementelor, valoarea precedentă incrementată cu 1. Enumerările se folosesc în
situaţiile în care variabilele pot avea un număr mic de valori întregi, asociind un nume sugestiv pentru fiecare
valoare.
Exemplu:
//1
enum boolean {FALSE, TRUE}; //definirea tipului boolean cu elementele FALSE si TRUE
//declaratie echivalenta cu enum boolean {FALSE=0, TRUE=1};
cout<<"FALSE este "<<FALSE<<'\n'; //FALSE este 0
//2
typedef enum temperatura {mica=-10, medie=10, mare=80};
//tipul enumerare temperatura, cu elementele mica (de valoare -10), medie (valoare 10), mare
(valoare 80)
temperatura t1, t2; //declararea variabilelor t1, t2 de tip enumerare temperatura
t1=medie;
cout<<"t1="<<t1<<'\n'; //t1=10

Exerciţiu: Să se citească (cu ajutorul unei funcţii de citire) următoarele informaţii despre elevii participanţi
la un concurs de admitere: nume, numărul de înscriere şi cele trei note obţinute. Să se afişeze, printr-o
funcţie, informaţiile citite. Să se afişeze o listă cu elevii participanţi la concurs, ordonaţi alfabetic, notele şi
media obţinută (funcţie de ordonare, funcţie de calculare a mediei). Să se afişeze lista elevilor înscrişi la
concurs, în ordinea descrescătoare a mediilor.

Sunt prezentate câteva modalităţi de implementare. În aceste variante apar doar funcţia cit_elev (de citire)
şi main. S-a definit tipul elev. Se lucrează cu vectori de tip elev. În funcţia cit_elev se validează fiecare
notă. Se va observa modul de acces la membri structurii în funcţia cit_elev. Dezavantajul principal al
acestui mod de implementare îl constituie risipa de memorie, deoarece în funcţia main se rezervă o zonă de
memorie continuă, pentru 100 de elemente de tip elev (100*sizeof(elev)).
#include <iostream.h>
#include <conio.h>
typedef struct elev{
char nume[20];int nr_matr;int note[3];
}; //definirea tipului elev
void cit_elevi(elev a[], int n)

108
CAPITOLUL 7 Tipuri de date definite de utilizator
{for (int i=0; i<n; i++){
cout<<"Nume elev:"; cin>>a[i].nume; //citirea numelui unui elev
cout<<"Nr. insriere:"; cin>>a[i].nr_matr;
for (int j=0; j<3; j++){ // citirea notelor obtinute
do{
cout<<"Nota :"<<j+1<<" ="; cin>>a[i].note[j];
if (a[i].note[j]<0 || a[i].note[j]>10) //validarea notei
cout<<"Nota incorecta!....Repeta!\n";
}while (a[i].note[j]<0 || a[i].note[j]>10);
}
}
}
void main()
{ int nr_elevi; clrscr();
cout<<"Nr. elevi:";cin>>nr_elevi;
elev p[100]; //declararea tabloului p, de tip elev
cit_elevi(p, nr_elevi); //apel functie
}

În varianta următoare, se lucrează cu pointeri către tipul elev, iar memoria este alocată dinamic.
typedef struct elev{
char nume[20];int nr_matr;int note[3];
}; //definirea tipului elev
void cit_elevi(elev *a, int n)
{
for (int i=0; i<n; i++){
cout<<"Nume elev:"; cin>>(a+i)->nume; //sau cin>>(*(a+i)).nume;
cout<<"Nr. insriere:"; cin>>(a+i)->nr_matr;
for (int j=0; j<3; j++){
do{
cout<<"Nota :"<<j+1<<" =";
cin>>(a+i)->note[j];
if ((a+i)->note[j]<0 || (a+i)->note[j]>10)
cout<<"Nota incorecta!....Repeta!\n";
}while ((a+i)->note[j]<0 || (a+i)->note[j]>10);
}
}
}
void main()
{ int nr_elevi; clrscr();
cout<<"Nr. elevi:";cin>>nr_elevi;
elev *p; //declararea pointerului p, către tipul elev
p=new elev[nr_elevi];
//alocarea dinamică a memoriei, pentru un tablou cu nr_elevi elemente
cit_elevi(p, nr_elevi); //apel functie
}

Implementarea tuturor funcţiilor:


#include <stdio.h>
#include <string.h>
#define DIM_PAG 24 //dimensiunea paginii de afisare
#define FALSE 0
#define TRUE 1
void ord_medii(elev *a, int n)
{
int gata =FALSE;int i;double med1, med2;elev aux;
while (!gata){
gata=TRUE;
for (i=0; i<=n-2; i++){
med1=0;med2=0;

109
CAPITOLUL 7 Tipuri de date definite de utilizator
for (int j=0; j<3; j++){
med1+=(a+i)->note[j]; med2+=(a+i+1)->note[j];
//calculul mediilor pentru elementele vecine
}
med1/=3; med2/=3;
if (med1<med2){
aux=*(a+i); *(a+i)=*(a+i+1);*(a+i+1)=aux;
gata=FALSE; }
}
}
}
void ord_alf(elev *a, int n)
{
int gata =FALSE;int i;double med1, med2;elev aux;
while (!gata){
gata=TRUE;
for (i=0; i<=n-2; i++){
if (strcmp( (a+i)->nume,(a+i+1)->nume) >0){
aux=*(a+i); *(a+i)=*(a+i+1);*(a+i+1)=aux;
gata=FALSE;}
}
}
}
void cit_elevi(elev *a, int n);
// functie implementata anterior
void antet_afis(const char *s)
{printf("%s\n", s);
}
void afis_elev(elev *a, int n, char c)
{clrscr();
if (c=='A')
antet_afis(" LISTA INSCRISILOR \n");
if (c=='O')
antet_afis(" LISTA ALFABETICA \n");
if (c=='R')
antet_afis(" LISTA MEDII \n");
printf("Nr.crt.|Nr. Matricol| NUME |Nota1|Nota2|Nota3|
MEDIA\ |\n");
printf("----------------------------------------------------------------\
\n");
int lin=3;
for (int i=0; i<n; i++){
printf("%7d|%12d|%-20s|",i+1,(a+i)->nr_matr,(a+i)->nume);
double med=0;
for (int j=0; j<3; j++){
printf("%-5d|", (a+i)->note[j]);
med+=(a+i)->note[j];
}
med/=3;printf("%-9.2f|\n", med);lin++;
if (lin==(DIM_PAG-1)){
printf(" Apasa o tasta...."); getch();
clrscr();
if (c=='A') antet_afis(" LISTA INSCRISILOR \n");
if (c=='O') antet_afis(" LISTA ALFABETICA \n");
if (c=='R') antet_afis(" LISTA MEDII \n");
printf("Nr.crt.| NUME |Nota1|Nota2|Nota3| MEDIA\
|\n");
printf("-----------------------------------------------------\
\n");
int lin=3;
}
}

110
CAPITOLUL 7 Tipuri de date definite de utilizator
printf(" Apasa o tasta...."); getch();
}
void main()
{ int nr_elevi; clrscr();
cout<<"Nr. elevi:";cin>>nr_elevi;
elev *p; p=new elev[nr_elevi];
cit_elevi(p, nr_elevi);
afis_elev(p, nr_elevi, 'A');//afisarea inscrisilor
ord_medii(p, nr_elevi);
afis_elev(p, nr_elevi, 'R');//afisarea in ordinea descrescatoare a
mediilor
ord_alf(p, nr_elevi); //ordonare alfabetica
afis_elev(p, nr_elevi, 'O');//afisarea in ordinea descrescatoare a
mediilor
}

S-au implementet următoarele funcţii:


cit_elevi - citeşte informaţiile despre elevii înscrişi.
afis_elevi - afişează informaţiile despre elevi. Această funcţie este folosită pentru cele trei afişări (lista
înscrişilor, lista alfabetică şi clasamentul în ordinea descrescătoare a mediilor). Afişarea se realizează cu
ajutorul funcţiei printf, care permite formatarea datelor afişate. Afişarea se realizează ecran cu ecran (se
foloseşte variabila lin care contorizează numărul de linii afişate), cu pauză după fiecare ecran. La începutul
fiecărei pagini se afişează titlul listei - corespunzător caracterului transmis ca parametru funcţiei - şi capul de
tabel. Deasemenea, pentru fiecare elev înscris se calculează media obţinută (variabila med).
ord_medii - ordonează vectorul de elevi (transmis ca parametru, pointer la tipul elev), descrescător, după
medii. Se aplică metoda BubbleSort, comparându-se mediile elementelor vecine (med1 reprezintă media
elementului de indice i, iar med2 - a celui de indice i+1) ale vectorului.
ord_alf - ordonează vectorul de elevi (transmis ca parametru, pointer la tipul elev), crescător, după
informaţia conţinută de membrul nume. Pentru compararea numelor se foloseşte funcţia strcmp.
Deoarece este foarte probabil ca vectorul înscrişilor să aibă multe elemente, pentru ordonări, ar fi fost mai
eficientă metoda QuickSort; s-a folosit BubbleSort pentru a nu complica prea mult problema.

ÎNTREBĂRI ŞI EXERCIŢII

Chestiuni teoretice

1. Variabilele tablou şi variabilele de tip definit 4. Cum se numesc componentele unei structuri?
de utilizator sunt exemple de variabile 5. Ce restricţii impune folosirea câmpurilor de
compuse (reprezintă date structurate). Care biţi?
este, totuşi, deosebirea dintre ele? 6. Există vreo restricţie referitoare la tipul
2. Ce posibilităţi de definire a unor noi tipuri de membrilor unei structuri? Dacă da, care este
date vă oferă limbajul C/C++? aceasta?
3. În ce constă diferenţa dintre structuri şi
uniuni?

Chestiuni practice

1. Să se implementeze programele cu exemplele prezentate.


2. Să se scrie programele pentru exerciţiile rezolvate care au fost prezentate.
3. Realizaţi următoarele modificări la exerciţiul prezentat la sfârşitul capitolului:
a) Completaţi cu o funcţie de calcul şi afişare a mediei notelor tuturor candidaţilor pentru fiecare probă
(media tuturor elevilor la proba1, media la proba2, etc).
b) Modificaţi lista alfabetică, astfel încât la elevii cu medie peste 5, să apară (alături de medie) mesajul
"Promovat", iar la ceilalţi, mesajul "Nepromovat".

111
CAPITOLUL 7 Tipuri de date definite de utilizator
c) Considerând că rezultatelor obţinute sunt utilizate la un concurs de admitere, la care există N locuri (N
introdus de la tastatură), şi de faptul că pentru a fi admis media trebuie să fie cel puţin 5, să se afişeze
lista admişilor şi lista respinşilor, în ordinea descrescătoare a mediilor, în limita locurilor disponibile.

2. Să se scrie un program care să permită memorarea datelor privitoare la angajaţii unei firme mici: nume
angajat, adresă, număr copii, sex, data naşterii, data angajării, calificare, salariul brut. Se vor implementa
următoarele funcţii:
a) Citirea informaţiilor despre cei N angajaţi (N introdus de la tastatură);
b) Căutarea - după nume - a unui angajat şi afişarea informaţiilor despre acesta;
c) Modificarea informaţiilor despre un anumit angajat;
d) Lista alfabetică a angajaţilor, în care vor apare: nume, adresă, data angajării, calificare, salariu;
e) Lista angajaţilor în ordone descrescătoare a vechimii;
f) Lista angajatilor cu un anumit numar de copii, C, introdus de la tastatură;
g) Lista angajaţilor cu vârsta mai mare decât V (V introdus de la tastatură);
h) Salariul minim, salariul mediu şi cel maxim din firmă;
i) Lista de salarii, în care vor apare: numele, calificarea, salariul brut şi salariul net. La sfârşitul listei vor
apare totalurile pentru salariile brute, impozite, salarii nete. Pentru calculul salariului net se aplică
următoarele reguli de impozitare:
i.1) I=15% pentru salariul brut (SB)<600000
i.2) I=50000+20% pentru 600000<=SB<1500000 (20% din ceea ce depăşeşte 600000)
i.3) I=100000+30% pentru 1500000<=SB<3000000
i.4) I=250000+40% pentru 3000000<=SB<15000000
i.5) I=45% pentru SB>=1500000

112
CAPITOLUL 8 Fişiere

FIŞIERE 8
8.1. Caracteristicile generale ale fişierelor 8.4.3. Prelucrarea la nivel de şir de caractere
8.2. Deschiderea unui fişier 8.4.4. Intrări/ieşiri formatate
8.3. Închiderea unui fişier 8.5. Intrări/ieşiri binare
8.4. Prelucrarea fişierelor text 8.6. Poziţionarea într-un fişier
8.4.1. Prelucrarea la nivel de caracter 8.7. Funcţii utilitare pentru lucrul cu fişiere
8.4.2. Prelucrarea la nivel de cuvânt 8.8. Alte operaţii cu fişiere

8.1. CARACTERISTICILE GENERALE ALE FIŞIERELOR

Noţiunea de fişier desemnează o colecţie de informaţii memorată pe un suport permanent (de obicei discuri
magnetice), percepută ca un ansamblu, căreia i se asociază un nume (în vederea conservării şi regăsirii
ulterioare).

Caracteristicile unui fişier (sub sistem de operare MS-DOS) sunt :


 Dispozitivul logic de memorare (discul);
 Calea (în structura de directoare) unde este memorat fişierul;
 Numele şi extensia;
 Atributele care determină operaţiile care pot fi efectuate asupra fişierului (de exemplu: R-read-only -
citire; W-write-only scriere; RW-read-write citire/scriere; H-hidden - nu se permite nici măcar vizualizarea;
S-system - fişiere sistem asupra cărora numai sistemul de operare poate realiza operaţii operaţii, etc.).

Lucrul cu fişiere în programare oferă următoarele avantaje:


 Prelucrarea de unei cantităţi mari de informaţie obţinută din diverse surse cum ar fi execuţia prealabilă a
unui alt program;
 Stocarea temporară pe suport permanent a informaţiei în timpul execuţiei unui program pentru a evita
supraîncărcarea memoriei de lucru;
 Prelucrarea aceleeaşi colecţii de informaţii prin mai multe programe.

În limbajul C, operaţiile asupra fişierelor se realizează cu ajutorul unor funcţii din biblioteca standard
(stdio.h). Transferurile cu dipozitivele periferice (tastatură, monitor, disc, imprimantă, etc.) se fac prin
intermediul unor dispozitive logice identice numite stream-uri (fluxuri) şi prin intermediul sistemului de
operare. Un flux de date este un fişier sau un dispozitiv fizic tratat printr-un pointer la o structură de tip
FILE (din header-ul stdio.h). Când un program este executat, în mod automat, se deschid următoarele
fluxuri de date predefinite, dispozitive logice (în stdio.h):
 stdin (standard input device) - dispozitivul standard de intrare (tastatura) - ANSII C;
 stdout (standard output device) - dispozitivul standard de ieşire (monitorul) - ANSII C;
 stderr (standard error output device) - dispozitivul standard de eroare (de obicei un fişier care
conţine mesajele de eroare rezultate din execuţia unor funcţii) - ANSII C;
 stdaux (standard auxiliary device) - dispozitivul standard auxiliar (de obicei interfaţa serială auxiliară)
- specifice MS-DOS;
 stdprn (standard printer) - dispozitivul de imprimare - specifice MS-DOS.

În abordarea limbajului C (impusă de stdio.h), toate elementele care pot comunica informaţii cu un program
sunt percepute - în mod unitar - ca fluxuri de date. Datele introduse de la tastatură formează un fişier de
intrare (fişierul standard de intrare). Datele afişate pe monitor formează un fişier de ieşire (fişierul
standard de ieşire). Sfârşitul oricărui fişier este indicat printr-un marcaj de sfârşit de fişier (end of file). În
cazul fişierului standard de intrare, sfârşitul de fişier se generează prin Ctrl+Z (^Z) (sub MS-DOS) (sau
Ctrl+D sub Linux). Acest caracter poate fi detectat prin folosirea constantei simbolice EOF (definită în

113
CAPITOLUL 8 Fişiere
fişierul stdio.h), care are valoarea -1. Această valoare nu rămane valabilă pentru fişierele binare, care pot
conţine pe o poziţie oarecare caracterul ’\x1A’.
De obicei, schimbul de informaţii dintre programe şi periferice se realizează folosind zone tampon. O zonă
tampon păstrează una sau mai multe înregistrări. Prin operaţia de citire, înregistrarea curentă este transferată
de pe suportul extern în zona tampon care îi corespunde, programul având apoi acces la elementele
înregistrării din zona tampon. În cazul operaţiei de scriere, înregistrarea se construieşte în zona tampon, prin
program, fiind apoi transferată pe suportul extern al fişierului. În cazul monitoarelor, înregistrarea se
compune din caracterele unui rând. De obicei, o zonă tampon are lungimea multiplu de 512 octeţi. Orice
fişier trebuie deschis inainte de a fi prelucrat, iar la terminarea prelucrării lui, trebuie închis.

Fluxurile pot fi de tip text sau de tip binar. Fluxurile de tip text împart fişierele în linii separate prin
caracterul ’\n’ (newline=linie nouă), putând fi citite ca orice fişier text. Fluxurile de tip binar transferă
blocuri de octeţi (fără nici o structură), neputând fi citite direct, ca fişierele text.

Prelucrarea fişierelor se poate face la două niveluri:


 Nivelul superior de prelucrare a fişierelor în care se utilizează funcţiile specializate în prelucrarea
fişierelor.
 Nivelul inferior de prelucrare a fişierelor în care se utilizează direct facilităţile oferite de sistemul de
operare, deoarece, în final, sarcina manipulării fişierelor revine sistemului de operare. Pentru a avea
acces la informaţiile despre fişierele cu care lucrează, sistemul de operare foloseşte câte un descriptor
(bloc de control) pentru fiecare fişier.

Ca urmare, există două abordări în privinţa lucrului cu fişiere:


 abordarea implementată în stdio.h, asociază referinţei la un fişier un stream (flux de date), un pointer
către o structură FILE.
 abordarea definită în header-ul io.h (input/output header) asociază referinţei la un fişier un aşa-numit
handle (în cele ce urmează acesta va fi tradus prin indicator de fişier) care din punct de vedere al tipului
de date este in;

Scopul lucrului cu fişiere este acela de a prelucra informaţia conţinută. Pentru a putea accesa un fişier va
trebui să-l asociem cu unul din cele două modalităţi de manipulare. Acest tip de operaţie se mai numeşte
deschidere de fişier. Înainte de a citi sau scrie într-un fişier (neconectat automat programului), fişierul trebuie
deschis cu ajutorul funcţiei fopen din biblioteca standard. Funcţia primeşte ca argument numele extern al
fişierului, negociază cu sistemul de operare şi retunează un nume (identificator) intern care va fi utilizat
ulterior la prelucrarea fişireului. Acest identificator intern este un pointer la o structură care conţine
informaţii despre fişier (poziţia curentă în buffer, dacă se citeşte sau se scrie în fişier, etc.). Utilizatorii nu
trebuie să cunoască detaliile, singura declaraţie necesară fiind cea pentru pointerul de fişier.
Exemplu: FILE *fp;

Operaţiile care pot fi realizate asupra fişierelor sunt:


 deschiderea unui fişier;
 scrierea într-un fişier;
 citirea dintr-un fişier;
 poziţionarea într-un fişier;
 închiderea unui fişier.

8.2. DESCHIDEREA UNUI FIŞIER

 Funcţia fopen
Crează un flux de date între fişierul specificat prin numele extern (nume_fişier) şi programul C.
Parametrul mod specifică sensul fluxului de date şi modul de interpretare a acestora. Funcţia returnează
un pointer spre tipul FILE, iar în caz de eroare - pointerul NULL (prototip în stdio.h).
FILE *fopen(const char *nume_fişier, const char *mod);
Parametrul mod este o constantă şir de caractere, care poate conţine caracterele cu semnificaţiile:

114
CAPITOLUL 8 Fişiere
 r : flux de date de intrare; deschidere pentru citire;
 w : flux de date de ieşire; deschidere pentru scriere (crează un fişier nou sau suprascrie conţinutul
anterior al fişierului existent);
 a : flux de date de ieşire cu scriere la sfârşitul fişierului, adăugare, sau crearea fişierului în cazul în
care acesta nu există;
 + : extinde un flux de intrare sau ieşire la unul de intrare/ieşire; operaţii de scriere şi citire asupra
unui fişier deschis în condiţiile r, w sau a.
 b : date binare;
 t : date text (modul implicit).
Exemple:
"r+" – deschidere pentru modificare (citire şi scriere);
"w+" – deschidere pentru modificare (citire şi scriere);
"rb" – citire binară;
"wb" – scriere binară;
"r+b" – citire/scriere binară.

 Funcţia freopen (stdio.h)


Asociază un nou fişier unui flux de date deja existent, închizând legătura cu vechiul fişier şi încercând
să deschidă una nouă, cu fişierul specificat. Funcţia returnează pointerul către fluxul de date specificat,
sau NULL în caz de eşec (prototip în stdio.h).
FILE*freopen(const char*nume_fiş,const char*mod,FILE *flux_date);

 Funcţia open
Deschide fişierul specificat conform cu restricţiile de acces precizate în apel. Returnează un întreg care
este un indicator de fişier sau -1 (în caz de eşec) (prototip în io.h).
int open(const char *nume_fişier, int acces [,int mod]);
Restricţiile de acces se precizează prin aplicarea operatorului | (disjuncţie logică la nivel de bit) între
anumite constante simbolice, definite în fcntl.h, cum sunt :
O_RDONLY - citire;
O_WRONLY - scriere
O_RDWR - citire şi scriere
O_CREAT - creare
O_APPEND - adăugare la sfârşitul fişierului
O_TEXT - interpretare CR-LF
O_BINARY - nici o interpretare.,
Restricţiile de mod de creare se realizează cu ajutorul constantelor:
S_IREAD - permisiune de citire din fişier
S_IWRITE - permisiune de scriere din fişier, eventual legate prin operatorul “|”.

 Funcţia creat
Crează un fişier nou sau îl suprascrie în cazul în care deja există. Returnează indicatorul de fişier sau -1
(în caz de eşec). Parametrul un_mod este obţinut în mod analog celui de la funcţia de deschidere
(prototip în io.h).
int creat(const char *nume_fişier, int un_mod);

 Funcţia creatnew
Crează un fişier nou, conform modului specificat. Returnează indicatorul fişierului nou creat sau rezultat
de eroare (-1), dacă fişierul deja există (prototip în io.h).
int creatnew(const char *nume_fişier, int mod);

După cum se observă, informaţia furnizată pentru deschiderea unui fişier este aceeaşi în ambele abordări,
diferenţa constând în tipul de date al entitaţii asociate fişierului. Implementarea din io.h oferă un alt tip de
control la nivelul comunicării cu echipamentele periferice (furnizat de funcţia ioctrl), asupra căruia nu
vom insista, deoarece desfăşurarea acestui tip de control este mai greoaie, dar mai profundă.

115
CAPITOLUL 8 Fişiere
8.3. ÎNCHIDEREA UNUI FIŞIER

 Funcţia fclose
int fclose(FILE *pf);
Funcţia închide un fişier deschis cu fopen şi eliberează memoria alocată (zona tampon şi structura
FILE). Returnează valoarea 0 la închiderea cu succes a fişierului şi -1 în caz de eroare (prototip în
stdio.h).

 Funcţia fcloseall
int fcloseall(void);
Închide toate fluxururile de date şi returnează numărul fluxurilor de date închise (prototip în stdio.h).

 Funcţia close
int close(int indicator);
Închide un indicator de fişier şi returnează 0 (în caz de succes) sau -1 în caz de eroare (prototip în io.h).

8.4. PRELUCRAREA FIŞIERELOR TEXT

După deschiderea unui fişier, toate operaţiile asupra fişierului vor fi efectuate cu pointerul său. Operaţiile de
citire şi scriere într-un fişier text pot fi:
 intrări/ieşiri la nivel de caracter (de octet);
 intrări/ieşiri la nivel de cuvânt (2 octeţi);
 intrări/ieşiri de şiruri de caractere;
 intrări/ieşiri cu formatare.

Comunicarea de informaţie de la un fişier către un program este asigurată prin funcţii de citire care transferă
o cantitate de octeţi (unitatea de măsură în cazul nostru) din fişier într-o variabilă-program pe care o vom
numi buffer, ea însăşi având sensul unei înşiruiri de octeţi prin declaraţia void *buf. Comunicarea de
informaţie de la un program către un fişier este asigurată prin funcţii de scriere care transferă o cantitate de
octeţi dintr-o variabilă-program de tip buffer în fişier.

Fişierele sunt percepute în limbajul C ca fiind, implicit, secvenţiale (informaţia este parcursă succesiv,
element cu element). Pentru aceasta, atât fluxurile de date cât şi indicatorii de fişier au asociat un indicator
de poziţie curentă în cadrul fişierului. Acesta este iniţializat la 0 în momentul deschiderii, iar operaţiile de
citire, respectiv scriere, se referă la succesiunea de octeţi care începe cu poziţia curentă. Operarea asupra
fiecărui octet din succesiune determină incrementarea indicatorului de poziţie curentă.

8.4.1. PRELUCRAREA UNUI FIŞIER LA NIVEL DE CARACTER

Fişierele pot fi scrise şi citite caracter cu caracter folosind funcţiile putc (pentru scriere) şi getc (citire).
 Funcţia putc
int putc (int c, FILE *pf);
c – este codul ASCII al caracterului care se scrie în fişier;
pf – este pointerul spre tipul FILE a cărui valoare a fost returnată de funcţia fopen.
Funcţia putc returnează valoarea lui c (valoarea scrisă în caz de succes), sau –1 (EOF) în caz de eroare
sau sfârşit de fişier.

 Funcţia getc
int getc (FILE *pf);
Funcţia citeşte un caracter dintr-un fişier (pointerul spre tipul FILE transmis ca argument) şi returnează
caracterul citit sau EOF la sfârşit de fişier sau eroare.

116
CAPITOLUL 8 Fişiere
Exerciţiu: Să se scrie un program care crează un fişier text în care se vor scrie caracterele introduse de la
tastatură (citite din fişierul standard de intrare), până la întâlnirea caracterului ^Z = Ctrl+Z.
#include <stdio.h>
#include <process.h>
void main()
{
int c, i=0; FILE *pfcar;
char mesaj[]="\nIntrodu caractere urmate de Ctrl+Z (Ctrl+D sub Linux):\n";
char eroare[]="\n Eroare deschidere fişier \n";
while(mesaj[i]) putchar(mesaj[i++]);
pfcar=fopen("f_car1.txt","w"); // crearea fişierului cu numele extern f_car1.txt
if(pfcar==NULL)
{
i=0;
while(eroare[i])putc(eroare[i++],stdout);
exit(1);
}while((c=getchar())!=EOF) // sau: while ((c=getc(stdin)) != EOF)
putc(c,pfcar); // scrierea caracterului în fişier
fclose(pfcar); // închiderea fişierului
}

Exerciţiu: Să se scrie un program care citeşte un fişier text, caracter cu caracter, şi afişează conţinutul
acestuia.
#include <stdio.h>
#include <process.h>
void main()
{
int c, i=0;
FILE *pfcar;
char eroare[]="\n Eroare deschidere fişier \n";
pfcar=fopen("f_car1.txt","r"); //deschiderea fişierului numit f_car1.txt în citire
if(pfcar==NULL)
{
i=0;
while(eroare[i])putc(eroare[i++],stdout);
exit(1);
} while((c=getc(pfcar))!=EOF) //citire din fişier, la nivel de caracter
putc(c,stdout);
//scrierea caracterului citit în fişierul standard de ieşire (afişare pe monitor)
fclose(pfcar);
}

8.4.2. PRELUCRAREA UNUI FIŞIER LA NIVEL DE CUVÂNT

Funcţiile putw şi getw (putword şi getword) sunt echivalente cu funcţiile putc şi getc, cu diferenţa că
unitatea transferată nu este un singur octet (caracter), ci un cuvânt (un int).
int getw(FILE *pf);
int putc (int w, FILE *pf);

Se recomandă utilizarea funcţiei feof pentru a testa întâlnirea sfârşitului de fişier.


Exemplu:
int tab[100];
FILE *pf;
// . . . deschidere fişier
while (!feof(pf)){
for (int i=0; i<100; i++){
if (feof(pf))
break;

117
CAPITOLUL 8 Fişiere
tab[i]=getw(pf);
//citire din fişier la nivel de cuvânt şi memorare în vectorul tab
// . . .
}
}
printf("Sfarşit de fişier\n");

8.4.3. PRELUCRAREA UNUI FIŞIER LA NIVEL DE ŞIR DE CARACTERE

Într-un fişier text, liniile sunt considerate ca linii de text separate de sfârşitul de linie ('\n'), iar în memorie,
ele devin şiruri de caractere terminate de caracterul nul ('\0'). Citirea unei linii de text dintr-un fişier se
realizează cu ajutorul funcţiei fgets, iar scrierea într-un fişier - cu ajutorul funcţiei fputs.

Funcţia fgets este indentică cu funcţia gets, cu deosebirea că funcţia gets citeşte din fişierul standard de
intrare (stdin). Funcţia fputs este indentică cu funcţia puts, cu deosebirea funcţia puts scrie în fişierul
standard de ieşire (stdout).

 Funcţia fputs
int fputs(const char *s, FILE *pf);
Funcţia scrie un şir de caractere într-un fişier şi primeşte ca argumente pointerul spre zona de memorie
(buffer-ul) care conţine şirul de caractere (s) şi pointerul spre structura FILE. Funcţia returnează ultimul
caracter scris, în caz de succes, sau -1 în caz de eroare.

 Funcţia fgets
char *fgets(char *s, int dim, FILE *pf);
Funcţia citeşte maximum dim-1 octeţi (caractere) din fişier, sau până la întâlnirea sfarşitului de linie.
Pointerul spre zona în care se face citirea caracterelor este s. Terminatorul null ('\0') este plasat automat la
sfârşitul şirului (buffer-lui de memorie). Funcţia returnează un pointer către buffer-ul în care este memorat
şirul de caractere, în caz de succes, sau pointerul NULL în cazul eşecului.

Exerciţiu: Să se scrie un program care crează un fişier text în care se vor scrie şirurile de caractere introduse
de la tastatură.
#include <stdio.h>
void main()
{
int n=250; FILE *pfsir;
char mesaj[]="\nIntrodu siruri car.urmate de Ctrl+Z(Ctrl+D sub Linux):\n";
char sir[250],*psir; fputs(mesaj,stdout);
pfsir=fopen("f_sir.txt","w"); //deschiderea fişierului f_şir.txt pentru scriere
psir=fgets(sir,n,stdin); // citirea şirurilor din fişierul standard de intrare
while(psir!=NULL)
{
fputs(sir,pfsir); // scrierea în fişierul text
psir=fgets(sir,n,stdin);
}
fclose(pfsir);
}

Exerciţu: Să se scrie un program care citeşte un fişier text, linie cu linie, şi afişează conţinutul acestuia
#include <stdio.h>
void main()
{
int n=250; FILE *pfsir; char sir[250],*psir;
pfsir=fopen("f_sir.txt","r"); psir=fgets(sir,n,pfsir);
while(psir!=NULL)
{

118
CAPITOLUL 8 Fişiere
fputs(sir,stdout); //sau: puts(sir);
//afişarea (scrierea în fişierul standard de ieşire) şirului (liniei) citit din fişierul text
psir=fgets(sir,n,pfsir); //citirea unei linii de text din fişier
}
fclose(pfsir);}

8.4.4. INTRĂRI/IEŞIRI FORMATATE

Operaţiile de intrare/ieşire formatate permit citirea, respectiv scrierea într-un fişier text, impunând un anumit
format. Se utilizează funcţiile fscanf şi fprintf, similare funcţiilor scanf şi printf (care permit
citirea/scrierea formatată de la tastatură/monitor).
 Funcţia fscanf
int fscanf(FILE *pf, const char *format, . . .);

 Funcţia fprintf
int fprintf(FILE *pf, const char *format, . . .);
Funcţiile primesc ca parametri ficşi pointerul (pf ) spre tipul FILE (cu valoarea atribuită la apelul funcţiei
fopen), şi specificatorul de format (cu structură identică celui prezentat pentru funcţiile printf şi scanf).
Funcţiile returnează numărul câmpurilor citite/scrise în fişier, sau -1 (EOF) în cazul detectării sfârşitului
fişierului sau al unei erori.

8.5. INTRĂRI/IEŞIRI BINARE

Reamintim că fluxurile de tip binar transferă blocuri de octeţi (fără nici o structură), neputând fi citite direct,
ca fişierele text (vezi paragraful 8.1.). Comunicarea de informaţie dintre un program şi un fişier este
asigurată prin funcţii de citire/scriere care transferă un număr de octeţi, prin intermediul unui buffer.

Funcţiile de citire
 Funcţia fread
Citeşte date dintr-un flux, sub forma a n blocuri (entităţi), fiecare bloc având dimensiunea dim, într-un
buffer (buf). Returnează numărul de blocuri citite efectiv, sau -1 în caz de eroare (prototip în stdio.h).
size_t fread(void *buf, size_t dim, size_t n, FILE *flux_date);

 Funcţia read
Citeşte dintr-un fişier (precizat prin indicatorul său, indicator) un număr de n octeţi şi îi memorează
în bufferul buf. Funcţia returnează numărul de octeţi citiţi efectiv (pentru fişierele deschise în mod text
nu se numără simbolurile de sfirşit de linie), sau -1 în caz de eroare (prototip în io.h).
int read(int indicator, void *buf, unsigned n);

Funcţiile de scriere
Fişierele organizate ca date binare pot fi prelucrate cu ajutorul funcţiilor fread şi fwrite. În acest caz, se
consideră că înregistrarea este o colecţie de date structurate numite articole. La o citire se transferă într-o
zonă specială, numită zona tampon, un număr de articole care se presupune că au o lungime fixă.

 Funcţia fwrite
Scrie informaţia (preluată din buffer, buf este pointerul spre zona tampon care conţine articolele citite)
într-un flux de date, sub forma a n entităţi de dimensiune dim. Returnează numărul de entităţi scrise
efectiv, sau -1 în caz de eroare (prototip în stdio.h).
size_t fwrite(const void *buf, size_t dim, size_t n, FILE *flx_date);

 Funcţia write
Scrie într-un fişier (desemnat prin indicatorul său, indicator) un număr de n octeţi preluaţi dintr-un
buffer (buf este pointerul spre acesta). Returnează numărul de octeţi scrişi efectiv sau -1 în caz de

119
CAPITOLUL 8 Fişiere
eroare (prototip în io.h).
int write(int indicator, void *buf, unsigned n);

Exerciţu: Să se scrie un program care crează un fişier binar în care se vor introduce numere reale, nenule.
#include <iostream.h>
#include <stdio.h>
int main()
{ FILE *f; double nr; int x;
if ((f= fopen("test_nrb.dat", "wb")) == NULL) //deschidere flux binar, scriere
{ cout<<"\nNu se poate deschide fişierul test_nrb.dat"<<'\n';
return 1;
}
cout<<"\nIntroduceţi numere(diferite de 0) terminate cu un 0:"<<'\n';
cin>>nr;
while(nr!=0)
{
x=fwrite(&nr, sizeof(nr), 1, f); //scriere în fişier
cin>>nr;
}
fclose(f);
return 0;
}

Exemplu: Să se scrie un program ce citeşte dintr-un fişier binar numere reale, nenule.
#include <iostream.h>
#include <stdio.h>
int main()
{ FILE *f; double buf;
if ((f= fopen("test_nrb.dat", "rb")) == NULL)
{
cout<<"\nNu se poate deschide fişierul test_nrb.dat"<<'\n';
return 1;
}
cout<<"\nNumerele nenule citite din fişier sunt:"<<'\n';
while((fread(&buf, sizeof(buf), 1, f))==1)
// funcţia sizeof(buf) care returneaza numarul de octeţi necesari variabilei buf.
cout<<buf<<" ";
fclose(f);
cout<<'\n';
return 0;
}

8.6. POZIŢIONAREA ÎNTR-UN FIŞIER

Pe lângă mecanismul de poziţionare implicit (asigurat prin operaţiile de citire şi scriere) se pot folosi şi
operaţiile de poziţionare explicită.

 Funcţia fseek
int fseek(FILE *pf, long deplasament, int referinţa);
Funcţia deplasează capul de citire/scriere al discului, în vederea prelucrării înregistrărilor fişierului
într-o ordine oarecare. Funcţia setează poziţia curentă în fluxul de date la n octeţi faţă de referinţă):
deplasament – defineşte numărul de octeţi peste care se va deplasa capul discului;
referinţa – poate avea una din valorile:
0 - începutul fişierului (SEEK_SET);
1 - poziţia curentă a capului (SEEK_CUR);
2 - sfârşitul fişierului (SEEK_END).
Funcţia returnează valoarea zero la poziţionarea corectă şi o valoare diferită de zero în caz de eroare
(prototip în stdio.h).

120
CAPITOLUL 8 Fişiere
 Funcţia lseek
int lseek(int indicator, long n, int referinta);
Seteaza poziţia curentă de citire/scriere în fişier la n octeţi faţa de referinţă. Returnează valoarea 0 în
caz de succes şi diferită de zero în caz de eroare (prototip în io.h).

 Funcţia fgetpos
int fgetpos(FILE *flux_date, fpos_t *poziţie);
Determină poziţia curentă (pointer către o structură, fpos_t, care descrie această poziţie în fluxul de
date). Înscrie valoarea indicatorului în variabila indicată de poziţie. Returnează 0 la determinarea cu
succes a acestei poziţii sau valoare diferită de zero în caz de eşec. Structura care descrie poziţia poate fi
transmisă ca argument funcţiei fsetpos (prototip în stdio.h).

 Funcţia fsetpos
int fsetpos(FILE *flux_date, const fpos_t *poziţie);
Setează poziţia curentă în fluxul de date (atribuie indicatorului valoarea variabilei indicate poziţie),
la o valoare obţinută printr apelul funcţiei fgetpos. Returnează valoarea 0 în caz de succes, sau diferită
de 0 în caz de eşec (prototip în stdio.h).

Există funcţii pentru modificarea valorii indicatorului de poziţie şi de determinare a poziţiei curente a
acestuia.
 Funcţia ftell
long ftell(FILE *pf);
Indică poziţia curentă a capului de citire în fişier. Funcţia returnează o valoare de tip long int care
reprezintă poziţia curentă în fluxul de date (deplasamentul în octeţi a poziţiei capului faţă de începutul
fişierului) sau -1L în caz de eroare (prototip în stdio.h).

 Funcţia tell
long tell(int indicator);
Returnează poziţia curentă a capului de citire/scriere în fişier (exprimată în număr de octeţi faţă de
începutul fişierului), sau -1L în caz de eroare (prototip în io.h).

 Funcţia rewind
void rewind(FILE *flux_date);
Poziţionează indicatorul la începutul fluxului de date specificat ca argument (prototip în stdio.h).

8.7. FUNCŢII UTILITARE PENTRU LUCRUL CU FIŞIERE

Funcţii de testare a sfârşitului de fişier


 Funcţia feof
int feof(FILE *flux_date);
Returnează o valoare diferită de zero în cazul întâlnirii sfârşitului de fişier sau 0 în celelalte cazuri
(prototip în stdio.h).

 Funcţia eof
int eof(int indicator);
Returnează valoarea 1 dacă poziţia curentă este sfârşitul de fişier, 0 dacă indicatorul este poziţionat în
altă parte, sau -1 în caz de eroare (prototip în io.h).

Funcţii de golire a fluxurilor de date


 Funcţia fflush
int fflush(FILE *flux_date);
Goleşte un fluxul de date specificat ca argument. Returnează 0 în caz de succes şi -1 (EOF) în caz de
eroare (prototip în stdio.h).

121
CAPITOLUL 8 Fişiere
 Funcţia flushall
int flushall(void);
Goleşte toate fluxurile de date existente, pentru cele de scriere efectuând şi scrierea în fişiere.
Returnează numărul de fluxuri asupra cărora s-a efectuat operaţia (prototip în stdio.h).

8.8. ALTE OPERAŢII CU FIŞIERE

Funcţii care permit operaţii ale sistemului de operare asupra fişierelor


 Funcţia remove
int remove(const char *nume_fişier);
Şterge un fişier. Returnează valoarea 0 pentru operaţie reuşită şi -1 pentru operaţie eşuată (prototip în
stdio.h).

 Funcţia rename
int rename(const char *nume_vechi, const char *nume_nou);
Redenumeşte un fişier. Returnează 0 pentru operaţie reuşita şi -1 în cazul eşecului (prototip în stdio.h).

 Funcţia unlink
int unlink(const char *nume_fişier);
Şterge un fişier. Returnează 0 la operaţie reuşită şi -1 la eşec; dacă fişierul are permisiune read-only,
funcţia nu va reuşi operaţia (prototip în io.h, stdio.h).

Funcţii care permit manipularea aceluiaşi fişier prin două indicatoare de fişier independente
 Funcţia dup
int dup(int indicator);
Duplică un indicator de fişier. Returnează noul indicator de fişier pentru operaţie reuşită sau -1 în cazul
eşecului (prototip în io.h).

 Funcţia dup2
int dup2(int indicator_vechi, int indicator_nou);
Duplică un indicator de fişier la valoarea unui indicator de fişier deja existent. Returnează 0 în caz de
succes şi -1 în caz de eşec (prototip în io.h).

Funcţii pentru aflarea sau modificarea dimensiunii în octeţi a fişierelor


 Funcţia chsize
int chsize(int indicator, long lungime);
Modifică dimensiunea unui fişier, conform argumentului lungime. Returnează 0 pentru operaţie reuşită
sau -1 în caz de eşec (prototip în stdio.h).

 Funcţia filelength
long filelength(int indicator);
Returnează lungimea unui fişier (în octeţi) sau -1 în caz de eroare (prototip în io.h).

Funcţii de lucru cu fişiere temporare care oferă facilităţi de lucru cu fişiere temporare prin generarea de
nume unice de fişier în zona de lucru.
 Funcţia tmpfile
FILE *tmpfile(void);
Deschide un fişier temporar, ca flux de date, în mod binar (w+b). Returnează pointerul către fişierul
deschis în cazul operaţiei reuşite, sau NULL în caz de eşec (prototip în stdio.h).

 Funcţia tmpnam
char *tmpnam(char *sptr);
Crează un nume unic pentru fişierul temporar (prototip în stdio.h).

122
CAPITOLUL 8 Fişiere
 Funcţia creattemp
int creattemp(char *cale, int attrib);
Crează un fişier unic ca nume, cu atributele specificate în argumentul attrib (prin _fmode,O_TEXT sau
O_BINARY), în directorul dat în argumentul cale. Returnează indicatorul (handler-ul) către fişierul
creat sau -1 (şi setarea errno) în cazul eşecului (prototip în io.h).

Exemplu: Să se creeze un fişier binar, care va conţine informaţiile despre angajaţii unei întreprinderi: nume,
marca, salariu. Să se afişeze apoi conţinutul fişierului.
#include<iostream.h>
#include <stdio.h>
#include <ctype.h>
typedef struct
{ char nume[20];int marca;double salariu;
}angajat;
union
{angajat a;char sbinar[sizeof(angajat)];}buffer;

int main()
{angajat a; FILE *pf; char cont;char *nume_fis;
cout<<"Nume fisier care va fi creat:"; cin>>nume_fis;
if ((pf= fopen(nume_fis, "wb")) == NULL)
{ cout<<"\nEroare creare fişier "<<nume_fis<<"!\n";
return 1; }
do
{cout<<"Marca : ";cin>>a.marca;
cout<<"Nume : ";cin>>a.nume;
cout<<"Salariu :";cin>>a.salariu;
buffer.a=a;
fwrite(buffer.sbinar,1,sizeof(angajat),pf);
cout<<"Continuati introducerea de date (d/n) ?";
cin>>cont;
} while(toupper(cont)!='N');
fclose(pf);
//citirea informatiilor
if ((pf= fopen(nume_fis, "rb")) == NULL)
{ cout<<"\nEroare citire fişier "<<nume_fis<<"!\n";
return 1; }
for(;;)
{
fread(buffer.sbinar,1,sizeof(a),pf);
a=buffer.a1;
if(feof(pf)) exit(1);
cout<<" Marca : "<<a.marca;
cout<<" Numele : "<<a.nume<<'\n';
cout<<" Salariul : "<<a.salariu<<'\n';
}
fclose(pf);
}

Exemplu: Aplicaţie pentru gestiunea materialelor dintr-un depozit. Aplicaţia va avea un meniu principal şi va
permite gestiunea următoarelor informaţii: codul materialului (va fi chiar "numărul de ordine"), denumirea
acestuia, unitatea de măsură, preţul unitar, cantitatea contractată şi cea recepţionată (vectori cu 4 elemente).
Memorarea datelor se va face într-un fişier de date (un fişier binar cu structuri), numit "material.dat".
Aplicaţia conţine următoarele funcţii:
1. help() - informare privind opţiunile programului
2. Funcţii pentru fişierele binare, care să suplinească lipsa funcţiilor standard pentru organizarea directă a
fişierelor binare:
citireb() - citire în acces direct din fişier;

123
CAPITOLUL 8 Fişiere
scrieb() - scriere în acces direct în fişier;
citmat() - citirea de la terminal a informaţiilor despre un material;
afismat() - afişarea informaţiilor despre un material (apelată de list);
lungfisis() - determinarea lungimii fişierului existent;
crefis() - creare fişier.
3. Funcţii pentru adaugarea, modificarea, ştergerea şi listarea de materiale.

#include <process.h>
#include <iostream.h>
#include <stdio.h>
#include <ctype.h>
typedef struct material
{ int codm,stoc,cant_c[4],cant_r[4];
char den_mat[20],unit_mas[4];
float preţ;
};
material mat;
FILE *pf;
void crefis(),adaug(),modif(),sterg(),list(),help();
void main()
{
char opţiune;
do //afişarea unui meniu de opţiuni şi selecţia opţiunii
{
cout<<'\n'<<"Opţiunea Dvs. de lucru este"<<'\n'
<<"(c|a|m|s|l|e|h pentru help) : ";
cin>>opţiune;
switch(opţiune)
{
case 'c':case 'C':crefis();break;
case 'a':case 'A':adaug();break;
case 'm':case 'M':modif();break;
case 's':case 'S':şterg();break;
case 'l':case 'L':list();break;
case 'h':case 'H':help();break;
case 'e':case 'E': break;
default:help(); break;
}
}while(toupper(opţiune)!='E');
}
void help() // afişare informaţii despre utilizarea meniului şi opţiunile acestuia
{cout<<"Opţiunile de lucru sunt :"<<'\n';
cout<<" C,c-creare fisier"<<'\n';
cout<<" A,a-adaugare"<<'\n';
cout<<" M,m-modificare"<<'\n';
cout<<" L,l-listare"<<'\n';
cout<<" S,s-ştergere"<<'\n';
cout<<" H,h-help"<<'\n';
cout<<" E,e-exit"<<'\n';
}
long int lungfis(FILE *f) // returnează lungimea fişierului
{long int posi,posf;
posi=ftell(f); fseek(f,0,SEEK_END);
posf=ftell(f); fseek(f,posi,SEEK_SET);
return posf;
}
void scrieb(int nr,void *a,FILE *f) //scriere în fişierul binar
{long depl=(nr-1)*sizeof(material);
fseek(f,depl,SEEK_SET);
if(fwrite(a,sizeof(material),1,f)!=1)
{cout<<"Eroare de scriere in fişier !"<<'\n';

124
CAPITOLUL 8 Fişiere
exit(1); }
}
void citireb(int nr,void *a,FILE *f) //citire din fişierul binar
{long depl=(nr-1)*sizeof(material);
fseek(f,depl,SEEK_SET);
if(fread(a,sizeof(material),1,f)!=1)
{cout<<"Eroare de citire din fişier !"<<'\n';
exit(2); }
}
void afismat(material *a) //afişarea informaţiilor despre un anumit material
{
int i;
if(a->codm)
{cout<<"Cod material : "<<a->codm<<'\n';
cout<<"Denumire material: "<<a->den_mat<<'\n';
cout<<"Cantitaţi contractate:"<<'\n';
for(i=0;i<4;i++)
cout<<"Contractat "<<i<<" : "<<a->cant_c[i]<<'\n';
cout<<"Cantitaţi recepţionate:"<<'\n';
for(i=0;i<4;i++)
cout<<"Receptionat "<<i<<" : "<<a->cant_r[i]<<'\n';
cout<<"Stoc : "<<a->stoc<<'\n';
cout<<"Unitate de masura: "<<a->unit_mas<<'\n';
cout<<"Preţ unitar : "<<a->preţ<<'\n';
}
else cout<<"Acest articol a fost şters !"<<'\n';
}
void citmat(material *a) //citirea informaţiilor despre un anumit material
{
int i;float temp;
cout<<"Introduceti codul materialului (0=End): ";cin>>a->codm;
if(a->codm==0) return;
cout<<"Introduceţi denumirea materialului : ";cin>>a->den_mat;
cout<<"Introduceţi unitatea de măsură : ";cin>>a->unit_mas;
cout<<"Introduceţi preţul : ";cin>>temp;a->preţ=temp;
cout<<"Introduceţi cantitaţile contractate : "<<'\n';
for(i=0;i<4;i++)
{cout<<"Contractat "<<i+1<<" : ";cin>>a->cant_c[i]; }
cout<<"Introduceţi cantitaţile recepţionate : "<<'\n';
for(i=0;i<4;i++)
{cout<<"Receptionat "<<i+1<<" : ";cin>>a->cant_r[i]; }
}
void crefis() //deschidere fisier
{
if((pf=fopen("material.dat","r"))!=NULL)
cout<<"Fişierul exista deja !"<<'\n';
else
pf=fopen("material.dat","w");
fclose(pf);
}
void adaug() //adăugare de noi materiale
{
int na; pf=fopen("material.dat","a");//deschidere pentru append
na=lungfis(pf)/sizeof(material);
do
{citmat(&mat);
if(mat.codm) scrieb(++na,&mat,pf);
} while(mat.codm);
fclose(pf);
}

125
CAPITOLUL 8 Fişiere
void modif() //modificarea informaţiilor despre un material existent
{
int na; char ch; pf=fopen("material.dat","r+");
do
{cout<<"Numarul articolului de modificat este (0=END): ";cin>>na;
if(na)
{citireb(na,&mat,pf);
afismat(&mat);
cout<<"Modificaţi articol (D/N) ? :";
do
{ cin>>ch;
ch=toupper(ch);
} while(ch!='D' && ch!='N');
if(ch=='D')
{citmat(&mat);
scrieb(na,&mat,pf);
}
}
}while(na);
fclose(pf);
}
void sterg() //ştergerea din fişier a unui material
{ int n;long int na; pf=fopen("material.dat","r+");
mat.codm=0; na=lungfis(pf)/sizeof(material);
do
{
do
{cout<<"Numarul articolului de şters este (0=END): ";cin>>n;
if(n<0||n>na) cout<<"Articol eronat"<<'\n';
}while(!(n>=0 && n<=na));
if(n) scrieb(n,&mat,pf);
}while(n);
fclose(pf);
}
void list() //afişare informaţii despre un anumit material
{
int na; pf=fopen("material.dat","r");
do
{cout<<"Numarul articolului de listat este (0=END): ";cin>>na;
if(na)
{citireb(na,&mat,pf);
afismat(&mat);
cout<<'\n';
}
}while(na);
fclose(pf);
}

ÎNTREBĂRI ŞI EXERCIŢII

Chestiuni practice

1. Scrieţi un program de tipărire a conţinuturilor mai multor fişiere, ale căror nume se transmit ca parametri
către funcţia main. Tipărirea se face pe ecran (lungimea paginii = 22) sau la imprimantă (lungimea
paginii = 61). Conţinutul fiecărui fişier va începe pe o pagină nouă, cu un titlu care indică numele
fişierului. Pentru fiecare fişier, paginile vor fi numerotate (cu ajutorul unui contor de pagini).
2. Scrieţi un program care citeşte un fişier text. Pornind de la conţinutul acestuia, se va crea un alt fişier,
prin înlocuirea spaţiilor consecutive cu unul singur. Se vor afişa pe ecran conţinutul fişierului de la care
s-a pornit şi conţinutul fişierului obţinut.

126
CAPITOLUL 8 Fişiere
3. Să se consulte conţinutul unui fişier şi să se afişeze următoarele informaţii statistice: numărul de cuvinte
din fişier, numărul de caractere, numărul de linii, numărul de date numerice (nu cifre, numere!).
4. Scrieţi un program care să compare conţinutul a două fişiere, şi afişaţi primele linii care diferă şi poziţia
caracterelor diferite în aceste linii.
5. Scrieţi un program care citeşte conţinutul unui fişier sursă scris în limbajul C şi afişează în ordine
alfabetică fiecare grup al numelor de variabile care au primele n caractere identice (n este citit de la
tastatură).
6. Scrieţi un program care consultă un fişier text şi afişează o listă a tuturor cuvintelor din fişier. Pentru
fiecare cuvânt se vor afişa şi numerele liniilor în care apare cuvântul.
7. Scrieţi un program care citeşte un text introdus de la tastatură şi afişează cuvintele distincte, în ordinea
crescătoare a frecvenţei lor de apariţie. La afişare, fiecare cuvânt va fi precedat de numărul de apariţii.
8. Scrieţi un program care citeşte un text introdus de la tastatură, ordonează alfabetic liniile acestuia şi le
afişează.
9. Scrieţi o aplicaţie pentru gestiunea informatiilor despre cărţile existente într-o bibliotecă. Aplicaţia va
avea un meniu principal care va permite:
a) Memorarea datelor într-un fişier (un fişier binar cu structuri), al cărui nume se introduce de la tastatură.
Fişierul va contine informaţiile: nume carte, autor, editura, anul apariţiei, preţ. Pentru fiecare carte, se va
genera o cotă (un număr unic care să constituie cheia de căutare).
b) Adaugărea de noi cărţi;
c) Afişarea informaţiilor despre o anumită carte;
d) Căutarea titlurilor după un anumit autor;
e) Modificarea informaţiilor existente;
f) Lista alfabetică a tuturor autorilor;
g) Ştergerea unei cărţi din bibliotecă;
h) Ordonarea descrescătoare după anul apariţiei;
i) Numele celei mai vechi cărţi din bibliotecă;
j) Numele celei mai scumpe cărţi din bibliotecă;
k) Numele autorului cu cele mai multe cărţi;
l) Valoarea totală a cărţilor din bibliotecă.

127
CAPITOLUL 9 Concepte de bază ale programării orientate obiect

CONCEPTE DE BAZĂ ALE PROGRAMĂRII 9


ORIENTATE OBIECT

9.1. Introducere 9.4. Încapsularea informaţiei


9.2. Abstractizarea datelor 9.5. Legarea dinamică (târzie)
9.3. Moştenirea 9.6. Alte aspecte

9.1. INTRODUCERE

Termenul "OOP" ("Object Oriented Programming") desemnează disciplina programării obiectuale


(orientate-obiect). Această disciplină care are la bază ideea unificării datelor cu modalităţile de prelucrare a
acestora şi manevrează entităţi reprezentate sub formă de obiecte (obiect=date+cod de tratare a acestor date).

Aşa cum s-a subliniat în capitolul 1.3., rezolvarea unei probleme se poate face pe 3 direcţii:
 Rezolvarea orientată pe algoritm (pe acţiune), în care organizarea datelor este neesenţială;
 Rezolvarea orientată pe date, acţiunile fiind determinate doar de organizarea datelor;
 Rezolvarea orientată obiect, care combină tendinţele primelor două abordări.

Programarea obiectuală oferă posibilităţi de modelare a obiectelor, a proprietăţilor şi a relaţiilor dintre ele,
dar şi posibilitatea de a descompune o problemă în componentele sale (soft mai mentenabil, adaptabil,
reciclabil). Câteva exemple de limbaje de programare orientată obiect: SIMULA(1965), SIMULA-2(1967),
Smalltalk, C++, Java (în plus, Java poate fi considerat un limbaj de programare orientată eveniment).

Facilităţile oferite de programarea orientată obiect (conform lui Pascou) sunt:


1. abstractizarea datelor;
2. moştenirea;
3. încapsularea (ascunderea) informaţiei;
4. legarea dinamică (“târzie”).

9.2. ABSTRACTIZAREA DATELOR

Obiectele sunt componente software care modelează fenomene din lumea reală. În general, un fenomen
implică tipuri diferite de obiecte. Obiectele care reprezintă aceeaşi idee sau concept sunt de acelaşi tip şi pot
fi grupate în clase (concrete sau abstracte). Clasele implementează tipuri de date (aşa cum s-a subliniat în
capitolul 2, un tip de date înseamnă o mulţime de valori pentru care s-a adoptatat un anumit mod de
reprezentare şi o muţime de operatori care pot fi aplicaţi acestor valori), deci şi operatorii destinaţi
manipulării acestora: Clasă = Date + Operaţii.
De exemplu, programatorul îşi poate defini tipul (clasa) matrice şi operatorii care pot fi aplicaţi matricilor
(* pentru înmulţirea a două matrici, + pentru adunarea a două matrici, - pentru scăderea a două matrici, etc).
Astfel, el poate folosi tipul matrice în mod similar unui tip predefinit:
matrice A, B;
matrice C=A+B;
Tipul unui obiect (şablon al obiectului) este o clasă. O clasă se caracterizează prin: numele clasei, atribute,
funcţii şi relaţii cu alte clase.
Instanţa este un obiect dintr-o clasă (A, B, C sunt obiecte, instanţe ale clasei matrice) şi are proprietăţile
definite de clasă. Pentru o clasă definită, se pot crea mai multe instanţe ale acesteia. Toate obiectele au o
stare şi un comportament. Starea unui obiect se referă la elementele de date conţinute în obiect şi la
valorile asociate acestora (datele membre). Comportamentul unui obiect este determinat de care acţiunile pe
care obiectul poate să le execute (metodele).

129
CAPITOLUL 9 Concepte de bază ale programării orientate obiect
Atributele specificate în definiţia unei clase descriu valoric proprietăţile obiectelor din clasă, sub diferite
aspecte. Cele mai multe limbaje orientate obiect fac următoarea distincţie între atribute:
 atribute ale clasei (au aceeaşi valoare pentru toate instanţele clasei);
 atribute ale instanţei (variază de la o instanţă la alta, fiecare instanţă având propria copie a
atributului).
În limbajul C++ atributele se numesc date membre. Toate datele membre sunt atribute instanţă. Atributele
de clasă se pot obţine în cazul datelor membre statice (aceeaşi adresă de memorare pentru orice instanţă a
clasei).

Metode (funcţii membre). La definirea unei clase se definesc şi metodele acesteia (numite şi funcţii
membre). Fiecare obiect are acces la un set de funcţii care descriu operaţiile care pot fi executate asupra lui.
Metodele pot fi folosite de instanţele clasei respective, dar şi de instanţele altor clase (prin mecanismul
moştenirii).
Clasa conţine atât structurile de date necesare descrierii unui obiect, cât şi metodele care pot fi aplicate
obiectului. Astfel, gradul de abstractizare este, mult mai ridicat, iar programele devin mult mai uşor de
înţeles, depanat sau întreţinut.

La crearea unui obiect, alocarea memoriei se poate fi face static sau dinamic (cu ajutorul unor funcţii
membre speciale, numite constructori). Eliberarea memoriei se realizează cu ajutorul unor funcţii membre
speciale, numite destructori, în momentul încheierii existenţei obiectului respectiv.

9.3. MOŞTENIREA

Moştenirea este o caracteristică a limbajelor de programare orientate obiect, care permite refolosirea codului
şi extinderea funcţionalităţii claselor existente. Între două clase pot exista multe diferenţe, dar şi multe
asemănări. Este bine ca informaţia comună unor clase să fie specificată o singură dată (conceptul de
clasă/subclasă, superclasă/clasă în OOP). Mecanismul moştenirii permite crearea unei ierarhii de clase şi
trecerea de la clasele generale la cele particulare. Procesul implică la început definirea clasei de bază care
stabileşte calităţile comune ale tuturor obiectelor ce vor deriva din bază (ierarhic superioară)(figura 9.1.).
Prin moştenire, un obiect poate prelua proprietăţile obiectelor din clasa de bază.
Clasa A reprezintă clasa de bază (este o generalizare) şi conţine informaţiile
comune (disponibile prin moştenire şi subclaselor acesteia). A
Clasa B reprezintă clasa derivată (este o particularizare, o specializare a clasei A)
care extinde funcţionalitatea clasei de bază şi conţine informaţiile specifice.
Să presupunem că A reprezintă clasa mamiferelor (cu proprietăţile caracteristice:
nasc pui vii, au sânge cald, îşi alăptează puii, etc), iar B reprezintă clasa
animalelor domestice. În momentul definirii clasei derivate B, aceasta moşteneşte B
toate caracteristicile clasei A, rămânând de specificat doar trăsăturile distinctive.
În acest caz, A este clasă de bază, iar B clasă derivată (subclasă a clasei A). Figura 9.1. Relaţia
Sau: B este clasă, iar A este o superclasă a clasei B. clasă de bază-clasă
Moştenirea poate fi: unică sau multiplă. derivată

9.3.1. MOŞTENIREA UNICĂ

În cazul moştenirii unice, fiecare clasă are doar o superclasă. Există două modalităţi de specializare a unei
clase de bază:
 introducerea de extra-atribute şi extra-metode în clasa derivată (particulare doar clasei derivate);
 redefinirea membrilor în clase derivate (polimorfism).

9.3.2. MOŞTENIREA MULTIPLĂ

În situaţia moştenirii multiple, o clasă are mai multe superclase. Astfel, moştenirea clasei va fi multiplă
(rezultând o structură de reţea).

130
CAPITOLUL 9 Concepte de bază ale programării orientate obiect

A A

B C D B C D

E F G H E F

Figura 9.2. Moştenirea simplă (unică) Figura 9.3. Moştenirea multiplă

Moştenirea multiplă este utilă, dar poate crea ambiguităţi (când pentru acelaşi atribut se moştenesc valori
diferite). Există mai multe strategii de rezolvare a conflictului (părintele cel mai apropiat, cel mai depărtat,
etc.). Deasemenea, este posibilă o moştenire repetată, în care o clasă ajunge să moştenească de la aceeaşi
clasă, pe drumuri diferite în reţea (vezi figura 9.3., în care clasa E moşteneşte de la aceeaşi clasă A, pe
drumurile A-B-E, A-C-E) . Aşa cum vedea în capitolele următoare, în aceste situaţii, limbajul C++ oferă
programatorului două strategii: 1) clasa E poate avea două copii ale lui A, una pentru fiecare drum; 2) clasa E
are o singură copie, iar A este clasă virtuală de bază şi pentru C şi pentru B. Ideea moştenirii multiple poate
duce la utilizarea unor clase pentru care nu există instanţe, care să ajute doar la organizarea structurii
(reţelei) de moştenire. În plus, limbajul C++ permite un control puternic asupra atributelor şi metodelor care
vor fi moştenite.

9.4. ÎNCAPSULAREA (ASCUNDEREA) INFORMAŢIEI

Încapsularea (ascunderea) informaţiei reflectă faptul că atributele instanţă şi metodele unui obiect îl
definesc doar pe acesta. Vom spune că metodele şi atributele unui obiect sunt “private”, încapsulate în obiect.
Interfaţa cu obiectul relevă foarte puţin din ceea ce se petrece în interiorul lui. Obiectul deţine controlul
asupra atributelor instanţă, care nu pot fi alterate de către alte obiecte. Excepţia de la această observaţie o
reprezintă doar atributele de clasă care nu sunt încapsulate, fiind partajate între toate instanţele clasei.
Această tehnică de "plasare" a valorilor în datele membre private ale obiectului, reprezintă un mecanism de
ascundere a datelor.

În limbajul C++ încapsularea poate fi forţată prin controlul accesului, deoarece toate datele şi funcţiile
membre sunt caracterizate printr-un nivel de acces (rezultând astfel o mare flexibilitate). Nivelul de acces la
membrii unei clase poate fi (figura 9.4.): public
protected Clasa A
 private: membrii (date şi metode) la care
accesul este private pot fi accesaţi doar prin private
metodele clasei (nivel acces implicit);
 protected: aceşti membri pot fi accesaţi
prin funcţiile membre ale clasei şi funcţiile
membre ale clasei derivate;
 public: membrii la care accesul este
public pot fi accesaţi din orice punct al
domeniului de existenţă a clasei respective;
 friend: aceşti membri pot fi accesaţi prin clasă derivată Clasa B
funcţiile membre ale funcţiei prietene
specificate.
Figura 9.4. Accesul la membrii unei clase
În limbajul C++, nivelul de acces poate preciza şi tipul de moştenire (capitolul 12).
 Publică, unde în clasa derivată nivelul de acces al membrilor este acelaşi ca în clasa de bază;
 Privată, unde membrii protected şi public din clasa bază devin private în clasa derivată.

131
CAPITOLUL 9 Concepte de bază ale programării orientate obiect
9.5. LEGAREA DINAMICĂ (“TÂRZIE”)

Obiectele unei clase părinte trebuie cunoscute în momentul compilării. Efectul combinat al moştenirii poate
determina ca o anumită metodă să fie specializată diferit (prin redefinire), pentru subclase diferite.
Polimorfismul reprezintă comportamente diferite ale unei metode în raport cu tipul unui obiect. Selectarea
unei metode redefinite poate fi realizată în faza de compilare (legarea iniţială), sau în momentul execuţiei
(legare târzie). În limbajul C++, legarea dinamică se poate realiza prin implementarea de:
 funcţii virtuale (pot fi redefinite polimorfic);
 funcţii virtuale pure (doar declarate, nu definite).

9.6. ALTE ASPECTE

 Comunicarea între obiecte


În limbajele de programare orientate obiect, obiectele comunică între ele prin mesaje, ceea ce conduce la
accentuarea conceptului de încapsulare. Un obiect poate “stimula” un altul să activeze (declanşeze) o
metodă, trimiţându-i un mesaj. După primirea mesajului, metoda respectivă este apelată cu parametrii
furnizaţi, asigurând comportarea corespunzătoare a obiectelor. Metodele sunt invocate prin trimiterea de
mesaje.
În limbajul C++ funcţiile membre (metodele) sunt accesate în mod similar oricarei funcţii, cu deosebirea
că este necesară specificarea obiectului căruia îi corespunde metoda.

 Pseudovariabile
Limbajele de programare orientate obiect posedă două variabile (numite pseudo-variabile) care diferă de
variabilele normale prin faptul că nu li se pot atribui valori în mod direct, de către programator. În general,
pseudovariabilele sunt o formă scurtă pentru “obiectul curent ” şi pentru “clasa părinte a obiectului
curent”. În limbajul C++ există doar una din aceste pseudovariabile, numită ”this” (pointer către obiectul
curent).

 Metaclasele
Metaclasele reprezintă “clase de clase”. O clasă este, de fapt, o instanţă a unei metaclase. Diferenţele dintre
clase şi metaclase sunt:
 Clasa defineşte caracteristici (atribute şi metode) ale instanţelor de acel tip. Metodele pot fi folosite
doar de obiectele clasei, nu şi de însăşi clasa (restricţie).
 Metaclasele furnizează un mijloc prin care variabilele clasă pot fi implementate: în unele limbaje
OOP, variabilele clasă sunt instanţieri ale unei metaclase.

Limbajul C++ nu include explicit metaclasele, dar suportă variabilele clasă sub forma datelor statice. Aşa
cum funcţiile membre obişnuite sunt încapsulate înăuntrul fiecarei instanţe, pentru o funcţie membru statică
a unei clase, se foloseşte o singură copie, partajată de către toate instanţele clasei. O asemenea funcţie nu este
asociată unei anumite instanţe.

 Persistenţa
Persistenţa reprezintă timpul de viaţă al unui obiect (între crearea obiectului şi ştergerea sa). Instanţele unei
clase au un timp de viaţă dat de execuţia unei metode sau a unui bloc, de crearea sau ştergerea specificată
explicit în program sau de durata întregului program. Persistenţa obiectelor este importantă în special în
aplicaţiile de baze de date.

 Supraîncarcarea operatorilor.
Limbajul C++ furnizează modalităţi de supraîncarcare a operatorilor (overloading): acelaşi operator are
semnificaţii diferite, care depind de numărul şi tipul argumentelor.

132
CAPITOLUL 10 Clase şi obiecte

CLASE ŞI OBIECTE 1
10.1. Definiţia claselor şi accesul la membrii 10.2. Funcţii inline
10.1.1. Legătura clasă-structură-uniune 10.3. Constructori şi destructori
10.1.2. Declararea claselor 10.3.1. Iniţializarea datelor
10.1.3. Obiecte 10.3.2. Constructori
10.1.4. Membrii unei clase 10.3.3. Destructori
10.1.5. Pointerul this 10.3.4. Tablouri de obiecte
10.1.6. Domeniul unui nume, 10.4. Funcţii prietene (friend)
vizibilitate şi timp de viaţă

10.1. DEFINIŢIA CLASELOR ŞI ACCESUL LA MEMBRII

10.1.1. LEGĂTURA CLASĂ-STRUCTURĂ-UNIUNE

Aşa cum s-a subliniat în capitolul 9, o clasă reprezintă un tip abstract de date, care încapsulează atât
elementele de date (datele membre) pentru care s-a adoptat un anumit mod de reprezentare, cât şi operaţiile
asupra datelor (funcţiile membre, metode). În limbajul C++, structurile şi uniunile reprezintă cazuri
particulare ale claselor, putând avea nu numai date membre, câmpuri de date (vezi capitolul 8), dar şi funcţii
membre. Singura diferenţă între structuri şi uniuni constă în faptul că la uniuni, pentru memorarea valorilor
datelor membre se foloseşte aceeaşi zonă de memorie. Deosebirea esenţială între structuri şi uniuni - pe de o
parte - şi clase - pe cealată parte - constă în modul de acces la membrii: la structuri şi uniuni membrii
(datele şi metodele) sunt implicit publici, iar la clase - implicit privaţi (membrii sunt încapsulaţi). Lipsa unor
modalităţi de protecţie a datelor, face ca tipurile de date introduse prin structuri sau uniuni să nu poată fi
strict controlate în ceea ce priveşte operaţiile executate asupra lor. În cazul claselor, modul de acces la
membrii tipului de date (în scopul protejării acestora) poate fi schimbat prin utilizarea modificatorilor de
control ai accesului: public, private, protected.

10.1.2. DECLARAREA CLASELOR

Modul de declarare a unei clase este similar celui de declarare a structurilor şi a uniunilor:
class nume_tip{
modificator_control_acces:
lista_membrilor;
} lista_variabile;

Clasa reprezintă un tip de date (definit de utilizator).


Membrii unei clase sunt:
 Datele membre - datele declarate în cadrul clasei;
 Metodele - funcţiile membre, funcţiile declarate sau definite în cadrul clasei. Se admite că în cadrul
declaraţiei de clasă să se specifice doar prototipurile funcţiilor membre, definiţiile putând fi făcute
oriunde în fişier, sau în alt fişier.
Pentru membrii care apar în lista_membrilor se poate preciza un anumit mod de acces.
Modificator_control_acces poate fi public, private sau protected (eventual friend, vezi
paragraful 10.4.). Dacă nu se specifică, este considerat cel implicit ( private). Modificatorul de acces
public se utilizează pentru membrii care dorim să fie neprotejaţi, ultimii doi modificatori asigurând
protecţia membrilor din domeniul de acţiune a lor. Membrii cu acces private pot fi accesaţi numai prin

133
CAPITOLUL 10 Clase şi obiecte
metodele clasei (sau prin funcţiile prietene, capitolul 10.4.). Cei cu acces protected posedă caracteristicile
celor privaţi, în plus, putând fi accesaţi şi din clasele derivate. Specificatorii modului de acces pot apare în
declararea clasei de mai multe ori, în orice ordine.
Domeniul unui modificator de acces ţine din punctul în care apare modificatorul respectiv, până la sfârşitul
declaraţiei clasei sau al întâlnirii altui modificator de acces (exemplele 1,2).
Observaţiile legate de prezenţa nume_tip sau lista_variabile (din capitolul 8) sunt valabile şi în cazul
claselor.
Variabilele din lista_variabile sunt de tipul nume_tip şi se numesc instanţe (obiecte) ale clasei.
Observaţie: În cazul tipurilor de date definite cu ajutorul structurilor, se pot aplica modificatorii de acces. În
cazul tipurilor definite cu ajutorul uniunilor, accesul implicit (public) nu poate fi modificat.

Exemplu:
class masina{
char *culoare; // dată membru la care accesul este, implicit, private
int capacit_cil; // dată membru la care accesul este, implicit, private
public:
void citire_date();
//metodă cu acces public, care permite introducerea datelor despre o instanţă a clasei
int ret_capacit(); //metodă cu acces public
};
Membrii culoare şi capacitate_cil (accesul private) pot fi accesaţi doar prin intermediul metodelor
clasei.
Exemplu:
class persoana{
char *nume; //dată membru privată
public:
void citire_inf_pers(); //metodă publică
private:
int varsta; //dată membru privată
};

Exerciţiu: Să se definească tipul de date dreptunghi, cu ajutorul unei structuri, a unei uniuni şi a unei
clase.
Datele membre sunt lungimea şi lăţimea (variabilele Lung, lat).
Funcţiile membre sunt:
void seteaza_dimen(double, double) - primeşte ca argumente două valori reale şi
iniţializează datele membre cu valorile argumentelor.
double arata_Lung( ) - returnează valoarea lungimii (a datei membre Lung).
double arata_Lat( ) - returnează valoarea lăţimii (a datei membre lat).
double calcul_arie( ) - returnează valoarea ariei dreptunghiului.

//a) Implementarea tipului dreptunghi cu ajutorul unei structuri.


#include <iostream.h>
struct dreptunghi{
double Lung, lat;
void seteaza_dimen(double, double );//prototipul funcţiei seteaza_dimen
double arata_Lung()
{return Lung;}
double arata_Lat()
{return lat;}
double calcul_arie()
{return Lung*lat;}
};

void dreptunghi::seteaza_dimen(double L, double l)


{Lung=L; lat=l;}

134
CAPITOLUL 10 Clase şi obiecte
void main()
{ dreptunghi a;
double l1, l2; cout<<"Lungime="; cin>>l1;
cout<<"Latime="; cin>>l2; a.seteaza_dimen(l1, l2);
// sau: Lung=l1; lat=l2;
cout<<"Dimensiunile dreptunghiului sunt:"<<a.arata_Lung();
cout<<" si"<<a.arata_Lat()<<'\n';
cout<<"Aria dreptunghiului:"<<a.calcul_arie()<<'\n';
cout<<"Dimens structurii:"<<sizeof(a)<<'\n';
}
//b) Implementarea tipului dreptunghi cu ajutorul unei uniuni
#include <iostream.h>
union dreptunghi{
double Lung, lat;
void seteaza_dimen(double, double );
double arata_Lung()
{return Lung;}
double arata_Lat()
{return lat;}
double calcul_arie(double s)
{return s*lat;}
};

void dreptunghi::seteaza_dimen(double L, double l)


{Lung=L; lat=l;}

void main()
{ dreptunghi a; double l1, l2;
cout<<"Lungime="; cin>>l1; cout<<"Latime="; cin>>l2;
a.seteaza_dimen(l1, l1); cout<<"Lung. drept:"<<a.arata_Lung()<<'\n';
double s1=a.arata_Lung(); a.seteaza_dimen(l2, l2);
cout<<"Latimea dreptunghiului este:"<<a.arata_Lat()<<'\n';
cout<<"Aria dreptunghiului:"<<a.calcul_arie(s1)<<'\n';
cout<<"Dimens. uniunii:"<<sizeof(dreptunghi)<<'\n';
}

În exerciţiul 1 a, b se defineşte tipul dreptunghi printr-o structură, respectiv o uniune. Tipul conţine atât
datele membre, cât şi metodele care implementează operaţiile care pot fi realizate asupra variabilelor de tipul
dreptunghi. Metodele arata_Lung, arata_Lat, calcul_arie sunt definite în structură (uniune). Metoda
seteaza_dimen este doar declarată în interiorul structurii (uniunii), fiind abia apoi definită. În varianta b
(implementarea cu uniune, unde pentru memorarea valorilor datelor membre se utilizează aceeaşi zonă de
memorie), pentru a păstra valorile atât pentru lungimea dreptunghiului, cât şi pentru lăţime, metodele au fost
modificate.

Nespecificând nici un modificator de control al accesului, toţi membrii (date şi metode) sunt implicit publici.
De aceea, de exemplu, atribuirea unei valori pentru data membră Lung se putea realiza, la fel de bine, în
corpul funcţiei main, astfel: Lung=l1; (în exerciţiul 1a, atribuirea se realizează cu ajutorul metodei
seteaza_dimen).

//c) Implementarea tipului dreptunghi cu ajutorul unei clase


#include <iostream.h>
class dreptunghi{
double Lung, lat;
public:
void seteaza_dimen(double, double );
double arata_Lung()
{return Lung;}
double arata_Lat()
{return lat;}
double calcul_arie()

135
CAPITOLUL 10 Clase şi obiecte
{return Lung*lat;}
};
void dreptunghi::seteaza_dimen(double L, double l)
{Lung=L; lat=l;}
void main()
{ dreptunghi a;double l1, l2;
cout<<"Lungime="; cin>>l1;cout<<"Latime="; cin>>l2;
a.seteaza_dimen(l1, l2);cout<<"Dimensiunile dreptunghiului sunt:";
cout<<a.arata_Lung()<<" si"<<a.arata_Lat()<<'\n';
cout<<"Aria dreptunghiului:"<<a.calcul_arie()<<'\n';
cout<<"Dimens :"<<sizeof(a)<<'\n';
}

În exerciţiul 1c se defineşte tipul de date dreptunghi cu ajutorul unei clase. Nivelul de acees implicit la
membrii clasei este private. Dacă pentru metode nu s-ar fi folosit modificatorul de acces public, metodele
nu ar fi putut fi folosite în funcţia main.

10.1.3. OBIECTE

Un obiect este o dată de tip definit printr-o clasă. În exerciţiul anterior, punctul c, în funcţia main, se declară
obiectul (variabila) a de tip dreptunghi. Spunem că obiectul a este o instanţă a clasei dreptunghi. Se pot
declara oricâte obiecte (instanţe) ale clasei. Aşa cum se observă din exemplu, declararea obiectelor de un
anumit tip are o formă asemănătoare celei pentru datele de tip predefinit:
nume_clasa lista_obiecte;
Exemple:
dreptunghi a;
dreptunghi b, c, d;

10.1.4. MEMBRII UNEI CLASE

Datele membru se alocă distinct pentru fiecare instanţă (atribute ale instanţei) a clasei (pentru declararea
obiectelor a, b, c, d de tip dreprunghi, vezi figura 10.1.). Excepţia de la această regulă o constituie datele
membru statice, care există într-un singur exemplar, comun, pentru toate instanţele clasei.

a.Lung b.Lung Lung c.Lung


Lung Lung

lat a.lat lat lat c.lat


b.lat
a b c

Lung d.Lung

lat d.lat
d

Figura 10.1. Alocarea memoriei pentru datele membre nestatice

Metodele figurează într-un singur exemplar, oricâte instanţe ale clasei ar exista.

În exemplul anterior, metoda seteaza_dimen este doar declarată în interiorul clasei, fiind abia apoi
definită. La definirea funcţiei (void dreptunghi::seteaza_dimen(double L, double l)) s-a
folosit operatorul :: (scope resolution operator) care specifică relaţia de apartenenţă a metodei la tipul

136
CAPITOLUL 10 Clase şi obiecte
dreptunghi. Operatorul cuplează nume_clasa::nume_functie_membru şi defineşte domeniul
(scopul) în care acea funcţie va fi recunoscută. Prezenţa numelui clasei în faţa funcţiei membru este
obligatorie, deoarece, altfel nu s-ar putea face distincţia între metode cu nume identice, care aparţin unor
clase diferite.
O funcţie membru se apelează totdeauna în strânsă dependenţă cu un obiect din clasa respectivă. Legătura
dintre obiect şi funcţia membră se face prin operatorul . sau operatorul ->, după cum obiectul este
desemnat prin nume sau prin pointer (vezi exemplu). În plus, metodele statice pot fi apelate independent de
un obiect al clasei, folosind operatorul de rezoluţie (::).
Accesul la o metodă presupune o activitate de adresare, transparentă utilizatorului. De fapt, în interiorul
obiectului creat se află doar punctatori la clasa din care provin. În interiorul definiţiei clasei se alocă o
singură copie a fiecărei funcţie membră şi punctatorii respectiv, prin care obiectul va avea acces la metoda
respectivă. Excepţia de la această regulă o constituie metodele virtuale (capitolul 12).
Exemplu: Fie clasa dreptunghi din exerciţiul anterior.
class dreptunghi{
double Lung, lat;
public:
void seteaza_dimen(double, double );
double arata_Lung();
double arata_Lat();
double calcul_arie();
};
//……………………………………………………………………………..
void main()
{ dreptunghi a;
//……………………………………………………………………
cout<<"Aria dreptunghiului:"<<a.calcul_arie()<<'\n';
dreptunghi *pa;
pa=&a;
double arie=pa->calcul_arie();
}

Exerciţiu: Să urmărim exerciţiul următor, care ilustrează problemele legate de membrii statici ai unei clase
(figura 10.2.).
#include <iostream.h>
#include <conio.h>
class exemplu
{ int i; // dată membră privată, acces la ea doar prin metode
public:
static int contor; // dată membră publica, neprotejată (scop didactic)
void inc(void)
{i++;} Memoria
void arata_i() statică Stivă
{cout<<"i="<<i<<'\n';}
void inc_contor(void)
{contor++;} exemplu:: a1.i
void init(void) contor
{i=0;}
static void arata_contor() a2.i
{cout<<"Contor="<<contor<<'\n';}
static void functie(exemplu*);
} a1, a2, a3; a3.i

int exemplu::contor=0; //iniţialiazarea datei membru statice

void exemplu::functie (exemplu *pe)


{//i+=3; //eroare, nu se cunoaste obiectul care-l poseda pe i
pe->i++; //corect, este specificat proprietarul lui i Figura 10.2. Alocarea
contor++; //variabilă statică, comună tuturor obiectelor memoriei pentru datele
} membru statice şi nestatice

137
CAPITOLUL 10 Clase şi obiecte

void main()
{clrscr();
a1.init(); a2.init(); a3.init(); //a1.i=0, a2.i=0, a3.i=0
a1.arata_i();a2.arata_i();a3.arata_i(); //i=0, i=0, i=0
a1.inc(); a2.inc(); a3.inc(); //a1.i=1, a2.i=1, a3.i=1
a1.arata_i();a2.arata_i();a3.arata_i(); //i=1, i=1, i=1
a1.functie(&a1); //contor=1, i=2
exemplu::functie(&a2); //contor=2, i=2
//functie(); //incorect
a1.inc_contor(); //contor=3
exemplu::arata_contor();
a2.inc_contor(); //contor=4
exemplu::arata_contor();
a3.inc_contor(); //contor=5
exemplu::arata_contor();
exemplu::arata_contor();
exemplu::contor+=100; //membru public; contor=105
cout<<"Contor="<<exemplu::contor<<'\n'; //Contor=105
}

Din exemplul anterior, se poate observa că metoda statică funcţie poate fi apelată ca o metodă obişnuită,
sau folosind operatorul ::. Pentru data membră statică contor se rezervă o zonă de memorie comună
obiectelor a1, a2, a3. Pentru data membră i se realizează o copie pentru fiecare instanţă a clasei.
Deasemenea, deoarece data membră contor este statică, nu aparţine unui anume obiect, ea apare prefixată de
numele clasei şi operatorul de apartenenţă.

Exerciţiu: Să se urmărească următorul exerciţiu, care defineşte tipul ex_mstat, cu o dată membru statică
(s) şi metodele statice (set_s, ret_s).
#include <iostream.h>
class ex_mstat{
int a; static double s;
public:
int ret_a(){return a;}
void set_a(int x){a=x;}
static double ret_s(){return s;}
static void set_s(double x){s=x;}
void set1_s(double x){s=x;}
double ret1_s(){return s;}
};
ex_mstat::s
double ex_mstat::s;
/*se rezervă spaţiu în memoria statică pentru data membră
statică s, care figurează într-un singur exemplar pentru toate
a a
instaţele clasei ex_mstat (figura 10.3.)*/ p q

void main()
{ex_mstat p,q; Figura 10.3. Alocarea memoriei
p.set_a(100); pentru membrii clasei ex_mstat
p.set_s(200); q.set_a(300);
cout<<"p.a="<<p.ret_a()<<" p.s="<<p.ret_s();
cout<<" ex_mstat::ret_s="<<ex_mstat::ret_s()<<'\n';//p.a=100 p.s=200 ex_mstat::ret_s=200
cout<<"q.a="<<q.ret_a()<<" q.s="<<q.ret1_s();
cout<<" ex_mstat::ret_s="<<ex_mstat::ret_s()<<'\n';//q.a=300 q.s=200 ex_mstat::ret_s=200
ex_mstat::set_s(500.20);
cout<<"p.a="<<p.ret_a()<<" p.s="<<p.ret_s();
cout<<" ex_mstat::ret_s()="<<ex_mstat::ret_s()<<'\n';
//p.a=100 p.s=500.20 ex_mstat::ret_s=500.20

138
CAPITOLUL 10 Clase şi obiecte
cout<<"q.a="<<q.ret_a()<<" q.s="<<q.ret1_s();
cout<<" ex_mstat::ret_s()="<<ex_mstat::ret_s()<<'\n';
//q.a=300 q.s=500.20 ex_mstat::ret_s=500.20
q.set1_s(800.80);
cout<<"p.a="<<p.ret_a()<<" p.s="<<p.ret_s();
cout<<" ex_mstat::ret_s()="<<ex_mstat::ret_s()<<'\n';
//p.a=100 p.s=800.20 ex_mstat::ret_s=800.20
cout<<"q.a="<<q.ret_a()<<" q.s="<<q.ret1_s();
cout<<" ex_mstat::ret_s()="<<ex_mstat::ret_s()<<'\n';
//q.a=300 q.s=800.20 ex_mstat::ret_s=800.20
p.set1_s(999);
cout<<"p.a="<<p.ret_a()<<" p.s="<<p.ret_s();
cout<<" ex_mstat::ret_s()="<<ex_mstat::ret_s()<<'\n';
//p.a=100 p.s=999 ex_mstat::ret_s=999
cout<<"q.a="<<q.ret_a()<<" q.s="<<q.ret1_s();
cout<<" ex_mstat::ret_s()="<<ex_mstat::ret_s()<<'\n';
//q.a=300 q.s=999 ex_mstat::ret_s=999
}

Aşa cum se observă din exemplul anterior, data membru statică s figurează într-un singur exemplar pentru
instanţele p şi q. Ea poate fi modificată prin metoda statică set_s sau prin metoda set1_s.
Apelul unei metode statice poate fi realizat ca un apel al unei metode obişnuite: p.ret_s(), sau folosind
operatorul de rezoluţie: ex_mstat::ret_s(). Datorită ultimului mod de apel, în care metoda statică nu
este asociată unui obiect anume, în corpul funcţiilor statice, nu pot fi accesate decât datele membre
statice.

Observaţie: Nu trebuie confundaţi membrii statici ai unei clase cu datele care au clasa de memorare static.

10.1.5. POINTERUL THIS

Fiecare funcţie membră posedă un argument ascuns, numit this, argument transmis în mod automat de către
compilator. Această variabilă (locală funcţiilor membru) reprezintă pointerul către obiectul curent (cel care
apelează metoda). Să reimplementăm metoda calcul_arie, folosind acest pointer (deşi în această situaţie
utilizarea pointerului this este redundantă).
Exemplu:
class dreptunghi{
double Lung, lat;
public:
//………………………………………………..
void seteaza_dimen(double, double);
};
void dreptunghi::seteaza_dimen(double L, double l)
{//Lung=L; lat=l;
this->Lung=L; this->lat=l;}

Deoarece o metodă statică se apelează independent de un obiect al clasei, pointerul this nu mai poate fi
utilizat.

10.1.6. DOMENIUL UNUI NUME, VIZIBILITATE ŞI TIMP DE VIAŢĂ

Înainte de a citi acest paragraf, trebuie revăzut capitolul 6.8.

10.1.6.1. Domeniul unui nume


Unui nume îi corespunde un domeniu, specificat prin declaraţia variabilei. În funcţie de poziţia declaraţiei
(definirii) unui nume, domeniul poate fi:

139
CAPITOLUL 10 Clase şi obiecte
 local (dacă numele este declarat într-un bloc);
 fişier (dacă numele este declarat în afara oricărui bloc sau declaraţie (definiţie) de clasă);
 clasă.
Dacă un nume care are ca domeniu un fişier este redefinit într-un bloc inclus în domeniul său, el poate fi
folosit în acest bloc dacă este precedat de operatorul rezoluţie.
Exemplu:
#include <iostream.h>
int i=80; // i declarat în afara oricărei funcţii, domeniul numelui este fişierul
void main()
{double i=99.9; // redeclararea variabilei i , începe domeniul local
cout<<"Valoarea lui i="<<i<<'\n'; //Valoarea lui i=80
cout<<"Valoarea lui i="<<::i<<'\n'; // Valoarea lui i=99.9
}

Domeniul numelui unui tip de date definit printr-o clasă (struct sau union, deoarece structurile şi
uniunile sunt cazuri particulare de clase cu membrii publici) se stabileşte în mod similar domeniului oricărei
variabile. Numele unui membru al unei clase are un domeniu de tip clasă. Ca orice nume, un nume de clasă
poate fi redeclarat.

Exemplu:
class a{
//……………
};
//…………..
{ //instrucţiune bloc aflată în domeniul numelui a
double a=99; //redeclararea lui a
class::a x; //x este un obiect de tipul a (definit printr-o clasă)
}

10.1.6.2. Vizibilitate

Domeniul de vizibilitate a unei variabile (obiect) este determinat de clasa de memorare a variabilei
(obiectului), aşa cum prezintă tabelul 6.1. (capitolul 6). De obicei, domeniul de vizibilitate al unui nume
coincide cu domeniul numelui.

10.1.6.3. Timp de viaţă

Timpul de viaţă a unei variabile (obiect) este determinat de clasa de memorare a variabilei (obiectului), aşa
cum prezintă tabelul 6.1. (paragrafele 6.8., 6.9.). În limbajul C++, alocarea dinamică a memoriei se
realizează cu operatorii new şi delete (capitolul 11).

10.2. FUNCŢII INLINE

La apelul unei funcţii obişnuite se întrerupe execuţia funcţiei apelante şi se execută un salt la adresa de
memorie la care se găseşte corpul funcţiei apelate. La terminarea execuţiei funcţiei apelate se revine în
funcţia apelantă, reluându-se execuţia cu instrucţiunea imediat următoare apelului de funcţie. În situaţiile în
care corpul funcţiei apelate este format din câteva instrucţiuni, operaţiile descrise anterior (implicate în apel
şi revenire) pot fi mai complicate decât un apel prin expandare (în care apelul funcţiei este înlocuit cu însuşi
corpul funcţiei apelate). Pentru eliminarea acestor dezavantaje, se folosesc funcţiile inline.
Prezenţa funcţiilor inline anunţă compilatorul să nu mai genereze instrucţiunile în cod maşină necesare
apelului şi revenirii, ceea ce conduce la mărirea timpului de compilare în favoarea micşorării timpului de
execuţie. Utilizarea funcţiilor inline se justifică doar în situaţiile în care codul generat de compilator pentru
execuţia corpului funcţiei este mai mic decât codul generat pentru apel şi revenire.
Practic, funcţiile care au corpul format din maximum trei instrucţiuni şi nu conţin instrucţiuni repetitive
(for, while, do-while), pot fi declarate inline.

140
CAPITOLUL 10 Clase şi obiecte
Declararea unei funcţii inline se realizează explicit, specificând în antetul funcţiei respective cuvântul cheie
inline.
inline tip_val_ret nume_fct (lista_declar_par_formali);
În cazul metodelor unei clase, dacă acestea sunt definite în interiorul clasei, ele sunt considerate, implicit,
funcţii inline (în exerciţiul anterior, funcţiile arată_Lung, arată_Lat şi calcul_arie sunt, implicit,
funcţii inline). Există şi posibilitatea de a declara metoda la declararea clasei şi de a specifica, explicit, că
este funcţie inline la definirea funcţiei.
Exemplu: Dacă se doreşte ca metoda seteaza_dim din exerciţiul anterior să fie funcţie inline, fără a
modifica declaraţia tipului dreptunghi, se poate proceda astfel:
class dreptunghi{
// . . .
public:
// . . .
void seteaza_dimen(double, double );// declararea metodei
// . . .
};
inline void dreptunghi::seteaza_dimen(double L, double l)//funcţie inline, explicit
{Lung=L; lat=l;}
Prefixarea definiţiei funcţiei seteaza_dimen cu cuvântul cheie inline este echivalentă cu definirea
metodei în cadrul declaraţiei clasei dreptunghi.

Exerciţiu: Să se definească tipul de date complex, cu datele membru parte reală şi parte imaginară.
Operaţiile care pot fi realizate asupra datelor de acest tip, vor fi:
Citirea unei date de tip complex (citirea valorilor pentru partea reală şi cea imaginară); afişarea unei date de
tip complex; calculul modulului unui complex; calculul argumentului unui complex; incrementarea părţii
imaginare; decrementarea părţii imaginare; funcţii care returnează valoarea părţii reale şi a părţii imaginare a
unei date de tip complex; adunarea a două date de tip complex; înmulţirea a două date de tip complex.

#include <iostream.h>
#include <math.h>
#define PI 3.14159
class complex{
double real, imag;
public:
int citire();
void afisare();
double modul();
double arg();
void incrpi()
//incrementeaza partea imaginara; FUNCŢIE INLINE, implicit, fiind definită în interiorul clasei
{ imag++;}
inline void decrpi();//decrementarea partii imaginare
double retreal(); //returneaza partea reala
double retimag(); //returneaza partea imaginara
void adun_c(complex, complex);//aduna 2 numere complexe
void inm_c(complex*, complex*);//produsul a 2 numere complexe
};

inline double complex::modul()


{ return sqrt(real*real+imag*imag);}
int complex::citire()
{ cout<<"P. reala:"; if (!(cin>>real)) return 0;
cout<<"P. imag:";if (!(cin>>imag)) return 0 ;
return 1; }
void complex::afisare()
{ if (imag>=0)
cout<<real<<"+"<<imag<<"*i"<<"\n";
else cout<<real<<imag<<"*i\n";}
double complex::arg()

141
CAPITOLUL 10 Clase şi obiecte
{if (real==0 && imag==0) return 0.0;
if (imag==0) //z=p. reala
if (real>0) return 0.0;
else return PI;
if (real==0)
if (imag>0) return PI/2;
else return (3*PI)/2;
double x=atan(imag/real);
if (real<0) return PI+x;
if (imag<0) return 2*PI+x;
return x;}

inline void complex::decrpi()


{ imag--;}
double complex::retreal()
{ return real;}
double complex::retimag()
{ return imag; }
void complex::adun_c (complex x1, complex x2)
{real=x1.real+x2.real;
imag=x1.imag+x2.imag;}
void complex::inm_c(complex *x1, complex *x2)
{real=x1->real*x2->real-x1->imag*x2->imag;
imag=x1->real*x2->imag+x1->imag*x2->real;}

void main()
{complex z1;z1.citire();
cout<<"z1=";z1.afisare();
complex z2;z2.citire();cout<<"z2=";z2.afisare();
cout<<"Modulul z2="<<z2.modul()<<'\n';
cout<<"Agument z2="<<z2.arg()<<'\n';
cout<<"P. reala z2="<<z2.retreal()<<"P imag z2="<<z2.retimag()<<'\n';
z2.incrpi();cout<<"Dupa increm p imag="<<z2.retimag()<<'\n';z2.afisare();
complex z3;z3.adun_c(z1,z2);cout<<"Adunare z1+z2=";z3.afisare();
complex*pz1=&z1,*pz2=&z2;z3.inm_c(pz1,pz2);
cout<<"Inmultire z1+z2=";z3.afisare();
}

10.3. CONSTRUCTORI ŞI DESTRUCTORI

10.3.1. INIŢIALIZAREA DATELOR

La declararea datelor de tip predefinit sau definit de utilizator prin structuri, uniuni sau clase, compilatorul
alocă o zonă de memorie corespunzătoare tipului respectiv. Este indicat ca în cazul în care datele structurate
au ca membrii pointeri, să se aloce memorie în mod dinamic.

În general, datele statice sunt iniţializate automat cu valoarea 0. Celelalte categorii de date, nu sunt
iniţializate. Iniţializarea datelor simple de tip predefinit se poate realiza după declararea acestora, sau în
momentul declarării.
Exemple:
int i; i=30;//declararea variabilei i, apoi iniţializarea ei prin atribuire
char c='A'; //declararea şi iniţializarea variabilei c
Iniţializarea datelor structurate se poate realiza în momentul declarării acestora, prin listele de iniţializare.
Exemple:
//1
int a[]={20, 30, 40, 50}; //declararea şi iniţializarea vectorului a
//2
double m[2][3]={{1.1,2.2,3.3}, {11.11,22.22,33.33}};//declararea şi iniţializarea matricii m

142
CAPITOLUL 10 Clase şi obiecte
//3
struct pers{
char nume[20]; int varsta; double salariu;
}p={"Popescu", 20, 3000000};

În cazul tipurilor de date definite cu ajutorul claselor, care au date membru private, listele de iniţializare nu
pot fi utilizate. Pentru a elimina aceste neajunsuri, limbajul C++ oferă mecanismul constructorilor şi al
desctructorilor.

10.3.1. CONSTRUCTORI

Constructorii sunt metode speciale care folosesc la crearea şi iniţializarea instanţelor unei clase.
Constructorii au acelaşi nume ca şi clasa căreia îi aparţin şi sunt apelaţi de fiecare dată când se crează noi
instanţe ale clasei. Constructorii asigură iniţializarea corectă a tuturor variabilelor membre ale obiectelor unei
clase şi constituie o garanţie a faptului că iniţializarea unui obiect se realizează o singură dată. O clasă poate
avea mai mulţi constructori (exemplul 3), care diferă între ei prin numărul şi tipul parametrilor acestora.
Acest lucru este posibil deoarece limbajul C++ permite supradefinirea (overloading) funcţiilor.

Supraîncarcarea (supradefinirea) reprezintă posibilitatea de a atribui unui nume mai multe semnificaţii, care
sunt selectate în funcţie de context. Practic, se pot defini funcţii cu acelaşi nume, dar cu liste de parametri
diferite, ca număr şi/sau ca tipuri de parametri. În momentul apelului funcţiei, selectarea funcţiei adecvate se
face în urma comparării tipurilor parametrilor efectivi cu tipurile parametrilor formali. De aceea, declararea
unor funcţii cu acelaşi nume şi acelaşi set de parametri este ilegală şi este semnalată ca eroare la compilare.

La întâlnirea declaraţiei unui obiect, se apelează automat un constructor al clasei respective. La fiecare
instanţiere a clasei se alocă memorie pentru datele membre. Deci pentru fiecare obiect declarat se alocă
memorie pentru datele membre ale clasei. Excepţie de la această regulă o constituie datele membru statice.
Acestea figurează într-un singur exemplar pentru toate instanţele clasei respective. Funcţiile membru există
într-un singur exemplar pentru toate instanţele clasei. Ordinea în care sunt apelaţi constructorii corespunde
ordinii declarării obiectelor.

Proprietăţile constructorilor:
 Constructorii au acelaţi nume ca şi numele clasei căreia îi aparţin;
 Nu întorc nici o valoare (din corpul lor lipseşte intrucţiunea return; în antetul constructorilor nu se
specifică niciodată - la tipul valorii returnate - cuvântul cheie void);
 Constructorii unei clase nu pot primi ca parametri instanţe ale clasei respective, ci doar pointeri sau
referinţe la instanţele clasei respective;
 Apelul constructorului se realizează la declararea unui obiect;
 Adresa constructorilor nu este accesibilă utilizatorului;
 Constructorii nu pot fi metode virtuale (capitolul 12);
 În cazul în care o clasă nu are nici constructor declarat de către programator, compilatorul generează
un constructor implicit, fără nici un parametru, cu lista instrucţiunilor vidă. Dacă există un constructor al
programatorului, compilatorul nu mai generează constructorul implicit (exemplul 2);
 Parametrii unui constructor nu pot fi de tipul definit de clasa al cărei membru este constructorul.

Ca orice altă funcţie în limbajul C++, constructorii pot avea parametri impliciţi (vezi capitolul 6.5.), fiind
numiţi constructori impliciţi. Varianta constructorului cu parametri impliciţi poate fi adoptată în toate
cazurile în care constructorul nu necesită argumente. Dacă toţi parametrii unui constructor sunt impliciţi,
apelul constructorului are forma unei simple declaraţii (exemplul 1). Constructorii pot fi apelaţi şi în mod
explicit (exemplul 1). În cazul în care dorim să instanţiem obiecte atât iniţializate, cât şi neiniţializate se
poate folosi un constructor implicit vid, care se va apela la instanţierea obiectelor neiniţializate (exemplul 3).

Exemplul1: Pentru clasa complex s-a definit un constructor cu parametri impliciţi; din acest motiv s-a putut
face declaraţia "complex z1;" . În ultima linie a programului, pentru obiectul z4, constructorul este apelat
în mod explicit.

143
CAPITOLUL 10 Clase şi obiecte
class complex
{ double real,imag;
public:
complex(double x=0, double y=0); // Constructor implicit
};
complex::complex(double x, double y)
{real=x; imag=y; }
void main()
{complex z1; //z1.real=0, z1.imag=0
complex z2(1); //z2.real=1, z2.imag=0
complex z3(2,4); //z3.real=2, z3.imag=4
complex z4=complex(); //apel explicit al constructorului
}

La apelul explicit al constructorului: complex z4=complex();


Evaluarea expresiei complex() conduce la:
 Crearea unui obiect temporar de tip punct (obiect cu o adresă precisă, dar inaccesibil);
 Apelul constructorului pentru acest obiect temporar;
 Copierea acestui obiect temporar în z4.

Exemplul2: Pentru clasa complex există un constructor explicit, compilatorul nu mai creează unul implicit.
class complex
{ double real,imag;
public:
complex(double x,double y) // funcţie constructor inline
{ real=x; imag=y;}
};
void main()
{complex z1(2,3);
complex z; // Eroare : nu există constructor implicit
}

Exemplul3: Definirea unui constructor implicit vid, care se va apela la instanţierea obiectelor neiniţializate.
#include<iostream.h>
class data{
int zi,luna,an;
public:
data() { } // constructor implicit vid
data(int z,int l,int a) // constructor cu parametri
{ zi=z;luna=l;an=a; }
};
void main()
{data d; // apelul constructorului vid
data d1(12,11,1998); // apelul constructorului cu parametri
}

10.3.1.1. Constructori cu liste de iniţializare

În exemplele anterioare, constructorii iniţializau membrii unui obiect prin atribuiri. Există şi modalitatea de a
iniţializa membrii printr-o listă de instanţiere (iniţializare), care apare în implementarea constructorului,
între antetul şi corpul acestuia. Lista conţine operatorul :, urmat de numele fiecărui membru şi valoarea de
iniţializare, în ordinea în care membrii apar în definiţia clasei.

Exemplu: Pentru clasa complex s-a implementat un constructor de iniţializare cu listă de instanţiere
class complex {
double real,imag;
public:

144
CAPITOLUL 10 Clase şi obiecte
complex(double x, double y); //constructor
};
complex::complex(double x, double y)
:real(x),imag(y) // Listă de iniţializare a membrilor
{ return; }
void main()
{ complex z1(1,3),z2(2,3); }
// Sau:
class complex {
double real,imag;
public:
complex(double x, double y) :real(x),imag(y) { }
//constructor cu listă de iniţializare
};

10.3.1.2. Constructori de copiere

Pentru o clasă, se poate defini un contructor de copiere, care să permită copierea obiectelor. Deoarece
parametrii unui constructor nu pot fi de tipul definit de clasa al cărei membru este, constructorul de copiere
pentru clasa cls, are, de obicei, prototipul:
cls (const cls &);
Parametrul transmis prin referinţă este obiectul a cărui copiere se realizează, modificatorul de acces const
interzicând modificarea acestuia. Constructorul de copiere poate avea şi alţi parametri, care trebuie să fie
impliciţi.
Dacă programatorul nu defineşte un constructor de copiere, compilatorul generează un asemenea constructor,
implicit.

În situaţiile în care un tip de date are ca membrii pointeri, este necesară implementarea unui constructor
pentru iniţializare (este de dorit să se aloce dinamic memorie) şi a unui constructor de copiere.

10.3.2. DESTRUCTORI

Destructorii sunt metode ale claselor care acţionează în sens invers, complementar, faţă de constructori.
Constructorii sunt folosiţi pentru alocarea memoriei, iniţializarea datelor membru sau alte operaţii (cum ar fi,
incrementarea unui contor pentru instanţele clasei). Constructorul este apelat în momentul declarării
obiectelor.
Destructorul eliberează memoria alocată de constructori. Destructorul este apelat automat, la ieşirea din
blocul în care este recunoscut acel obiect.

Proprietăţile destructorilor
 Destructorul are acelaşi nume ca şi clasa a căror metodă este;
 Numele destructorului este precedat de semnul ~;
 O clasă are un singur destructor;
 Destructorul nu are parametri şi nu returnează nici o valoare (antetul nu conţine cuvântul cheie void, iar
în corpul destructorului nu apare instrucţiunea return;);
 Dacă programatorul nu a definit un destructor, compilatorul generează automat un destructor pentru
clasa respectivă;
 Destructorii se apelează la încheierea timpului de viaţă a obiectelor, în ordine inversă apelurilor
constructorilor;
 Obiectele dinamice nu se distrug automat, deoarece doar programatorul ştie când nu mai este necesar un
astfel de obiect.

145
CAPITOLUL 10 Clase şi obiecte
Exerciţiu:
Să se definească tipul punct, cu datele membre x şi y, reprezentând abscisa şi ordonata unui punct.
Operaţiile care pot fi realizate asupra obiectelor de tip punct, sunt: afişare (afişează coordonatele unui
punct), deplasare (deplasează un punct, noile coordonate ale punctului fiind obţinute prin adunarea unor
valori transmise ca parametri, la valorile anterioare ale coordonatelor), abscisa (returnează valoarea
abscisei), ordonata (returnează valoarea ordonatei). Se vor implementa, deasemenea, constructor cu
parametri impliciţi, constructor având ca parametri valorile abscisei şi a ordonatei, constructor de copiere şi
destructor.
Să se definească tipul segment, cu datele membre A şi B, de tip punct, reprezentând capetele unui segment
(originea şi vârful). Operaţiile care pot fi realizate asupra obiectelor de tip segment, sunt: afişare (afişează
coordonatele capetellor segmentului), deplasare (translatează un segment, deplasând capetele acestuia cu
valorile transmise ca parametri), origine (returnează originea segmentului), vârf (returnează vârful
segmentului). Se vor implementa, deasemenea, constructor, constructor de copiere şi destructor.
Să se testeze tipurile de date punct şi segment.

#include <iostream.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
//CLASA PUNCT
class punct
{ double x,y;
public:
punct()
{x=0;y=0;cout<<"Constr. implicit pentru punct("<<x<<","<<y<<")\n";}
//constructor initializare
punct(double,double);
punct(punct&); //constructor copiere
~punct(); //destructor
double abscisa(){return x;}
double ordonata(){return y;}
void afisare();
void deplasare(double,double);
};

//CLASA SEGMENT
class segment
{private:
punct A,B;
public:
segment(punct&,punct&); //constructor
segment(segment&); //constructor copiere
~segment(); //destructor
punct origine();
punct varf();
void afisare();
void translatie(double,double);
};

//METODELE CLASEI PUNCT


punct::punct(double valx,double valy)
{ x=valx;y=valy; cout<<"Constructor punct ("<<x<<","<<y<<")\n"; }
punct::~punct()
{cout<<"Destructor punct ("<<x<<","<<y<<")\n";}
punct::punct( punct &P)
{ x=P.x;y=P.y; cout<<"Constructor copiere pct ("<<x<<","<<y<<")\n";}
void punct::deplasare(double dx,double dy)
{x+=dx; y+=dy;}

146
CAPITOLUL 10 Clase şi obiecte
void punct::afisare()
{ cout<<"Punct ("<<x<<','<<y<<')';}

//METODELE CLASEI SEGMENT


segment::segment(punct &A1,punct &B1)
{ A=A1;B=B1;cout<<"Constructor segment[";A.afisare();B.afisare();
cout<<"]\n";}
segment::segment(segment &AB)
{ A=AB.A; B=AB.B; cout<<"Constructor copiere segment [";
A.afisare(); B.afisare();cout<<"]\n";}
punct segment::origine()
{ return A;}
punct segment::varf()
{ return B;}
void segment::afisare()
{ cout<<"[";A.afisare();cout<<','; B.afisare();cout<<"]"<<'\n'; }
segment::~segment()
{ cout<<"Destructor segment [";A.afisare(); cout<<",";B.afisare();
cout<<"]\n";}
void segment::translatie(double dx,double dy)
{ A.deplasare(dx,dy); B.deplasare(dx,dy);}

void main()
{clrscr();
punct P(7.8,-20.4),Q(-4.82,8.897),A,B;
/*Constructor punct (7.8,-20.4) (Pentru punctul P)
Constructor punct (-4.82,8.897) (Pentru punctul Q)
Constr. implicit pentru punct(0,0)
Constr. implicit pentru punct(0,0) (pentru punctele A, B)*/
punct P3, Q3;
// Constr. implicit pentru punct(0,0)Constr. implicit pentru punct(0,0) (pentru punctele P3, Q3)
segment S(P,Q);
/* Constr. implicit pentru punct(0,0)Constr. implicit pentru punct(0,0)
(pentru membrii A, B ai obiectului S, deci pentru S.A şi S.B)
Constructor segment[Punct (7.8,-20.4)Punct (-4.82,8.897)] (pentru obiectul S, de tip segment) */
segment S1(P3,Q3);
/* Constr. implicit pentru punct(0,0)
Constr. implicit pentru punct(0,0) (pentru membrii A, B ai obiectului S1, deci pentru S1.A şi S1.B)
Constructor segment[Punct (0,0)Punct (0,0)] (pentru obiectul S1, de tip segment) */
printf("Apasa un car. ptr. continuare!\n"); getch();
cout<<"Punctele:\n";
P.afisare();cout<<'\n'; Q.afisare();cout<<'\n';
P3.afisare();cout<<'\n'; Q3.afisare();cout<<'\n';
A.afisare(); cout<<'\n'; B.afisare();cout<<'\n';
cout<<"\nSegment:"; S.afisare(); cout<<'\n';
/* Punctele:
Punct (7.8,-20.4) (pentru obiectul P)
Punct (-4.82,8.897) (pentru obiectul Q)
Punct (0,0) (pentru obiectul A)
Punct (0,0) (pentru obiectul B)
Punct (0,0) (pentru obiectul P3)
Punct (0,0) (pentru obiectul Q3)
Segment:[Punct (7.8,-20.4),Punct (-4.82,8.897)] */
punct D(1,2); punct C;
// Constructor punct (1,2)Constr. implicit pentru punct(0,0) (pentru punctele D, C)
C=D; //operaţie de atribuire
C.afisare(); // Punct (1,2) (pentru punctul C)
getch();
punct CC=C; // Constructor copiere pct (1,2)
cout<<"In urma copierii:"; CC.afisare();

147
CAPITOLUL 10 Clase şi obiecte
// În urma copierii:Punct (1,2) (pentru punctul CC)
cout<<"Se deplaseaza punctul CC cu valorile 10, 20. Noile coord.=";
CC.deplasare(10, 20); CC.afisare();
// Se deplaseaza punctul CC cu valorile 10, 20. Noile coord.=Punct (11,22)
cout<<"Abscisa CC="<<CC.abscisa()<<" Ordonata CC="<<CC.ordonata()<<'\n';
//Abscisa CC=11 Ordonata CC=22
cout<<"Varf segment S="; (S.varf()).afisare();
// Varf segment S=Constructor copiere pct (-4.82,8.897) (metoda varf returneaza un punct, copiere)
// Punct (-4.82, 8.897)
//Destructor punct (-4.82,8.897)
cout<<"Origine segment S="; CC=S.origine();
/* Origine segment S=Constructor copiere pct (7.8,-20.4) (metoda origine returneaza un punct,
copiere) Destructor punct (7.8,-20.4) */
CC.afisare(); // Punct (-4.82, 8.897)
S1=S; //operatie de atribuire
S1.afisare();
// Punct (7.8,-20.4)[Punct (7.8,-20.4),Punct (-4.82,8.897)]
cout<<"Translatie S1 cu 100,1000. S1 translatat este:";
S1.translatie(100, 1000); S1.afisare();
// Translatie S1 cu 100,1000. S1 translatat este:[Punct (107.8,979.6),Punct (95.18,1008.897)]
segment S2=S1; /* Constr. implicit pentru punct(0,0) (pentru S2.A)
Constr. implicit pentru punct(0,0) (pentru S2.B)
Constructor copiere segment [Punct (107.8,979.6)Punct (95.18,1008.897)]
Destructor segment [Punct (107.8,979.6),Punct (95.18,1008.897)]*/
cout<<"Segment S2 obtinut prin copiere:";
S2.afisare(); // Segment S2 obtinut prin copiere:[Punct (107.8,979.6),Punct (95.18,1008.897)]
cout<<"Iesire din main\n"; // Iesire din main
}
/* La ieşirea din funcţia main, deci la terminarea duratei de viaţă a obiectelor, se apelează automat
destructorii, în ordinea inversă în care au fost apelaţi constructorii, astfel:

Destructor segment [Punct (107.8,979.6),Punct (95.18,1008.897)] (pentru segmentul S2)


Destructor punct (95.18,1008.897) (pentru membrii B, respectiv A, ai segmentului S2: S2.B, apoi S2.A)
Destructor punct (107.8,979.6)
Destructor punct (7.8,-20.4) (pentru CC)
Destructor punct (1,2) (pentru C)
Destructor punct (1,2) (pentru D)
Destructor segment [Punct (107.8,979.6),Punct (95.18,1008.897)] (pentru segmentul S1)
Destructor punct (95.18,1008.897) (pentru membrii B, respectiv A, ai segmentului S1: S1.B, apoi S1.A)
Destructor punct (107.8,979.6)
Destructor segment [Punct (7.8,-20.4),Punct (-4.82,8.897)] (pentru segmentul S)
Destructor punct (-4.82,8.897) (pentru membrii B, respectiv A, ai segmentului S: S.B, apoi S.A)
Destructor punct (7.8,-20.4)
Destructor punct (0,0) (pentru punctul Q3)
Destructor punct (0,0) (pentru punctul P3)
Destructor punct (0,0) (pentru punctul B)
Destructor punct (0,0) (pentru punctul A)
Destructor punct (-4.82,8.897) (pentru punctul Q)
Destructor punct (7.8,-20.4) (pentru punctul P) */

Exerciţiul evidenţiază următoarele probleme:


1. În situaţia în care o clasă C1 are ca date membre obiecte ale altei clase C2 (clasa segment are ca date
membre obiecte de tipul punct), la construirea unui obiect din C1, se apelează întâi constructorul C2
pentru membrii (de tip C2), apoi constructorul C1.
Un astfel de exemplu îl constituie declararea segmentului S: segment S(P,Q);

148
CAPITOLUL 10 Clase şi obiecte
Se apelează întâi constructorul implicit al clasei punct pentru membrii A şi B ai segmentului S (deci
pentru S.A şi S.B), apoi constructorul clasei segment (figura 10.4.). La distrugerea obiectului din clasa
C1, destructorii sunt apelaţi în ordinea inversă constructorilor (întâi se apelează destructorul clasei
segment - învelişul exterior, apoi destructorul pentru membrii de tip punct).
2. Să revedem secvenţa:
punct D(1,2); punct C; C=D;
În acest caz, se realizează o atribuire, membru cu membru, echivalentă cu C.x=D.x şi C.y=D.y.
3. Să revedem secvenţa:
punct CC=C; A.x A.y
În acest caz, se apelează constructorul de copiere, care crează
punctul CC prin copierea punctului C. Apelul constructorului de 7.8 -20.4
A
copiere se poate realiza şi explicit:
punct CC=punct(C); S B.x B.y
4. Parametrii transmişi unei funcţii pot fi obiecte, pointeri către
obiecte sau referinţe către obiecte. Valoarea returnată de o funcţie B -4.82 8.897
poate fi un obiect, pointer către obiect sau referinţă către obiect.
Să luăm ca exemplu constructorul clasei segment:
Figura 10.4. Apelul constructorilor
segment::segment(punct &A1,punct &B1)
{ A=A1;B=B1;cout<<"Constructor segment\n";}
Constructorul primeşte ca parametri referinţe către obiecte de tipul punct. Apelul constructorului:
segment S(P, Q);
Parametrii efectivi P şi Q sunt referinţe pentru A1 şi B1 (aceleaşi obiecte). Ca urmare, se apelează cei doi
constructori impliciţi pentru membrii A şi B ai segmentului S. În urma operaţiei de atribuire din corpul
constructorului segmentului, ei sunt iniţializaţi. Mesajele:
"Constructor pct implicit!!" (pentru membrul A al segmentului S)
"Constructor pct implicit!!" (pentru membrul B al segmentului S)
"Constructor segment" (pentru segmentului S)

Constructorului puteau să i se transmită parametri prin pointeri:


segment::segment(punct *A1,punct *B1)
{ A=*A1;B=*B1;cout<<"Constructor segment\n";}
Apelul: segment S(&P, &Q);
Parametrii formali A1 şi B1 sunt iniţializaţi în momentul apelului constructorului cu adresele punctelor P,
respectiv Q. Situaţia este similară celei anterioare, mesajele obţinute sunt identice celor obţinute în cazul
transmiterii parametrilor prin referinţă.

Constructorului puteau să i se transmită parametri prin valoare:


segment::segment(punct A1,punct B1)
{ A=A1;B=B1;cout<<"Constructor segment\n";}
Apelul: segment S(P, Q);
În această situaţie, la apel, pentru parametrii formali A1 şi B1 se rezervă memorie pe stivă: obiectele locale
constructorului, A1 şi B1, sunt iniţializate prin copiere (la transmiterea parametrilor prin valoare, se
realizează o copiere a parametrilor efectivi în parametrii formali, vezi capitolul 6.3.1.). La terminarea
execuţiei corpului funcţiei, punctele A1 şi B1 sunt distruse. De aceea, mesajele din această situaţie, sunt:
"Constructor copiere punct!!" (pentru A1, local constructorului)
"Constructor copiere punct!!" (pentru B1, local constructorului)
"Constructor pct. implicit!!" (pentru membrul A al segmentului)
"Constructor pct. implicit!!" (pentru membrul B al segmentului)
"Constructor segment!" (pentru segmentul S)
"Destructor punct!" (pentru B1, la ieşirea din constructor)
"Destructor punct!" (pentru A1, la ieşirea din constructor)

Exerciţiu: Pentru tipurile punct şi segment implementate anterior, să se scrie şi să se testeze următorul
program, în care obiectele A, B (de tip punct) şi AB (de tip segment) sunt globale (declarate în afara oricărei
funcţii). Se folosesc, deasemenea, variabile locale statice. Pentru variabilele globale (A, B, AB) şi cele locale
declarate explicit statice (P1 din test1, U şi V din blocul interior funcţiei main), se alocă memorie statică.

149
CAPITOLUL 10 Clase şi obiecte
Pentru variabilele locale se alocă memorie automatic, pe stivă. Să se urmărească evidenţieze crearea şi
distrugerea obiectelor statice şi automatici, domeniul de vizibilitate şi timpul de viaţă.

class punct{ //. . . . .


};

class segment
{ //. . . . .
};
//Implementarea metodelor clasei punct
//Implementarea metodelor clasei segment

punct test1()
{ cout<<"Intrare in test1\n";
static punct P1(20,10);
P1.deplasare(1,1); cout<<"Iesire din test1\n";return P1;}
punct test2()
{ punct P1(100,100);P1.deplasare(10,20);return P1; }

punct A(1,2), B;
segment AB(A, B);
void main()
{cout<<"S-a intrat in main!\n"; punct E(1, 1), F, G;
F=test1(); cout<<"F="; F.afisare();
getch();G=test2();cout<<"Intrare in test1\n";cout<<"G="; G.afisare();
{
cout<<"Intrare in blocul interior\n";
static punct U(5,2);punct C(9,9), D(5.5,6.6);static punct V(8,3);getch();
F=test1(); cout<<"Intrare in test1\n";F.afisare();G=test2();
cout<<"Intrare in test2\n";G.afisare();
cout<<"Iesire din blocul interior\n";
}
getch();A.afisare();F.afisare();AB.afisare();AB.translatie(10, 10);
cout<<"Segment translatat:"; AB.afisare();
cout<<"Segmentul AB are originea:"; (AB.origine()).afisare();
cout<<"Segmentul AB are varful:"; (AB.varf()).afisare();
cout<<"Iesire din main()\n";
}

10.3.4. TABLOURI DE OBIECTE

Obiectele de acelaşi tip pot fi grupate în tablouri. Dacă un tablou este declarat fără a fi iniţializat, pentru
construirea elementelor sale se apela constructorul implicit. Elementele unui tablou pot fi iniţializate şi cu
ajutorul constructorilor cu parametri.
Exemplu: Fie clasele punct şi segment din exerciţiul anterior.
class punct{ //………
};
class segment{ //………
};
//implementarea metodelor claselor punct si segment
void main()
{punct P(7, -7), Q(-8, 8);
punct PC=P, QC=Q;
punct V[3]; //apelul constructorului implicit pentru fiecare din cele 3 elemente ale vectorului V
punct V1[2]={P, Q}; //apelul constructorului de copiere pentru elementele V1[0] si V1[1]
punct V2[2]={punct(10,10), punct(100,100)};
//apelul constructorului cu parametri pentru fiecare din cele 2 elemente, V2[0] si V2[1]
segment SV[2];
//EROARE: deoarece exista un constructor cu parametri, nu se genereaza automat constructorul implicit

150
CAPITOLUL 10 Clase şi obiecte
segment SV[2]={segment(PC, P), segment(QC, Q)};
segment SV[2]={segment(punct(5,5), punct(55,55)), segment (punct(10,10),
punct(100,100))};
}

10.4. FUNCŢII PRIETENE

Funcţiile prietene (friend) sunt funcţii ne-membre ale unei clase, care au acces la datele membre private
ale unei clase. Funcţiile prietene ale unei clase trebuie precizate în definiţia clasei. În acest sens, prototipurile
unor astfel de funcţii sunt precedate de cuvântul cheie friend. Spre deosebire de funcţiile membre,
funcţiile prietene ale unei clase nu posedă pointerul implicit this. De aceea, deosebirea esenţială două
funcţii care realizează aceleaşi prelucrări, o funcţie membră şi o funcţie prietenă, constă în faptul că funcţia
prietenă are un parametru în plus faţă de funcţia membru.
O funcţie poate fi în acelaşi timp funcţie membră a unei clase şi funcţie prietenă a altei clase. În exemplul
următor, f1 este funcţie membră a clasei cls1 şi funcţie prietenă a clasei cls2.
Exemplu:
class cls1{ // . . .
int f1(int, char); // f1 - metodă a clasei cls1
// . . .
};
class cls2{ //. . .
friend int cls1::f1(int, char); // f1 - prietenă a clasei cls2
//. . .
};

În cazul în care se doreşte ca toate funcţiile membre ale unei clase să aibă acces la membrii privaţi ai altei
clase (să fie funcţii prietene), prima clasă poate fi declarată clasa prietenă pentru cea de-a doua clasă. În
exemplul următor, clasa cls1 este clasa prietenă a clasei cls2.
Exemplu:
class cls1;
class cls2{ //. . .
friend cls1; // clasa cls1 este clasă prietenă a clasei cls2
//. . .
};

Relaţia de clasa prietenă nu este tranzitivă. Astfel, dacă clasa cls1 este clasa prietenă a clasei cls2, iar clasa
cls2 este clasă prietenă a clasei cls3, aceasta nu implică faptul că cls1 este clasă prietenă pentru cls3.

ÎNTREBĂRI ŞI EXERCIŢII

Chestiuni teoretice

1. Când acţionează constructorul unei clase? 9. Ce funcţii au acces la membrii privaţi ai unei
2. Când acţionează destructorul unei clase? clase?
3. Când este absolut necesară definirea unui 10. Ce observaţie aveţi în legătură cu metodele
constructor de copiere? definite în interiorul clasei şi funcţiile inline?
4. Când se justifică utilizarea funcţiilor inline? 11. Ce operator permite referirea unui membru al
5. Caracteristicile destructorului unei clase. structurii ?
6. Care este utilitatea moştenirii? 12. Ce sunt clasele?
7. Care sunt deosebirile între o funcţie membră a 13. Ce sunt constructorii impliciţi?
unei clase şi o functie prietenă a unei clase? 14. Ce sunt destructorii ?
8. Ce fel de metode pot acţiona asupra datelor 15. Ce sunt funcţiile inline?
membre statice ale unei clase? 16. Ce este o metodă?

151
CAPITOLUL 10 Clase şi obiecte
17. Constructorii unei clase pot primi ca 27. Niveluri de acces la membrii şi metodele unei
parametri instanţe ale clasei respective? Dacă clase.
da, în ce condiţii? 28. O clasă poate avea mai mulţi desctructori?
18. Ce sunt funcţiile prietene? Dacă da, în ce condiţii?
19. Cine impune comportamentul unui obiect? 29. O clasă poate fi prietenă a altei clase ? Dacă
20. Cum se alocă memoria pentru datele membre da, ce înseamnă acest lucru?
nestatice în momentul declarării mai multor 30. Operatorul :: şi rolul său.
obiecte din aceeasi clasă? 31. Prin ce se caracterizează datele membre
21. Cum se declară funcţiile prietene? statice?
22. Deosebiri între stucturi şi clase. 32. Prin ce se realizează comunicarea între
23. Enumerati facilitatile oferite de programarea obiectele unei clase?
orientata obiect. 33. Prototipul constructorului de copiere.
24. Explicaţi conceptul de încapsulare a datelor. 34. Nici funcţiile prietene, nici metodele statice
25. Explicaţi în câteva cuvinte ce este mostenirea ale unei clase nu primesc ca argument implicit
multiplă. pointerul this. Explicaţi care sunt, totuşi,
26. Explicaţi în câteva cuvinte ce este moştenirea. diferenţele dintre ele.

Chestiuni practice

1. Completaţi tipul de date complex, cu funcţiile (membre sau prietene) care vor realiza următoarele
operaţii: Adunarea unei date de tip complex cu o dată de tip real; scăderea a două date de tip complex;
scăderea unui real dintr-un complex; împărţirea a două date de tip complex; înmulţirea unui real cu un
complex; ridicarea la o putere întreagă a unei date de tip complex; compararea a două date de tip
complex.
2. Să se scrie un program care citeşte câte două date de tip complex, până la întâlnirea perechii de date
(z1=0+i*0, z2=0+i*0). Pentru fiecare pereche de date, să se afişeze suma, diferenţa, produsul şi câtul.
3. Să se scrie un program care citeşte datele a, b, c de tip complex, rezolvă şi afişează rădăcinile ecuaţiei de
gradul doi: ax 2 +bx+c=0.
4. Care sunt greşelile din următoarele secvenţe?
a) class ex1{
char *nume; int lungime;
void init (char *s, int l)
{strcpy(nume, s); lungime=l;}
};
ex1 A; A.init("teava", 20);
b) union numar{
private:
char exponent, mantisa[3];
public:
char exp();
};
c) class complex{
int real, imag;
complex (int x, int y)
{real=x; imag=y;}
};
void main()
{ complex Z(20, 30); }

152
CAPITOLUL 11 Supraîncărcarea operatorilor

SUPRAÎNCĂRCAREA OPERATORILOR 1
11.1. Moduri de supraîncărcare a operatorilor 11.6. Supraîncărcarea operatorului de
11.1.1. Supraîncărcarea prin funcţii membre atribuire =
11.1.2. Supraîncărcarea prin funcţii prietene 11.7. Supraîncărcarea operatorului de
11.2. Restricţii la supraîncărcarea operatorilor indexare [ ]
11.3. Supraîncărcarea operatorilor unari 11.8. Supraîncărcarea operatorilor new şi
11.4. Membrii constanţi ai unei clase delete
11.5. Supraîncărcarea operatorilor 11.9. Supraîncărcarea operatorului ( )
insertor şi extractor 11.10. Supraîncărcarea operatorului ->
11.11. Conversii

11.1. MODURI DE SUPRAÎNCĂRCARE A OPERATORILOR

Supraîncărcarea (supradefinirea, termenul overloading) operatorilor permite atribuirea de noi semnificaţii


operatorilor uzuali (operatorilor intâlniţi pentru tipurile de date predefinite). Aşa cum am subliniat în
numeroase rânduri, clasa reprezintă un tip de date (o mulţime de valori pentru care s-a adoptat un anumit
mod de reprezentare şi o mulţime de operaţii care pot fi aplicate acestora). Astfel, operatorul + foloseşte la
adunarea a două date de tip int, float sau double, însă aceluiaşi operator i se poate atribui semnificaţia de
"alipire" a două obiecte de tipul şir, sau de adunare a două obiecte de tipul complex, vector sau
matrice.
Observaţie: Operatorii sunt deja supradefiniţi pentru a putea opera asupra mai multor tipuri de bază (de
exemplu, operatorul + admite operanzi de tip int, dar şi float sau double), sau pot avea seminificaţii diferite
(de exemplu, operatorul * poate fi folosit pentru înmulţirea a doi operanzi numerici sau ca operator de
deferenţiere, operatorul >> poate avea semnificaţia de operator extractor sau operator de deplasare pe bit).

Prin supraîncărcarea operatorilor, operaţiile care pot fi executate asupra instanţelor (obiectelor) unei clase pot
fi folosite ca şi în cazul tipurilor de date predefinite.

Exemplu: Pentru clasa punct (vezi capitolul 10), putem atribui operatorului + semnificaţia: expresia a+b (a,
b sunt obiecte din clasa punct) reprezintă "suma" a două puncte şi este un punct ale cărui coordonate sunt
date de suma coordonatelor punctelor a şi b. Astfel, supradefinirea operatorului + constă în definrea unei
funcţii cu numele: operator +
tip_val_întoarsă operator op (lista_declar_parametri)
{
// . . . . corpul funcţiei
}
Deci, limbajul C++ permite supradefinirea operatorului op prin definirea unei funcţii numite
operator op
Funcţia trebuie să poată accesa datele membre private ale clasei, deci supradefinirea operatorilor se poate
realiza în două moduri:
 printr-o funcţie membră a clasei;
 printr-o funcţie prietenă a clasei.

11.1.1. SUPRAÎNCĂRCAREA OPERATORILOR PRIN FUNCŢII MEMBRE

În situaţia în care supraîncărcarea operatorului + se realizează printr-o funcţie membră, aceasta primeşte ca
parametru implicit adresa obiectului curent (pentru care este apelată). Deci primul operand al operatorului
este transmis implicit.

153
CAPITOLUL 11 Supraîncărcarea operatorilor
Exemplu:
class punct{
double x, y;
public:
// . . . . . .
punct operator + (punct);
};
//Metodele clasei punct……………………

punct punct::operator + (punct a)


{ punct p;
p.x=x + a.x; //echivalent cu p.x=this->x+a.x;
p.y=y + a.y; //echivalent cu p.y=this->y+a.y;
return p;
}
void main()
{ punct A(1.1, 2.2); A.afişare();
punct B(-5.5, -6.6); B.afişare();
punct C;
C=A+B; C.afişare();
C=A+B+C; C.afişare();
}

Expresia C=A+B este interpretată ca C = A.operator + (B).


Expresia C=A+B+C poate fi interpretată, în funcţie de compilator, astfel:
Unele compilatoare crează un obiect temporar T: T = A.operator + (B)
C = T.operator + (C)
Alte compilatoare interpretează expresia ca: C=(A.operator + (B)).operator + (C).

11.1.2. SUPRAÎNCĂRCAREA OPERATORILOR PRIN FUNCŢII PRIETENE

Fie clasa punct definită anterior. Reamintind faptul că funcţiile prietene au acces la membrii privaţi ai unei
clase, însă nu primesc ca argument implicit pointerul către obiectul curent (this), să supraîncărcăm
operatorul + printr-o funcţie prietenă a clasei punct:
class punct{
double x, y;
public:
// . . . . . .
friend punct operator + (punct, punct);
};
//Metodele clasei punct…………………….
punct operator + (punct a, punct b)
{ punct p;
p.x=a.x + b.x; p.y=a.y + b.y;
return p;
}
void main()
{ punct A(1.1, 2.2); A.afişare();
punct B(-5.5, -6.6); B.afişare();
punct C;
C=A+B; C.afişare();
C=A+B+C; C.afişare();
}

Expresia C=A+B este interpretată de compilator ca C=operator + (A, B).


Expresia C=A+B+C este evaluată ţiinându-se cont de regulile de prioritate şi de asociativitate a operatorului:
(A+B)+C , ceea ce conduce la un apel de forma: operator + (operator + (A, B), C).

154
CAPITOLUL 11 Supraîncărcarea operatorilor
Observaţie: În exemplul anterior, transmiterea parametrilor către funcţia prietenă de supraîncărcare a
operatorului + se realizează prin valoare. Parametrii pot fi transmişi şi prin referinţă, pentru a evita crearea
(în momentul apelului funcţiei) unor copii locale ale parametrilor efectivi în cei formali. La transmiterea
parametrilor prin referinţă, funcţia operator + are prototipul:
punct operator + (punct &, punct &);
Pentru a proteja argumentele transmise prin referinţă la eventualele modificări, se poate folosi modificatorul
de acces const: punct operator + (const punct &, const punct &);

11.2. RESTRICŢII LA SUPRAÎNCĂRCAREA OPERATORILOR

Supraîncărcarea operatorilor se poate realiza, deci, prin funcţii membre sau funcţii prietene. Dacă
supraîncărcăm acelaşi operator printr-o metodă şi printr-o funcţie prietenă, funcţia prietenă va avea,
întotdeauna, un parametru în plus faţă de metodă (deoarece funcţiei prietene nu i se transmite ca parametru
implicit pointerul this).

Totuşi, supraîncărcarea operatorilor este supusă următoarelor restricţii:


 Se pot supraîncărca doar operatorii existenţi; nu se pot crea noi operatori.
 Nu se poate modifica aritatea (numărul de operanzi) operatorilor limbajului (operatorii unari nu pot fi
supraincărcaţi ca operatori binari, şi invers).
 Nu se poate modifica precedenţa şi asociativitatea operatorilor.
 Deşi operatorii supraîncărcaţi păstrează aritatea şi precedenţa operatorilor predefiniţi, ei nu moştenesc şi
comutativitatea acestora.
 Nu pot fi supraîncărcaţi operatorii . ::? şi :

Observaţii:
 În tabelul 2.8. (capitolul 2) sunt prezentaţi operatorii existenţi, precedenţa şi asociativitatea acestora.
 Dacă operatorul = nu este supraîncărcat, el are o semnificaţie implicită.
 Operatorii , new delete [ ] -> şi cast impun restricţii suplimentare care vor fi discutate ulterior.
 Funcţia operator trebuie să aibă cel puţin un argument (implicit sau explicit) de tipul clasei pentru care s-
a supraîncărcat operatorul. Astfel:
 La supraîncărcarea unui operator unar printr-o funcţie membră a clasei, aceasta are un argument
implicit de tipul clasei (obiectul care îl apelează) şi nici un argument explicit. La supraîncărcarea
operatorului unar printr-o funcţie prietenă, aceasta are un argument explicit de tipul clasei.
 La supraîncărcarea unui operator binar printr-o funcţie membră a clasei, aceasta are un argument
implicit de tipul clasei (obiectul care îl apelează) şi un argument explicit. La supraîncărcarea
operatorului binar printr-o funcţie prietenă, aceasta are două argumente explicite de tipul clasei.
 Se poate atribui unui operator orice semnificaţie, însă este de dorit ca noua semnificaţie să fie cât mai
apropiată de semnificaţia naturală. De exemplu, pentru adunarea a două obiecte se poate supraîncărca
operatorul * , dar este mai naturală folosirea operatorului + cu semnificaţia de adunare.
 În cazul supradefinirii operatorilor, nu se poate conta pe comutativitatea acestora.
De exemplu, dacă se supraîncarcă operatorul + pentru clasa complex printr-o funcţie prietenă a clasei
complex: complex operator + (complex, double)
Operatorul poate fi folosit în expresii cum ar fi: a+7.8 (a este obiect al clasei complex), dar nu în
expresii ca: 7.8 + a.
 Dacă un operator trebuie să primească ca prim parametru un tip predefinit, acesta nu poate fi supradefinit
printr-o funcţie membră.
 Operatorii care prezintă şi alte particularităţi, vor fi trataţi separat (vezi 11.7.,11.8., 11.10., 11.11.).
 În principiu, metodele care supraîncarcă un operator nu sunt statice. Excepţia o constituie operatorii new
şi delete (vezi 11.8.).
 Diferenţa între forma prefixată şi postfixată, la supraîncărcarea operatorilor predefiniţi ++ şi --, se poate
face doar de anumite compilatoare (de exemplu, compilatorul de BorlandC, versiune>3.0, se poate face
diferenţa).

155
CAPITOLUL 11 Supraîncărcarea operatorilor
11.3. SUPRAÎNCĂRCAREA OPERATORILOR UNARI

Operatorii unari pot fi supradefiniţi printr-o funcţie membră nestatică (fără parametri expliciţi) sau printr-o
funcţie prietenă cu un parametru explicit de tipul clasă.
Ca exemplu, să supraîncarcăm operatorul unar ++ pentru clasa punct, pentru a putea fi folosit atât în formă
prefixată, cât şi postfixată (doar pentru compilatoarele care permit acest lucru!!). Vom folosi clasa punct
implementată anterior, cu modificarea ca datele membre sunt de tipul int.
class punct{
int x, y;
public: // . . .
punct & operator ++ (int ); //forma postfixată
punct & punct::operator++(); //forma prefixată
};

punct punct::operator ++ (int)


{punct p=*this;x++; y++;return p;}

punct & punct::operator++()


{x++;y++; return *this;}

void main()
{ punct A(11, 10); punct C=A++;A.afişare( ); C.afişare( );
punct C=++A;A.afişare( ); C.afişare( );
}

11.4. MEMBRII CONSTANŢI AI UNEI CLASE

Aşa cum s-a subliniat în capitolul 10, o clasă poate avea membrii statici: date membru statice (figurează într-
un singur exemplar pentru toate instanţele clasei) sau metode statice (nu li se transmite pointerul this şi pot
modifica doar date membru statice). Deasemenea, o clasă poate avea metode constante. O metodă este
declarată constantă prin utilizarea modificatorului const în antetul ei (vezi exemplul de la pagina 150),
după lista parametrilor formali. Metodele constante nu modifică obiectul pentru care sunt apelate.

Ca oricăror variabile de tip predefinit, şi obiectelor de tip definit de utilizator li se poate aplica modificatorul
const. Pentru un obiect constant este permis doar apelul metodelor constante, a constructorilor şi a
destructorilor.

11.5. SUPRAÎNCĂRCAREA OPERATORILOR INSERTOR ŞI EXTRACTOR

Operatorul << se numeşte operator insertor, deoarece inserează date în stream-ul (fluxul) de ieşire.
Operatorul >> se numeşte operator extractor, deoarece extrage date din stream-ul (fluxul) de intrare.
În exemplul următor, aceşti operatori sunt supraîncărcaţi pentru clasa complex, astfel încât să poată fi folosiţi
ca pentru obiectele de tip predefinit.
Exemplu:
complex z1, z2;
cin>>z1>>z2; //extrage valorile lui z1 şi z2
cout<<"z1="<<z1<<'\n'; //inserează şir constant, apoi valoarea lui z1
cout<<"z2="<<z2<<'\n'; //inserează şir constant, apoi valoarea lui z2
Deoarece întotdeauna operandul stâng este de tip istream (cin este obiect predefinit, de tip istream) sau
ostream (cout este obiect predefinit, de tip ostream), şi nu de tipul introdus prin clasă, operatorii << şi >>
pot fi supraîncărcaţi numai prin funcţii prietene. Prototipurile operatorilor sunt:
friend ostream &operator << (ostream &,const complex&);//operator afişare complex
friend istream & operator >> (istream &,complex&); //operator citire complex

156
CAPITOLUL 11 Supraîncărcarea operatorilor

Definiţiile funcţiilor operator:


ostream &operator<<(ostream &ecran, const complex &z)
{ecran<<"("<<z.re;
if (z.im>=0) ecran<<'+';ecran<<z.im<<"*i)"; return ecran;}
istream &operator>>(istream &tastatura, complex &z)
{tastatura>>z.re>>z.im;return tastatura;}

Prototipurile funcţiilor operator << şi >> pentru un tip abstract tip, sunt:
friend ostream &operator<<(ostream &,const tip&);
friend istream &operator >> (istream &,tip&);

11.6. SUPRAÎNCĂRCAREA OPERATORULUI DE ATRIBUIRE =

În cazul în care operatorul de atribuire nu este supraîncărcat explicit, compilatorul generează unul implicit
(ca în exemplul clasei punct sau segment). În absenţa unei supraîncărcări explicite, operatorul copie valorile
datelor membre ale operandului drept în datele membre ale operandului stâng.
Exemplu:
punct a (8,9), b;
b=a; /* operator atribuire implicit: zona de memorie ocupat de a se copie, bit cu bit, în zona de
memorie ocupată de b: b.x=a.x si b.y=a.y */
Operatorul de atribuire implicit este nesatisfăcător în situaţiile în care obiectele clasei au ca date membre
pointeri, sau în situaţiile în care memoria este alocată în mod dinamic.
O supraîncărcare explicită a operatorului pentru clasa complex (ambii operanţi de tip complex) poate fi
făcută fie prin metodă, fie prin funcţie prietenă.
temp.re temp.im
temp
class complex
{ double re,im;
public: a.re a.im
a
complex operator = (complex );
};
complex complex::operator = (complex z) b.re b.im
b
{ re=z.re; im=z.im; return *this;
//this este pointer către obiectul curent, a în main z.re z.im
} z
void main()
{complex a, b; (Obiectul z este local funcţiei operator=)
a = b; //a.operator=(b); (figura 11.1.) Figura 11.1. Supraîncărcarea operatorului =
} prin metodă a clasei complex

Deoarece funcţia operator= returnează valoare de tip complex, se construieşte un obiect temporar temp, a
cărui valoare se atribuie lui a.

O altă modalitate, mai eficientă, de a supraîncărca operatorul de atribuire prin metodă a clasei complex, este
aceea prin care funcţia primeşte ca parametru referinţă către operandul drept (se lucrează, astfel, chiar cu
obiectul b, deoarece z şi b sunt variabile referinţă; în plus, modificatorul const interzice modificarea
operandului transmis ca parametru referinţă; în plus, nu se mai crează obiectul local z, se ia ca referinţă
obiectul existent) şi returnează o referinţă (adresa obiectului a), aşa cum prezintă figura 11.2.

complex &complex::operator = (const complex &z)


{ re=z.re; im=z.im; return *this;} a.re a.im
void main() a
{complex a, b;
a = b; //a.operator=(b); (figura 11.2.) b,z
b.re b.im
}
Figura 11.2. Supraîncărcarea operatorului =
prin metodă a clasei complex

157
CAPITOLUL 11 Supraîncărcarea operatorilor

Deasemenea, operatorul binar de atribuire poate fi supraîncărcat prin funcţie prietenă (în acest caz, nu
primeşte parametrul implicit this, deci are doi operanzi).
Paramertrii z1, z2 sunt transmişi prin referinţă, deci se lucrează chiar cu obiectele a, b. Funcţia returnează
adresa obiectului a. Modificatorul const interzice modificarea operandului drept.
class complex
{ double re,im;
public:
friend complex&operator=(complex&,const complex&); //funcţie prietenă constantă
};
complex & operator = (complex &z1, complex &z2) a.re a.im
a,z1
{ z1.re=z2.re; z1.im=z2.im; return z1;}
void main()
{complex a, b; b.re b.im
b,z2
a = b; //a.operator=(b); (figura 11.3.)
} Figura 11.3. Supraîncărcarea operatorului =
prin funcţie prietenă a clasei complex

Deoarece întotdeauna operandul stâng al operatorului de atribuire este de tipul clasei pentru care se
supraîncarcă, este preferabil ca supraîncărcarea să se realizeze prin metodă a clasei. Reamintim că
asociativitatea operatorului este de la dreapta la stânga. Operatorul poate apare în expresii de forma:
a=b=c=d;

//FISIERUL complex.h
#define PI 3.14159265358979
#include <iostream.h>
class complex
{ double re,im;
public:
complex (double r=0,double i=0); //constructor
complex (const complex&); //constructor copiere
~complex(){cout<<"Destructor complex("<<re<<","<<im<<")\n";} //destructor
double modul(); //metoda care returneaza modulul unui complex
double arg(); //metoda care returneaza argumentul unui complex
void ipi(); // metoda care incrementeaza partea imag.
void dpi(); //met. de decrem. a partii imag
//operator + binar
friend complex operator+(const complex&,const complex&);//complex+complex
friend complex operator+(const double,const complex &); //real+complex
friend complex operator +(const int,const complex &); //int+complex
friend complex operator +(const complex&,const double); // complex+double
complex operator - (const complex &) const; //operator - binar: complex-complex
//operator inmultire binar: complex*complex
friend complex operator * (const complex &,const complex &);
complex operator *(const complex ) const;
/*TEMA:
friend complex operator / (const complex &,const complex &);
complex operator / (const complex &); */
complex & operator + () const; //operator + unar; metodă constantă
complex operator - () const; //operator - unar
complex &operator=(const complex &);
complex & operator += (const complex &z);
complex operator += (const double);
complex operator -= (const complex&);
complex & operator /= (const complex &z);
/* TEMA
complex operator *= (const complex&);

158
CAPITOLUL 11 Supraîncărcarea operatorilor
complex operator /= (const complex&);*/
complex & operator ++ ();
complex operator ++ (int); //forma postfixată
complex operator--(); //decrementarea părţii reale a obiectului complex curent
complex operator ! (); //calcul. rădăcinii pătrate a obiectului complex curent
int operator == (complex &z); //compară doi complecşi şi returnează 1 în caz de egalit.
friend int operator == (complex &, complex &); //return. 1 daca 2 compl egali
int operator != (complex &);
friend int operator != (complex &, complex &);
friend ostream &operator<<(ostream &,const complex&); //operator afisare complex
friend istream & operator >> (istream &,complex&); //operator citire complex
};

// FISIERUL complex.cpp
#include "complex.h"
#include <stdlib.h>
#include <math.h>
inline complex::complex(double r,double i)
{re=r;im=i;cout<<"Constructor implicit ptr complex("<<re<<","<<im<<")\n";}
complex::complex(const complex & z)
{re=z.re;im=z.im;cout<<"Constructor copiere ptr complex(";
cout<<re<<","<<im<<")\n";}
inline double complex::modul()
{ return sqrt(re*re+im*im); }
double complex::arg()
{ double a;
if (re==0 && im==0) return (double)0;
if (im==0)
if (re>0) return 0.0;
else return PI;
if (re==0)
if (im==0) return PI/2;
else return (3*PI)/2;
a=atan(im/re);
if (re<0) return PI+a;
if (im<0) return 2*PI+a;
return a;
}
inline void complex::ipi()
{ im++; }
inline void complex::dpi()
{ im--; }
complex operator +(const complex &a, const complex &b)
{complex z; z.re=a.re+b.re; z.im=a.im+b.im; return z; }
complex operator +(const double d, const complex &a)
{complex z;z.re=d+a.re;z.im=a.im;return z;}
complex operator +(const int d, const complex &a)
{complex z;z.re=d+a.re;z.im=a.im;return z;}
complex operator +(const complex &a, const double d)
{complex z;z.re=d+a.re;z.im=a.im;return z;}
complex complex::operator-(const complex &a) const
{complex z;z.re=re+a.re;z.im=im+a.im;return z;}
complex operator *(const complex &x,const complex &y)
{complex z;z.re=x.re*y.re-x.im*y.im;z.im=x.re*y.im+x.im*y.re;return z;}
complex complex::operator *(const complex x) const
{complex z;z.re=re*x.re-im*x.im;z.im=re*x.im+im*x.re;return z;}
complex & complex::operator +() const
{return *this;}
complex complex::operator -() const
{complex z;z.re=-re;z.im=-im;return z;}
complex & complex::operator=(const complex &a)

159
CAPITOLUL 11 Supraîncărcarea operatorilor
{re=a.re;im=a.im;return *this;} // returnează obiectul curent
complex & complex::operator+=(const complex &x)
{double re1=re*x.re-im*x.im;double im1=re*x.im+im*x.re;
re=re1; im=im1;return *this;}
complex complex::operator+=(const double d)
{re+=d; return *this;}
complex complex::operator-=(const complex &x)
{re-=x.re;im-=x.im; return *this;}
complex &complex::operator /= (const complex &z)
{double numitor=z.re*z.re+z.im*z.im;
double re1=(double)(re*z.re+im*z.im)/numitor;
double im1=(double)(im*z.re-re*z.im)/numitor;
re=re1; im=im1;return *this;}
complex & complex::operator++() //forma prefixata
{cout<<"F. prefixata!\n";re++; return *this;}
complex complex::operator ++ (int) //forma postfixata
{ cout<<"F. postfixata!\n";complex z=*this; re++; return z;}
complex complex::operator--()
{re--; return *this;}
complex complex::operator ! ()
{ complex w; double d,e;
if ((d=modul())==0) return w;
e=arg();d=sqrt(d); e=e/2;w.re=d*cos(e);w.im=d*sin(e);return w;}
int complex::operator==(complex &x)
{return re==x.re && im==x.im;}
int operator==(complex &x, complex &y)
{return (x.re==y.re && x.im==y.im);}
int complex::operator!=(complex &x)
{return !(re==x.re && im==x.im);}
int operator!=(complex &x, complex &y)
{return !(x.re==y.re && x.im==y.im);}
ostream &operator<<(ostream &ecran, const complex &z)
{ecran<<"("<<z.re;
if (z.im>=0) ecran<<'+';ecran<<z.im<<"*i)";
return ecran;}
istream &operator>>(istream &tastatura, complex &z)
{tastatura>>z.re>>z.im;return tastatura;}

//FISIERUL de test
#include "complex.cpp"
#include <conio.h>
#include <stdio.h>

complex a(2, -6), b;


void main()
{ cout<<"Intrare in main\n";
complex x(3,7), y(-1, 28), z; cout<<"b="<<b<<'\n';cout<<"x="<<x<<'\n';
cout<<"y="<<y<<'\n';cout<<"z="<<z<<'\n'; cout<<"a="<<a<<'\n';
cout<<"a++="<<a++<<'\n';cout<<"++a="<<++a<<'\n';
printf("Pentru continuare introdu un car!\n"); getch();
{ complex w; cout<<"Introduceti w:\n";
cin>>w; cout<<"w citit este: "<<w<<'\n';
w.ipi(); cout<<"Dupa increm. p. imag:"<<w<<'\n';
w.dpi(); cout<<"Dupa decrem. p. imag:"<<w<<'\n';
cout<<"Modulul lui w este:"<<w.arg()<<'\n';
cout<<"Argumentul lui w este:"<<w.arg()<<'\n';
printf("Pentru continuare introdu un car!\n"); char rasp;
cout<<"Se iese din blocul interior!\n";
}
printf("Pentru continuare introdu un car!\n");getch();
cout<<"a="<<a<<'\n';
++a; cout<<"Dupa increm. p. reale: "<<a<<'\n';

160
CAPITOLUL 11 Supraîncărcarea operatorilor
--a; cout<<"Dupa decrem. p. reale: "<<a<<'\n';
a=x;cout<<"x="<<x<<'\n';cout<<"Dupa atribuire: a="<<a<<'\n';getch();
a=x++;cout <<"a = "<<a<<'\n';complex k1=a;cout<<"k="<<k1<<'\n';
a=++x;cout <<"a = "<<a<<'\n';complex k=a;cout<<"k="<<k<<'\n';getch();
k=-a;cout<<"- unar aplicat lui k:"<<k<<'\n';cout<<"-k="<<-k<<'\n';
k=x+y;cout<<x<<" + "<<y<<" = "<<k<<'\n';getch();
k=4+x;cout<<" 4 + "<< x <<" ="<<4+x<<'\n';k=a*x;
cout<<a<<" * "<<x<<" = "<<k<<'\n';
}

//FISIERUL tst_compl1.cpp
#include <iostream.h>
#include "complex.cpp"
complex a1(2,-6),b1;
void main()
{
cout<<"Intrare in main!!\n";
complex a(1,3), b(-1.3, 2.4),c;cout<<"a="<<a<<'\n';cout<<"b="<<b<<'\n';
c=(a+=b);cout<<"c="<<c<<'\n';complex x(3,7),y(-1,28),z;
cout<<"x="<<x<<" y="<<y<<" z="<<z<<'\n';c=a*b;
cout<<"a*b="<<c<<'\n';complex d;cout<<"Astept d ";cin>>d;
cout<<"d="<<d<<'\n';
cout<<"\nIntrare in blocul interior!!\n";
{complex w; cout<<"w="<<w<<'\n'; w=a;
cout<<"Dupa atribuirea w=a: w="<<w<<'\n';
w=-a; cout<<"Dupa atribuirea w=-a: w="<<w<<'\n';w=+a;
cout<<"Dupa atribuirea w=-a: w="<<w<<'\n'; w+=a;
cout<<"Dupa atribuirea w+=a: w="<<w<<'\n'; b=x+2;
cout<<"Dupa atribuirea b=x+2: w="<<w<<'\n'; z=2+x;
cout<<"Dupa atribuirea b=2+x: w="<<w<<'\n';
cout<<"Iesire din blocul interior\n";
}
cout<<"a+4="<<(a+4)<<'\n';cout<<"4+a="<<(4+a)<<'\n';cout<<"Iesire din main!!\n";
}

Exerciţiu: În exerciţiul următor se implementează clasa fracţie. Ea are ca date membre private nrt şi nmt
(numărătorul şi numitorul). Tot ca metodă privată este definită metoda simplifică(), folosită pentru
evitarea unor calcule cu numere mari.
Ca metode publice sunt definite: un constructor, un destructor, funcţia numărător care returnează valoarea
datei membre nrt, funcţia numitor care returnează valoarea datei membre nmt, funcţia valoare care
returnează valoarea reală obţinută prin împărţirea numărătorului la numitor şi funcţia afişare.
Se supraîncarcă operatorii +, -, *, / prin funcţii prietene ale clasei fracţie. Semnificaţia dată este cea de a
realiza operaţii de adunare, scădere, înmulţire şi împărţire a obiectelor din clasa fracţie.
Se supraîncarcă operatorii +=, -=, *=, /= prin funcţii membre ale clasei fracţie.
Funcţia cmmdc (care implementează algoritmul lui Euclid pentru aflarea celui mai mare divizor comun a
două numere) nu este nici funcţie membră a clasei fracţie, nici funcţie prietenă. Ea este apelată de funcţia
simplifică. Funcţia simplifică este utilizată pentru a evita obţinerea unor valori mari pentru datele
membre nrt şi nmt.

#include <iostream.h>
class fracţie
{ int nrt,nmt; // numărător,numitor
void simplifică(); //metodă de simplificare a fracţiei
public:
fracţie(int nrti=0, int nmti=1); // constructor iniţializare
~fracţie() {cout<<"DESTRUCTOR!!!\n";}; //destructor
int numărător() {return nrt;}
int numitor() {return nmt;}
double valoare() {return (double)nrt/(double)nmt;}
void afişare();

161
CAPITOLUL 11 Supraîncărcarea operatorilor
friend fracţie operator+(const fracţie&, const fracţie&);
friend fracţie operator-(const fracţie&, const fracţie&);
friend fracţie operator*(fracţie&, fracţie&);
friend fracţie operator/(fracţie&, fracţie&);
fracţie& operator =(const fracţie&);
fracţie& operator +=(const fracţie&);
fracţie& operator -=(const fracţie&);
fracţie& operator *=(const fracţie&);
fracţie& operator /=(const fracţie&);
};
int cmmdc(int x,int y) //calculează şi returnează cmmdc pentru x, y
{int z; if (x==0 || y==1) return 1;
if (x<0) x=-x;
if (y<0) y=-y;
while (x!=0){
if (y>x) {z=x;x=y;y=z;}
x%=y;
}return y;}

void fracţie::simplifică()
{int cd;
if (nmt<0) {nrt=-nrt;nmt=-nmt;}
if (nmt>1){ cd=cmmdc(nrt,nmt);if (cd>1) {nrt/=cd; nmt/=cd;} }
}
fracţie::fracţie(int nri, int nmi)
{nrt=nri; nmt=nmi; simplifică(); cout<<"Constructor!\n";}
fracţie operator +(const fracţie &f1, const fracţie &f2)
{int dc; fracţie f;
dc=cmmdc(f1.nmt,f2.nmt);f.nmt=(f1.nmt/dc)*f2.nmt;
f.nrt=f1.nrt*(f2.nmt/dc)+f2.nrt*(f1.nmt/dc);f.simplifică();return f;}
fracţie operator -(const fracţie &f1, const fracţie &f2)
{int dc; fracţie f;dc=cmmdc(f1.nmt,f2.nmt);f.nmt=(f1.nmt/dc)*f2.nmt;
f.nrt=f1.nrt*(f2.nmt/dc) - f2.nrt*(f1.nmt/dc);f.simplifica();return f;}
fractie operator * (fractie &f1, fractie &f2)
{ int dc;fractie f;dc=cmmdc(f1.nrt,f2.nmt);
if (dc>1) {f1.nrt/=dc;f2.nmt/=dc;}
dc=cmmdc(f2.nrt,f1.nmt);
if (dc>1) {f2.nrt/=dc;f1.nmt/=dc;}
f.nrt=f1.nrt*f2.nrt; f.nmt=f1.nmt*f2.nmt;return f; }
fractie operator / (fractie & f1, fractie & f2)
{ int dc;fractie f;dc=cmmdc(f1.nrt,f2.nrt);
if (dc>1) {f1.nrt/=dc;f2.nrt/=dc;}
dc=cmmdc(f2.nmt,f1.nmt);if (dc>1) {f2.nmt/=dc;f1.nmt/=dc;}
f.nrt=f1.nrt*f2.nmt; f.nmt=f1.nmt*f2.nrt;return f;}
void fractie::afisare()
{cout<<"f="<<nrt<<'/'<<nmt<<'\n';}
fractie& fractie::operator=(const fractie &f1)
{ nmt=f1.nmt;nrt=f1.nrt; return *this;}
fractie& fractie::operator+=(const fractie &f1)
{ int dc=cmmdc(nmt,f1.nmt);
nmt=(nmt/dc)*f1.nmt;nrt=nrt*(f1.nmt/dc)+f1.nrt*(nmt/dc);
simplifica();return *this;}
fractie& fractie::operator-=(const fractie &f1)
{ int dc=cmmdc(nmt,f1.nmt);
nmt=(nmt/dc)*f1.nmt;nrt=nrt*(f1.nmt/dc)-f1.nrt*(nmt/dc);simplifica();
return *this;}
fractie& fractie::operator *=(const fractie &f1)
{ int dc;dc=cmmdc(nrt,f1.nmt);if (dc>1) {nrt/=dc;f1.nmt/=dc;}
dc=cmmdc(f1.nrt,nmt);if (dc>1) {f1.nrt/=dc;nmt/=dc;}
nrt=nrt*f1.nrt;nmt=nmt*f1.nmt;simplifica();return *this;}
fractie& operator /=(const fractie &f1)
{ int dc;dc=cmmdc(nrt,f1.nrt);

162
CAPITOLUL 11 Supraîncărcarea operatorilor
if (dc>1) {nrt/=dc;f1.nrt/=dc;}
dc=cmmdc(f1.nmt,nmt); if (dc>1) {f1.nmt/=dc;nmt/=dc;}
nrt=nrt*f1.nmt; nmt=nmt*f1.nrt;return *this;}

void main()
{ double n1, n2;fractie f(4,5);f.afisare();
fractie f1(5,4);fractie sum=f+f1;sum.afisare();
cout<<"NOUA AFISARE:\n"<<sum;cout<<"Numarator:"; cin>>n1;
cout<<"Numitor:"; cin>>n2;fractie f4(n1, n2); f4.afisare();
f4+=f1;f4.afisare();
f4=f=f2; f4.afisare();
fractie f2; f2.afisare();
}

Observaţii:
 Nu a fost necesară definirea unui constructor de copiere şi nici supraîncărcarea operatorului de atribuire,
deoarece clasa fracţie nu contine pointeri către date alocate dinamic. În ambele situaţii se face o copiere
bit cu bit, conform procedurii standard de copiere a structurilor din limbajul C. Într-o atribuire cum ar fi:
f4=f; (unde f4, f de tip fractie), se realizează copierea bit cu bit a fracţiei f în fracţia f4.
 Operatorii binari simpli +, -, *, / au fost supraîncărcaţi prin funcţii prietene, pentru a putea fi folosiţi în
expresii de tipul n+f, în care n este operand de tip int şi f este operand de tip fracţie. Dacă aceşti
operatori ar fi fost supraîncărcaţi prin metode ale clasei fracţie, ar fi putut fi utilizaţi doar în expresiile în
care operandul stâng era de tip fracţie.
 Operatorii binari compuşi +=, -=, *=, /= au fost supraîncărcaţi prin funcţii membre, deoarece operandul
stâng este întotdeauna de tip fracţie.
 În programul de test fracţiile f şi f1 au fost iniţializate cu valorile 4 si 5, respectiv 5 si 4. Fracţia f2 a
fost iniţializată cu 0 şi 1, datorită parametrilor impliciţi ai constructorului clasei. Acest lucru se observă
în urma apelului funcţiei afisare pentru obiectele f, f1, f2.
 În cazul unei atribuiri de forma f=n; , unde f este fracţie si n este de tip int, înainte de atribuirea propriu-
zisă are loc o conversie a lui n în fracţie. Întregul n este convertit automat în fracţie (n,1). Acelaşi efect
s-ar fi obţinut în urma unei conversii explicite: (fractie) n.
 Pentru un obiect din clasa fracţie, constructorul poate fi apelat explicit: f=fractie(4,4); (în loc de
fractie f(4,5); ).

Pentru a evita lucrul cu fişiere care au sute de linii de cod, se folosesc două abordări:
a) Se crează fişierul sursă numit fractie.h (header al utilizatorului) care conţine declararea clasei
fracţie.
Se crează fişierul fractie.cpp în care se implementează metodele clasei fracţie. În acest fişier se
include header-ul "fractie.h".
Se crează un al treilea fişier care testează tipul de date fracţie, în care se include fişierului
"fractie.cpp". Se compilează, se linkeditează şi se lansează în execuţie fişierul executabil obţinut.
b) Se construieşte un proiect. De exemplu, dacă se lucrează sub un mediu integrat, cum ar fi BorlandC, se
crează cele trei fişiere (fractie.h care conţine declararea clasei, fractie.cpp care implementează
metodele clasei şi fişierul de test ( test_fractie.cpp)). Fişierul header fractie.h va fi inclus atât în
fractie.cpp, cât şi în test_fractie.cpp. Din meniul "Project" se selectează "Open
Project", apoi comanda "Add item…", acre permite adăugarea fişierelor fractie.cpp si
test_fractie.cpp.

Pentru a evita includerea aceluiaşi fişier header de mai multe ori, se folosesc directivele de compilare
condiţionată.
Exemplu:
#ifndef _fractie_h
#include "fractie.h"
#define _fractie_h
#endif
Exerciţiu: Se defineşte tipul şir, cu date membre (private):

163
CAPITOLUL 11 Supraîncărcarea operatorilor
 int lung
Lungimea propriu-zisă (nr. de caractere din şir), fără terminator
 char *sirul
Adresa început şir (şirul-pointer către început şir)
Metode:
 sir();
Constructor vid
 sir (char *);
Constructor de iniţializare care primeşte ca parametru un pointer către un şir de caractere (alocare
dinamică).
 sir(const sir&);
Constructor de copiere: primeşte ca argument o referinţă către un obiect din clasa şir şi realizează
copierea.
 ~sir();
Destructor care eliberează memoria alocată dinamic.
 int lungime();
Returnează valoarea datei membre lung (nr. de carctere din şir).
 const char *continut();
Returnează conţinutul unui obiect de tip şir.
 sir &operator=(const sir&);
Supraîncărcarea operatorului de atribuire printr-o funcţie membră. A fost necesară supraîncarcarea
operatorului de atribuire datorită faptului că această clasă conţine ca date membre, pointeri.
 sir &operator+=(const sir&);
Operator supraîncarcat prin funcţie membră care realizează concatenarea obiectului curent
(operandul implicit, de tip şir) cu obiectul de tip şir primit ca parametru.
 friend sir operator+(const sir& s1, const sir& s2);
Supraîncarcă operatorul de adunare printr-o funcţie prietenă. Acesta concatenează obiectele de tip şir
primite ca parametri. Returnează şirul obţinut în urma concatenării.
 friend ostream &operator<<(ostream &, const sir&);
Supraîncarcă operatorul insertor printr-o funcţie prietenă a clasei şir.
 friend istream &operator>>(istream &, sir&);
Supraîncarcă operatorul extractor printr-o funcţie prietenă a clasei şir.

// FISIERUL sir.h
#include <iostream.h>
class sir
{ int lung; //lungimea propriu-zisa, fara terminator
char *sirul; //adresa inceput sir (sirul-pointer catre inceput sir)
public:
sir(); //constructor vid: construieste un sir vid
sir (char *); //constructor initializare primeste ca arg. un sir standard
sir(const sir&);
// constructor copiere: primeste ca arg. o referinta catre un obiect din cls. sir si trebuie sa faca o copiere
~sir(); //destructor
int lungime() //metoda care return. nr de car din componenta sirului
const char *continut() //metoda inline-returneaza continutul sirului curent
sir &operator=(const sir&); /*supraincarcare operator atribuire(necesar dat. faptului
ca, in cls sir, exista un pointer catre data membra "sirul" supraincarcat prin f-ctie membra, ptr ca intotdeauna
membrul stang este un obiect din clasa sir */
sir &operator+=(const sir&); //concateneaza argumentul primit la sirul curent
friend sir operator+(const sir&, const sir&); //concateneaza argumentele
friend ostream &operator<<(ostream &,const sir&);//supraincarcare operator insertor
friend istream &operator>>(istream &,sir&);//supraincarcare operator extractor
};
// FISIERUL sir.cpp
//conţine definiţiile funcţiilor din clasa şir.

164
CAPITOLUL 11 Supraîncărcarea operatorilor
#ifndef _sir_h
#include "sir.h"
#define _sir_h
#endif

#ifndef _stdio_h
#include "stdio.h"
#define _stdio_h
#endif

#ifndef _string_h
#include "string.h"
#define _string_h
#endif

#ifndef _iostream_h
#include "iostream.h"
#define _iostream_h
#endif

sir::sir()
{sirul=0;lung=0; cout<<"Constructor vid\n";}
sir::sir(char *s)
{cout<<"Apel constructor\n";
lung=strlen(s);
if (lung>0){sirul=new char[lung+1];
if (sirul!=0) strcpy(sirul,s);
else lung=0;
}
else sirul=0;
}
sir::sir(const sir &s)
{cout<<"Constructor copiere\n";
if (s.lung>0){
sirul=new char[s.lung+1];
if (sirul!=0){
strcpy(sirul,s.sirul);
lung=s.lung;
}
else lung=0;
}
else {lung=0;sirul=0;}
}
sir::~sir()
{cout<<"Destructor\n";if (sirul!=0) delete sirul;}
int sir::lungime()
{return lung;}
const char *sir::continut()
{return sirul;}
sir &sir::operator=(const sir&s)
{cout<<"Operator de atribuire\n";
if (sirul!=0) delete sirul;
if (s.lung>0){
sirul=new char[s.lung+1];
if (sirul!=0){
strcpy(sirul,s.sirul);
lung=s.lung;
}
else lung=0;
}
else {sirul=0; lung=0;}
return *this;

165
CAPITOLUL 11 Supraîncărcarea operatorilor
}
sir & sir::operator+=(const sir &s)
{ if (s.lung>0){
char *ps; int lung1; lung1=lung+s.lung; ps=new char[lung1+1];
if (ps!=0){
strcpy(ps,sirul); strcat(ps,s.sirul);
delete sirul; sirul=ps;lung=lung1;
}
}
return *this;
}
sir operator+(const sir &s1, const sir &s2)
{ sir s;
s.lung=s1.lung+s2.lung; s.sirul=new char[s.lung+1];
if (s.sirul!=0){
if (s1.lung>0)
strcpy(s.sirul,s1.sirul);
else strcpy(s.sirul,"");
if (s2.lung>0)
strcat(s.sirul,s2.sirul);
}
else {s.lung=0; s.sirul=0;}
return s;
}
ostream &operator<<(ostream &ies, const sir &s)
{ if (s.lung>0) ies<<s.sirul; return ies; }
istream &operator>>(istream &intr, sir &s)
{ char s1[100];printf("Astept sir:"); scanf("%s", s1);s=s1;return intr;}

// FISIERUL test_sir.cpp
// Program de test pentru clasa şir
#include "sir.cpp"
#include <conio.h>
void main( )
{ const char *p;clrscr();cout<<"Declaratii obiecte din cls sir\n";
sir s1("SIR INITIALIZAT!"), s2(s1), s3, s4;getch();cout<<"\nAfisari\n";
cout<<"s1="<<s1<<'\n'; cout<<"s2="<<s2<<'\n'; cout<<"s3="<<s3<<'\n';
cout<<"s4="<<s4<<'\n';s3=s2;cout<<"\nAtribuire: s3=s2 :s3="<<s3<<'\n';
getch();s4="Proba de atribuire";cout<<"s4="<<s4<<'\n';
cout<<"\nConcatenare s1 (ob. curent) cu s4.\n";
s1+=s4;cout<<"s1="<<s1<<'\n';
cout<<"\nConcatenare s1 cu s4, rezultat in s3\n";s3=s1+s4;
cout<<"s3="<<s3<<'\n';getch();sir q;cout<<"Astept car. din sirul q:";
cin>>q;cout<<"Nr car. introduse in sir:"<<q.lungime()<<'\n';
cout<<"Continutul sirului:"<<q.continut()<<'\n';getch(); }

Aşa cum se observă din exerciţiul prezentat, în corpul constructorului se alocă memorie dinamic (cu
operatorul new). Destructorul eliberează memoria alocată dinamic (cu operatorul delete).

11.7. SUPRAÎNCĂRCAREA OPERATORULUI DE INDEXARE [ ]

Operatorul de indexare este un operator binar şi are forma generală: nume[expresie]. Să considerăm clasa
vector, definită astfel:
class vector{
private:
int nrcomp; //nr. componente
double *tabcomp; //tabloul componentelor; adresa de început
public:
double &operator[](int);
}

166
CAPITOLUL 11 Supraîncărcarea operatorilor

Pentru tipul abstract vector, operatorul de indexare poate fi supradefinit, astfel încât să permită accesarea
elementului de indice n. În acest caz, operatorul de indexare se poate supradefini printr-o funcţie membră a
clasei (deoarece operandul stâng este de tipul clasei), şi poate fi folosit sub forma: v[n] (unde v este obiect
al clasei vector; n-expresie întreagă). Expresia v[n] este echivalentă cu v.operator[](n) (apelul explicit
al funcţiei operator []). Transferul parametrului către funcţia care supraîncarcă operatorul se poate face
prin valoare sau prin referinţă. În mod obligatoriu, funcţia trebuie să returneze referinţa către elementul
aflat pe poziţia n (pentru a permite eventualele modificări ale elementului, deoarece vector[n] este
lvalue).
Pentru un tip abstract, prototipul funcţiei care supradefineşte operatorul de indexare este (const protejează
argumentul la modificările accidentale):
tip_element & operator [ ] (const int);

În cazul în care operatorul se supraîncarcă printr-o funcţie prietenă, prototipul funcţiei este:
tip_elem & operator (tip, int);

Exerciţiu: Să definim clasa vector are ca date membre (private) (figura 11.4.):
 int nrcomp; - numărul elementelor vectorului
 int err; - indice de eroare
 double *tabcomp; - adresa de început a tabloului componentelor
Metode:
 vector(int nrc=0);
Constructor iniţializare cu parametru implicit: număr elemente 0. Crează dinamic un vector de
elemente reale (double) şi iniţializează elementele cu valoarea 0.
 vector(const vector&);
Constructor de copiere: Pe baza vectorului primit ca argument, crează un nou vector (de aceeaşi
dimensiune, cu aceleaşi valori ale componentelor).
 virtual ~vector();
Destructor: eliberează memoria alocată dinamic la crearea unui obiect din clasa vector.
 int dimens() const;
Returnează dimensiunea (numărul elementelor) pentru obiectul curent.
 double &operator[](int);
Supraîncarca operatorul de indexare [] prin funcţie membră (metodă). Returnează referinţa către
elementul cu numărul de ordine indicat ca argument.
 vector &operator=(const vector&);
Supraîncarcarea operatorului de atribuire printr-o funcţie membră. A fost necesară supraîncarcarea
operatorului de atribuire datorită faptului că această clasă conţine pointeri către datele membre.
 int nrerori() const;
Metodă constantă (nu poate modifica obiectul curent) care returnează valoarea datei membre err;
 void anulari();
Metoda care anulează indicele de eroare.
 int comparare(const vector&) const;
Metoda constantă care compară obiectul curent (operand stâng, argument implicit) cu obiectul primit
ca parametru (tot vector). Returnează o valoare întreagă, care este: 2 dacă vectorii au număr de
componente diferit; 0 dacă obiectul curent are 0 elemente sau dacă vectorii comparaţi au aceleaşi
elemente; 1 dacă vectorii au cel puţin un element diferit. Metoda nu modifică operandul stâng. Mod
de apelare: a.comparare(b); (unde a, b vectori).
 friend int prodscal(const vector& v1, const vector& v2, double& p);
Funcţie prietenă a clasei vector care calculează şi returnează valoarea produsului scalar pentru
vectorii v1 şi v2, transmişi ca argumente:
nrcomp −1
p= ∑ v1[k ] * v2[k ]
k =0
 friend int suma(const vector& v1, const vector& v2, vector& v);
Funcţie prietenă care calculează vectorul sumă v:
v[ k ] = v1[ k ] + v 2[ k ]
167
CAPITOLUL 11 Supraîncărcarea operatorilor
Returnează o valoare întreagă: 1 dacă numărul de elemente din v1 este diferit de numărul
elementelor din v2; 0 dacă v2 are 0 elemente, sau dacă s-a calculat suma; 3 dacă v are 0 elemente
 friend int diferenta(const vector& v1, const vector& v2, vector& v);
Funcţie prietenă care calculează vectorul diferenţă v:
v[ k ] = v1[ k ] −v 2[ k ]
Returnează o valoare întreagă: 1 dacă numărul de elemente din v1 este diferit de de numărul
elementelor din v2; 0 dacă v2 are 0 elemente, sau dacă s-a calculat diferenţa; 3 dacă v are 0 elemente
 friend ostream &operator<<(ostream &ies, const vector&);
Operator de afişare supraîncarcat prin funcţie prietenă. Apelează metoda privată constantă afişare.
 virtual void afisare(ostream &)const;
Cuvântul virtual care apare în antetul funcţiei indică faptul că metoda este o funcţie virtuală. Ea
poate fi eventual redefinită (cu acelaşi prototip) şi în clasele derivate din clasa vector. În cazul unei
redefiniri, funcţia ar fi supusă "legării dinamice", ceea ce înseamnă că selecţia ei se va face abia în
momentul execuţiei.
 double operator *(const vector& v1) const;
Operator de înmulţire supraîncărcat prin metodă constantă care returnează o valoare reală
reprezentând produsul scalar dintre obiectul curent şi cel primit ca argument. În funcţie nu se crează
o copie a lui v1, se lucrează cu v1 din programul apelant (parametru transmis prin referinţă).
 vector operator+(const vector&) const;
Operator de adunare supraîncărcat prin metodă constantă care returnează un vector (întoarce o copie
care poate fi utilizată în programul apelant) reprezentând suma dintre obiectul curent şi cel primit ca
argument.
 vector operator-(const vector&) const;
Operator de scădere supraîncărcat prin metoda constantă care returnează un vector (întoarce o copie
care poate fi utilizată în programul apelant) reprezentând diferenţa dintre obiectul curent şi cel primit
ca argument.
 vector &operator+=(const vector& b);
Operator supraîncărcat prin metodă, deoarece întotdeauna operandul stâng este de tipul vector. Este
folosit în expresii cum ar fi: a+=b (a şi b de tipul vector).
 vector &operator-=(const vector&);
Operatorul -= supraîncărcat prin metodă, deoarece întotdeauna operandul stâng este de tipul vector.
 int sort(char='A');
Metodă care testează argumentul primit. Dacă acesta este valid ('A' sau 'D') apelează metoda
quicksort, pentru ordonarea crescătoare sau descrescătoare a alementelor unui vector.
 void quicksort(int, int, char);
Metoda este protejată şi realizează ordonarea crescătoare (argumentul 'A') sau descrescătoare
(argumentul 'D').

v.nrcomp

v.err

v.tabcomp
vector v

Figura 11.4. Obiectul v, de tip vector

Se prezintă varianta de lucru în care se crează un proiect.


// FISIERUL vector.h
#ifndef _iostream_h
#include <iostream.h>
#define _iostream_h
#endif
class vector{

168
CAPITOLUL 11 Supraîncărcarea operatorilor
private:
int nrcomp; //nr. componente
int err; //indice eroare
double *tabcomp; //tabloul componentelor; adresa de început
public:
vector(int nrc=0); //constructor initializare pentru un vector vid
vector(const vector&); //constr. copiere
~vector(); //destructor
int dimens() const //metoda constanta
{return nrcomp;}
double &operator[](int); //supraincarc operator indexare
vector &operator=(const vector&); //supraincarcare operator de atribuire
int nrerori() const
{return err;}
void anulari()
{err=0;}
int comparare(const vector&) const; //compara 2 vectori
friend int prodscal(const vector&, const vector&, double&);
friend int suma(const vector&, const vector&, vector&);
friend int diferenta(const vector&, const vector&, vector&);
friend ostream &operator<<(ostream &ies, const vector&);
double operator *(const vector&) const;
vector operator+(const vector&) const;
//intoarce copie care poate fi utilizata in progr. apelant
vector operator-(const vector&) const;
vector &operator+=(const vector&); //a+=b
vector &operator-=(const vector&);
int sort(char='A');
private:
void afisare(ostream &)const;
void quicksort(int,int,char);
};

// FISIERUL vector.cpp
#ifndef _vector_h
#include "vector.h"
#define _vector_h
#endif

#ifndef _string_h
#include <string.h>
#define _string_h
#endif

#ifndef _ctype_h
#include <ctype.h>
#define _ctype_h
#endif

vector::vector(int nrc)
{err=0;cout<<"Constructor vector\n";
if (nrc>0){ nrcomp=nrc; tabcomp=new double[nrcomp+1];
if (tabcomp==0) nrcomp=0;
else
for (int i=0;i<=nrcomp;i++) tabcomp[i]=0;
//initializare elemente vector cu 0
}
else{ nrcomp=0; tabcomp=0; }
}

169
CAPITOLUL 11 Supraîncărcarea operatorilor
vector::vector(const vector &v)
{ err=v.err; cout<<"Constructor copiere!\n";
if (v.nrcomp>0){nrcomp=v.nrcomp; tabcomp=new double[nrcomp+1];
if (tabcomp==0){ nrcomp=0; tabcomp=0; err=1; }
else{
for(int k=0;k<=nrcomp;k++) tabcomp[k]=v.tabcomp[k];}
}
else{ nrcomp=0; tabcomp=0; } }
vector::~vector()
{cout<<"Destructor!\n"; if (tabcomp!=0) delete tabcomp; }
double &vector::operator[](int i)
{ if (i<0 || i>=nrcomp){err++; return tabcomp[nrcomp]; }
else return tabcomp[i]; }

int vector::comparare(const vector&v) const


//w.comparare(v)
{ int k;
if (nrcomp!=v.nrcomp) return 2;
if (nrcomp==0) return 0;
for (k=0;k<nrcomp && tabcomp[k]==v.tabcomp[k];k++)
if (k<nrcomp) return 1; //vectorii au cel putin un elem. diferit
return 0;
}
//v.afisare(cout)
void vector::afisare(ostream &ies) const
{ ies<<'[';
for (int i=0;i<(nrcomp-1);i++) ies<<tabcomp[i]<<", ";
if (nrcomp>0) ies<<tabcomp[nrcomp-1];
ies<<']';
}
ostream &operator<<(ostream &ies, const vector &v)
{ v.afisare(ies); return ies; }
vector &vector::operator=(const vector &v)
{ cout<<"Operator atribuire!\n";
if (tabcomp!=0) delete tabcomp;
nrcomp=0; err=0; tabcomp=0;
if (v.nrcomp>0){
tabcomp=new double[v.nrcomp+1];
if (tabcomp!=0){
nrcomp=v.nrcomp; err=v.err;
for (int k=0;k<=nrcomp;k++)
tabcomp[k]=v.tabcomp[k];
}
}
return *this;
}
int prodscal(const vector &v1, const vector &v2, double &p)
//p=SUMA(v1[k]v2[k])
{ p=0;
if (v1.nrcomp!=v2.nrcomp) return 1;
if (v1.nrcomp==0) return 2;
for (int k=0;k<v1.nrcomp;k++) p+=v1.tabcomp[k]*v2.tabcomp[k];
// sau: p+=v1[k]*v2[k] pentru ca s-a supraincarcat operatorul de indexare
//mod apel j=prodscal(w,v,p);
//mod apel prodscal(w,v,p);
return 0;
}
int suma(const vector &v1, const vector &v2, vector &v)
{ v.nrcomp=v.err=0;
if (v.tabcomp!=0) delete v.tabcomp;
v.tabcomp=0;

170
CAPITOLUL 11 Supraîncărcarea operatorilor
if (v1.nrcomp!=v2.nrcomp) return 1;
if (v2.nrcomp==0) return 0;
v.tabcomp=new double[v1.nrcomp+1];
if (v.tabcomp==0) return 3;
v.nrcomp=v1.nrcomp;
for (int k=0;k<=v1.nrcomp;k++)v.tabcomp[k]=v1.tabcomp[k]+v2.tabcomp[k];
return 0;
}

int diferenta(const vector &v1, const vector &v2, vector &v)


{ v.nrcomp=v.err=0;
if (v.tabcomp!=0) delete v.tabcomp;
v.tabcomp=0;
if (v1.nrcomp!=v2.nrcomp) return 1;
if (v2.nrcomp==0) return 0;
v.tabcomp=new double[v1.nrcomp+1];
if (v.tabcomp==0) return 3;
v.nrcomp=v1.nrcomp;
for (int k=0;k<=v1.nrcomp;k++) v.tabcomp[k]=v1.tabcomp[k]-v2.tabcomp[k];
return 0;
}
double vector::operator*(const vector &b) const
//z=a*b; a-operandul din stânga
{ double z=0.0;
if (nrcomp!=b.nrcomp){
// err++;
cout<<"Nr. componente diferit! Nu se poate face produs scalar!\n";
return 4;}
else if (nrcomp>0)
for (int k=0;k<nrcomp;k++) z+=tabcomp[k]*b.tabcomp[k];
return z;
}
vector vector::operator+(const vector &b) const
//c=a+b
{if (nrcomp!=b.nrcomp) {vector c;c.err=1;return c;}
if (nrcomp==0) {vector c;return c;}
vector c(nrcomp);
for (int k=0;k<nrcomp;k++) c.tabcomp[k]=tabcomp[k]+b.tabcomp[k];
return c;
}
vector vector::operator-(const vector &b) const
//c=a-b
{if (nrcomp!=b.nrcomp){vector c;c.err=1;return c;}
if (nrcomp==0) {vector c;return c;}
vector c(nrcomp);
for (int k=0;k<nrcomp;k++) c.tabcomp[k]=tabcomp[k]-b.tabcomp[k];
return c;
}
vector &vector::operator+=(const vector &b)
{ if (nrcomp!=b.nrcomp) err++;
else
if (nrcomp>0)
for (int k=0;k<nrcomp;k++) tabcomp[k]+=b.tabcomp[k];
return *this;
}
vector &vector::operator-=(const vector &b)
{ if (nrcomp!=b.nrcomp) err++;
else
if (nrcomp>0)
for (int k=0;k<nrcomp;k++) tabcomp[k]-=b.tabcomp[k];
return *this;
}

171
CAPITOLUL 11 Supraîncărcarea operatorilor
void vector::quicksort(int i1,int i2,char modsort)
{int i,j; double a,y;i=i1;j=i2;a=tabcomp[(i1+i2)/2];
do{
switch (modsort)
{ case 'A': while (tabcomp[i]<a) i++;
while (tabcomp[j]>a) j--;
break;
case 'D': while (tabcomp[i]>a) i++;
while (tabcomp[j]<a) j--;
}
if (i<=j)
{y=tabcomp[i];tabcomp[i]=tabcomp[j];tabcomp[j]=y;i++;j--;}
} while (i<=j);
if (i1<j) quicksort(i1,j,modsort);
if (i<i2) quicksort(i,i2,modsort);
}
int vector::sort(char modsort)
{ modsort=toupper(modsort);
if (modsort!='A' && modsort!='D') return 1;
if (nrcomp==0) return 2;
if (nrcomp==1) return 0;
quicksort(0,nrcomp-1,modsort);
return 0;
}

//FISIERUL test_vec.cpp
#ifndef _vector_h
#include "vector.h"
#define _vector_h
#endif

void main()
{int ier;
vector v(10),w(10),z; double t[10]={4,2,6,1,4,9,-3,2,5,2};
int k,i2,i3; double p,p1;cout<<"v="<<v<<'\n';
cout<<"w="<<w<<'\n';cout<<"z="<<z<<'\n';cout<<"v[3]="<<v[3]<<'\n';
for (k=0;k<10;k++)
{ v[k]=t[k];cout<<"intr. w["<<k<<"]=";cin>>w[k];}
cout<<"Vect. v neordonat:\n"<<v<<'\n';
cout<<"Nr. erori:"<<v.nrerori()<<'\n';
ier=v.sort('A');cout<<"Vect. v ordonat crescator:"<<v<<'\n';
cout<<"ier="<<ier<<'\n';
cout<<"Vect. w neordonat:\n"<<w<<'\n';ier=w.sort('D');
cout<<"Vect. w ordonat descrescator:"<<w<<'\n';cout<<"ier="<<ier<<'\n';
vector cc(v);cout<<"cc="<<cc<<'\n';
int i1=prodscal(v,w,p); cout<<"Produsul scalar="<<p<<'\n';
cout<<"Produs scalar="<<(v*w)<<'\n';
i2=suma(v,w,z);cout<<"Vector suma:"<<z<<'\n';
cout<<"Vector suma:"<<(v+w)<<'\n';
i3=diferenta(v,w,z);cout<<"Vector diferenta:"<<z<<'\n';
cout<<"Vector diferenta:"<<(v-w)<<'\n';
cout<<"Inainte de atribuire:\n";cout<<"z="<<z<<'\n';cout<<"v="<<v<<'\n';
z=v;cout<<"Dupa atribuire:\n";cout<<"z="<<z<<'\n';cout<<"v="<<v<<'\n';
z+=v;cout<<"z="<<z<<'\n';cout<<"v="<<v<<'\n';
int test=z.comparare(z);
if (test==1) cout<<"Siruri egale!\n";
else cout<<"Siruri diferite!\n";
test=z.comparare(v);
if (test==1) cout<<"Siruri egale!\n";
else cout<<"Siruri diferite!\n";
}

172
CAPITOLUL 11 Supraîncărcarea operatorilor

11.7. SUPRAÎNCĂRCAREA OPERATORILOR NEW ŞI DELETE

Avantajul alocării dinamice a memoriei şi a eliberării acesteia cu ajutorul operatorilor new şi delete, faţă
de utilizarea funcţiilor malloc, calloc sau free (vezi capitolul 6.9.), constă în faptul că operatorii alocă
(eliberează) memorie pentru obiecte, date de tip abstract. Acest lucru este posibil deoarece aceşti operatori au
o supraîncărcare globală, standard.

În cazul în care supraîncărcarea standard este insuficientă, utilizatorul poate supraîncărca operatorii prin
metode (implicit!) statice.
Pentru operatorul new, funcţia care supraîncarcă ooperatorul new are prototipul:
void * nume_clasa::operator new (size_t lungime);
Funcţia returnează un pointer generic a cărui valoare este adresa de început a zonei de memorie alocate
dinamic. Tipul size_t este definit în stdlib.h (vezi capitolul 6.9.). La aplicarea operatorului, nu se indică
nici o valoare pentru parametrul lungime (mărimea zonei de memorie necesare obiectului pentru care se
alocă dinamic memorie), deoarece compilatorul o determină, automat.
Modul de utilizare pentru operatorul new:
nume_clasa *p = new nume_clasa;
Sau: nume_clasa *p = new nume_clasa(p1, p2, p3);
Aplicarea operatorului new supradefinit de utilizator determină, automat, apelul constructorului
corespunzător clasei, sau al constructorului implicit. În a doua formă, la alocarea dinamică a memoriei apar
şi parametrii constructorului (p1, p2, p3).

Operatorul delete se supradefineşte printr-o funcţie cu prototipul:


void nume_clasa::operator delete (void *);
La aplicarea operatorului delete se apelează, automat, destructorul clasei.
Exemple:
class c1{
double n1, n2;
public:
c1(){n1=0; n2=0;}
};
//. . . .
void main( )
{ c1 *pc1=new c1; //se alocă memorie pentru păstrarea unui obiect de tip c1
c1 *pc2=new c1(7, 5); //odată cu alocarea dinamică, se realizează şi iniţializarea
}

Operatorii new, delete permit alocarea dinamică şi pentru tablouri. În aceste situaţii, se utilizează întotdeauna
operatorii supraîncărcaţi global predefiniţi şi se apelează constructorul fără parametri (dacă acesta există).
Exemplu:
class c1{
double n1, n2;
public:
c1(){n1=0; n2=0;}
c1(double x, double y)
{n1=x; n2=y;}
};
c1 *pct1;
pct1=new c1[100]; /*Se rezervă memorie ptr. 100 obiecte de tip c1. Se apelează constructorul
implicit de 100 de ori */ a n 7.53
}

Exemplu:
#include <iostream.h> b n 2.14
class numar
{ double *n;

p1 n -1.74
173

Figura 11.5. Obiectele a, b, p1 de tip


număr şi pointer spre număr
CAPITOLUL 11 Supraîncărcarea operatorilor
public:
număr (double nr1);
~număr();
double val(){return *n;}
};
număr::număr(double nr1)
{n=new double(nr1);}
număr::~număr() { delete n;}
void main()
{număr a(7.53),b(2.14);
număr *p1,*p2; p1=new număr(-1.74);
cout<<p1<<'\n'; cout<<&p1<<'\n';
*p1=a; cout<<"p1="<<p1<<'\n'; delete p1; }

11.9. SUPRAÎNCĂRCAREAOPERATORULUI ( )

Supraîncărcarea operatorului "apel de funcţie" permite crearea unui operator binar, nestatic, de forma:
expresie (lista_param_efectivi);
Avantajele unui astfel de operator sunt:
 Evaluarea şi verificarea listei de argumente în mod similar unei funcţii obişnuite;
 Modul de funcţionare a mecanismului de apel. Deşi operatorul este binar, cel de-al doilea operand fiind o
listă de argumente (inclusiv listă vidă), funcţia operator poate avea oricâţi parametri.

În cazul în care numele funcţiei este un pointer către o anumită funcţie (vezi pointeri către funcţii), apelul
funcţiei se realizează prin:
(*point_f) (lista_param_efectivi);

Funcţia care supraîncarcă operatorul trebuie să fie metodă nestatică. Supraîncărcarea operatorului ( ) se
utilizează în mod frecvent la definirea aşa-numitului iterator. Iteratorii se utilizează în legătură cu tipuri
abstracte de date, care conţin colecţii de elermente (liste, arbori, tabele de dispersie, etc.). Pe lângă protecţia
datelor, iteratorii oferă un mijloc simplu de acces la elementele unei colecţii, fără a intra în detaliile legate de
implementarea colecţiei (independenţă a utilizării unei colecţii şi implementării unei colecţii). În principiu,
un iterator se implementează printr-o clasă ataşată unui tip abstract care conţine o colecţie de elemente. Fie,
de exemplu, clasa container care este o colecţie de obiecte de tip oarecare, care poate fi implementată ca
un tablou de obiecte, ca o listă de noduri, ca un arbore binar, etc. În funcţie de această organizare, clasa
container conţine o dată membră de tip obiect sau un pointer către obiect şi este capabil să livreze,
pe rând, obiectele elemente ale colecţiei.

11.10. SUPRAÎNCĂRCAREAOPERATORULUI ->

Supraîncărcarea operatorului unar -> se realizează printr-o metodă nestatică. Expresia


obiect -> expresie va fi interpretată ca (obiect.operator->())->expresie
De aceea, funcţia-operator trebuie să returneze fie un pointer la un obiect al clasei, fie un obiect de un tip
pentru care este supradefinit operatorul ->.
Exemplu:
#include <iostream.h>
typedef struct ex{ int membru; };

class ex1{
ex *pointer;
public:
void set(ex &p) {pointer=&p;}
ex * operator -> (void) {return pointer;}
};

class ex2{

174
CAPITOLUL 11 Supraîncărcarea operatorilor
ex1 *pointer;
public:
void set(ex1 &p) {pointer=&p;}
ex1 * operator -> (void) {return pointer;}
};

void main()
{ex A; ex1 B; ex2 C; B.set(A);
B->membru=10; //apel al funcţiei ex1::operator->()
cout<<B->membru<<'\n'; }
Exerciţiu: Se implementeaz clasa matrice. Matricea este privită ca un vector de linii.
Date membre (protected):
 int Dim1,Dim2; // nr. linii, nr. coloane
 double *tab; // pointer către tabloul componentelor
 int err; // indice de eroare
Metode:
 matrice (int dim1=0, int dim2=0);Constructor matrice, cu alocare dinamică.
 matrice(const matrice&); Constructor de copiere
 ~matrice(); Destructor, cu rolul de a elibera memoria alocată dinamic.
 int pune(int ind1, int ind2, double elem);
Iniţializează elementul de indici (ind1, ind2) cu valoarea transmisă ca argument (al treilea
parametru). Întoarce valoarea întreagă 1 dacă indicii sunt incorecţi.
 int dim1()const; Metodă constantă care returnează numărul de linii.
 int dim2() const; Metodă constantă care returnează numărul de coloane.
 int nrerori() const;
Metodă constantă (nu poate modifica obiectul curent) care returnează valoarea datei membre err;
 void anulari(); Metodă care anulează indicele de eroare.
 double elem(int ind1, int ind2) const;
Metodă constantă care returnează valoarea elementuluilui de indici (ind1,ind2).
 friend ostream &operator<<(ostream &, const matrice&);
Supraîncărcarea operatorului de inserţie printr-o funcţie membră. Apelează metoda afişare.
 void afişare(ostream &)const;
 matrice &operator=(const matrice&);
Supraîncărcarea operatorului de atribuire printr-o funcţie membră. A fost necesară
supraîncărcarea operatorului de atribuire datorită faptului că această clasă conţine pointeri
către datele membre.
 int dimtab()const;
Metodă constantă care returnează numărul de elemente din matrice (Dim1*Dim2).
 virtual int comparare(const matrice&) const;
Metodă constantă care compară obiectul curent cu obiectul primit ca argument.
 matrice operator+(const matrice&) const;
Operator de adunare supraîncărcat prin metodă constantă care returnează o matrice (întoarce o copie
care poate fi utilizată în programul apelant) reprezentând suma dintre obiectul curent şi cel primit ca
argument.
 matrice operator-(const matrice&) const;
Operator de scădere supraîncărcat prin metodă constantă care returnează o matrice (întoarce o copie
care poate fi utilizată în programul apelant) reprezentând suma dintre obiectul curent şi cel primit ca
argument.
 matrice &operator+=(const matrice&);
Operator supraîncărcat prin metodă, deoarece întotdeauna operandul stâng este de tipul
matrice. Este folosit în expresii cum ar fi: a+=b (a şi b de tipul matrice).
 matrice &operator-=(const matrice&);
Operatorul -= supraîncărcat prin metodă, deoarece întotdeauna operandul stâng este de tipul matrice.
Este folosit în expresii cum ar fi: a-=b (a şi b de tipul matrice).
 friend matrice operator*(double, const matrice&);

175
CAPITOLUL 11 Supraîncărcarea operatorilor
Operator supraîncărcat prin funcţie prietenă, pentru a putea fi utilizat în expresii n*M, unde n este de
tip real sau întreg şi M de tipul matrice.
 matrice operator*(const matrice&) const;
Operator supraîncărcat prin funcţie membră, care înmulţeşte obiectul curent (tip matrice) cu obiectul
primit ca argument (tot tip matrice).
 int indtab(int i,int j) const;
Metodă care returnează indicele din tabloul unidimensional (matricea este privită ca un tablou
unidimensional, cu elementele memorate într-un tablou unidimensional, întâi elementele primei linii,
în continuare elementele celei de-a doua linii, etc.) pentru elementul[i][j].
 int erind(int, int) const; Metodă care testează eventualele erori de indexare.
 matrice transp() const; Metoda calculează şi returnează matricea transpusă pentru obiectul
curent. Nu modifică obiectul curent.
 double operator ( ) (int i, int j);
Supraîncărcarea operatorului ( ) prin metodă a clasei care returnează valoarea elementului de indici i
şi j pentru obiectul curent, sau 0 în cazul în care apare o eroare de indexare (vezi şi metoda elem).
Apelul metodei elem: A.elem(1, 3) este echivalent cu apelul A(1, 3).

//FISIERUL matrice.h
#ifndef _iostream_h
#include <iostream.h>
#define _iostream_h
#endif

class matrice{
int Dim1,Dim2;
double *tab;
int err;
public:
matrice (int dim1=0,int dim2=0);
matrice(const matrice&);
~matrice();
int pune(int ind1, int ind2, double elem);
//pune elem. elem pe poz. de indici (ind1, ind2); întoarce 1 dacă indicii sunt incorecţi
friend ostream &operator<<(ostream &, const matrice&);
matrice transp() const;
matrice &operator=(const matrice&);
int dim1()const {return Dim1;}
int dim2() const {return Dim2;}
int dimtab() const;
int nrerori() const {return err;}
void anulerori() {err=0;}
double elem(int ind1, int ind2) const; //întoarce val. elem-lui de indici (ind1,ind2)
int comparare(const matrice&) const;
matrice operator+(const matrice&) const;
matrice operator-(const matrice&) const;
matrice &operator+=(const matrice&);
matrice &operator-=(const matrice&);
friend matrice operator*(double, const matrice&);
matrice operator*(const matrice&) const;
// PTR MATRICI SIMETRICE:
double operator()(int i, int j);

private:
int indtab(int i,int j) const
{return i*Dim2+j;}
//indicele din tab al unui elem.
int erind(int, int) const;
//test er. de indexare
void afisare(ostream &)const;

176
CAPITOLUL 11 Supraîncărcarea operatorilor
matrice inv() const;
};

// FISIERUL matrice.cpp
#ifndef _matrice_h
#include "matrice.h"
#define _matrice_h
#endif

matrice::matrice (int d1,int d2)


// constructor matrice
{int k,dtab; err=0;
if (d1<=0 || d2<=0) {Dim1=0;Dim2=0;tab=0;}
else{ dtab=d1*d2; tab=new double[dtab];
if (tab!=0) { Dim1=d1;Dim2=d2;
for (k=0;k<dtab;k++) tab[k]=0;
cout<<"Construit matr. de dim. "<<d1*d2<<'\n';
}
else {Dim1=0;Dim2=0;err=1;cout<<"Construit matr. de dim. 0"<<'\n';}
}
}
matrice::matrice(const matrice &M)
//constructor copiere
{cout<<"Constructor copiere!\n";err=0;int k,dtab;
if (M.Dim1<=0 || M.Dim2<=0) {Dim1=0;Dim2=0;tab=0;}
else{ dtab=M.Dim1*M.Dim2;tab=new double[dtab];
if (tab!=0)
{ Dim1=M.Dim1; Dim2=M.Dim2;
for (k=0;k<dtab;k++)
tab[k]=M.tab[k];
}
else {Dim1=0;Dim2=0;}
}
}
matrice::~matrice()
{ if (tab!=0) delete [] tab;}
int matrice::pune(int i,int j, double val)
{ int iret; iret=erind(i,j); if (iret!=0) return iret;
tab[indtab(i,j)]=val; return 0;
}
int matrice::erind(int i,int j) const
{ if (Dim1==0 ||Dim2==0) return 2;
if (i<0 || i>=Dim1 || j<0 || j>=Dim2) return 1;
return 0;
}
void matrice::afisare(ostream & ies) const
{ int i,j;
if (tab!=0){
ies<<'\n';
for (i=0;i<Dim1;i++){
for (j=0;j<Dim2;j++)
ies<<tab[indtab(i,j)]<<' ';
cout<<'\n';}
}
}
ostream &operator<<(ostream &ies, const matrice &M)
{ M.afisare(ies);return ies;}
matrice &matrice::operator=(const matrice &M)
{ int k,dtab,vdtab;cout<<"Operator atribuire!\n";
err=M.err;dtab=M.Dim1*M.Dim2;
//dimens. M
if (dtab==0){ Dim1=0;Dim2=0;

177
CAPITOLUL 11 Supraîncărcarea operatorilor
if (tab==0){delete [] tab;tab=0;}
}
else{ vdtab=Dim1*Dim2;
if (vdtab!=dtab){
delete [] tab;
tab=new double [dtab];
if (tab!=0){Dim1=0;Dim2=0;err=1;}
}
if (tab!=0){
Dim1=M.Dim1;Dim2=M.Dim2;
for (k=0;k<dtab;k++)
tab[k]=M.tab[k];
}
}
return *this; }
int matrice::comparare(const matrice &M) const
{ int k,dtab;if (M.Dim1!=Dim1 || M.Dim2!=Dim2) return 2;
dtab=Dim1*Dim2;
for (k=0;k<dtab;k++)
if (M.tab[k]!=tab[k]) return 1;
return 0; }
matrice matrice::operator+(const matrice &B) const
{ matrice C;int k,dtab;
if (Dim1!=B.Dim1 || Dim2!=B.Dim2)C.err=1;
else{
dtab=Dim1*Dim2; C.tab=new double [dtab];
if (C.tab==0) {C.Dim1=0; C.Dim2=0; C.err=2; }
else {C.Dim1=Dim1;C.Dim2=Dim2;
if (dtab!=0)
for (k=0;k<dtab;k++) C.tab[k]=tab[k]+B.tab[k];
}
}
return C; }
matrice matrice::operator-(const matrice &B) const
{ matrice C;int k,dtab;
if (Dim1!=B.Dim1 || Dim2!=B.Dim2) C.err=1;
else{
dtab=Dim1*Dim2;C.tab=new double [dtab];
if (C.tab==0) {C.Dim1=0; C.Dim2=0; C.err=2; }
else {C.Dim1=Dim1;C.Dim2=Dim2;
if (dtab!=0)
for (k=0;k<dtab;k++) C.tab[k]=tab[k]-B.tab[k];
}
}
return C; }
matrice &matrice::operator+=(const matrice &B)
{ int dtab;
if (Dim1!=B.Dim1 || Dim2!=B.Dim2) err++;
else { dtab=Dim1*Dim2;
if (dtab!=0)
for (int k=0;k<dtab;k++) tab[k]+=B.tab[k];
}
return *this; }
matrice &matrice::operator-=(const matrice &B)
{ int dtab;
if (Dim1!=B.Dim1 || Dim2!=B.Dim2)
err++;
else { dtab=Dim1*Dim2;
if (dtab!=0)
for (int k=0;k<dtab;k++) tab[k]-=B.tab[k];
}
return *this; }

178
CAPITOLUL 11 Supraîncărcarea operatorilor
matrice operator*(double a, const matrice &B)
{ if (B.tab==0) { matrice C; C.err=B.err; return C;}
{ int k,dtab; matrice C(B.Dim1, B.Dim2);
if (B.tab==0) {C.err=3;return C;}
dtab=C.Dim1*C.Dim2; for (k=0;k<dtab;k++) C.tab[k]=a*B.tab[k];
return C;
}
}
matrice matrice::operator*(const matrice &B) const
{ if (Dim2!=B.Dim2) { matrice C; C.err=1; return C;}
if (tab==0 && B.tab==0) {matrice C; return C;}
if (tab==0 || B.tab==0) {matrice C; C.err=2; return C;}
{ int i,j,k; double S; matrice C(Dim1, B.Dim2);
if (C.tab==0) { C.err=3; return C;}
for (i=0;i<Dim1;i++)
for (j=0;j<B.Dim2;j++)
{ S=0;
for (k=0;k<Dim2;k++)
S+=tab[indtab(i,k)]*B.tab[B.indtab(k,j)];
C.tab[C.indtab(i,j)]=S;
}
return C;
}
}
matrice matrice::transp() const
{ int i,j,dtab; dtab=Dim1*Dim2;
if (dtab==0) {matrice C; C.err=err; return C; }
{ matrice C(Dim2,Dim1);
if (C.tab==0) {C.err=1; return C; }
for (i=0;i<C.Dim1;i++)
for (j=0;j<C.Dim2;j++)
C.tab[C.indtab(i,j)]=tab[indtab(j,i)];
return C;
}
}
double matrice::elem(int i,int j) const
{ if (erind(i,j)) return 0;
return tab[indtab(i,j)];}
int matrice::dimtab() const
{ return Dim1*Dim2; }

/*PTR. MATRICI SIMETRICE:


double matrice::operator ()(int i, int j)
{ if (erind(i,j)) return 0;
return tab[indtab(i,j)];
}*/

// FISIERUL test_matrice.cc
#ifndef _iostream_h
#include <iostream.h>
#define _iostream_h
#endif

#ifndef _matrice_h
#include "matrice.h"
#define _matrice_h
#endif

void main()
{int M,N; cout <<"Nr. linii:"; cin>>M; cout <<"Nr. coloane:"; cin>>N;
{matrice A(M,N),B(4,4);matrice C();int i,j; double val;
// introduc matr. A(M,N)

179
CAPITOLUL 11 Supraîncărcarea operatorilor
for (i=0;i<M;i++)
for (j=0;j<N;j++) {
cout<<"A["<<i<<","<<j<<"]="; cin>>val;
A.pune(i,j,val); }
cout<<"Matr. introdusa:\n";cout<<A<<'\n';matrice E(A); //apel constr. copiere
cout<<"E="<<E<<'\n'; matrice D=A; //constr. copiere
cout<<"D="<<D<<'\n'; matrice F(M,N);cout<<"Inainte de atrib. F=\n";
cout<<F<<'\n';F=A;cout<<"Dupa atrib.F=\n"<<F<<'\n';int comp=F.comparare(A);
if (comp==0) cout<<"Matrici identice\n!";
else if (comp==2) cout<<"Matrici de dim. diferite!\n";
else cout<<"Matr. cu elem. diferite!\n";
E.pune(0,0,100.5); comp=E.comparare(A);
if (comp==0) cout<<"Matrici identice\n!";
else if (comp==2) cout<<"Matrici de dim. diferite!\n";
else cout<<"Matr. cu elem. dif!\n";
cout<<"A+A="<<(A+A)<<'\n';cout<<"A-A="<<(A-A)<<'\n';
A+=E; cout<<"A="<<A<<'\n';cout<<"D=A"<<(D=A)<<'\n';cout<<"A="<<A<<'\n';
cout<<"A*A="<<(A*A)<<'\n'; cout<<(A.transp())<<'\n';
}
matrice G(5); }

11.11. CONVERSII

Există următoarele tipuri de conversii:


 Conversii implicite;
 Conversii explicite.

Conversiile implicite au loc în următoarele situaţii:


 În cazul aplicării operatorului de atribuire: operandul drept este convertit la tipul operandului stâng.
 La apelul unei funcţii: Dacă tipul parametrilor efectivi (de apel) diferă de tipul parametrilor formali, se
încearcă conversia tipului parametrilor efectivi la tipul parametrilor formali.
 La revenirea dintr-o funcţie: Dacă funcţia returnează o valoare în programul apelant, la întâlnirea
instrucţiunii return expresie; se încearcă conversia tipului expresiei la tipul specificat în antetul funcţiei.

Conversiile explicite pot fi :


a) tip_predefinit_1 -> tip_predefinit_2
b) tip_predefinit -> tip_definit_de_utilizator (clasă)
c) clasă -> tip_predefinit
d) clasă_1 -> clasă_2

11.11.1. CONVERSII DIN TIP PREDEFINIT1 ÎN TIP PREDEFINIT2

Pentru realizarea unor astfel de conversii, se foloseşte operatorul unar de conversie explicită (cast), de forma:
(tip) operand
Exemplu:
int k; double x;
x = (double) k / (k+1);
/* În situaţia în care se doreşte obţinerea rezultatului real al împărţirii întregului k la k+1, trebuie
realizată o conversie explicită, vezi capitolul 2.7. */
În limbajul C++ acelaşi efect se poate obţine şi astfel:
x= double (k) / (k+1);
deoarece se apelează explicit constructorul tipului double.

11.11.2. CONVERSII DIN TIP PREDEFINIT ÎN CLASĂ

180
CAPITOLUL 11 Supraîncărcarea operatorilor
Astfel de conversii se pot realiza atât implicit, cât şi explicit, în cazul în care pentru clasa respectivă există un
constructor cu parametri impliciţi, de tipul predefinit.
Exemplu: Pentru clasa fracţie definită în cursurile anterioare:
class fracţie{
int nrt, nmt;
public:
fracţie( int nrt = 0, int nmt = 1);
// . . .
};
fracţie f;
f = 20;
/* Conversie IMPLICITĂ: înaintea atribuirii se converteşte operandul drept (de tip int) la tipul
operandului stâng (tip fracţie). */
f = fractie(20);
/*Conversie EXPLICITĂ: se converteşte întregul 20 într-un obiect al clasei fracţie (nrt=20 şi
nmt=1). */ }

11.11.3. CONVERSII DIN CLASĂ ÎN TIP PREDEFINIT

Acest tip de conversie se realizează printr-un operator special (cast) care converteşte obiectul din clasă la
tipul predefinit. Operatorul de conversie explicită se supraîncarcă printr-o funcţie membră nestatică.
nume_clasa:: operator nume_tip_predefinit( );
La aplicarea operatorului se foloseşte una din construcţiile:
(nume_tip_predefinit)obiect;
nume_tip_predefinit (obiect);

Exemplu: Pentru clasa fracţie, să se supraîncarce operatorul de conversie explicită, care să realizeze
conversii fracţie -> int.
#include <iostream.h>
class fracţie{
long nrt, nmt;
public:
fracţie(int n=0, int m=1) {nrt=n; nmt=m;}
friend ostream &operator<<(ostream &, const fracţie &);
operator int( ) {return nrt/nmt;} //conversie fracţie -> int
};
ostream &operator<<(ostream &ies, const fracţie &f)
{ies<<'('<<f.nrt<<'/'<<f.nmt<<")\n"; return ies;}

void main()
{ fracţie a(5,4), b(3), c; int i=7, j=14; c=a; c=7;
cout<<(fracţie)243<<'\n'; cout<<"(int)a="<<(int)a<<'\n';
//conversie explicită
cout<<"int(a)="<<int(a)<<'\n'; //conversie explicită
int x=a; //conversia se face implicit, înainte de atribuire
}

11.11.4. CONVERSII DIN CLASĂ1 ÎN CLASĂ2

Conversia din tip_abstract_1 în tip_abstract_2 (din clasă1 în clasă2), se realizează cu ajutorul unui
constructor al clasei2, care primeşte ca parametri obiecte din clasa1 (fracţie -> complex).
Exemplu:
#include <iostream.h>
class fracţie{
int nrt, nmt;
public:

181
CAPITOLUL 11 Supraîncărcarea operatorilor
fracţie(int nr=0, int nm=1) {nrt=nr; nmt=nm;}
operator int() const {return nrt/nmt;} //conversie fracţie -> int
friend ostream &operator<<(ostream&, const fracţie&);
friend class complex;
/*cls. complex este clasa prietena ptr. clasa fracţie, fiecare funcţie din complex este prietena pentru fracţie*/
int întreg(){return nrt/nmt;}
};
ostream &operator <<(ostream &ostr, const fracţie &f)
{ostr<<'('<<f.nrt<<'/'<<f.nmt<<")\n";return ostr;}

class complex{
double re, im;
public:
complex (double r=0, double i=0) {re=r; im=i;}
complex (fracţie &f) {re=(double)f.nrt/f.nmt; im=0;}
// conversie fracţie->complex
operator double() const {return re;}//conversie complex->double
friend ostream &operator<<(ostream &, const complex &);
};
ostream &operator<<(ostream &ies, const complex &z)
{ies<<'('<<z.re<<','<<z.im<<")=\n"; return ies;}

void main()
{ complex a(6.98, 3.2), b(9), c, d, e, q, s; int i=12, j=5, k;
double x=1234.999, y=74.9897, u, z; c=i; fractie r(7, 3), r1(9), t;
d=x; //conversie double->complex (constructor complex cu arg. double)
e=complex(y,j); //apel explicit al constr. complex; întâi conversie j de la int la double
k=a; //conversie complex->double->int
u=a; //conversie complex->double
z=(double)a/3; //conversie complex->double
cout<<"r="<<r<<" q="<<q<<'\n';
cout<<"int(r)="<<int(r)<<" (int)r="<<(int)r<<'\n';
//conversie fractie->int
cout<<"r="<<r<<'\n'; cout<<r.intreg()<<'\n';
s=r; // conversie fracţie->complex
cout<<"(complex)r="<<(complex)r<<" complex (r)="<<complex (r)<<'\n';
// conversie fracţie->complex
}

ÎNTREBĂRI ŞI EXERCIŢII

Chestiuni teoretice

1. Cum se realizează conversia din clasă1 în 5. Ce observaţii puteţi face în legatură cu


clasă2? Daţi un exemplu. aritatea unui operator şi modul de
2. Prin ce modalităţi se pot supraîncărca supraîncarcare a acestuia?
operatorii? 6. Cum se realizează conversia din tip predefinit
3. În ce situaţii se realizează conversiile în clasă?
implicite? 7. Ce restricţii impune mecanismul de
4. Cum se poate realiza conversia dintr-un tip supraîncărcare a operatorilor?
abstract (clasă) într-un tip predefinit? 8. În cazul supradefinirii metodelor, cum se
Exemplu. poate realiza selecţia unei metode ?

Chestiuni practice

182
CAPITOLUL 11 Supraîncărcarea operatorilor
1. Pentru toate tipurile de date implementate, să se completeze programele de test, astfel încât să se verifice
toţi operatorii supraîncărcaţi.
2. Pentru clasa fracţie, să se supraîncarce operatorul unar ++ printr-o funcţie membră şi operatorul -- printr-
o funcţie prietenă. Să se completeze funcţia main, astfel încât să se testeze toţi operatorii supradefiniţi.
3. Fie clasa complex, cu datele membre parte reala şi parte imaginară. Să se supraîncarce operatorul
extractor. Să se supraîncarce operatorul binar de împărţire, care realizează operaţii de forma c/d, unde c
este complex şi d este real. Să se supraîncarce operatorul de scădere printr-o funcţie membră.
4. Fie clasa fracţie, cu membrii numitor şi numărător. Să se supraîncarce operatorul / binar astfel încât să se
poata realiza operaţii de forma b/f, unde b este întreg, iar f este fracţie. Să se supraîncarce operatorul ++
care realizează incrementarea unei fracţii.
5. Fie clasa şir, declarată conform modelului din curs. Să se defineasca pentru aceasta un constructor de
copiere. Să se supraîncarce operaratorul == care compară două şiruri.
6. Fie clasa fracţie, cu membrii numitor şi numărător. Să se supraîncarce operatorul insertor. Să se
supraîncarce operatorul binar de înmulţire, printr-o funcţie membră.
7. Fie clasa complex. Să se supraîncarce operatorul de înmulţire a 2 numere complexe, printr-o funcţie
prietenă. Să se supraîncarce operatorul de înmulţire, astfel încât sa fie posibile operaţii de forma c*a,
unde a este un întreg, iar c este un obiect de tip abstract complex. Să se supraîncarce operatorul de
împărţire a două obiecte complexe, printr-o funcţie membră. Să se supraîncarce operatorul – unar care
schimbă semnul părţilor reale şi imaginare ale unui complex.
8. Fie clasa şir. Să se supraîncarce operatorul + care realizează concatenarea a 2 şiruri. Să se implementeze
metoda caută_nr_apariţii care caută de câte ori apare un caracter transmis ca argument într-un şir şi
returnează numărul de apariţii sau 0 în cazul în care acel caracter nu este găsit. Să se supraîncarce
operatorul ! care transformă caracterele din conţinutul şirului din litere mari în litere mici. Să se
definească destructorul pentru şir. Să se supraîncarce operatorul binar ŞI logic (pe cuvânt) care din două
şiruri s1 şi s2, construieşte un alt şir, care conţine caracterele comune lui s1 si s2. Să se supradefinească
operatorul != care testează existenţa unui caracter (dat ca argument) într-un şir. Dacă acel caracter este
conţinut în şir, se returnează valoarea 1, altfel – valoarea 0. Să se supraîncarce operatorii relaţionali care
compară lexicografic conţinutul a două şiruri. Să se supraîncarce operatorul – unar care realizează
conversia tuturor caracterelor alfabetice din conţinutul unui obiect de tip şir, din litere mari în litere mici.
9. Fie clasa vector. Să se supraîncarce operatorul + care realizează adunarea a 2 vectori. Să se supraîncarce
operatorul * care realizează produsul scalar a 2 vectori.
10. Să se definească tipul abstract dată calendaristică. Data calendaristică se va introduce sub forma
zz/ll/aaaa, fiind validată (se va ţine cont de anii bisecţi). Se vor supraîncărca operatorii insertor, extractor,
a+= (adună la dată un număr de zile),-= (scade dintr-o dată un număr de zile), == (compară 2 date), -
(returnează numărul de zile dintre două date), + (adună două date), ++ (incrementează luna), şi --
(decrementează luna).
11. Să se adauge la clasele punct şi segment metode de desenare, de rotire a unui segment în jurul vârfului.
12. Să se scrie un program care translatează coordonatele vârfurilor unui triunghi, şi desenează triunghiul
înainte şi după translaţie, folosindu-se o clasă punct. Se va modifica ulterior programul declarându-se o
clasa triunghi care conţine ca membri 3 obiecte de tipul punct. Se va modifica programul, astfel încât
clasa triunghi să aibă ca membru un vector de puncte.
13. Pentru clasa şir, să se supraîncarce următorii operatori:
int intr_p(const sir &s) const;
Determină prima apariţie a şirului s în şirul curent. Dacă s este subşir, returnează indicele
primului caracter al acestei intrări; altfel, returnează -1.
int intr_n(const sir &s, const unsigned n) const;
Determină a n-a apariţie a şirului s în şirul curent. Returnează indicele primului caracter al
acestei intrări; altfel, returnează -1.
sir stregcar (unsigned i, unsigned n) const;
Returnează şirul rezultat prin ştergerea din obiectul curent a cel mult n caractere, începând
cu poziţia i. Dacă i sau n sunt eronate, se returnează obiectul curent nemodificat.
sir operator - (const sir &s) const;
Returnează obiectul rezultat prin eliminarea sufixului s din şirul curent. Returnează şirul
curent dacă s nu este sufix, 0 pentru memorie insuficientă.
sir operator % (const sir &s) const;

183
CAPITOLUL 11 Supraîncărcarea operatorilor
Returnează obiectul rezultat prin eliminarea prefixului s din şirul curent. Returnează şirul
curent dacă s nu este prefix, 0 pentru memorie insuficientă.
sir operator * (const sir &s) const;
Returnează obiectul rezultat prin eliminarea primei intrări a lui s în şirul curent.
sir operator / (const sir &s) const;
Returnează obiectul rezultat prin eliminarea ultimei intrări a lui s în şirul curent.
sir operator( ) (const sir &s1, const sir &s2, int poz);
Returnează obiectul rezultat prin înlocuirea unei intrări a lui s1, cu s2. Dacă poz este o, se
substiuie prima intrare, altfel - ultima intrare. Dacă s1 nu este subşir al obiectului curent, se
returnează obiectul curent.
sir operator( ) (const sir &s1, const sir &s2) const;
Returnează obiectul rezultat prin înlocuirea în obiectul curent a tuturor intrărilor lui s1, cu
s2.

184
CAPITOLUL 12 Crearea ierahiilor de clase

CREAREA IERARHIILOR DE CLASE 1


12.1. Mecanismul moştenirii 12.5. Moştenirea multiplă
12.2. Modul de declarare a claselor derivate 12.6. Redefinirea membrilor unei clase de bază
12.3. Constructorii claselor derivate în clasa derivată
12.4. Moştenirea simplă 12.7. Metode virtuale

12.1. MECANISMUL MOŞTENIRII

Moştenirea este o caracteristică a limbajelor de programare orientate obiect, care permite refolosirea codului
şi extinderea funcţionalităţii claselor existente (vezi capitolul 9). Mecanismul moştenirii permite crearea unei
ierarhii de clase şi trecerea de la clasele generale la cele particulare. (Un concept poate fi implementat
printr-o clasă). Această proprietate se manifestă prin faptul că din orice clasă putem deriva alte clase.
Procesul implică la început definirea clasei de bază care stabileşte calităţile comune ale tuturor obiectelor ce
vor deriva din bază (ierarhic superioară). Prin moştenire, un obiect poate prelua proprietăţile obiectelor din
clasa de bază.
Moştenirea poate fi:
 Unică (o clasă are doar o superclasă, rezultând o structură arborescentă);
 Multiplă (o clasă are mai multe superclase, rezultând o structură de reţea).

Informaţia comună apare în clasa de bază, iar informaţia specifică - în clasa derivată. Clasa derivată
reprezintă o specializare a clasei de bază. Orice clasă derivată moşteneşte datele membru şi metodele clasei
de bază. Deci acestea nu trebuie redeclarate în clasa derivată.

În limbajul C++ încapsularea poate fi forţată prin controlul accesului, deoarece toate datele şi funcţiile
membre sunt caracterizate printr-un nivel de acces. Nivelul de acces la membrii unei clase poate fi:
 private: membrii (date şi metode) la
care accesul este private pot fi accesaţi doar
prin metodele clasei (nivel acces implicit); public
 protected: aceşti membri pot fi accesaţi protected Clasa A
prin funcţiile membre ale clasei şi funcţiile private
membre ale clasei derivate;
 public: membrii la care accesul este
public pot fi accesaţi din orice punct al
domeniului de existenţă a clasei respective;
 friend: aceşti membri pot fi accesaţi public, protected
prin funcţiile membre ale funcţiei prietene sau private
specificate.
clasă derivată Clasa B
În limbajul C++, nivelul de acces poate preciza
şi tipul de moştenire (figura 12.1.):

 Publică, unde în clasa derivată nivelul de Figura 12.1. Accesul la membrii unei clase.
acces al membrilor este acelaşi ca în clasa Moştenirea publică, protejată sau
de bază; privată
 Privată, unde membrii protected şi public
din clasa bază devin private în clasa
derivată;
 Protejată (la compilatoarele mai noi).

185
CAPITOLUL 12 Crearea ierahiilor de clase
Când o clasă moşteneşte membrii unei alte clase, membrii clasei de bază devin membrii ai clasei derivate.
Moştenirea protejată este intermediară celei publice şi celei private. În cazul moştenirii protejate, comparativ
cu moştenire privată, singura diferenţă este că membrii publici ai clasei de bază devin protejaţi în timpul
derivărilor ulterioare. În funcţie de modificatorii de acces la membrii clasei de bază, la membrii clasei
derivate şi de tipul moştenirii, lucrurile se pot rezuma astfel (tabelul 12.1.):

Tabelul 12.1.
Modificator Accesul în clasa Accesul în clasa derivată Accesul în clasa derivată
acces la membrii derivată (noul acces) (noul acces) dobândit (noul acces) dobândit prin
clasei de bază dobândit prin moştenire prin moştenire protejată moştenire privată
publică
private private private private
public public protected private
protected protected protected private

Aşa cum se observă, în toate cazurile, elementele private ale clasei de bază rămân particulare acesteia şi nu
sunt accesibile claselor derivate; cele protejate sunt accesibile clasei derivate.

12.2. MODUL DE DECLARARE A CLASELOR DERIVATE

La modul general, la declararea unei clase derivate, se specifică o listă a claselor de bază, precedate de
modificatorul de acces care precizează tipul moştenirii.
class <nume_cls_deriv>: <modificator_de_acces> <nume_clasă_de_bază>
{ //corpul clasei derivate - elemente specifice clasei derivate
};

Exemplu: Declararea clasei derivate angajat, cu clasa de bază persoana (moştenire simplă):
class persoana{
// corpul clasei de bază
};
class angajat: protected persoana{
double salariu;
};

Exemplu: Declararea clasei derivate interfaţă, cu clasele de bază fereastră şi meniu (moştenire
multiplă):
class fereastra{
//membrii clasei
};
class meniu{
//membrii clasei
};
class interfata: public fereastra, public meniu{
//membrii clasei
};

În ceea ce priveşte folosirea (compilarea şi editarea de legături) clasei derivate în sensul programării, clasa de
bază şi cea derivată pot apare în acelaţi fisier sursă, sau declarate în fişiere diferite (figura 12.1.).

12.3. CONSTRUCTORII CLASELOR DERIVATE

Constructorii şi destructorii sunt funcţii membre care nu se moştenesc. La instanţierea unui obiect din clasa
derivată se apelează mai intâi constructorii claselor de bază, în ordinea în care aceştia apar în lista din

186
CAPITOLUL 12 Crearea ierahiilor de clase
declararea clasei derivate. La distrugerea obiectelor, se apelează întâi destructorul clasei derivate, apoi
destructorii claselor de bază.
Transmiterea argumentelor unei funcţii constructor din clasa de bază se face folosind o formă extinsă a
declaraţiei constructorului clasei derivate, care transmite argumentele unui sau mai multor constructori din
clasa de bază.
În general, clasele utilizează constructori definiţi de programator. În cazul în care aceştia lipsesc,
compilatorul generează automat un constructor implicit pentru clasa respectivă. Acelaşi lucru se întâmplă şi
în cazul constructorilor de copiere.
La instanţierea unui obiect din clasă derivată, o parte din valorile primite ca parametri folosesc la iniţializarea
datelor membru ale claselor de bază, iar restul iniţializează datele membru specifice clasei derivate.

Declararea
clasei de bază Compilare
Modul obiect al
Definirea clasei de bază
clasei de bază

Editare Fişier
Declararea executa-
Modul obiect al de
clasei derivate Compilare bil
clasei derivate legături

Definirea
clasei derivate

Programul de test Modul obiect al


(utilizează cele Compilare programului de
două tipuri) test

Figura 12.1. Editarea de legături la utilizarea clasei derivate

12.4. MOŞTENIREA SIMPLĂ

Pentru a evidenţia aspectele prezentate, să considerăm următoarele exemple, în care moştenirea este simplă:

Exemplu:
Se construieşte ierarhia de clase din figura 12.2.: clasa bază

#include <iostream.h>
class bază public protected private
{
int a;
clasa deriv1 clasa deriv2 clasa deriv3
protected:
double w;
void setează_a(int a1){a=a1;}
Figura 12.2. Ierarhie de clase
void setează_w(int w1){w=w1;}
public:
int c;
baza (int a1, double w1, int c1)
{a=a1; w=w1; c=c1;cout<<"Constructor cls. bază\n";}
~bază()
{cout<<"Destructor bază\n";}
void arată()

187
CAPITOLUL 12 Crearea ierahiilor de clase
{cout<<a<<' '<<w<<' '<<c<<'\n';}
double calcul()
{return a+w+c;}
friend ostream & operator<<(ostream &, const bază &);
};

class deriv1: public bază


{
int b;
public:
deriv1 (int a1, double w1, int c1, int b1):bază(a1, w1, c1)
{b=b1; cout<<"Constructor deriv1\n";}
~deriv1()
{cout<<"Destructor deriv1\n";}
double calcul()
{return w+c+b;} // membrul a este încapsulat, nu poate fi folosit, fiind private
// o alternativă pentru obţinerea sumei tuturor datelor membre este:
// double calcul(){return bază::calcul()+b;}
friend ostream &operator<<(ostream &, const deriv1 &);
};

class deriv2: protected bază


{
int b;
public:
deriv2(int a1, double w1, int c1, int b1):bază(a1, w1, c1)
{b=b1; cout<<"Constructor deriv2\n";}
~deriv2()
{cout<<"Destructor deriv2\n";}
double calcul()
{return w+c+b;}
friend ostream &operator<<(ostream &, const deriv2 &);
};

class deriv3: private bază


{
int b;
public:
deriv3(int a1, double w1, int c1, int b1):baza(a1, w1, c1)
{b=b1; cout<<"Constructor deriv3\n";}
~deriv3()
{cout<<"Destructor deriv3\n";}
double calcul()
{return w+c+b;}
friend ostream &operator<<(ostream &, const deriv3 &);
};

ostream &operator<<(ostream &ies, const baza &b)


{ies<<b.a<<' '<<b.w<<' '<<b.c<<'\n'; return ies;}
ostream &operator<<(ostream &ies, const deriv1& d1)
{ies<<d1.w<<' '<<d1.c<<' '<<d1.b<<'\n'; // a private
return ies;}
ostream &operator<<(ostream &ies, const deriv2& d2)
{ies<<d2.w<<' '<<d2.c<<' '<<d2.b<<'\n'; // a private
return ies;}
ostream &operator<<(ostream &ies, const deriv3& d3)
{ies<<d3.w<<' '<<d3.c<<' '<<d3.b<<'\n'; // a private
return ies;}

void main()
{

188
CAPITOLUL 12 Crearea ierahiilor de clase
baza x(1, 1.23, 2); // Constructor cls. baza
deriv1 y(2, 2.34, 3, 4); // Constructor cls. baza Constructor deriv1
deriv2 z(3, 3.45, 4, 5); // Constructor cls. baza Constructor deriv2
deriv3 v(4, 5.67, 6, 7); //Constructor cls. baza Constructor deriv3
cout<<"x="<<x<<'\n'<<"z="<<z<<'\n'<<"v="<<v<<'\n';
// x=1 1.23 2 (x.a, x.w, x.c)
// z=3.45 4 5
// v=5.67 6 7
cout<<"x.calcul()="<<x.calcul()<<'\n'; // x.calcul()=4.23
cout<<"y.calcul()="<<y.calcul()<<'\n'; // y.calcul()=9.34
cout<<"z.calcul()="<<z.calcul()<<'\n'; // z.calcul()=12.45

cout<<"v.calcul()="<<v.calcul()<<'\n'; // v.calcul()=18.67
cout<<"x.c="<<x.c<<'\n'; // x.c=2
cout<<"y.c="<<y.c<<'\n'; // y.c=3
/* Destructor deriv3 Destructor baza ptr. v
Destructor deriv2 Destructor baza ptr. z
Destructor deriv1 Destructor baza ptr. y
Destructor baza ptr x */
}

Observaţii:
În clasa de bază data membră a este private, w este protected şi c este public. În clasa de bază, cât şi
în clasele derivate există constructori care iniţializează datele membru. Membrii private dintr-o clasă de
bază (clasa bază, în cazul nostru) pot fi folosiţi doar în cadrul acesteia (de metodele sale), nu şi în clasele
derivate.
Clasa deriv1:
Membrii privaţi moşteniţi din clasa bază sunt inaccesibili (a există, dar este încapsulat). Pentru a
putea fi accesaţi, se folosesc metodele clasei bază (metoda calcul). Deoarece în clasa deriv1
există o metodă cu acelaşi nume cu al unei metode din clasa de bază (redefinirea unei metode în
clasa derivată), se foloseşte operatorul de rezoluţie.
baza::calcul( ) sau y.baza::calcul( )
Clasa deriv2:
Deoarece moştenirea este protejată, membrii publici sau protejaţi din clasa bază devin protejaţi în
clasa deriv2. De aceea, dacă în funcţia main am încerca folosirea :
cout<<z.baza::calcul( ) , metoda calcul inaccesibilă, ea devenind protejată în clasa
deriv3.
Clasa deriv3:
Deoarece moştenirea este privată, membrii public sau protected din clasa bază au devenit privaţi în
clasa deriv3. Se pot folosi toţi membrii clasei de bază, cu excepţia celor privaţi (a).

La construirea unui obiect dintr-o clasă derivată din clasa bază, se apelează mai întîi constructorul din clasa
de bază, apoi constructorul clasei derivate Astfel, un obiect y din clasa deriv2 incorporează un obiect deja
iniţializat cu ajutorul constructorului din clasa bază.
Dacă pentru clasa deriv1 defineam un constructor de forma:
deriv1(int a1, double b1, int c1, int b1){a=a1; b=b1; c=c1; d=d1;}
nu era corect, deoarece clasa bază nu are constructori fără parametri, deci nu există constructor implicit, iar
data a este inaccesibilă în deriv1. Apelarea constructorului se realizează apelând explicit constructorul din
clasa bază.

Exerciţiu: Fie clasa punct şi clasa punct colorat, derivată din clasa punct. Metoda afisare este redefinită
în clasa derivată (punct_col).

#include <iostream.h>
#include <conio.h>

189
CAPITOLUL 12 Crearea ierahiilor de clase
class punct{
int x, y; //date membru private, inaccesibile în clasa punct_col
public:
punct (int abs=0, int ord=0)
{x=abs; y=ord; cout<<"Constr punct "<<x<<","<<y<<'\n';}
punct (const punct& p)
{x=p.x; y=p.y; cout<<"Constr copiere punct ";
cout<<x<<","<<y<<"\n";}
~punct()
{cout<<"Destr punct "<<x<<","<<y<<"\n";}
void afisare()
{cout<<"P("<<x<<","<<y<<")\n";}
};

class punct_col:public punct{


short cul; //date membru private
public:
punct_col (int, int, short);
punct_col (const punct_col & p):punct (p)
{cul=p.cul; cout<<"Constr copiere punct col "<<cul<<'\n';}
~punct_col()
{cout<<"Destr punct colorat "<<cul<<"\n";}
void afisare()
{cout<<"-----------------\n";
cout<<"Punct colorat:";punct::afisare();
cout<<"Culoare:"<<cul<<"\n-------------------\n";}
};

punct_col::punct_col(int abs=0, int ord=0, short cl=1):punct(abs, ord)


{cul=cl; cout<<"Constr punct colorat "<<"culoare="<<cul<<'\n';}

void main()
{clrscr();
punct_col A(10, 15, 3); //Constr punct 10,15 Constr punct colorat culoare=3
punct_col B(2,3); //Constr punct 2,3 Constr punct colorat culoare=1
punct_col C(12); //Constr punct 12,0 Constr punct colorat culoare=1
punct_col D; // Constr punct 0,0 Constr punct colorat culoare=1
D.afisare();
/*----------------- apelul metodei afisare a clasei punct_col
Punct colorat:P(0,0)
Culoare:1
------------------- */
D.punct::afisare(); // P(0,0) apelul metodei afisare a clasei punct
punct_col *pp;
pp=new punct_col(12,25); //Constr punct 12,25 Constr punct colorat culoare=1
// obiect dinamic; se apeleaza constructorii
delete pp; //Destr punct colorat 1 Destr punct 12,25
// eliberare memorie
punct P1; //Constr punct 0,0
punct P2=P1; //Constr copiere punct 0,0
punct_col C1=C; //Constr copiere punct 12,0 Constr copiere punct col 1
}
//Destr punct colorat 1 Destr punct 12,0 (pentru C1)
//Destr punct 0,0 (pentru P1)
//Destr punct 0,0 (pentru P2)
//Destr punct colorat 1 Destr punct 0,0 (pentru D)
//Destr punct colorat 1 Destr punct 12,0 (pentru C)
//Destr punct colorat 1 Destr punct 2,3 (pentru B)
//Destr punct colorat 3 Destr punct 10,15 (pentru A)

190
CAPITOLUL 12 Crearea ierahiilor de clase

:
Exerciţiu: Se implementează ierahia de clase din figura 12.3.
Clasele persoana, student, student_bursier au ca date membre persoana
date de tipul şir (implementat în capitolul 11). sir numele, prenumele
char sexul
#include "sir.cpp"
#include <conio.h>
#include <iostream.h>
class persoană { student
protected: sir facultatea, specializarea
şir numele,prenumele; int anul, grupa
char sexul;
public:
persoana () //constructor vid
{numele="";prenumele="";sexul='m'; student_bursier
cout<<"Constr PERS vid!\n";} char tipul_bursei
persoană(const şir&,const şir&,const char);
//constructor
persoană (const persoana&); //constr. copiere Figura 12.3.
virtual ~persoană(); //destructor
const şir& nume()const;
const şir&prenume() const;
char sex() const;
virtual void afişare();
friend ostream & operator<<(ostream &, const persoana &);
friend istream & operator>>(istream &, persoana &);
};

class student:public persoană {


protected:
şir facultatea,specializarea;
int anul,grupa;
public:
student(const şir&,const şir&,const char,const şir&,const şir&,const int,const
int);
student(const persoana&,const şir&,const şir&,const int,const int);
student(const student&);
virtual ~student();
const şir& facult(){return facultatea;}
const şir& spec(){return specializarea;}
int an(){return anul;}
int grup(){return grupa;}
virtual void afişare();
friend ostream & operator<<(ostream &, const student &);
/* TEMA
friend istream & operator>>(istream &, student &);*/
};

class student_bursier:public student {


protected:
char tipul_bursei;
public:
student_bursier(const student&,char);
student_bursier(const student_bursier&);
virtual ~student_bursier();
char tip_bursa() {return tipul_bursei;}
double valoare_bursa();
virtual void afişare();
//TEMA friend ostream & operator<<(ostream &, const student_bursier &);
//TEMA friend istream & operator>>(istream &, student_bursier &);

191
CAPITOLUL 12 Crearea ierahiilor de clase
};

// METODELE CLASEI PERSOANA


persoană::persoană(const şir& nume,const şir& prenume,const char sex)
{numele=nume;prenumele=prenume;sexul=sex;
cout<<"Constr. PERSOANĂ\n";}
persoana::persoana(const persoana& pers)
{ numele=pers.numele;prenumele=pers.prenumele;sexul=pers.sexul;
cout<<"Constructor copiere PERSOANA\n";}
persoană::~persoană()
{cout<<"Destructor PERSOANĂ\n";}
const şir& persoană::nume()const
{return numele;}
const şir& persoană::prenume()const
{return prenumele;}
char persoană::sex()const
{return sexul;}
void persoană::afişare()
{ cout<<"Afişare PERSOANĂ:\n";
cout<<numele<<" "<<prenumele<<" ";
if (toupper (sexul)=='M') cout<<"SEX barbatesc\n";
else cout<<"SEX femeiesc\n";}
ostream & operator<<(ostream &monitor, const persoana &p)
{monitor<<"\nNume pers:"<<p.numele<<"\nPrenume pers:"<<p.prenumele;
cout<<"\nSex pers:";
if (p.sexul=='m') cout<<"BARBATESC";
else cout<<"FEMEIESC";
return monitor;}
istream & operator>>(istream & tastat, persoana &p)
{tastat>>p.numele>>p.prenumele>>p.sexul; return tastat;}

// METODELE CLASEI STUDENT


student::student(const şir&nume,const şir&prenume,const char sex,const şir&
facult,const şir& spec,const int an,const int gr):persoana(nume,prenume,sex)
{numele=nume;prenumele=prenume;
sexul=sex;facultatea=facult; specializarea=spec; anul=an; grupa=gr;
cout<<"Construct STUD 1\n"; }
student::student(const persoana &pers,const şir& facult,const şir& spec,const
int an,const int gr):persoana(pers)
{ numele=pers.nume();prenumele=pers.prenume();
facultatea=facult; specializarea=spec;anul=an;grupa=gr;
cout<<"Construct STUD 2\n";}
student::student(const student&
stud):persoana(stud.numele,stud.prenumele,stud.sexul)
{ facultatea=stud.facultatea; specializarea=stud.specializarea;
anul=stud.anul; grupa=stud.grupa;cout<<"Construct copiere STUD!\n"; }
student::~student()
{ cout<<"Destructor student!!\n"; }
void student::afişare()
{ cout<<numele<<" "<<prenumele<<'\n';cout<<"Sex:"<<sexul<<'\n';
cout<<"Facultatea: "<<facultatea<<" Specializare: "<<specializarea<<'\n';
cout<<"Anul: "<<anul<<" Grupa:"<<grupa<<'\n'; }
ostream & operator<<(ostream &monitor, const student &s)
{monitor<<"\nNume stud:"<<s.numele<<"\nPrenume stud:"<<s.prenumele;
cout<<"\nSex stud:"<<((s.sexul=='m')?"BARBATESC":"FEMEIESC");
monitor<<"\nFacultate :"<<s.facultatea<<;
cout<<"\nSpecializare :"<<s.specializarea;
monitor<<"\nAnul :"<<s.anul<<"\nGrupa :"<<s.grupa;
return monitor;}
//TEMA friend istream & operator>>(istream &, student &);

//METODE CLASEI STUDENT_BURSIER

192
CAPITOLUL 12 Crearea ierahiilor de clase
/* TEMA
student_bursier(student&,char);
student_bursier(const student_bursier&);*/

student_bursier::student_bursier(const student&stud,char tip_burs):student(stud)


{tipul_bursei=tip_burs;}
student_bursier::student_bursier(const student_bursier
&stud):student(stud.numele,stud.prenumele,stud.sexul,stud.facultatea,stud.specia
lizarea,stud.anul,stud.grupa)
{tipul_bursei=stud.tipul_bursei;}
double student_bursier::valoare_bursa()
{ double val;
switch (tipul_bursei)
{ case 'A': val=850000; break;
case 'B': val=700000; break;
}
return val;
}
student_bursier::~student_bursier()
{cout<<"Desctructor student bursier\n";}
void student_bursier::afişare()
{ student::afişare();
cout<<"Tip bursa: "<<tipul_bursei<<" Valoare: "<<valoare_bursa()<<'\n';}

void main()
{clrscr();persoana x("POP","ION",'m'); //Constructor PERSOANA
x.afisare();cout<<'\n'; // POP ION m
cout<<"Apasa tasta...\n";getch();
persoana x1(x); //Constructor copiere PERSOANA
cout<<x1<<'\n'; //Nume pers: POP Prenume pers: ION Sex pers: BARBATESC
cout<<"Apasa tasta...\n";getch();
cout<<"Introduceti inf. despre persoana:\n";
persoana x2; //Constr PERS vid!
cin>>x2;
cout<"Inf introduse:\n";
cout<<x2;
cout<<"Apasa tasta...\n";getch();
//x1.afisare(); cout<<'\n';
student s(x, "N.I.E.", "EA", 1, 2311);
//Constructor copiere PERSOANA Construct STUD 2!
s.afisare();cout<<'\n';
/* POP ION Sex: m Facultatea: N.I.E. Specializare: EA Anul: 1 Grupa:2311 */
cout<<"Apasa tasta...\n";getch();
student s1(s); //Constr. PERSOANA Construct copiere STUD!
cout<<s1<<'\n';
/* Nume stud:POP Prenume stud:ION Sex stud:BARBATESC Facultate :N.I.E.
Specializare :EA Anul :1 Grupa :2311*/
cout<<"Apasa tasta...\n";getch();
student s3("STAN", "POPICA", 'm', "MECANICA", "I.M.T.", 1, 320);
//Constr. PERSOANA Construct STUD 1!
cout<<s1<<'\n'; /* Nume stud:POP Prenume stud:ION Sex stud:BARBATESC
Facultate :N.I.E. Specializare :EA Anul :1 Grupa :2311 */
s3=s1;
cout<<"In urma atribuirii s3="<<s3<<'\n';
/* In urma atribuirii s3= Nume stud:POPPrenume stud:ION Sex stud:BARBATESC
Facultate :N.I.E. Specializare :EA Anul :1 Grupa :2311 */
s3.afisare( );
}

Observaţii:

193
CAPITOLUL 12 Crearea ierahiilor de clase
1. Să se completeze exemplul cu funcţiile date ca temă. Să se completeze programul de test (funcţia main).
2. Funcţia afişare este declarată virtuală în clasa de bază şi redefinită în clasa derivată. Redefinirea
funcţiei în clasa derivată are prioritate faţă de definirea funcţiei din clasa de bază. Astfel, o funcţie
virtuală declarată în clasa de bază acţionează ca un substitut pentru păstrarea datelor care specifică o
clasă generală de acţiuni şi declară forma interfeţei. Funcţia afişare are acelaşi prototip pentru toate
clasele în care a fost redefinită (vezi paragraful 12.7.).

12.5. MOŞTENIREA MULTIPLĂ

O clasă poate să moştenească mai multe clase de bază, ceea ce înseamnă că toţi membrii claselor de bază vor
fi moşteniţi de clasa derivată. În această situaţie apare mecanismul moştenirii multiple. În paragraful 12.2. a
fost prezentat modul de declarare a unei clase cu mai multe superclase.
Exerciţiu: Se implementează ierahia de clase din figura 12.4.

#include <iostream.h>
class bază1 {
protected: bază2
bază1
int x;
public:
bază1 (int xx)
{x=xx;cout<<"Constructor cls. bază1\n"; derivat
cout<<x<<'\n';}
~baza1()
{cout<<"Destructor bază1\n"<<x<<'\n';} Figura 12.4. Schemă de moştenire
void aratax() multiplă
{cout<<"x="<<x<<'\n';}
};

class bază2 {
protected:
int y;
public:
bază2 (int yy)
{y=yy; cout<<"Constructor bază2\n"<<y<<'\n';}
~bază2()
{cout<<"Destructor bază2\n";}
void aratay(){cout<<"y="<<y<<'\n';}
};

class derivat: public bază1, public bază2 {


public:
derivat(int xx, int yy):bază1(xx), bază2(yy)
{cout<<"Constructor derivat\n"; cout<<x<<' '<<y<<'\n';}
~derivat()
{cout<<"Destructor derivat\n"; cout<<x<<' '<<y<<'\n';}
int arata(){cout<<x<<' '<<y<<'\n';}
void seteaza(int xx, int yy){x=xx; y=yy;}
};

void main()
{
derivat obiect(7,8);/*Constructor cls. bază1 7 Constructor bază2 8 Constructor derivat 7 8 */
obiect.arata(); // 7 8
obiect.seteaza(1,2);
obiect.aratax(); // x=1
obiect.aratay(); // y=2
obiect.arata(); // 1 2
/* Destructor derivat 1 2 Destructor bază2 2 Destructor bază1 1 */

194
CAPITOLUL 12 Crearea ierahiilor de clase
}
Aşa cum ilustrează exemplul, la declararea obiectului obiect de tipul derivat s-au apelat constructorii
claselor de bază (bază1 şi bază2), în ordinea în care apar în declararea clasei derivate: mai întâi
constructorul clasei bază1, în care x este dată membru protejată (accesibilă din clasa derivat); apoi
constructorul clasei bază2 , în care y este dată membru protejată (accesibilă din clasa derivat); apoi
constructorul clasei derivat care le încorporează pe acestea într-un singur obiect. Clasa derivat nu are date
membre, ci doar metode (figura 12.5.).
7
x
După ieşirea din blocul în care a fost declarată variabila
obiect, se apelează automat destructorii, în ordine
y 8
inversă apelării constructorilor.
obiect

Figura 12.5. Variabila obiect de tip derivat

12.6. REDEFINIREA MEMBRILOR UNEI CLASE DE BAZĂ ÎN CLASA DERIVATĂ

Aşa cum s-a observat deja din exerciţiul anterior, unii membrii (fie date membru, fie metode) ai unei clase de
bază pot fi redefiniţi în clasele derivate din aceasta.

Exemplu în care se redefinesc datele membre ale clasei de bază în clasa derivată
class bază{
protected:
double x, y;
public:
bază(double xx=0, double yy=0) {x=xx; y=yy;}
};

class deriv:public bază{


protected:
double x, y;
public:
deriv(double dx=0, double dy=0, double bx=0, double by=0): baza (bx, by)
{x=dx; // x - membru redefinit în clasa derivată
y=dy; // y - membru redefinit în clasa derivată
}
void arată() const;
};
void deriv::arată() const
{cout<<"x din clasă de bază:"<<bază::x;
cout<<"\ty din clasa de bază:"<<bază::y<<'\n';
cout<<"x din clasa derivată:"<<x;cout<<"\ty din clasa derivată:"<<y<<'\n';
}
În metoda arată a clasei deriv, pentru a face distincţie între datele membru ale clasei deriv şi cele ale
clasei bază, se foloseşte operatorul de rezoluţie.

Dacă ne întoarcem la exemplul în care implementam ierarhia de clase persoana, student,


student_bursier, remarcăm faptul că metoda afisare din clasa persoana supraîncărcată în clasele
derivate student şi student_bursier. Redefinirea unei metode a unei clase de bază într-o clasă derivată
se numeşte polimorfism.

Fie schema de moştenire prezentată în figura 12.6.

195
CAPITOLUL 12 Crearea ierahiilor de clase
La declararea unui obiect din clasa D, membrii clasei A (int a) sunt moşteniţi de obiectul din clasa D în dublu
exemplar (figura 12.7.). Pentru a evita această situaţie, clasa A va fi declarată virtuală, pentru clasele
derivate B şi C. Metoda arată este redefinită în clasele B,C, D (polimorfism) (vezi exerciţiul următor şi
figura 12.8.).

Clasa A x (obiect din clasa D)


int a
obiect din clasa B obiect din clasa C

Clasa B Clasa C obiect din clasa A obiect din clasa A


int b int c
int a int a
int b int c
Clasa D
int d
int d
Figura 12.6.
Figura 12.7.

Exerciţiu:
#include <iostream.h>
class A{
int a;
public:
A(int aa)
{a=aa; cout<<"Constructor A"<<a<<'\n';}
~A()
{cout<<”Destructor A”;}
void arată()
{cout<<"A.a="<<a<<'\n';}
};

class B: virtual public A{


int b;
public:
B(int bb, int aa=0):A(aa)
{b=bb;cout<<"Constructor B"<<b<<'\n';}
~B()
{cout<<”Destructor B”;}
void arata()
{cout<<"B.b="<<b<<'\n';}
};

class C: virtual public A{


int c;
public:
C (int cc, int aa=0):A(aa)
{c=cc; cout<<"Constructor C"<<c<<'\n';}
~C()
{cout<<”Destructor C”;}
void arata()
{cout<<"C.c="<<c<<'\n';}
};

class D: public B, public C{


int d;

196
CAPITOLUL 12 Crearea ierahiilor de clase
public:
D(int aa, int bb, int cc, int dd):A(aa), B(bb), C(cc)
{d=dd;cout<<"Constructor D"<<d<<'\n';}
~D()
{cout<<”Destructor D”;}
void arată()
{cout<<"D.d="<<d<<'\n';}
};
void main()
{
D x(1,2,3,4); /* Constructor A1 Constructor B2 Constructor C3 Constructor D4 */
x.arată(); // apelul metodei arată din clasa D, pentru obiectul x D.d=4
x.B::arată(); // apelul metodei arată din clasa B B.b=2
x.C::arată(); // apelul metodei arată din clasa C C.c=3
x.A::arată(); // apelul metodei arată din clasa A A.a=1
} /* Destructor D Destructor C Destructor B Destructor A */

x - obiect din clasa D

obiect din clasa A


int a

obiect din clasa B obiect din clasa C


int b int c

int d

Figura 12.8. Clasa A, clasă virtuală


Exerciţiu: Fie următorul program de test pentru ierarhia de clase din figura 12.6., în care A este clasă
virtuală.
void main()
{
A u(10); // Constructor A 10 - ptr. obiectul u, de tp A
B v1(9, 7); // Constructor A 9 Constructor B 7 - ptr. obiectul v1, de tip B
C v2(8,12); // Constructor A 8 Constructor C 12 - ptr. obiectul v2, de tip C
D w(31, 9, 14, 35);// Constructor A 31 Constructor B 9 Constructor C 14
// ConstructorD 35 - ptr. obiectul w, de tip D
// Se apelează metoda arată, pentru obiectul curent
u.arată(); // A.a=10
v1.arată(); // B.b=7
v2.arată(); // C.c=12
w.arată(); // D.d=35
}
/* Destructor D Destructor C Destructor B Destructor A - ptr. obiectul w
Destructor C Destructor A - ptr. obiectul v2
Destructor B Destructor A - ptr. obiectul v1
Destructor A - ptr. obectul u */

Aşa cum se observă din exemplu, metoda arată din clasa de bază A a fost redefinită în clasele derivate B,
C, D. În plus, metoda are aceeaşi semnătură în toate clasele. Dacă nu ar fi fost redefinită în clasele derivate,
metoda arată (publică) din clasa de bază A ar fi fost moştenită de clasele derivate; redefinirea a fost
necesară pentru a putea vizualiza şi datele membre proprii claselor derivate. În cazul de faţă, identificarea

197
CAPITOLUL 12 Crearea ierahiilor de clase
metodei apelate se realizează chiar în etapa compilării, datorită legăturii cu obiectul pentru care a fost
apelată. De exemplu, la apelul w.arată() se aplică metoda din clasa D (obiectul w este de tip D).

Concluzionând, identificarea unei metode din clasa de bază redefinite în clasele derivate, se face prin una din
modalităţile:
 Diferenţele de semnătură ale metodei redefinite;
 Prin legătura cu obiectul asupra căruia se aplică metoda (vezi apelurile metodei arată pentru obiectele
u, v1, v2, w);
 Prezenţa operatorului de rezoluţie (de exemplu, dacă pentru obiectul w se doreşte apelarea metodei arată
din clasa B, apelul va acea forma: w.A::arată(); ).

Un pointer către o clasă de bază poate primi ca valoare adresa unui obiect dintr-o clasă derivată (figura
12.9.). În această situaţie, se apelează metoda din clasa pointerilor, şi nu din clasa obiectului spre care
pointează pointerul.
Pentru exemplificare, vom considera ierarhia de clase (A, B, C, D) ulterioară, şi programul de test:

void main()
{
A u(10), *PA; // Constructor A 10
B v1(9, 7), *PB; // Constructor A 9 Constructor B 7 - ptr. v1
C v2(8,12), *PC; // Constructor A 8 Constructor C 12 - ptr. v2
D w(31, 9, 14, 35), *PD; // Constructor A 31 Constructor B 9
// Constructor C 14 Constructor D 35
PA=&u; PA->arată(); // Se selectează metoda arată din clasa A A.a=10
PA=&v1; PA->arată(); // Se selectează metoda arată din clasa A A.a=9
PB=&v1; PB->arată(); // Se selectează metoda arată din clasa B B.b=7
PA=&w; PB=&w; PD=&w;
u.arată(); // Apelul metodei arată ptr. obiectul curent, clasa A A.a=31
PA->arată(); // Se selectează metoda arată din clasa A A.a=31
PB->arată(); // Se selectează metoda arată din clasa B B.b=9
PD->arată(); // Se selectează metoda arată din clasa D D.d=35
}

u
Aşa cum se observă din exemplu, pa a=10 A.a=10
pa, pb, pc şi pd sunt pointeri de
tipurile A, B, C, respectiv D: v1
A *pa;B *pb;C *pc;D *pd;
a=9 B.b=7
În urma atribuirii pb sau:
pa=&v1; b=7 B v1(9, 7)
pointerul pa (de tip A) va conţine
adresa obiectului v1 (de tip B).
Apelul metodei arată redefinite v2
în clasa derivată B
pa->arată(); pc a=8 C.c=12
sau:
va determina selecţia metodei din c=12 C v2(8, 12)
clasa pointerului (A), şi nu a
metodei din clasa obiectului a cărui
adresa o conţine pointerul. w

a=31
D.d=35
pd
sau:
b=9 c=14
D w(31,9,14,35)
d=35
198
Figura 12.9. Un pointer către o clasă de bază iniţializat cu
adresa unui obiect dintr-o clasă derivată
CAPITOLUL 12 Crearea ierahiilor de clase

În toate cazurile prezentate anterior, identificarea metodei redefinite se realizează în faza de compilare. Este
vorba de o legare inţială, "early binding", în care toate informaţiile necesare selectării metodei sunt
prezentate din timp şi pot fi utilizate din faza de compilare.

12.7. METODE VIRTUALE

Aşa cum s-a subliniat, un pointer la o clasă de bază poate primi ca valoare adresa unui obiect dintr-o clasă
derivată. Deci, având un tablou de pointeri la obiecte de tip A, putem lucra cu tablouri de obiecte eterogene,
cu elemente de tipuri diferite (B, C sau D). În unele situaţii, informaţiile privind tipul obiectului la care
pointează un element al tabloului sunt disponibile abia în momentul execuţiei programului. O rezolvare a
identificării metodei în momentul execuţiei programului o constituie funcţiile virtuale.

Identificarea unei metode supradefinite, în momentul execuţiei, se numeşte legare ulterioară, "late
binding".

Dacă dorim ca selectarea metodei arată, din exemplul anterior, să se realizeze în momentul execuţiei,
metoda va fi declarată metodă virtuală .
Exemplu:
class A {
public:
virtual void arată();
//în loc de void arată()
// . . . .
};
class B : virtual public A {
public:
virtual void arată();
//în loc de void arată()
// . . . .
};
class C : virtual public A {
public:
virtual void arată();
//în loc de void arată()
// . . . .
};
class B : public B, public C{
public:
virtual void arată();
//în loc de void arată()
// . . . .
};

În urma acestei modificări, rezultele execuţiei programului anterior ar fi fost:

void main()
{
A u(10), *PA; // Constructor A 10
B v1(9, 7), *PB; // Constructor A 9 Constructor B 7 - ptr. v1
C v2(8,12), *PC; // Constructor A 8 Constructor C 12 - ptr. v2
D w(31, 9, 14, 35), *PD; // Constructor A 31 Constructor B 9

199
CAPITOLUL 12 Crearea ierahiilor de clase
// Constructor C 14 Constructor D 35
PA=&u; PA->arată(); // Se selectează metoda arată din clasa A A.a=10
PA=&v1; PA->arată(); // Se selectează metoda arată din clasa B B.b=7
PB=&v1; PB->arată(); // Se selectează metoda arată din clasa B B.b=7
PA=&w; PB=&w; PD=&w;
u.arată(); // Apelul metodei arată ptr. obiectul curent, clasa A A.a=10
PA->arată(); // Se selectează metoda arată din clasa D D.d=35
PB->arată(); // Se selectează metoda arată din clasa D D.d=35
PD->arată(); // Se selectează metoda arată din clasa D D.d=35
}

Observaţie:
1. Deoarece metoda arată este virtuală, s-a selectat metoda pentru clasa obiectului spre care pointează
pointerul.
2. Dacă în clasa de bază se declară o metodă virtuală, în clasele derivate metodele cu aceeaşi semnatură
vor fi considerate implicit virtuale (chiar dacă ele nu sunt declarate, explicit, virtuale).

În cazul unei funcţii declarate virtuală în clasa de bază şi redefinite în clasa derivată, redefinirea metodei în
clasa derivată are prioritate faţă de definirea ei din clasa de bază. Astfel, o funcţie virtuală declarată în clasa
de bază actionează ca un substitut pentru păstrarea datelor care specifică o clasă generală de acţiuni şi
declară forma interfeţei. La prima vedere, redefinirea unei funcţii virtuale într-o clasă derivată pare similară
cu supraîncărcarea unei funcţiei obişnuite. Totuşi, nu este aşa, deoarece prototipul unei metode virtuale
redefinite trebuie să coincidă cu cel specificat în clasa de bază. În cazul supraîncărcării unei funcţii normale,
caracteristicile prototipurilor trebuie să difere (prin tipul returnat, numărul şi/sau tipul parametrilor).

Exerciţiu: Fie ierahia de clase din figura 12.10. Metoda virtuală virt_f , din clasa bază, este redefinită în
clasele derivate.

#include <iostream.h>
class baza{ bază
public:
bază()
{cout<<"Constructor bază\n";}
derivat1 derivat2
~bază()
{cout<<"Destructor bază\n";}
virtual void virt_f()
{cout<<"Metoda virt_f() din bază\n";} derivat1a derivat2a
};
class derivat1: public baza{
public: Figura 12.10. Ierarhie de clase
derivat1():baza()
{cout<<"Constructor derivat1\n";}
~derivat1()
{cout<<"Destructor derivat1\n";}
virtual void virt_f()
{cout<<"Metoda virt_f() din derivat1\n";}
};
class derivat2: public baza{
public:
derivat2():baza()
{cout<<"Constructor derivat2\n";}
~derivat2()
{cout<<"Destructor derivat2\n";}
virtual void virt_f()
{cout<<"Metoda virt_f() din derivat2\n";}
};
class derivat1a: public derivat1{
public:
derivat1a():derivat1()

200
CAPITOLUL 12 Crearea ierahiilor de clase
{cout<<"Constructor derivat1a\n";}
~derivat1a()
{cout<<"Destructor derivat1a\n";}
virtual void virt_f()
{cout<<"Metoda virt_f() din derivat1a\n";}
};
class derivat2a: public derivat2{
public:
derivat2a():derivat2()
{cout<<"Constructor derivat2a\n";}
~derivat2a()
{cout<<"Destructor derivat2a\n";}
virtual void virt_f()
{cout<<"Metoda virt_f() din derivat2a\n";}
};
void main()
{ baza *p; //Constructor bază
baza b; //Constructor bază
derivat1 d1; // Constructor bază Constructor derivat1
derivat2 d2; // Constructor bază Constructor derivat2
derivat1a d1a; // Constructor bază Constructor derivat1 Constructor derivat1a
derivat2a d2a; // Constructor bază Constructor derivat2 Constructor derivat2a
p=&b; p->virt_f(); // Metoda virt_f() din bază
p=&d1;p->virt_f(); // Metoda virt_f() din derivat1
p=&d2;p->virt_f(); // Metoda virt_f() din derivat2
p=&d1a;p->virt_f(); // Metoda virt_f() din derivat1a
p=&d2a;p->virt_f(); // Metoda virt_f() din derivat2a
}
// Destructor derivat2a Destructor derivat2 Destructor bază (pentru d2a)
// Destructor derivat1a Destructor derivat1 Destructor bază (pentru d1a)
// Destructor derivat2 Destructor bază (pentru d2)
// Destructor derivat1 Destructor bază (pentru d1)
// Destructor bază (pentru b)

Exerciţu: Fie ierarhia de clase din figura 12.11. Se prezintă o modalitate de lucru cu un tablou eterogen, cu
5 elemente, care conţine pointeri atât spre clasa baza, cât şi spre clasele derivat1 şi derivat2. Pentru a
putea trata în mod uniform cele trei tipuri de obiecte, s-a creat clasa lista_eterogena. Aceasta are ca dată
membru pointerul la tipul baza şi metoda afis (virtuală, redefinită în clasele derivate).

#include <iostream.h>
#include <conio.h>
class baza{
protected: baza
int val;
public:
baza()
{cout<<"Constructor baza\n";} derivat1 derivat2
~baza()
{cout<<"Destructor baza\n";}
void set_val(int a) Figura 12.11.
{val=a;}
virtual void afis()
{cout<<"Element baza="<<val<<"\n";}
};
class derivat1: public baza{
public:
derivat1():baza()
{cout<<"Constructor derivat1\n";}
~derivat1()

201
CAPITOLUL 12 Crearea ierahiilor de clase
{cout<<"Destructor derivat1\n";}
void afis()
{cout<<"Element derivat1="<<val<<"\n";}
};
class derivat2: public baza{
public:
derivat2():baza()
{cout<<"Constructor derivat2\n";}
~derivat2()
{cout<<"Destructor derivat2\n";}
void afis()
{cout<<"Element derivat2="<<val<<"\n";}
};
class lista_eterogena {
baza *p;
public:
void set_l(baza *pp) {p=pp;}
void afis() {p->afis();}
};

void main()
{ clrscr();
baza B[3]; //Constructor baza Constructor baza Constructor baza
// (pentru elementele tabloului B, de tip baza

derivat1 D1; //Constructor baza Constructor derivat1 (pentru D1, de tip derivat1)
derivat2 D2; //Constructor baza Constructor derivat2 (pentru D2, de tip derivat2)
lista_eterogena L[5];
cout<<"Apasa o tasta. . .\n";getch();
B[0].set_val(10); B[1].set_val(100); B[2].set_val(1000);
D1.set_val(444); D2.set_val(555);
L[0].set_l(&B[0]); //L[0].set_val(B);
L[1].set_l(&D1);
L[2].set_l((baza*) &D2);
L[3].set_l(&B[1]);
//L[3].set_l(B+1);
//L[4].set_l(&B[2]);
L[4].set_l(B+2);
for (int i=0; i<5; i++) L[i].afis();
/*Element baza=10 Element derivat1=444 Element derivat2=555
Element baza=100 Element baza=1000*/
}

În cazul unei ierarhii de clase şi a unei metode virtuale a clasei de bază, toate clasele derivate care moştenesc
această metodă şi nu o redefinesc, o moştenesc întocmai. Pentru aceeaşi metodă moştenită şi redefinită în
clasele derivate, selecţia se realizează în momentul executării programului (legarea târzie).
Funcţiile virtuale nu pot fi metode statice ale clasei din care fac parte.
Funcţiile virtuale nu pot fi funcţii prietene sau constructori, dar pot fi destructori. Destructorii virtuali sunt
utili în situaţiile în care se doreşte distrugerea uniformă a unor masive de date eterogene.

Metode virtuale pure


În unele situaţii, o clasă de bază (din care se derivează alte clase) a unei ierarhii, poate fi atât de generală,
astfel încât unele metode nu pot fi descrise la acest nivel (atât de abstract), ci doar în clasele derivate. Aceste
metode se numesc funcţii pure . Metodele virtuale pure sunt metode care se declară, nu se definesc la acest
nivel de abstractizare. O metodă virtuală pură trebuie să fie prezentă în orice clasă derivată.
Exemple:
class bază{
public:
virtual void virt_f()=0; }; //metoda virt_f este o metodă virtuală pură
class vieţuitoare {

202
CAPITOLUL 12 Crearea ierahiilor de clase
public:
virtual void nutriţie()=0; }; //metoda nutriţie este o metodă virtuală pură

O clasă cu cel puţin o metodă virtuală pură se numeşte clasă abstractă (clasa vieţuitoare este abstractă
şi, ca urmare, nu poate fi instanţiată).

ÎNTREBĂRI ŞI EXERCIŢII

Chestiuni teoretice

1. Ce este o clasă derivată şi ce caracteristici 7. Modul de declarare a unei clase derivate, cu


are? mai multe superclase.
2. Funcţiile prietene pot fi funcţii virtuale? 8. Ce este o metodă virtuală ?
3. Destructorii se moştenesc? 9. Funcţiile virtuale pot fi membrii statici ai
4. Ce este o clasă virtuală şi în ce situaţii este clasei din care fac parte ?
utilă? 10. Redefinirea unei funcţii virtuale într-o clasă
5. Ce este o metodă virtuală pură şi cum se derivată este similară cu supraincarcarea
declară aceasta? funcţiei respective? Argumentati răspunsul.
6. Explicaţi ce înseamnă legarea iniţială (early 11. Care este utilitatea moştenirii?
binding). 12. Explicaţi ce înseamnă legarea ulterioară (late
binding).

Chestiuni practice

1. Să se implementeze ierarhia de clase din figura 12.12., cu


persoana
membrii pe care îi consideraţi necesari.

2. Concepeţi o ierarhie de clase a figurilor geometrice.


Ca date membre pot fi considerate poziţia, dimensiunile angajat student
şi atributele de desenare (culoare, tip linie). Metodele
vor permite operaţii de afişare, deplasare, ştergere,
bugetar student
modificarea atributelor figurii. Clasa de bază va avea bursier
proprietăţile generale ale oricărei figuri: coordonatele (cu salariul
pe ecran şi vizibilitate. de bază fix)

3. Din clasa matrice, să se deriveze clasa c_matrice, care muncitor


reprezintă o matrice de complecşi. (salariu în acord
global: nr_ore*
tarif_oră)

Figura 12.12.

203
CAPITOLUL 12 Crearea ierahiilor de clase

204
CAPITOLUL 13
Intrări/ieşiri

INTRĂRI/IEŞIRI 1
13.1. Principiile de bază ale sistemului de 13.4. Metodele clasei istream
I/O din limbajul C++ 13.5. Metodele clasei ostream
13.2. Testarea şi modificarea stării unui flux 13.6. Manipulatori creaţi de utilizator
13.3. Formatarea unui flux 13.7. Fluxuri pentru fişiere
13.3.1. Formatarea prin manipulatori 13.8. Fişiere binare
13.3.2. Formatarea prin metode

13.1. PRINCIPIILE DE BAZĂ ALE SISTEMULUI DE INTRĂRI/IEŞRI


DIN LIMBAJUL C++

În limbajul C++ există două sisteme de intrări/ieşiri:


 Unul tradiţional, moştenit din limbajul C, în care operaţiile de intrare/ieşire se realizează cu ajutorul unor
funcţii din biblioteca standard a sistemului (vezi capitolul 8);
 Unul propriu limbajului C++, orientat pe obiecte.

Sistemul de I/O orientat pe obiecte al limbajului C++ tratează în aceeaşi manieră operaţiile de I/O care
folosesc consola şi operaţiile care utilizează fişiere (perspective diferite asupra aceluiaşi mecanism).

La baza sistemului de I/E din C++ se află:


 două ierarhii de clase care permit realizarea operaţiilor de I/O;
 conceptul de stream (stream-ul este un concept abstract care înglobează orice flux de date de la o sursă
(canal de intrare) la o destinaţie (canal de ieşiere), la un consumator. Sursa poate fi tastatura (intrarea
standard), un fişier de pe disc sau o zonă de memorie. Destinaţia poate fi ecranul (ieşirea standard), un
fişier de pe disc sau o zonă de memorie (figura 13.1).
Fluxuri din Fluxuri din
clasa istream clasa ostream

Memoria internă

Fluxuri din Fluxuri din


clasa clasa
ifstream fişiere ofstream

Figura 13.1. Stream-ul - baza sistemului de I/O

Avantajele utilizării stream-urilor sunt:


 Flexibilitate mare în realizarea operaţiilor de I/O (mai mare decât în cazul folosirii funcţiilor din C);
 Posibilitatea de eliminare a erorilor care apar în mod frecvent la apelul funcţiilor de I/O obişnuite, când
numărul specificatorilor de format diferă de cel al parametrilor efectivi;

205
CAPITOLUL 13
Intrări/ieşiri
 Posibilitatea de a realiza operaţii de I/O nu numai cu date de tipuri predefinite, ci şi cu obiecte de tip
abstract.
Biblioteca de clase de I/O a limbajului C++ utilizează moştenirea, polimorfismul şi clasele abstracte. Ierarhia
are două clase de bază (figura 13.2.):
 Clasa virtuală ios care oferă informaţii despre fluxurile de date (variabile de stare, metode) şi facilităţi
tratarea erorilor;
 Clasa streambuf (clasă prietenă cu ios), destinată operaţiilor de I/O cu format.

ios streambuf

istream ostream strstreambase fstreambase

iostream

ifstream ofstream

fstream filebuf

Figura 13.2. Ierarhia claselor de intrare/ieşire


Utilizarea ierarhiilor de clase de mai sus implică includerea headerului iostream.h.
 Clasa ios are un pointer către streambuf. Are date membru pentru a gestiona interfaţa cu streambuf şi
pentru tratarea erorilor. Clasele derivate din clasa de bază ios:
 Clasa istream, care gestionează intrările: class istream:virtual public ios
 Clasa ostream gestionează ieşirile: class ostream: virtual public ios
 Clasa iostream, derivată din istream şi ostream, gestionează intrările şi ieşirile. Fiecărui flux
de date i se asociază în memorie o zonă tampon numită buffer. Clasa furnizează funcţii generale
pentru lucrul cu zonele tampon şi permite tratarea operaţiilor de I/O fără a avea în vedere
formatări complexe.
class iostream:public istream, public ostream
Clasele istream, ostream şi iostream sunt, fiecare, clase de bază pentru clasele derivate:
class istream_withassign:public istream
class ostream_withassign:public ostream
class iostream_withassign:public iostream

Clasele cu sufixul _withassign furnizează următoarele fluxurile predefinite (instanţe), deja cunoscute:
1. cout (console output) obiect al clasei ostream_withassign, similar fişierului standard de ieşire
definit de pointerul stdout (în C). Se foloseşte cu operatorul insertor, supraîncărcat pentru tipurile
predefinite:
Exemplu: int n; cout<<n;
Converteşte din format binar în zecimal valoarea lui n, şi trimite (inserează) în fluxul cout
caracterele corespunzătoare fiecărei cifre obţinute.
2. cin (console input) obiect al clasei istream_withassign, similar fişierului standard de intrare
definit de pointerul stdin (în C). A fost folosit cu operatorul extractor, supraîncărcat pentru tipurile
predefinite:
Exemplu: int n; cin>>n;

206
CAPITOLUL 13
Intrări/ieşiri
Extrage din fluxul de intrare caracterele corespunzătoare, le converteşte din zecimal în binar şi le
depune în memorie.
3. cerr: flux de ieşire conectat la ieşirea standard pentru erori (stderr în C) (fără buffer intermediar).
4. clog: flux de ieşire conectat la ieşirea standard pentru erori (fără buffer intermediar).
 Clasa streambuf este clasă prietenă cu ios. Ea furnizează funcţii generale pentru lucrul cu
zonele tampon (buffere) şi permite tratarea operaţiilor de I/O fără formatări complexe. Din clasa
streambuf deriva clasa filebuf .

Să urmărim care este rolul buffere-lor (zonă tampon asociată fiecărui flux de date), prin următorul exemplu:

Exemplu: Memorie internă


void main()
{int a; double x; char sir[20];
cin>>a>>x>>şir; a
cout<<a<<' '<<x<<endl; date de x
cout<<sir<<endl; intrare
}
sir

Zona tampon (buffer-ul) este interfaţa buffer cin


dintre program şi sistemul de operare.
buffer cout

Să urmărim cum se realizează transferul informaţiei


în cazul operaţiilor multiple: Figura 13.3. Rolul buffere-lor în operaţiile de I/O
cin>>a>>x>>şir;
cin>>a;
Compilatorul verifică dacă numărul introdus este întreg (se opreşte când întâlneşte altceva decât o cifră: în
acest caz - un blanc). Îl citeşte printr-o metodă a tipului int, îl converteşte în binar şi îl transmite în memoria
internă în spaţiul rezervat pentru variabila a. În încheiere, cin>>a devine cin.
cin>>a>>x; // fluxul de date se transmite şi lui x
Presupunem că am introdus de la tastatură 325 –17.45e5 exemplu de şir, valorile pentru a, x şi
sir. La apăsarea tastei Enter, se transmite către sistemul de operare un octet cu semnificaţia de sfârşit
introducere date. Sistemul de operare transmite un semnal zonei tampon şi abia acum se transmite valoarea
(fluxul de date) lui a. La citirea şirului de caractere (până la primul blank), se transferă octet cu octet (i se
adaugă automat caracterul NULL), din zona tampon în memoria internă.

Observaţii:
1. Stream-ul este secvenţial.
2. El poate realiza intrări/ieşiri formatate.
3. Este bine că în cazul unui flux de intrare să se verifice mai întâi dacă în zona tampon se găseşte ceva.
Dacă nu, se poziţionează cursorul la începutul zonei tampon.
4. Informaţia de la buffer către memorie este gestionată prin program; informaţia de la zona tampon către
alte periferice este gestionată de către sistemul de operare.

13.2. TESTAREA ŞI MODIFICAREA STĂRII UNUI FLUX

Clasa ios, clasă de bază a ierahiei claselor de I/O, defineşte o mulţime de tipuri, variabile şi metode
comune tuturor tipurilor de stream-uri.

Starea unui stream (rezultatul ultimului acces la acesta) este păstrată în cuvântul de stare, care este dată
membră a clasei ios. Fiecărei instanţieri a unei clase de intrare/ieşire i se asociază propriul cuvânt de stare

207
CAPITOLUL 13
Intrări/ieşiri
(o mulţime de indicatori de stare), care păstrează toate informaţiile aferente erorilor apărute în cursul
operaţiilor cu stream-ul. Aceşti indicatori sunt memoraţi la nivel de bit în data membru state.

class ios{
//……
protected:
int state; //păstrează la nivel de bit valorile indicatorilor de stare
public:
enum io_state{
goodbit=0x00, //ultima operaţie de intrare/ieşire corectă
eofbit=0x01, //s-a întâlnit sfârşitul de fişier într-o operaţie de intrare (lipsa
//caracterelor disponibile pentru citire)
failbit=0x02, //setat dacă ultima operaţie de intrare/ieşire a eşuat; stream-ul
//respectiv nu va mai putea fi folosit în operaţii de I/O până când
//bitul nu va fi şters
badbit=0x04, //ultima operaţie de intrare/ieşire invalidă; în urma resetării flag-ului
//este posibil ca stream-ul să mai poată fi utilizat
hardbit=0x08 //eroare irecuperabilă
};
};

Testarea valorii cuvântului de stare asociat unui stream se realizează cu ajutorul metodelor:
 int good();//Returnează valoare diferită de zero dacă cuvântul de stare este 0
 int eof();//Returnează valoare diferită de zero dacă eofbit este setat
 int fail();//Returnează valoare diferită de zero dacă failbit, hardbit sau badbit sunt setaţi
 int bad();//Funcţionează ca metoda fail, dar nu ia în considerare flagul failbit.

Modificarea valorii cuvântului de stare asociat unui stream se realizează cu ajutorul metodelor:
 void clear(int i=0);
Setează data membru state la valoarea parametrului (implicit 0). Este capabilă să şteargă orice flag, cu
excepţia flag-ului hardfail.
Exemplu:: a.clear(ios::badbit);
Setează bitul failbit şi anulează valorile celorlaţi biţi din cuvântul de stare a fluxului a. Pentru ca
ceilalţi biţi să rămână nemodificaţi, se apelează metoda rdstate.
Exemplu: a.clear(a.rdstate()|val_f);
Se setează un singur flag, lăsându-le nemodificate pe celelalte; val_f are una din valorile definite de
enumerarea io_state.
 int rdstate();
Returnează valoarea cuvântului de stare, sub forma unui întreg (valoarea datei membru state).
Exemplu: a.clear(ios::badbit | a.rdstate());
Setează bitul failbit, fără a modifica valorile celorlalţi biţi:

Testarea cuvântului de stare se poate realiza şi prin folosirea operatorilor ! şi void* :


 Operatorul ! este supraîncărcat printr-o funcţie membră, cu prorotipul:
int operator ! ();
care returnează 0 dacă un bit de eroare din cuvântul de stare este setat (ca şi metoda fail).
Exemplu:
if (!cin) //echivalent cu if (!cin.good())
cout<<"Bit de eroare setat în cuvântul de stare!\n";
else cin>>a;
 Operatorul void* converteşte stream-ul într-un pointer generic. Conversia are ca rezultat zero dacă cel
puţin un bit de eroare este setat:
operator void *();

208
CAPITOLUL 13
Intrări/ieşiri
Exemplu: O construcţie de forma: cin>>s; are ca valoare o referinţă la stream-ul cin, din
clasa istream. Această referinţă poate fi utilizată sub forma: if (cin>>s). . .

Pentru scoaterea unui stream din starea de eroare, fie dispare cauza erorii, fie trebuie şterse flagurile
care semnalizează eroarea.

13.3. FORMATAREA DATELOR DIN FLUXURILE DE INTRARE/IEŞIRE

Unul dintre principalele avantaje oferite de sistemul de I/O din limbajul C++ îl reprezintă ignorarea
aspectului formatării, folosindu-se o formatare implicită. În plus, se permite definirea unei formatări
specifice pentru o anumită aplicaţie. Aşa cum s-a subliniat în cazul cuvântului de eroare, cuvântul de format
poate fi privit ca un întreg, pentru care fiecare bit reprezintă o constantă predefinită din clasa ios. În cadrul
acestui cuvânt sunt definite câmpuri de biţi (cuvântul de format este dată membră care conţine un număr
de indici ce sunt biţi individuali).

class ios {
//……
protected:
long flag_x; //păstrează la nivel de bit indicatorii de format
int x_width; //numărul de caractere utilizate pentru afişarea unei valori pe ecran
public:
enum
{skipws = 0x0001, // salt peste spaţiile albe de la intrare
left = 0x0002, // aliniere la stânga la ieşire
right = 0x0004, // aliniere la dreapta la iesire
internal = 0x0008, // aliniere după semn sau specificator al bazei la ieşire
dec = 0x0010, // conversie în baza 10
oct = 0x0020, // conversie octală la intrare/ieşire
hex = 0x0040, // conversie hexa la intrare/ieşire
showbase = 0x0080, // afişarea bazei la ieşire
showpoint = 0x0100, // afişarea punctului zecimal pentru numere reale la ieşire
uppercase = 0x0200, // afişarea cu majuscule a cifrelor hexa şi a literei E la ieşire.
showpos = 0x0400, // afişarea semnului + pentru numerele pozitive la ieşire
scientific = 0x0800, //folosirea formatului exponenţial (ştiinţific) pentru numerele reale
fixed = 0x1000, // folosirea formatului în virgulă fixă pentru numere reale la
unitbuf = 0x2000, // goleşte zona tampon după fiecare ieşire
stdio=0x4000 // goleşte "stdout" şi "stdin" după fiecare inserare
};
};
În figura 13.4. sunt prezentate numele câmpurilor de biţi (acolo unde este cazul). În cadrul fiecărui câmp de
biţi (adjustfield, basefield, floatfield) un singur bit poate fi activ.

floatfield
floatfield basefield adjustfield
scientific

showpoint
uppercase

showbase

internal
unitbuf

showpos

skipws
fixed

right

left
oct

dec
hex

209
Figura 13.4. Câmpurile de biţi din cuvântul de stare
CAPITOLUL 13
Intrări/ieşiri

Modificarea cuvântului de format se poate realiza în următoarele moduri:


1. Cu ajutorul manipulatorilor (cu sau fără parametri);
2. Cu ajutorul unor funcţii membre ale claselor istream sau ostream.

13.3.1. FORMATAREA PRIN MANIPULATORI

Manipulatorii sunt funcţii speciale, asemănătoare operatorilor, care pot fi folosite împreună cu operatorii de
inserţie într-un flux de ieşire sau de extracţie dintr-un flux de intrare, în scopul modificării caracteristicilor
formatului informaţiilor de intrare/ieşire. Manipulatorii furnizează, ca rezultat, fluxul obţinut în urma acţiunii
manipulatorilor, ceea ce permite ca aceştia să fie trataţi ca informaţii de transmis. Manipulatorii permit,
deasemenea, înlănţuirea operatorilor insertori sau extractori care utilizează formate diferite.

Manipulatorii pot fi:


 Manipulatori fără parametri;
 Manipulatori cu parametri;

13.3.1.1. Manipulatori fără parametri

Prototipul manipulatorilor fără parametri este:


ostream & nume_manipulator(ostream &);
istream & nume_manipulator(istream &);
Manipulatorii fără parametri (prezentaţi în tabelul 13.1.) se folosesc astfel:
flux_ieşire<<manipulator;
flux_intrare>>manipulator;

Tabelul 13.1
Manipula Intrare/ Acţiune
tor Ieşire
dec I/O Formatează datele numerice în zecimal (activează bitul de conversie zecimală)
hex I/O Formatează datele numerice în hexa (activează bitul de conversie hexazecimală)
oct I/O Formatează datele numerice în octal (activează bitul de conversie octală)
ws I Ignoră caracterele "spaţii albe" (activează bitul de salt peste spaţiile albe)
endl O Afişează (inserează) un caracter '\n' şi eliberează fluxul
ends O Inserează un caracter null, de sfârşit de flux (\0)
flush O Videază (goleşte) buffer-ul, eliberează fluxul

13.3.1.2. Manipulatori cu parametri

Prototipul manipulatorilor fără parametri (prezentaţi în tabelul 13.2.) este:


istream & manipulator (argument);
ostream & manipulator (argument);

Tabelul 13.2.
Manipulator Intrare/ Acţiune
Ieşire
setbase(int baza) I/O Stabileşte baza de conversie
resetiosflags(long f) I/O Atribuie valoarea 0 tuturor biţilor indicaţi de argument,

210
CAPITOLUL 13
Intrări/ieşiri
lăsând restul biţilor nemodificaţi (dezactivează indicatorii
specificaţi de f)
setiosflags (long f) I/O Atribuie valoarea 1 tuturor biţilor indicaţi de argument,
lăsând restul biţilor nemodificaţi (activează indicatorii
specificaţi de f)
setfill (int c) I/O Defineşte caracterul de umplere (cel implicit este spaţiul
liber, blank-ul)
setprecision (int p) I/O Defineşte precizia pentru numerele reale
setw (int w) I/O Defineşte lăţimea câmpului (numărul de octeţi care vor fi
citiţi sau afisaţi)
Utilizarea manipulatorilor impune includerea header-ului iomanip.h.

Exerciţiu: Exemplificarea modului de folosire a manipulatorilor pentru intrări/ieşiri formatate.

#include <iostream.h>
#include <iomanip.h>
void main()
{int a,b,c;double alfa,x,y;long ll;char xx,yy;
float u,v; unsigned int w; unsigned char ab,db;
a=125,b=10; ll=100*a*b; cout<<"100*a*b="<<ll<<endl; //100*a*b=-6072
alfa=75.50;y=1.345678;
xx=35;yy=6; x=y/alfa-xx/yy; cout<<"x="<<x<<endl; //x=-4.982176
//setarea mărimea câmpului de afişare
cout<<"x="<<setw(8)<<x<<endl; //x=-4.982176
cout<<"x="<<setw(20)<<x<<endl; //x= -4.982176
//setarea lungimii câmpului de afişare şi a caracterului de umplere
cout<<"x="<<setw(20)<<setfill('*')<<x<<endl; //x=***********-4.982176
// precizie
cout<<"x="<<setw(8)<<setprecision(2)<<x<<endl; //x=***-4.98
cout<<"x="<<setw(8)<<setprecision(0)<<x<<endl; //x=-4.982176
// afisarea octetului semnificativ var & 0377 si a celui mai putin semnificativ (var>>8) & 0377
w=34; ab='34' & 0377; db=('34'>>8)&0377;
cout<<"w="<<w<<" ab="<<ab<<" db="<<db<<endl; //w=34 ab=3 db=4
u=2579.75;v=12345.567E+10;
//formatare in octal si hexa
cout<<"a in baza 10="<<a<<" in octal="<<oct<<a<<" in hexa="<<hex<<a<<endl;
//a in baza 10=125 in octal=175 in hexa=7d
cout<<"b="<<dec<<b<<endl; //b=10
}

13.3.2. FORMATAREA PRIN METODE

Formatarea fluxurilor de intrare/ieşire se poate realiza şi prin metode ale clasei ios.

 Funcţia membră setf permite modificarea cuvântului de stare a formatării. Ea este supradefinită astfel:
long setf (long) ;
Primeşte ca parametru un număr întreg long şi setează pe 1 biţii specificaţi prin argument. Returnează
valoarea anterioară a cuvântului de stare a formatării. Ea poate fi folosită numai pentru flag-urile care nu
fac parte dintr-un câmp de biţi.
Exemplu: cout. setf (ios:: showpos); //setează bitul showpos pe 1
long setf (long flags, long field) ;
Setează pe 1 biţii specificaţi prin primul argument, doar în câmpul de biţi definit ca al doilea argument.
Ea modifică pe 0 toţi biţii din câmpul respectiv, după care setează (pe 1) bitul respectiv.
Exemple:

211
CAPITOLUL 13
Intrări/ieşiri
cout. setf (ios:: showpos | ios :: uppercase | ios:: internal)
//toţi biţii vor fi setaţi pe valoarea 1
cout.setf (ios :: hex, ios :: basefield) //setează indicele hex din câmpul basefield
cout. setf (ios :: skipws | ios :: hex | ios:: showbase, ios :: basefield)
 Funcţia membră fill permite testarea sau modificarea caracterului de umplere şi returnează codul
caracterului curent de umplere:
int fill ( ) ;
Exemplu: cout.fill ( ); // întoarce codul caracterului de umplere pt. cout
int fill (char) ;
Setează noul caracter de umplere şi returnează codul vechiului caracter de umplere.
Exemplu: cout.fill(‘# ’); // setează un alt caracter de umplere ('#').
 Funcţia membră precision permite testarea sau modificarea preciziei. Numim precizie numărul de
cifre semnificative (în GNU) sau numărul de cifre după virgulă (în TurboC)
int precision ( );
Returnează valoarea actuală a preciziei numerice.
int precision (int);
Setează precizia la valoarea parametrului şi returnează vechea valoare a preciziei.
 Funcţia membră width returnează valoarea actuală a lungimii câmpului de date sau setează valoarea
lungimii câmpului de date.
int width ( ) ;
.int width (int) ;
Exemplu: cout. width (10); //data se va afişa într-un câmp de cel puţin 10 caractere.
Astfel, dacă argumentul este 0, la inserţia într-un flux de ieşire se transmit în stream atâţia octeţi câţi are
data respectivă. Dacă argumentul este diferit de 0, dar lungimea necesară afişării este mai mare, se
transmite numărul de octeţi necesari; iar dacă lungimea necesară afişării este mai mică, se transmite
numărul de octeţi necesari, iar restul se completează cu caracterul de umplere.

Exerciţiu:
#include <iostream.h>
void main()
{int i=123, j=456; double x=1234.567890123456, y=5678;
cout.width(5); cout<<i<<' '<<j<<endl; //123 456
cout<<i; //toate metodele sunt persistente, cu excepţia metodei width
cout.width(5); cout<<j<<endl; //123 456
cout.setf(ios::showpos); //afis. +
cout<<i<<' '<<j<<endl; //+123 +456
cout.setf(ios::hex, ios::basefield);
cout.width(20); cout.precision(15); cout.fill('*');
cout.setf(ios::showpos | ios::showpoint);
cout<<x<<' '<<y<<endl; //***+1234.56789012346 +5678.00000000000
cout<<i<<' '<<j<<endl; //7b 1c8
cout.setf(ios::dec, ios::basefield);
cout.unsetf(ios::showpos); cout.fill(' ');
cout<<i<<' '<<j<<endl; //123 456
cout<<x<<' '<<y<<endl; //1234.56789012346 5678.00000000000
}

Observaţie: Toate metodele sunt persistente, cu excepţia metodei width care este valabilă numai pentru
operaţia următoare, după care se setează la 0.

13.4. METODELE CLASEI istream

Clasa istream este derivată din clasa ios: ios:istream.


 Supradefinirea operatorului de extracţie >>

212
CAPITOLUL 13
Intrări/ieşiri
Operatorul de extracţie din stream-ul (fluxul) de intrare este supraîncărcat printr-o funcţie membră,
pentru toate tipurile de bază. El are rolul de a extrage din fluxul de intrare caracterele necesare pentru a
obţine o valoare dintr-un tip de bază.
istream & operator >> (&tip_de_bază);
Primul argument este implicit (this): clasa care îl apelează. Al doilea argument este o referinţă către un
tip_ de_bază. Operatorul poate fi supraîncărcat printr-o funcţie prietenă (vezi capitolul 11), pentru a
extrage din fluxul de intrare informaţiile despre un obiect dintr-un tip definit de utilizator (clasă):
friend istream & operator >>(istream &, clasa &);
 Metoda get
istream & get (char &);
Metoda extrage din fluxul de intrare un caracter şi îl memorează în variabila referinţă, transmisă ca
parametru. Întoarce o referinţă către istream.
Exemplu: char c; cin.get(c);
Metoda get este supraîncărcată şi astfel:
int get ( );
Extrage din fluxul de intrare un singur caracter şi îl returnează sub forma unui întreg. Această metodă
este utilizată, în special, la testarea sfârşitului de fişier (EOF, -1).
Metoda get este supraîncărcată şi astfel:
istream & get (char * şir, int lungime, char delimitator = ‘\n’)
Se extrage din fluxul de date de intrare un şir de caractere (char * şir = pointer către şir), cu
lungimea maximă specificată prin argumentul lungime, până la întâlnirea delimitatorului
(delimitator nu se extrage din fluxul de date de intrare). Rezultatul se depune în variabila şir.
 Metoda getline
Metoda determină preluarea din fluxul de intrare a unui şir de caractere, terminat cu un caracter
cunoscut.
istream & getline (char * şir, int lungime, char delimitator = EOF);
Acţionează asemănător cu metoda get supraîncărcată prin ultima formă, cu deosebirea că din fluxul de
intrare se extrage şi delimitatorul. Delimitatorul nu se introduce în şir.
Exemplu: char şir[50];cin.get (şir, 50, ’\ n’); // sau cin.get(şir, 50);
Din fluxul de date de intrare se extrag caracterele până la sfârşit de linie, cel mult 50 de caractere.
Extragerea caracterelor din fluxul de intrare se termină fie la întâlnirea terminatorului, fie atunci când
s-a citit un număr de caractere egal cu lungime-1 .
 Metoda gcount returnează un întreg care reprezinată numărul efectiv de caractere preluat prin
getline.
int gcount();
 Metoda read extrage din fluxul de intrare un şir de caractere (octeţi) de lungime impusă şi-l
depozitează în zona de memorie şir.
istream & read (char * şir, int lungime);
Eemplu: char t[30]; cin.read(t, 20);
 Metoda ignore este utilizată la golirea zonei tampon a stream-ului de intrare.
istream & ignore (int lungime, char delimitator = ‘ | ’);
Se extrag din fluxul de intrare caracterele până la delimitator, dar nu mai multe decât numărul indicat de
parametru lungime. Caracterele extrase sunt eliminate, nu sunt memorate.
 Metoda peck furnizează primul caracter din fluxul de intrare, fără a-l extrage însă din flux. Caracterul
va fi primul caracter extras la următoarea citire.
int peck( );
Exemplu: c = cin.peck ( );
 Metoda putback inserează în fluxul de intrare caracterul trimis ca argument.
istream & putback(char &);
Observaţii:
1) int a; cin.get (a) ⇔ cin >> a;
2) Metodele pot fi folosite în serie: Exemplu: cin.get(a).get(b).get (c);

213
CAPITOLUL 13
Intrări/ieşiri
13.5. METODELE CLASEI ostream

 Supradefinirea operatorului de inserţie <<


Operatorul de inserţie în fluxul de ieşire este supraîncărcat printr-o funcţie membră pentru toate tipurile
de bază. El are rolul de a introduce (insera) în fluxul de ieşire caracterele necesare pentru a afişa o
valoare dintr-un tip de bază.
ostream & operator << (tip_de_bază);
Primul argument este implicit (this). Al doilea argument este o expresie de tip de bază.
Operatorul poate fi supraîncărcat printr-o funcţie prietenă, pentru a insera în fluxul de ieşire informaţiile
despre un obiect dintr-un tip definit de utilizator (clasă):
friend ostream & operator << (ostream &, clasa &);
 Metoda put inserează în fluxul de date de ieşire caracterul transmis ca parametru.
ostream put (char);
Exemple:
char c='S'; cout . put ( c ); // echivalent cu cout << c;
char c1='A',c2='B',c3='C';cout.put(c1).put(c2).put(c3);
//echivalent cu:cout.put(c1);cout.put(c2); cout.put(c3);
// echivalent cu cout<<c1<<c2<<c3;
 Metoda write inserează în fluxul de date de ieşire un număr de caractere, de lungime impusă,
existente în zona tablou.
ostream & write (char * tablou, int lungime);
Exemplu: char t[]="Bună ziua!\n"; cout.write(t, 4);
//Inserează în fluxul de ieşire 4 caractere, începând de la adresa t.

Exerciţiu: Se ilustrează formatarea unui flux de intrare/ieşire atât prin funcţii membre, cât şi cu ajutorul
manipulatorilor (cu sau fără parametri).
#include <iostream.h>
#include <iomanip.h>
void main()
{int i=123; char car, mesaj[]=" Apasă tasta Enter\n";
cout<<setw(5)<<resetiosflags(ios::internal |ios::right);
cout<<setiosflags(ios::left)<<setfill('#')<<i<<endl;
cout<<setw(5)<<setiosflags(ios::showpos)<<setfill(' ')<<i<<endl;
cout<<setw(5)<<resetiosflags(ios::left)<<setfill('0')<<setiosflags(ios::right);
cout<<i<<endl;
cout<<"\n hexagesimal:"<<setw(5)<<hex<<i<<endl;
cout<<"\n octal:"<<setw(5)<<oct<<i<<endl;
cout<<"\n zecimal:"<<setw(5)<<dec<<i<<endl;
cout<<mesaj; cin.get(car);
double d=123.456789012345678901234567890123456789;
cout<<"Afisare d cu precizia implicită:"<<d<<endl;
cout<<"Afisare d cu lung 25 si precizie
15:"<<setw(25)<<setprecision(15)<<d<<endl;
cout<<"Schimbare caracter umplere &:"<<setw(25)<<setfill('&')<<d<<endl;
cout<<endl;
}

13.6. MANIPULATORI CREAŢI DE UTILIZATOR

Manipulatorii sunt funcţii speciale care pot fi utilizate alături de operatorii de inserţie/extracţie pentru a
formata datele de ieşire/intrare. Pe lângă manipulatorii predefinţi, utilizatorul îşi poate crea manipulatori
proprii.

Crearea manipulatorilor fără parametri


ostream &nume_manipulator (ostream &); //pentru un flux de ieşire

214
CAPITOLUL 13
Intrări/ieşiri
istream &nume_manipulator (istream &); //pentru un flux de intrare
Crearea manipulatorilor cu parametri
Crearea manipulatorilor fără parametri necesită folosirea a două funcţii:
ostream & nume_manipulator (ostream &, int);

omanip <int> nume_manipulator (int n) // funcţie şablon (template) cu parametru de tip int
{return omanip <int> (nume_manipulator, n);}

Deci, manipulatorii sunt funcţii ale căror corpuri sunt stabilite de utilizator.

Exemplu: În exemplul următor se definesc manipulatorii indentare (indentare = înaintea liniei propriu-
zise, se lasă un număr de spaţii albe), convhex (realizează conversia din zecimal în hexa), init
(iniţializează lungimea câmpului la 10 caractere, stbileşte precizia la 4 şi caracterul de umplere $).

#include <iostream.h>
#include <iomanip.h>
ostream &convhex (ostream &ies)
{ies.setf(ios::hex, ios::basefield); ies.setf(ios::showbase); return ies;}
ostream &indentare (ostream &ies, int nr_spaţii)
{for (int i=0; i<nr_spatii; i++) ies<<' '; return ies;}
ostream &init (ostream &ies)
{ies.width(10); stream.precision(4); ies.fill('$'); return ies;}
omanip <int> indentare (int nr_spaţii)
{return omanip <int> (indentare, nr_spaţii);}
void main()
{cout<<712<<' '<<convhex<<712<<endl;
cout<<indentare(10)<<"Linia1 text\n"<<indentare(5)<<"Linia2 text\n";
cout init<<123.123456;}

13.7. FLUXURI DE DATE PENTRU FIŞIERE

Un flux de intrare/ieşire poate fi asociat unui fişier.

Pentru a asocia un flux de ieşire unui fisier, este necesară crearea unui obiect de tipul ofstream. Clasa
ofstream este derivată din clasele ostream (moşteneşte metodele care permit inserarea informaţiilor
într-un flux de ieşire) şi fstreambase (moşteneşte operaţiile privitoare la asocierea fişierelor).

Pentru a asocia un flux de intrare unui fişier, este necesară crearea unui obiect de tipul ifstream. Clasa
ifstream este derivată din clasele istream (moşteneşte metodele care permit extragerea informaţiilor
dintr-un flux de intrare) şi fstreambase (moşteneşte operaţiile privitoare la asocierea fişierelor).

Pentru crearea unor obiecte din clasele ifstream sau ofstream se impune includerea headere-lor
iostream.h şi fstream.h.

Constructorii clasei ofstream sunt:


ofstream();
Fluxul nu este asociat nici unui fişier
ofstream(const char *nume_fisier, int mod_acces=ios:out);
Parametri constructorului sunt numele fişierului şi modul de acces la acesta (scriere, fişier de ieşire).
ofstream (int fd, char *buffer, int lung_buffer);
Parametrii sunt numărul intern al fisierului (fd), zona tampon de intrări/ieşiri (buffer) şi lungimea zonei
tampon (lung_buffer).

Constructorii clasei ifstream sunt:


ifstream();

215
CAPITOLUL 13
Intrări/ieşiri
Fluxul nu este asociat nici unui fişier
ifstream(const char *nume_fisier, int mod_acces=ios:in);
Parametri constructorului sunt numele fişierului şi modul de acces la acesta (citire, fişier de intrare).
ifstream (int fd, char *buffer, int lung_buffer);
Parametrii sunt numărul intern al fişierului (fd), zona tampon de intrări/ieşiri (buffer) şi lungimea zonei
tampon (lung_buffer).

Exemplu:
Se declară obiectul numit fis_ies, de tipul ofstream, obiect asociat unui fişier cu numele DATE.txt,
deschis apoi pentru ieşire (scriere). Scrierea în fişierul asociat obiectului se face printr-un flux care
beneficiază de toate "facilităţile" clasei ostream.
ofstream fis_ies("DATE.txt", ios::out); //sau: ofstream fis_ies("DATE.txt");
Scrierea în fişierul DATE.txt: fis_ies<< . . . << . . .;
Pentru scriere formatată sau scriere binară în fişierul asociat: fis_ies.write(. . . );
Pentru examinarea stării fluxului de eroare corespunzător: if (fis_ies) . . .;

Se declară obiectul numit fis_intr, de tipul ifstream, obiect asociat unui fişier cu numele NOU.txt,
deschis apoi pentru intrare (citire). Citirea din fişierul asociat obiectului se face printr-un flux care
beneficiază de toate "facilităţile" clasei istream.
ifstream fis_intr("DATE.dat", ios::in); //sau: ifstream fis_intr("NOU.txt");
Citirea în fişierul NOU.txt: fis_intr>> . . . >> . . .;
Pentru citire formatată sau citire binară din fişierul asociat: fis_intr.read(. . . );
Pentru examinarea stării fluxului de eroare corespunzător: if (fis_intr) . . .;

Observaţii: Conectarea (asocierea) unui flux unui fişier presupune:


 Fie existenţa a două tipuri de obiecte: un flux şi un fişier;
 Fie declararea unui flux care va fi asociat ulterior unui fişier.

Clasa fstream moşteneşte atât clasele ifstream, cât şi ofstream. Ea permite accesul în citire/scriere.
Constructorul cu parametri al clasei fstream:
fstream(char *nume_fişier,int mod_deschid,int protec=filebuf::openprot);

Modul de deschidere (mod_deschid) a unui fişier poate fi:


ios::in fişier de intrare
ios::out fişier de ieşire
ios::ate după deschiderea fişierului, poziţia curentă este sfârşitul acestuia
ios::app mod append (de scriere la sfârşitul unui fişier existent)
ios::trunc dacă fişierul există, datele existente se pierd
ios::nocreate dacă fişierul asociat nu există, nu va fi creat decât la scriere
ios::binary fişier binar
ios::noreplace fişierul nu trebuie să existe

Modul de deschidere este definit printr-un cuvânt de stare, în care fiecare bit are o semnificaţie particulară.
Pentru setarea (activarea) mai multor biţi, se foloseşte operatorul | (sau logic, pe bit), ca în exemplu.
class ios{
//……..
public:
enum open_mode{
in=0x01, out=0x02, ate=0x04,
app=0x08, trunc=0x10, nocreate=0x20,
noreplace=0x40, binary=0x80
};
};

216
CAPITOLUL 13
Intrări/ieşiri
Exemple: ifstream f1("DATE.txt", ios::in|ios::nocreate);
fstream f2("pers.dat", ios::in|ios::out);
 Metoda open
Prelucrarea unui fişier începe cu deschiderea acestuia, care se poate realiza:
 La instanţierea obiectelor din clasele ifstream, ofstream sau fstream, cu ajutorul constructorului cu
parametri (constructorul apelează automat funcţia open);
 Prin apelul explicit al funcţiei open.
Metoda open este definită în clasa fstreambase şi este supraîncărcată în funcţiile derivate din
aceasta. Ea are aceeaşi parametri ca şi constructorul clasei şi prototipul:
void open (char *nume_fişier, int mod_deschidere, int protecţie);

 Metoda close realizează închiderea unui fişier:


void close();

Exerciţiu: În exemplul următor se asociază fluxului de ieşire (obiectului fis_ies) fişierul numit
text.txt. Se testează cuvântul de stare de eroare, iar dacă nu au fost probleme la deschidere, în fişier se
scrie un text (2 linii), apoi, o serie de constante numerice, formatate. Se închide fişierul. Acelaşi fişier este
asociat unui flux de intrare (pentru citire). Textul (scris anterior) este citit în variabila şir, apoi este afişat.
Se citesc din fişier şi apoi sunt afişate constantele numerice. Se închide fişierul.

#include <fstream.h>
#include <iomanip.h>
int main()
{ofstream fis_ies("text.txt"); //deschidere fişier text.txt
if (!fis_ies){ cout<<"Eroare la dechiderea fişierului!\n"; return 1;}
fis_ies<<"Scrierea unui text \n în fişier\n";
fis_ies<<100<<hex<<setw(10)<<100<<setw(20)<<setprecision(12);
cout<<123.456789012356789<<endl;
fis_ies.close(); //închiderea fişierului
ifstream fis_intr("text.txt"); //deschiderea fis. ptr. citire
if (!fis_intr){cout<<"Eroare la deschiderea fis. ptr. citire!\n";return 1;}
char sir[100];
for (int k=0; k<9; k++) {fis_intr>>sir; cout<<sir<<'\n';}
cout<<endl; int i, j; double u; fis_intr>>i>>hex>>j>>u;
cout<<i<<' '<<j<<' '<<u<<endl; fis_intr.close(); return 0; }

Observaţii:
Deschiderea şi închiderea fişierului s-ar fi putut realiza şi astfel:
fstream fis_ies;fis_ies.open("text.txt", ios::out);fis_ies.close();
fstream fis_in;fis_in.open("text.txt", ios::in);fis_in.close();

Exemplu: Să se scrie un program care concatenează două fişiere, depunând rezultatul în al treilea fişier. Se
crează trei fluxuri, f1, f2 şi dest, care sunt ataşate fişierelor corespunzătoare (prin metoda open). Copierea
în fişierul destinaţie se realizează prin folosirea metodelor get, put şi funcţia copy. Metoda close
întrerupe legătura logică dintre fluxuri şi fişiere.

#include <iostream.h>
#include <process.h>
#include <fstream.h>
void semn_er(char *c)
{ cerr<<"\n\n Eroare la deschiderea fisierului "<<c<<endl;exit(1); }
void copiere(ofstream &dest, ifstream &sursa)
{char C;
while (dest && sursa.get(C))
dest.put(C); }
void main()

217
CAPITOLUL 13
Intrări/ieşiri
{char nume_sursa1[14], nume_sursa2[14], destinatie[14];
ifstream f1, f2; ofstream dest;
cout<<"Numele celor 3 fisiere:"<<"Destinatie Sursa1 Sursa2\n";
cin.read(destinatie,13);cin.read(nume_sursa1,13);cin.read(nume_sursa2,13);
destinatie[13]=nume_sursa1[13]=nume_sursa2[13]='\0';
f1.open(nume_sursa1, ios::nocreate);
if (!f1) semn_er(nume_sursa1); //utiliz operator ! redefinit
f2.open(nume_sursa2, ios::nocreate);
if (!f2) semn_er(nume_sursa2); //utiliz operator ! redefinit
dest.open(destinatie);
if (!dest) semn_er(destinatie); //utiliz operator ! redefinit
copiere (dest, f1); copiere(dest, f2);
f1.close();f2.close();dest.close();
}

Exerciţiu: Se ilustrează folosirea fluxurilor pentru operaţii de citire/scriere în fişiere text.

#include <fstream.h>
#include <iostream.h>
void main()
{ ofstream fisierout("nou.txt"); // Deschide fisierul text nou.txt..
fisierout << " Teste fisiere "; //Scrie un text in fisier
fisierout.close(); // Inchide fisierul.
// Deschide un alt fisier text si scrie in acesta niste numere (numerele sunt separate prin spatii)
fisierout.open("numere.txt"); fisierout <<15<<" "<<42<<" "<<1;fisierout.close();
// Deschide fisierul nou.txt pentru citire.
ifstream fisierin; fisierin.open("nou.txt");
char p[50]; // Stabileste o zona de memorie folosita pentru citirea textului.
fisierin >>p; // Citeste si afiseaza primele doua cuvinte din fisier.
cout <<p<<" "; fisierin >>p; cout <<p<<endl;
fisierin.close(); // Inchide fisierul.
int numar; fisierin.open("numere.c");
// Deschide fisierul text numere.c pentru citirea numerelor intregi
// Citeste numere din fisier pana cand ajunge la sfarsitul fisierului.
while (!fisierin.eof())
{fisierin >> numar; // Citeste un numar intreg.
if (!fisierin.eof()) cout<<numar<<endl;
// Daca nu a ajuns la sfarsitul fisierului afiseaza numarul.
}
fisierin.close(); // Inchide fisierul.
// se citeste textul din fisierul nou.txt cuvant cu cuvant.
fisierin.open("nou.txt");
while (!fisierin.eof()) // Citeste cuvinte din fisier pana ajunge la sfarsitul fisierului.
{ // Citeste cuvant cu cuvant.
fisierin >>p; cout <<p<<endl;
}
}

13.8. FIŞIERE BINARE

Fişierele binare reprezintă o succesiune de octeţi, asupra cărora la intrare sau la ieşire nu se realizează nici o
conversie. Ele sunt prelucrate binar, spre deosebire de exemplul anterior, în care prelucrarea se realiza în
mod text (un număr în format intern este reprezentat binar, dar în format extern el apare ca un şir de
caractere. Un şir în formatul extern are terminatorul '\n', iar în format intern are terminatorul '\0'). Deoarece

218
CAPITOLUL 13
Intrări/ieşiri
nu există un terminator de linie, trebuie specificată lungimea înregistrării. Metodele write, read nu
realizează nici o conversie.

 Metoda write scrie în fişier un număr de n octeţi:


ostream &write(const char * tab, int n);
 Metoda read citeşte din fişier n octeţi.
istream &read (const char * tab, int n);

Limbajul C++ oferă (ca şi limbajul C) posibilitatea de acces direct într-un fişier conectat la un flux de date.
După fiecare operaţie de citire (extragere din fluxul de intrare) sau citire (inserţie în fluxul de ieşire),
pointerul care indică poziţia curentă în fluxul de date este incrementat cu un număr egal cu numărul de octeţi
transferaţi. În această situaţie se realizează un acces secvenţial (ca în cazurile precedente).

Clasele ifstream şi ofstream au ca metode funcţiile seekg (membră a clasei istream), respectiv
seekp (membră a clasei ostream) care permit modificarea valorii pointerului.
istream & seekg(long);
istream & seekg(long, seek_dir);
ostream & seekp(long);
ostream & seekp(long, seek_dir);

Ambele metode primesc doi parametri:


 un întreg reprezentând deplasarea pointerului în raport cu baza (referinţa) precizată de al doilea
parametru;
 o constantă întreagă care precizează baza. Valorile constantei sunt definite în clasa ios:

class ios{ //. . .


public:
enum seek_dir{
beg=0, deplasare faţă de începutul fişierului (implicit)
cur=1, deplasare faţă de poziţia curentă
end=2 deplasare faţă de sfârşitul fişierului
};
};

Pentru aflarea valorii pointerului, clasa ifstream are metoda tellg, iar clasa ofstream are metoda
tellp, cu prototipurile:
long istream::tellg();
long ostream::tellp();

Exemplu: Se crează ierarhia de clase din figura 13.5., în care se implemen-


tează clasa student şi clasa proprie fisier_stud, clasă class fstream
derivată din fstream. În acelaşi fişier, binar,
se realizează şi scrierea şi citirea. clase prietene
Datorită posibilităţii de acces direct,
se pot modifica informaţiile referitoare class student class fisier_stud
la studentul înregistrat în fişier pe poziţia k.
Figura 13.5. Ierarhia de clase
#include <fstream.h>
#include <iomanip.h>
class student
{ char nume[20];
int grupa, note[3];
public:
void citire_date();
friend ostream &operator<<(ostream &, const student &);

219
CAPITOLUL 13
Intrări/ieşiri
friend class fisier_stud;
};

class fisier_stud:public fstream //fişier binar cu obiecte din cls student


{public:
fisier_stud(){;};
fisier_stud(const char*num_f,int mod,int
protecţie=filebuf::openprot):fstream(num_f,mod |ios::binary,protecţie){}
void open(const char *num_f, int mod, int protectie=filebuf::openprot)
{fstream::open(num_f,mod|ios::binary,protectie);}//apelul met. open din cls. fstream
int citeste_f(student &);
int scrie_f (const student &);
};

void student::citire_date()
{ int gr, OK;
for (int k=0; k<21; k++) nume[k]=0;
cin.ignore(1000, '\n'); cout<<"Numele studentului:";
cin.get(nume, 21);cin.ignore(1000, '\n');
do{
cout<<"Grupa:"; cin>>gr; OK=(cin && gr>0 && gr<8000);
if (!OK){
cout<<"Eroare. Repetaţi introducerea!\n"; cin.clear(); }
else {grupa=gr; cin.ignore(1000, '\n');}
}while (!OK);
for (int k=0; k<3; k++){
int n;
do{
cout<<"nota "<<k+1<<":"; cin>>n; OK=cin && n>0 && n<=10;
if (!OK){
cout<<"Nota gresită.Repetati!\n";cin.clear();cin.ignore(1000, '\n');
}
}while (!OK);
note[k]=n;cin.ignore(1000, '\n');
}
}
ostream &operator << (ostream &ies, const student &stud)
{ies<<"Student:"<<stud.nume<<" Grupa:"<<stud.grupa<<" Note:";
for (int k=0; k<3; k++) ies<<stud.note[k]<<' ';
ies<<endl;return ies;}

typedef union {student s; char sbin[sizeof(student)];} stud;

int fisier_stud::scrie_f(const student &s)


{stud s1; s1.s=s; write(s1.sbin, sizeof(student)); return bad(); }

int fisier_stud::citeste_f(student &s)


{stud s1; read(s1.sbin, sizeof(student)); s=s1.s; return bad();}

main()
{char Nume_Fis[]="Stud_bin.bin"; student s; fisier_stud fs; int gata;
//deschidere fişier ptr. scriere
fs.open (Nume_Fis, ios::out);
if (!fs){
cout<<"eroare la deschiderea fişierului "<<Nume_Fis<<" ptr. scriere\n";
return 1;}
cout<<"Poz. în fiş la deschidere:"<<fs.tellp()<<endl; gata=0;
while(!gata)
{char rasp;
cout<<"Datele studentului:\n";s.citire_date();cout<<"Date introduse:"<<s<<endl;
do{

220
CAPITOLUL 13
Intrări/ieşiri
cout<<"Scrieţi date în fişier [D|N] ?";
rasp=toupper(cin.get());cin.ignore(1000, '\n');
} while (răsp!='D' && răsp!='N');
if (răsp == 'D'){
fs.scrie_f(s); cout<<"Poz. în fişier: "<<fs.tellp()<<endl;}
do{
cout<<"Continuaţi introducerea [D|N]?";răsp=toupper(cin.get());
cin.ignore(1000, '\n');
} while (răsp!='D' && răsp!='N');
if (răsp=='N') gata=1;
}
fs.close();
//citire
fs.open(Nume_Fis, ios::in);
if (!fs){
cout<<"Eroare la deschiderea fiş-lui "<<Nume_Fis<<" ptr. citire\n";
return 1; }
cout<<"Poz. în fiş la deschidere:"<<fs.tellg()<<endl;
cout<<"Date citite din fişier:\n"; int k=0;
while (!fs.eof())
{ fs.citeşte_f(s);
if (!fs && !fs.eof()){ cout<<"Eroare la citire\n";return 1; }
if (!fs.eof()){cout<<s<<endl;cout<<"Poz în fişier:"<<fs.tellg();
cout<<endl;k++;}
}
cout<<"S-au citit din fişier:"<<k<<" studenţi\n";
fs.close();

fs.open(Nume_Fis, ios::in | ios::out); //deschidere fisier actualizare (intr/ies)


if (!fs){
cout<<"Eroare la deschidere fis. "<<Nume_Fis<<" ptr. citire/ scriere\n";
return 1;
}
cout<<"Daţi numărul stud-lui pentru care vreţi să inlocuiţi datele:";cin>>k;
cin.ignore(1000, '\n'); fs.seekg(k*sizeof(stud));s.citire_date();fs.scrie_f(s);
fs.seekg(0); k=0;
while(!fs.eof()){
fs.citire_f(s);
if (!fs && !fs.eof()){cout<<"Eroare la citirea din fiş:"<<Nume_Fis<<endl;
return 1; }
if (!fs.eof()){
cout<<s<<endl;cout<<"Poz în fişier:"<<fs.tellg()<<endl;k++;}
}
cout<<"S-au gasit în fişier:"<<k<<" studenţi\n";
fs.close();
return 0;
}

ÎNTREBĂRI ŞI EXERCIŢII

Chestiuni teoretice

1. Ce înţelegeţi prin conceptul de stream? 3. Ce sunt manipulatorii?


2. Care consideraţi că ar fi avantajele utilizării 4. Cum îşi poate crea utilizatorul manipulatorii
abordării orientate pe obiecte a sistemului de proprii?
I/O?

221
CAPITOLUL 13
Intrări/ieşiri
Chestiuni practice

Rezolvaţi problemele din capitolul 8, folosind abordarea orientată pe obiecte.

222
Bibliografie

BIBLIOGRAFIE

[1] Borland C++ 4, Ghidul programatorului, Editura Teora, 1996


[2] Brookshear, J.G., Introducere în informatică, Editura Teora, Bucureşti, 1998
[3] Bumbaru, S., Note de curs
[4] Somnea, D., Turturea, D., Iniţiere în C++ - Programarea orientată pe obiecte, Editura Tehnică,
Bucureşti, 1993
[5] Spircu, C., Lopătan, I., POO-Analiza, proiectarea şi programarea orientate spre obiecte,
Editura Teora, Bucureşti, 1996
[6] Stroustrup, B., A Beginners C++, www.cs.uow.edu.av/people/nabg/ABC/ABC.html
[7] Stroustrup, B., C++ Annotations, www.icce.rug.nl/docs/cplusplus/cplusplus.html

223
MOSTENIRE
Mostenirea permite crearea ierarhiilor de clase, deci a ierarhiilor de concepte (Un concept poate fi implementat
printr-o clasa. Clasele grupeaza obiecte de acelasi tip; reprezinta aceeasi idee, acelasi concept).
Aceasta proprietate se manifesta prin faptul ca din orice clasa putem deriva alte clase.

Class A

Class B Class C Class D

Class E Class F

A – clasa de baza;
B, C, D – clase derivate din clasa de baza A
E, F – clase derivate din clasa de baza B
Informatia comuna apare in clasa de baza, iar informatia specifica - in clasa derivata. Clasa derivata reprezinta o
specializare a clasei de baza. Orice clasa derivata mosteneste datele membru si metodele clasei de baza. Deci
acestea nu trebuie redeclarate in clasa derivata.

Mostenirea poate fi: simpla (orice clasa are o singura superclasa) sau multipla (o clasa are mai multe superclase).

Mostenirea simpla

Declararea unei clase derivate


class <nume_clasa_derivata>: <modificator_de_acces> <nume_clasa_de_baza>
{ //corpul clasei derivate - elemente specifice clasei derivate
};

cls. de baza

In functie de modificatorii de acces care apar in clasa de baza, in declararea clasei derivate si in clasa derivata,
lucrurile se pot rezuma astfel:

Modificator acces in clasa de Modificator de acces Accesul in clasa derivata la


baza (protectie) din declararea elementul mostenit de la clasa
clasei derivate de baza
private private, protected, public inaccesibil
protected sau public private private
public protected protected
protected protected protected
protected public protected
public public public

1
MOSTENIRE
Constructorii claselor derivate
Constructorii si destructorii sunt functii membre care nu se mostenesc. La instantierea unui obiect din
clasa derivata se apeleaza mai intai constructorul clasei de baza, apoi constructorul clasei derivate. La
distrugerea obiectelor, se apeleaza intati destructorul clasei derivate, apoi destructorul clasei de baza.
Transmiterea argumentelor unei functii constructor din clasa de baza se face folosind o forma extinsa a
declaratiei constructorului clasei derivate, care transmite argumentele unui sau mai multor constructori din clasa
de baza.
In general, clasele utilizeaza constructori definiti de programator. In cazul in care acestia lipsesc,
compilatorul genereaza automat un constructor implicit pentru clasa respectiva. Acelasi lucru se intampla si in
cazul constructorilor de copiere.
La instantierea unui obiect din clasa derivata, o parte din valorile primite ca parametri folosesc la
initializarea datelor membru ale claselor de baza, iar restul initializeaza datele membru specifice clasei derivate.
In exemplul urmator, este construita urmatoarea ierarhie de clase:

baza

private
public protected

deriv1 deriv2 deriv3

#include <iostream.h>
class baza
{
int a;
protected:
double w;
void seteaza_a (int a1){a=a1;}
void seteaza_w (int w1) {w=w1;}
public:
int c;
baza (int a1, double w1, int c1)
{a=a1; w=w1; c=c1;cout<<"Constructor cls. baza\n";}
~baza()
{cout<<"Destructor baza\n";}
void arata()
{cout<<a<<' '<<w<<' '<<c<<'\n';}
double calcul()
{return a+w+c;}
friend ostream & operator<<(ostream &, const baza &);
};

class deriv1: public baza


{
int b;
public:
deriv1 (int a1, double w1, int c1, int b1):baza(a1, w1, c1)
{b=b1; cout<<"Constructor deriv1\n";}
~deriv1()
{cout<<"Destructor deriv1\n";}
double calcul()
{return w+c+b;} // a nu poate fi folosit, fiind private
// o alternativa:
// double calcul(){return baza::calcul()+b;}
friend ostream &operator<<(ostream &, const deriv1 &);
};

class deriv2: protected baza


{
int b;

2
MOSTENIRE
public:
deriv2(int a1, double w1, int c1, int b1):baza(a1, w1, c1)
{b=b1; cout<<"Constructor deriv2\n";}
~deriv2()
{cout<<"Destructor deriv2\n";}
double calcul()
{return w+c+b;}
friend ostream &operator<<(ostream &, const deriv2 &);
};

class deriv3: private baza


{
int b;
public:
deriv3(int a1, double w1, int c1, int b1):baza(a1, w1, c1)
{b=b1; cout<<"Constructor deriv3\n";}
~deriv3()
{cout<<"Destructor deriv3\n";}
double calcul()
{return w+c+b;}
friend ostream &operator<<(ostream &, const deriv3 &);
};

ostream &operator<<(ostream &ies, const baza &b)


{ies<<b.a<<' '<<b.w<<' '<<b.c<<'\n'; return ies;}

ostream &operator<<(ostream &ies, const deriv1& d1)


{ies<<d1.w<<' '<<d1.c<<' '<<d1.b<<'\n'; // a private
return ies;}

ostream &operator<<(ostream &ies, const deriv2& d2)


{ies<<d2.w<<' '<<d2.c<<' '<<d2.b<<'\n'; // a private
return ies;}

ostream &operator<<(ostream &ies, const deriv3& d3)


{ies<<d3.w<<' '<<d3.c<<' '<<d3.b<<'\n'; // a private
return ies;}
void main()
{
baza x(1, 1.23, 2); // Constructor cls. baza
deriv1 y(2, 2.34, 3, 4); // Constructor cls. baza
// Constructor deriv1
deriv2 z(3, 3.45, 4, 5); // Constructor cls. baza
// Constructor deriv2
deriv3 v(4, 5.67, 6, 7); // Constructor cls. baza
// Constructor deriv3
cout<<"x="<<x<<endl<<"z="<<z<<endl<<"v="<<v<<endl;
// x=1 1.23 2
// z=3.45 4 5
// v=5.67 6 7
cout<<"x.calcul()="<<x.calcul()<<endl; // x.calcul()=4.23
cout<<"y.calcul()="<<y.calcul()<<endl; // y.calcul()=9.34
cout<<"z.calcul()="<<z.calcul()<<endl; // z.calcul()=12.45

cout<<"v.calcul()="<<v.calcul()<<endl; // v.calcul()=18.67
cout<<"x.c="<<x.c<<endl; // x.c=2
cout<<"y.c="<<y.c<<endl; // y.c=3
/* Destructor deriv3
Destructor baza ptr. v
Destructor deriv2
Destructor baza ptr. z
Destructor deriv1
Destructor baza ptr. y

3
MOSTENIRE
Destructor baza ptr x */
}

In clasa de baza membrul a este private, w este protected si c este public.


In clasa de baza, cat si in clasele derivate exista constructori care initializeaza datele membru.
Membrii private dintr-o clasa de baza pot fi folositi doar in cadrul acesteia (de metodele sale), nu si in clasele
derivate.
Pentru clasa deriv1:
- Membrii privati din clasa baza sunt inaccesibili (a exista, dar este incapsulat)
- Pentru a putea fi folositi, se acceseaza metoda din clasa de baza in care apare a
- Daca in clasa derivata exista o metoda cu acelasi nume cu al unei metode din clasa de baza
(redefinirea unei metode in clasa derivata), aceasta din urma se poate utiliza in clasa derivata
folosind un apel de forma:
baza::calcul( ) sau y.baza::calcul( )
Pentru clasa deriv2:
- Membrii publici din clasa de baza devin protejati in clasa deriv2
- Membrii protejati din clasa de baza devin protejati in clasa deriv2
- Daca in functia main( ) am incerca folosirea :
cout<<z.baza::calcul( ) , metoda calcul din z este inaccesibila, ea devenind protejata in clasa
deriv3.
Pentru clasa deriv3:
- Membrii public sau protected din clasa de baza au devenit privati in clasa deriv3.
- Se pot folosi toti membrii clasei de baza, cu exceptia celor privati (a).
- In cazul constructorilor, se apeleaza constructorul din clasa de baza
- Un obiect y din clasa deriv2 va incorporeaza un obiect deja initializat cu ajutorul constructorului
din clasa de baza

OBS:
Daca aveam: deriv1(int a1, double b1, int c1, int b1){a=a1; b=b1; c=c1; d=d1;}
nu era corect, deoarece clasa baza nu are constructori fara parametri, deci nu exista constructor implicit; data a
este private in deriv1.
Apelarea constructorului se face apeland explicit constructorul din clasa de baza.
O clasa poate poate contine mai multe obiecte cu aceeasi structura (aceleasi date membru si metode), dar ele
difera prin valorile luate.

EX:
#include "sir.h"
#include <conio.h>

class persoana
{ protected:
sir numele,prenumele;
char sexul;

public:
persoana () //constructor vid
{numele="";prenumele="";sexul='m';}
persoana(const sir&,const sir&,const char); //constructor
persoana (const persoana&); //constr. copiere
virtual ~persoana(); //destructor
const sir& nume();
const sir&prenume();
char sex();
virtual void afisare();
friend ostream & operator<<(ostream &, const persoana &);
friend istream & operator>>(istream &, persoana &);
};

class student:public persoana


{ protected:
sir facultatea,specializarea;
int anul,grupa;

4
MOSTENIRE

public:
student();
student(const sir&,const sir&,const char,const sir&,const sir&,const int,const int);
student(const persoana&,const sir&,const sir&,const int,const int);
student(const student&);
virtual ~student();
const sir& facult(){return facultatea;}
const sir& spec(){return specializarea;}
int an(){return anul;}
int grup(){return grupa;}
virtual void afisare();
friend ostream & operator<<(ostream &, const student &);
// friend istream & operator>>(istream &, student &);
};

class student_bursier:public student


{protected:
char tipul_bursei;

public:
student_bursier(const student&,char);
student_bursier(const student_bursier&);
virtual ~student_bursier();
char tip_bursa() {return tipul_bursei;}
double valoare_bursa();
virtual void afisare();
// friend ostream & operator<<(ostream &, const student_bursier &);
// friend istream & operator>>(istream &, student_bursier &);
};

persoana::persoana(const sir& nume,const sir& prenume,const char sex)


{numele=nume;prenumele=prenume;sexul=sex;
cout<<"Constr. PERSOANA\n";}

persoana::persoana(const persoana& pers)


{ numele=pers.numele;prenumele=pers.prenumele;sexul=pers.sexul;
cout<<"Constructor copiere PERSOANA\n";}

persoana::~persoana()
{cout<<"Destructor PERSOANA\n";}

const sir& persoana::nume()


{return numele;}

const sir& persoana::prenume()


{return prenumele;}

char persoana::sex()
{return sexul;}

void persoana::afisare()
{cout<<"Afisare PERSOANA:\n";
cout<<numele<<" "<<prenumele<<" "<<sexul;}

ostream & operator<<(ostream &monitor, const persoana &p)


{monitor<<"\nNume pers:"<<p.numele<<"\nPrenume pers:"<<p.prenumele<<"\nSex
pers:"<<((p.sexul=='m')?"BARBATESC":"FEMEIESC");
return monitor;}

5
MOSTENIRE
istream & operator>>(istream & tastat, persoana &p)
{tastat>>p.numele>>p.prenumele>>p.sexul;
return tastat;}

// METODE CLS. STUDENT


student::student(const sir&nume,const sir&prenume,const char sex,const sir& facult,const sir& spec,const int
an,const int gr):persoana(nume,prenume,sex)
{numele=nume;prenumele=prenume;
sexul=sex;facultatea=facult; specializarea=spec; anul=an; grupa=gr; }

student::student()
{numele="";prenumele="";sexul=0;facultatea=0;specializarea="";anul=0; grupa=0;}

student::student(const persoana &pers,const sir& facult,const sir& spec,const int an,const int
gr):persoana(pers)
{ numele=pers.nume();prenumele=pers.prenume();
facultatea=facult; specializarea=spec;anul=an;grupa=gr; }

student::student(const student& stud):persoana(stud.numele,stud.prenumele,stud.sexul)


{ facultatea=stud.facultatea; specializarea=stud.specializarea; anul=stud.anul;
grupa=stud.grupa; }

student::~student()
{ cout<<"Destructor student!!\n"; }

void student::afisare()
{ cout<<numele<<" "<<prenumele<<'\n';
cout<<"Sex:"<<sexul<<'\n';
cout<<"Facultatea: "<<facultatea<<" Specializare: "<<specializarea<<'\n';
cout<<"Anul: "<<anul<<" Grupa:"<<grupa<<'\n';
}

ostream & operator<<(ostream &monitor, const student &s)


{monitor<<"\nNume stud:"<<s.numele<<"\nPrenume stud:"<<s.prenumele<<"\nSex
stud:"<<((s.sexul=='m')?"BARBATESC":"FEMEIESC");
monitor<<"\nFacultate :"<<s.facultatea<<"\nSpecializare :"<<s.specializarea;
monitor<<"\nAnul :"<<s.anul<<"\nGrupa :"<<s.grupa;
return monitor;}

// friend istream & operator>>(istream &, student &);

//METODE CLS. STUDENT_BURSIER


/* student_bursier(student&,char);
student_bursier(const student_bursier&);*/

student_bursier::student_bursier(const student &stud,char tip_bursa):student(stud)


{tipul_bursei=tip_bursa;}

student_bursier::student_bursier(const student_bursier
&stud):student(stud.numele,stud.prenumele,stud.sexul,stud.facultatea,stud.specializarea,stud.anul,stud.grupa)
{tipul_bursei=stud.tipul_bursei;}

double student_bursier::valoare_bursa()
{ double val;
switch (tipul_bursei)
{ case 'A':val=85000;
break;
case 'B':val=70000;
break;
}
return val;
}

6
MOSTENIRE
student_bursier::~student_bursier()
{cout<<"Desctructor cls. 3\n";}

void student_bursier::afisare()
{ student::afisare();
cout<<"Tip bursa: "<<tipul_bursei<<" Valoare: "<<valoare_bursa()<<'\n';}

void main()
{clrscr();
persoana x("POP","ION",'m');
x.afisare();cout<<'\n'; getch();
persoana x1(x); cout<<x1<<'\n'; getch();
cout<<"Inf despre persoana:\n";
persoana x2; cin>>x2;
cout<"Inf introduse:\n"; cout<<x2; getch();
x1.afisare(); cout<<'\n';
student s(x, "N.I.E.", "EA", 1, 2311);
s.afisare();cout<<'\n'; getch();
student s1(s); cout<<s1<<'\n'; getch(); }