Sunteți pe pagina 1din 154

CPP

Cuprins
Cap. 1 Reprezentarea informaţiilor
1.1 Reprezentarea numerelor întregi în sistemul binar
1.2 Deplasarea numeral binare cu semn
1.3 Reprezentarea numerelor reale
1.4 Reprezentarea caracterelor
Cap. 2 Constante, variabile şi expresii
2.1 Tipuri fundamentale
2.2 Variabile
2.3 Modificatori de tip
2.4 Operatorul typedef
2.5 Constante
2.6 Constante cu nume
2.7 Expresii aritmetice
2.8 Tablouri
2.9 Instrucţiunea de atribuire
2.10 Prototipuri de funcţii
2.11 Operaţii de intrare / ieşire
2.12 Funcţia main
2.13 Execuţia unui program
2.14 Operatorii ++ şi - -
2.15 Operaţii cu numere întregi la nivel de bit
2.15.1 Operatori de depasare
2.15.2 Operaţii logice la nivel de bit
Cap. 3 Structuri de control fundamentale
3.1 Algoritme
3.2 Expresii relaţionale
3.3 Expresii booleene
3.4 Operatorul do-while
3.5 Operatorul while
3.6 Operatorul for
3.7 Operatorul if
3.8 Operatorul ?
3.9 Operatorul switch
3.10 Operatorul ,
Cap. 4 Funcţii
4.1 Definirea funcţiilor
4.2 Pasarea parametrilor funcţiilor
4.3 Recursivitatea
4.4 Sabloane de funcţii
Cap. 5 Pointeri şi referinţe
5.1 Pointeri
5.2 Referinţe
5.3 Parametri funcţiilor
5.4 Pointeri la funcţii
5.5 Declalararea variabilelor tip pointer la funcţie
5.6 Pointeri şi tablouri unidimensionale
5.7 Siruri tip C
5.8 Pointeri şi tablouri multidimensionale
5.9 Parametrii funcţiei main
Cap. 6 Fişiere tip C
6.1 Fişiere text
6.1.1 Funcţii intrare / ieşire cu format
6.1.2 Funcţii intrare / ieşire tip character
6.2 Fişiere binare
Cap. 7 Structuri tip C
Cap. 8 Clase
8.1 Definirea unei clase
8.1.1 Definirea unei clase
8.1.2 Pointerul this
8.1.3 Spaţii de nume
8.2 Constructori şi destructori
8.3 Funcţii prietene
8.4 Fişiere standard în C++
Cap. 9 Supraîncărcarea operatorilor
9.1 Supraîncărcarea operatorului de atribuire
9.2 Supraîncărcarea operatorilor aritmetici
9.3 Supraîncărcarea operatorilor << şi >>
Cap. 10 Moştenirea
10.1 Pointeri la obiecte
10.2 Moştenirea
10.3 Funcţii virtuale. Polimorfism
10.4 Date şi funcţii statice
Cap. 11 Fişiere tip CPP
11.1 Fişiere text
11.1.1 Funcţii intrare / ieşire cu format
11.1.2 Funcţii intrare / ieşire tip caracter
11.2 Fişiere binare
Cap. 12 Siruri tip C++
Cap. 13 Tratarea excepţiilor
13.1 Excepţii
13.2 Excepţii lansate de funcţii
13.3 Excepţii standard
Cap. 14 Aplicaţii
14.1 Funcţii de timp
14.2 Fire de execuţie
Cap. 15 Biblioteca de şabloane standard
15.1 Funcţii generice
15.2 Vectori
15.3 Liste
Cap 1. Reprezentarea informaţiilor
1.1 Reprezentarea numerelor întregi în sistemul binar

Un număr natural se reprezintǎ ca o colecţie de cifre. In sistemul poziţional, poziţia unei


cifre determină ponderea cifrei în mărimea numărului. Fie numărul
N = ( d n −1 d n −2 ... d 0 ) r
Mărimea numărului natural corespunzător este:
n −1
N = d n −1 r n −1 + d n −2 r n −2 + ... + d 0 r 0 = ∑ d i r i
i =0

unde: r>1 este baza, n este numărul de cifre, iar d i este cifra de pe poziţia i. Avem
totdeauna 0 ≤ d i < r . In sistemul zecimal cifrele utilizate sunt 0, 1, 2, …, 9. In sistemul
binar cifrele utilizate sunt 0 şi 1, în sistemul octal 0, 1, 2, …, 7, iar în sistemul
hexazecimal cifrele utilizate sunt: 0, 1, 2, …, 9,A,B,C,D,E,F unde: A=10, B=11, C=12,
D=13, E=14 şi F=15. Cifrele sistemului binar se numesc biţi.

Conversia zecimal-binarǎ

Fie un numǎr natural reprezentat în sistemul binar


n −1
N = ∑ bi 2 i = bn −1 2 n −1 + ... + b1 21 + b0
i =0

In partea dreaptă avem un polinom de puteri ale lui 2. Coeficientul bi este 0 sau 1. Din
expresia de mai sus a numǎrului se observǎ cǎ cifrele bo , b1 , etc se pot obţine ca
resturile impǎrţirilor repetate ale numǎrului N cu 2. Vom nota:
N = q0
şi vom scrie:
q 0 = q1 2 + r0
de unde deducem:
r0 = b0
q1 = bn −1 2 n −2 + ... + b1
Vom scrie
q1 = q 2 2 + r1
de unde deducem:
r1 = b1
q 2 = bn −1 2 n −3 + ... + b2
Dupǎ n astfel de operaţii vom avea:
q n −1 = q n 2 + rn −1
unde:
rn −1 = bn −1
qn = 0
După cum se observǎ, resturile obţinute reprezintǎ chiar cifrele numǎrului binar.
Exemplu. Sǎ convertim numarul 14 din baza 10 în baza 2.
14 = q1 2 + r0 = 7 * 2 + 0
Avem deci: b0 = r0 = 0
7 = q 2 2 + r1 = 3 * 2 + 1
Avem: b1 = r1 = 1
3 = q 3 2 + r2 = 1 * 2 +1
de unde obţinem:
b2 = r2 = 1
In final:
1 = q 4 2 + r3 = 0 * 2 + 1
de unde obţinem:
b3 = r3 = 1
deci reprezentarea numǎrului 14 în binar este:
(14 ) 10 = (1110 ) 2
In acelaşi fel obţinem:
(10 ) 10 = (1010 ) 2
(11) 10 = (1011 ) 2
(12 ) 10 = (1100 ) 2
(13) 10 = (1101) 2
(15 ) 10 = (1111 ) 2
Algoritmul de conversie a unui numǎr din baza 10 în baza 2 este urmǎtorul:
1. q 0 = N
2. i = 0
3. cât timp qi ≠ 0
{
qi
q i +1 =
2
ri = qi %2
i = i +1
}
Resturile obţinute sunt cifrele numǎrului binar, primul rest fiind cifra cea mai puţin
semnificativǎ.
Conversia din baza 10 în baza 8 sau 16 se face prin împǎrţiri repetate cu 8 şi respectiv 16.
Unitatea de bază a informaţiei în calculator este un octet sau byte, ce cuprinde 8 cifre
binare (biţi). Numerele întregi se reprezintǎ în calculator pe 8, 16, 32 sau 64 de biţi.
Având un număr în baza 2, pentru reprezentarea sa în baza 16 se grupează câte 4 cifre
binare.
Exemplu.
(12 ) 10 = (1100 ) 2 = ( C ) 16
(10011110 ) 2 = ( 9 E ) 16 = ( 9 *16 + 14 ) 10 = (158 ) 10
Reprezentarea în baza 16 este importantǎ deoarece un octet poate fi reprezentat prin două
cifre hexazecimale.
Având un numǎr în baza 2, pentru reprezentarea în baza 8 se grupează câte 3 cifre binare.
Exemplu.
( 28 ) 10 = (11100 ) 2 = ( 34 ) 8
Pentru verificare
( 34 ) 8 = (3 * 81 + 4)10 = ( 28) 10
Conversia unui număr din baza 16 în baza 2 se face reprezentând fiecare cifrǎ
hexazecimalǎ prin 4 cifre binare.
Conversia unui numǎr din baza 8 în baza 2 se face convertind fiecare cifrǎ octalǎ prin 3
cifre binare. Pentru conversii de numere între bazele 2, 8, 10 şi 16 şi operaţii cu numere
în aceste baze se poate folosi aplicaţia Calculator a sistemului de operare Windows.

Reprezentarea numerelor binare cu semn

In cazul numerelor binare cu semn, bitul cel mai semnificativ este bitul de semn. El este 0
pentru numere pozitive şi 1 pentru numere negative. Există trei reprezentǎri ale
numerelor binare cu semn.

Reprezentarea în mǎrime şi semn

Numǎrul pozitiv X se reprezintǎ ca:


n−2
X = 0 * 2 n −1 + ∑ a i 2 i
i =0
Numǎrul negativ X se reprezintǎ ca:
n −2
X = −1 * 2 n −1 + ∑ a i 2 i
i =0
Exemple. Vom considera numere întregi reprezentate pe 8 biţi, un bit de semn şi 7 biţi ai
numǎrului:

Număr zecimal Reprezentare binarǎ Reprezentare hexazecimalǎ


13 0000 1101 0D
-13 1000 1101 8D
25 0001 1001 19
-7 1000 0111 87
127 0111 1111 7F
-127 1111 1111 FF

Gama numerelor întregi reprezentabile pe un octet în mǎrime şi semn este [-127, 127].
Putem scrie formula de reprezentare a numerelor binare în mǎrime şi semn ca:
n −2
X = ( −1) a n −1 2 n −1
+ ∑a i 2 i
i =0

unde coeficientul a n −1 are valoarea 0 sau 1. Primul bit va fi interpretat ca şi coeficientul


lui − 2 n −1 .
Reprezentarea în complement faţǎ de 1

Numǎrul pozitiv X se reprezintǎ în complement faţǎ de 1 ca:


n−2
X = 0*2 n −1
+ ∑ ai 2 i
i =0
Numǎrul negativ X se reprezintǎ ca:
n −2
X = −1 * 2 n −1 + ∑ a i 2 i
i =0

unde: ai =1 − ai . Pentru a reprezenta un numǎr negativ în complement faţǎ de 1


complementǎm toate cifrele lui.
Exemple de numere reprezentate în complement faţǎ de 1 pe un octet.

Număr zecimal Reprezentare binarǎ Reprezentare hexazecimalǎ


15 0000 1111 0F
-15 1111 0000 F0
-19 1000 1100 8C
19 0001 0011 13

Reprezentarea numerelor binare în complement faţǎ de 2

Numerele pozitive se reprezintǎ în complement faţǎ de 2 ca:


n−2
X = 0 * 2 n −1 + ∑ ai 2 i
i =0
Numerele negative se reprezintǎ în complement faţǎ de 2 ca:
n −2
X = −1 * 2 n −1 + ∑ a i 2 i + 1
i =0

unde: a i =1 − a i .
Exemple de numere reprezentate în complement faţǎ de 2 pe un octet.

Număr zecimal Reprezentare binarǎ Reprezentare hexazecimalǎ


13 0000 1101 0D
-13 1111 0011 F3
-7 1111 1001 F9
127 0111 1111 7F
-127 1000 0001 81

Menţionǎm cǎ în calculatoare numerele întregi se reprezintǎ în general în complement


faţǎ de 2. Considerăm formula ce reprezintǎ un număr negativ în complement faţǎ de 2.
Avem relaţia:
n −2

∑2
i =0
i
+ 1 = 2 n −1

In consecinţǎ, putem scrie:


n −2 n −2 n −2
− 1 * 2 n −1 + ∑ (1 − a i )2 i + 1 = −1 * 2 n −1 + 2 n −1 − ∑ ai 2 i = −∑ a i 2 i
i =0 i =0 i =0

1.2 Deplasarea numerelor binare cu semn


Inmulţirea unui numǎr binar cu semn cu 21 sau 2 −1 este echivalentǎ cu deplasarea
numǎrului binar la stânga sau la dreapta cu o cifra. La deplasarea numărului binar bitul de
semn rǎmane neschimbat.

Cazul numerelor pozitive.

Se deplaseazǎ toate cifrele numărului, iar cifrele adaugate sunt zerouri. Regula este
evidentǎ din formula de reprezentare a numerelor pozitive. Fie numărul pozitiv
n−2
X = 0 * 2 n −1 + ∑ a i 2 i
i =0
şi fie numărul înmulţit cu 2
n−2
Y = 2 * X = 0 * 2 n −1 + ∑ bi 2 i
i =0
de unde se deduce:
bi +1 = a i
b0 = 0
Am presupus cǎ prin înmulţirea cu 2 a numǎrului X nu apare depǎsire. In acelaşi fel se
aratǎ cǎ regula este valabilǎ pentru împǎrţirea cu 2.
Exemple. Să deplasǎm numărul 28 cu o cifrǎ binarǎ la dreapta şi la stânga.

Număr zecimal Număr binar Număr hexazecimalǎ


28 0001 1100 1C
14 0000 1110 0E
56 0011 1000 38

Numere negative reprezentate în complement faţǎ de 2

Regula de înmulţire a acestor numere cu 21 sau 2 −1 este urmǎtoarea:


• la deplasarea la stânga cifrele adǎugate sunt zerouri,
• la deplasarea la dreapta cifrele adaugate sunt unuri,
• reamintim ca bitul de semn rǎmâne neschimbat.
Exemplu.

Numǎr zecimal Numǎr binar Numǎr hexazecimal


-28 1110 0100 E4
-14 1111 0010 F2
-56 1100 1000 C8

1.3 Reprezentarea numerelor reale

Numerele reale se reprezintǎ în calculator în virgulǎ mobilǎ. Numarul real R este pus sub
forma
R = f * be
unde: f este un număr subunitar (mantisa), b este baza iar e este exponent sau
caracteristicǎ. Pentru exemplificare vom considera baza zece.
Exemple de reprezentare a numerelor reale.

32 .45 = 0.3245 * 10 2 0.3245 2

− 0.035 = −0.35 * 10 −1 -0.35 -1


Mantisa f are totdeauna un număr finit de cifre şi în plus:
f <1
In continuare, pentru exemplificare vom considera cǎ mantisa are 8 cifre. Deoarece
mantisa are un numǎr finit de cifre, în unele cazuri se pierd cifrele mai puţin
semnificative din numǎr.
Exemplu. Numǎrul
1
= 0.( 3) = 0.3333333333 ...
3
se reprezintǎ ca:
0.33333333 * 10 0
Pentru mǎrirea preciziei calculelor se cautǎ ca prima cifră din mantisǎ sǎ fie diferitǎ de
zero (vezi exemplul de mai sus). Numerele în această formă se numesc normalizate.

Efectuarea operaţiilor cu numere în virgulǎ mobilǎ

Pentru adunare numerele se aduc la acelaşi exponent (numǎrul mai mic la exponentul
celui mai mare) şi apoi se adunǎ mantisele. Aducerea numǎrului mai mic la exponentul
celui mai mare poate duce la pierderea cifrelor cel mai puţin semnificative din mantisǎ.
Exemplu. Sǎ efectuăm adunarea:
12.48+0.35
Primul numǎr este reprezentat ca:
0.1248 * 10 2 0.1248 2
iar al doilea
0.35 * 10 0 0.35 0
Pentru adunare se aduc numerele la acelaşi exponent

0.1248 2
0.035 2
şi se adunǎ mantisele.
0.1283 2

Vom prezenta un exemplu în care se pierd cifrele cele mai puţin semnificative din
rezultat. Sǎ adunǎm numerele:
1237.55 + 0.000425
Numerele sunt reprezentate astfel:
0.123755 * 10 4 0.123755 4
0.425 * 10 −3
0.425 -3
La adunare mantisa celui de-al doilea numǎr este deplasatǎ la dreapta cu 7 cifre.
Considerând lungimea mantisei de 8 cifre, se pierd doua cifre semnificative din al doilea
numǎr la aducerea la acelaşi exponent.
0.123755 4

0.00000004 4
Rezultatul adunǎrii este:
0.12375504 4
Observǎm că s-au pierdut douǎ cifre, cele mai puţin semnificative.
Operaţia de scǎdere se face aducând numerele la acelaşi exponent şi scǎzând mantisele.
La înmulţire se adunǎ exponenţii şi se înmulţesc mantisele. La împǎrţire se scad
exponenţii şi se împart mantisele. Dupǎ efectuarea unei operaţii aritmetice se cautǎ ca
prima cifra din mantisǎ sǎ fie diferită de zero, pentru o precizie maximǎ a rezultatului
(normalizare).
Pentru creşterea preciziei operaţiilor, în unitatea de calcul mantisa mai are încǎ o cifrǎ.
Operaţiile se fac deci cu o mantisǎ mai lungǎ, dupǎ care se reţin opt cifre din rezultat.
Exemplu. Fie de efectuat operaţia de scădere:
103.45678 – 23.456789
Numerele se reprezintǎ astfel:
0.10345678 3

0.23456789 2
Dacǎ mantisa are doar opt cifre semnificative, când deplasǎm la dreapta cu o cifrǎ,
mantisa celui de-al doilea număr, pierdem o cifră semnificativǎ.
0.02345678 3
Rezultatul scăderii celor douǎ numere este:
0.08 3
şi dupǎ normalizare obtinem rezultatul 80. Presupunem acum că în unitatea de calcul se
lucreazǎ cu o mantisǎ cu nouǎ cifre semnificative. In acest caz cele douǎ numere sunt
0.103456780 3

0.023456789 3
După scǎderea celor două numere obţinem:
0.079999991 3
iar dupǎ normalizare obţinem rezultatul corect:
0.79999991 2
Mentionǎm cǎ exponentul poate fi pozitiv sau negativ. Pentru a nu avea douǎ semne, unul
pentru numǎr şi altul pentru exponent, se adaugǎ un numǎr constant la exponent astfel ca
sǎ fie totdeauna pozitiv. De exemplu, dacǎ gama exponentului este [-63, 63] se adaugǎ
valoarea 63 astfel încât gama exponentului va fi [0, 126].
In calculatoarele personale numerele reale se reprezintǎ în virgulǎ mobilǎ în felul urmǎtor
R = semn * 1.mantisa * 2 exponent
In reprezentarea în virgulǎ mobilă scurtǎ (simpla precizie) numǎrul se reprezinta pe 32
biţi. Domeniul exponentului este [-127, 127]. La exponent se adaugǎ totdeauna valoarea
127 astfel încat exponentul are domeniul [0,254]. Bitul de semn are valoarea 0 pentru
numere pozitive şi 1 pentru cele negative. Bitul de semn ocupă bitul 31, exponentul
ocupǎ biţii 23-30 iar mantisa biţii 0-22. Gama numerelor reale ce se pot reprezenta în
acest format este
( −10 37 ,10 37 )
iar mantisa corespunde la opt cifre zecimale.
Exemple. Fie numărul R = 1.0 = 1.0 * 2 0 . Bitul de semn este zero iar exponentul 0+127.
In consecinţă, primii nouă biti din număr sunt
001111111
Numărul R va fi reprezentat în hexazecimal ca 3F800000.
Fir numărul R = 0.5 = 1.0 * 2 −1 . Bitul de semn este zero iar exponentul este
-1+127=126. Primii nouă biţi din număr sunt
001111110
Numărul se reprezintă în hexazecimal ca 3F000000.
Menţionǎm cǎ dacǎ exponentul este 255 iar mantisa este 0, numărul este ± ∞ Dacǎ
exponentul este 255 şi mantisa este diferitǎ de zero numărul este NaN (not a number).
Valoarea NaN apare atunci când efectuǎm urmatoarele operaţii
∞−∞
0*∞
0/0
∞/ ∞
sau când un operand este NaN.
In reprezentarea în virgulǎ mobilă lungǎ (dublǎ precizie) numǎrul se reprezintă pe 64 biţi.
Domeniul exponentului este [-1023, 1023]. La exponent se adaugǎ cantitatea 1023, deci
exponentul are domeniul [0, 2046]. Bitul de semn este bitul 63, exponentul ocupǎ biţii
52-62 iar mantisa ocupă biţii 0-51. Gama numerelor reale ce se pot reprezenta este
( −10 307 ,10 307 )

1.4 Reprezentarea caracterelor

Unul dintre sistemele de reprezentare a caracterelor este codul ASCII în care fiecare
caracter este reprezentat pe un octet. De exemplu caracterul A are codul hexazecimal 41,
caracterul a are codul hexazecimal 61, cifra 0 are codul hexazecimal 30, etc. Codurile
ASCII ale caracterelor sunt prezentate în tabela de mai jos.

y\x 0 1 2 3 4 5 6 7
0 Nul Dle 0 P p
1 Soh dc1 ! 1 A Q a q
2 Stx dc2 “ 2 B R b r
3 Etx dc3 # 3 C S c s
4 Eot dc4 $ 4 D T d t
5 Enq Nak % 5 E U e u
6 Ack Syn & 6 F V f v
7 Bel Elb ‘ 7 G W g w
8 Bs Can ( 8 H X h x
9 Ht Em ) 9 I Y i y
10 Lf Sub * : J Z j z
11 Vt Esc + : K [ k {
12 Ff Fs , < L \ l |
13 Cr Gs - = M ] m }
14 So Rs . > N ^ n ~
15 Si Us / ? O _ o del

Tabelul 1.1 Codul ASCII

Codul zecimal unui caracter este 16*x+y. De exemplu caracterul A are codul zecimal 65
sau, echivalent, codul hexazecimal 41.
Un alt sistem de reprezentare a caracterelor este UNICODE. în varianta UNICODE-16
fiecare caracter este reprezentat pe 2 octeţi.

Cap 2. Constante variabile şi expresii

2.1 Tipuri fundamentale

In matematică variabilele se clasifică după tipul lor. Tipul unei variabile este mulţimea
valorilor pe care le poate lua acea variabilă şi operaţiile ce se pot efectua cu acele valori.
Tipul unei variabile este făcut explicit cu o declaraţie de tip. Tipurile predefinite în
limbajul C++ sunt cele din tabelul 1.
Tabelul 1. Tipuri de bază ale limbajului C++

Tip Dimensiune Domeniu de Operaţii


în octeţi valori
int Numere întregi 4 [−2  2 31 − 1]
31
+-*/%
float Numere reale în virgulă 4 (−10 38
10 38
) +-*/
mobilă scurtă
double Numere reale în virgulă 8 (−10 307 10 307 ) +-*/
mobilă lungă
char Caractere în cod ASCII şi 1 [ −2 7  2 7 −1] +-*/%
numere întregi pe un octet
wchar_t Numere pozitive şi caractere 2 [0  216 −1] +-*/%
UNICODE pe doi octeţi
bool Valoare booleană 1 false,true and, or,
not

Pentru fiecare tip există constante predefinite ce dau limitele minima şi maximă ale
domeniului de valori.
Menţionăm că, pentru tipurile char şi int, primul bit este utilizat pentru semn, ceilalţi biţi
sunt biţi ai numărului. In cazul tipului bool valoarea false este reprezentată prin zero iar
true prin unu.

2.2 Variabile

Numele unei variabile este format din cifre, litere şi caracterul _ (underscore), şi începe
totdeauna cu o literă sau caracterul _. Literele mari sunt diferite de cele mici.
Instrucţiunea de declarare a tipului are forma
tip listă de variabile;
Lista de variabile este formată din nume de variabile separate de virgule. Diagrama
sintactică este cea de mai jos.

De exemplu, instrucţiunile
int a;
char b;
declară o variabilă a de tipul int şi o variabilă b de tipul char. Conform instrucţiunii de
declarare a tipului de mai sus, mai multe variabile de acelaşi tip pot fi declarate cu o
singură instrucţiune, scriind numele lor separate de virgule. De exemplu, instrucţiunea
float f1, f2, f3;
declară trei variabile de tipul float. O variabilă poate fi iniţializată la declararea tipului ei,
conform următoarei diagrame sintactice

De exemplu, instrucţiunea
double d = 1 + sin(2.4);
declară o variabilă de tip double pe care o iniţializează la valoarea 1+sin(2.4).
Menţionăm că tipul bool nu este definit în limbajul C.

2.3 Modificatori de tip

Modificatorii de tip schimbă domeniul de valori pe care le poate lua o variabilă de tip int
sau char. Aceşti modificatori sunt:
• unsigned. La utilizarea acestui modificator, valorile variabilelor sunt pozitive.
Toţi biţii sunt biţi ai numărului. Tipul unsigned int are domeniul de valori
[0  2 32 −1] , iar tipul unsigned char are domeniul de valori [0  2 8 −1]
• signed. Valorile variabilelor sunt numere cu semn. Primul bit este bit de semn.
• short. Se aplică tipului int. De exemplu, tipul short int are domeniul de valori
[−215  215 −1] , iar tipul unsigned short int are domeniul de valori
[0  216 −1] . Tipul wchar_t este tipul unsigned short int predefinit.
• long

2.4 Operatorul typedef

Operatorul typedef permite să definim tipuri de date bazate pe tipuri existente. Forma
instrucţiunii este
typedef tip-existent tip-nou
Exemple. Instructiunea
typedef float REAL32
defineşte tipul REAL32 ce poate fi utilizat în locul tipului float.
In limbajul C nu există tipul predefinit bool. Putem să definim tipul bool folosind tipul
char
typedef char bool

2.5 Constante

Tipurile de constante din limbaj corespund tipurilor de date.


• constantele întregi sunt numere întregi, pozitive sau negative. Ele nu pot începe cu
cifra 0. Exemple de constante întregi sunt
275, -325, +12
• constante hexazecimale sunt numere întregi reprezentate în baza 16. Ele încep cu
0x sau 0X. Exemple de constante hexazecimale
0xE sau 0xe sau 0Xe sau 0XE care reprezintă valoarea zecimală 14
-0xf sau -0xF, reprezintă valoarea zecimală -15.
• constante octale sunt numere întregi reprezentate în baza 8. Ele încep obligatoriu
cu 0. Exemple. Constanta octală 016 reprezintă numărul zecimal 14 (deoarece
(16 ) 8 = (14 )10 ) iar constanta octală -014 reprezintă numărul zecimal -12
(deoarece (−12 )10 = (−14 ) 8 )
• constante reale în virgulă mobilă scurtă. Partea subunitară este separată de cea
întreagă prin punct zecimal. Exemple.
1.5 -4.23 27.0
Constantele reale pot avea exponent, un număr întreg ce reprezintă puterea lui
zece cu care se înmulţeşte constanta. De exemplu numărul 2,31 * 10 1 se scrie ca
2.31e1 sau 2.31e+1 sau +2.31E1
Numărul real −41 ,2 * 10 −1 se scrie ca şi constantă reală
-41.2e-1 sau -4.12
• constante reale în virgulă mobilă lungă. Se scriu ca şi constantele reale în virgulă
mobilă scurtă şi cu sufixul L sau l. De exemplu, numărul real 1,5 se scrie ca şi
constantă în virgulă mobilă lungă
1.5L sau 15e-1L
• constante de tip character reprezintă un character al codului ASCII scris între
apostrofuri. De exemplu, caracterul ASCII a se scrie
‘a’ sau echivalent, ca şi constante întregi, 0x61 sau 97
Caracterul ASCII A se scrie
‘A’ sau echivalent, 0x41 sau 65.
Un alt mod de a defini constante tip character este de a le scrie sub forma ‘\xhh’
unde h este o cifră hexazecimală. Numărul hexazecimal hh reprezintă echivalentul
hexazecimal al caracterului în codul ASCII. De exemplu, caracterele ‘a’ şi ‘A’ se
reprezintă ca şi ‘\x61’ şi respective ‘\x41’. Caracterele speciale ale codului ASCII
se reprezintă folosind secvenţa de evitare
‘\n’ CR
‘\r’ LF
‘\t’ tab orizontal
‘\”’ ghilimele
‘\’’ apostrof
‘\\’ backslash
• constante tip şir de caractere. Ele reprezintă un şir de caractere ASCII scris între
ghilimele. De exemplu
“abcd”
Un şir de caractere este reprezentat de codurile ASCII ale caracterelor pe câte un
octet, urmate de un octet ce conţine valoarea 0. Caracterele speciale se reprezintă
în interiorul unui şir folosind secvenţa de evitare. De exemplu
“ab\”c”
“a\\bc”
“abc\n”
Menţionăm diferenţa între constanta tip caracter ‘a’ şi constanta tip şir de
caractere “a”. Constanta tip caracter ‘a’ se reprezintă pe un singur octet,
constanta “a” se reprezintă pe doi octeţi.

• constante tip caracter UNICODE corespund tipului wchar_t. Ele se definesc sub
forma L’\xhhhh’ unde h reprezintă o cifră hexazecimală.
• constante booleene sunt true şi false şi se reprezintă prin valorile unu şi
respective zero.

2.6 Constante cu nume

In program putem defini constante cu nume în următoarele feluri


• utilizarea directivei define . Compilatoarele limbajelor C şi C++ au un
preprocessor care modifică programul sursă înainte de compilare. Preprocesorul
citeşte diversele directive şi modifică programul sursă corespunzător acestora.
Directiva define ce defineşte o constantă are forma
# define nume valoare
unde valoare este interpretată de preprocessor ca un şir de caractere. De exemplu,
directiva
# define PI 3.14
are ca efect înlocuirea numelui PI în program cu şirul de caractere 3.14
• enumerări. O enumerare defineşte un tip întreg şi valorile asociate acelui tip.
Instrucţiunea enum de declarare a unui tip are forma
enum numetip {listă de nume};
Diagrama sintactică corespunzătoare este următoarea

De exemplu,
enum CLRX {aaa, bbb, ccc};
defineşte trei constante aaa, bbb, ccc, prima are valoarea zero şi fiecare constantă
următoare are o valoare mărită cu unu.
aaa = 0
bbb = 1
ccc = 2
In program constantele sunt înlocuite cu valorile lor. Valoarea fiecărei constante
poate fi specificată printr-o expresie întreagă constantă. De exemplu
enum cstval {ppp = 2, dd, cc = -1, nn = 2 + 3 * 4};
defineşte constantele
ppp = 2
dd = 3
cc = -1
nn = 14
Putem defini variabile corespunzând tipului definit prin enum. Aceste variabile
pot primi ca valori doar constantele definite în instrucţiunea enum. Fie de
exemplu tipul enumx definit mai jos
enum enumx = {ena, enb, enc};
Instrucţiunea următoare defineşte variabila z de tipul enumx
enumx z;
Putem atribui o valoare variabilei z astfel
z = ena;
Instrucţiunea enum are şi o formă mai simplă
enum {listă de nume};
care defineşte doar constante şi nu un tip.
• utilizarea cuvantului cheie const. O asemenea instrucţiune are forma
const tip nume = valoare;
unde tip este un tip al limbajului iar valoare este o expresie constantă. De
exemplu, instrucţiunile

const int a = 7;
const char b = ‘a’;
const float c = 2.5e-2 * 4.14;
const float x = sin(0.2) + cos(1.5);
const float z = log(12.5) / 2.3;

definesc constantele a, b, c, x şi z, ce au valorile specificate în instrucţiune.

2.7 Expresii aritmetice

Expresiile aritmetice sunt formate din constante, variabile şi funcţii. Operatorii sunt +, - *
şi /, şi în cazul operanzilor de tip întreg, şi % (restul împărţirii a două numere întregi).
Restul împărţirii a două numere întregi, a şi b, se defineşte astfel
a % b = a – (a / b) * b
De exemplu,
15 % 4 = 3
11 % 5 = 2
Pentru gruparea termenilor se folosesc paranteze rotunde ( şi ). De exemplu, expresia
a −b
a +b
se scrie ca
(a – b) / (a + b)
iar expresia
a + b * x + c * x2
2 + m2
se scrie ca
(a + b * x + c * x * x) / (2 + m*m)
Funcţiile matematice standard uzuale ale limbajelor C şi C++ sunt cele de mai jos

acos cos exp ceil fabs pow


asin sin log floor sqrt
atan tan log10

Funcţia floor(x) calculează valoarea  x  (cel mai mare număr întreg cuprins în x), iar
funcţia ceil(x) calculează valoarea  x  (cel mai mic număr întreg mai mare ca x). Toate
funcţiile de mai sus au argumente de tip double şi rezultatul de tip double. Funcţia pow
are prototipul
double pow(double a, double b)
şi calculează expresia a b . Apelarea unei funcţii se face scriind numele funcţiei ca termen
într-o expresie urmat în paranteze de parametrii actuali. Exemple de expresii aritmetice şi
scrierea lor sunt prezentate mai jos. Vom presupune că variabilele din aceste expresii au
fost declarate în prealabil de tip double şi au primit valori.

a * cos 2 ( x ) + b * sin( x )
2.75 + x
(a*cos(x)*cos(x)+b*sin(x))/(2.75+fabs(x))
e x + e −2 x
(exp(x)+exp(-2*x))/5
5
log( x +2) +ln 1 +cos( x ) log10(fabs(x)+2)+log(fabs(1+cos(x)))
Menţionăm că în expresii putem folosi orice fel de constante întregi, reale sau character.
De exemplu, expresia a + 27 se poate scrie
a + 27
sau
a + 0x1b
sau
a + 033
Constantele hexazecimală 0x1b şi octală 033 reprezintă valoarea zecimală 27. Expresia
x + 100 se poate scrie
x + 100
sau
x + 0x64
sau
x + ‘d’
Codul ASCII al caracterului d este 0x64. La evaluarea unei expresii constantele
hexazecimale, octale sau character sunt convertite în numărul întreg corespunzător.
Evaluarea expresiilor aritmetice se face ţinand cont de priorităţile operatorilor şi de
asociativitatea lor.
Prioritatea operatorilor Asociativitate
+ unar, - unar
*/% Asociativ la stânga
+- Asociativ la stânga

Conform tabelei de mai sus operatorii *, / şi % au o prioritate mai mare decât + şi -


Asociativitatea la stânga a operatorilor înseamnă următorul mod de execuţie. Expresia
a/b/c
este interpretată ca
(a / b) / c
Reamintim că, pentru a modifica ordinea de execuţie o operaţiilor, se utilizează paranteze
rotunde.
Deoarece în expresii intervin operanzi de diverse tipuri, se fac conversii. Regulile după
care se fac aceste conversii sunt următoarele:
• Tipul float se converteşte la double
• Tipurile char şi short int se convertesc la int
• Tipurile unsigned char şi unsigned short int se convertesc la unsigned int
• Dacă în expresie există un operand de tipul long int, tipul int şi unsigned int se
convertesc la long int. Tipul expresiei se determină conform tabelei de mai jos

int long double


int int long double
long long long double
double double double double

Menţionăm în final că tipurile char, short int şi variantele lor unsigned char, unsigned
short int şi float sunt doar pentru memorare. Calculele se efectuează doar cu variabile
de tip int, long int şi double.
Valoarea unei expresii poate fi convertită într-un tip diferit dacă este nevoie.
Diagrama sintactică este
(tip) expresie
De exemplu, dacă i este o variabilă întreagă cu valoarea 7 şi f este o variabilă de tip
double cu valoarea 3.47, expresia
(i + f) % 2
nu este corectă deoarece are tipul double. Expresia
(int)(i + f) % 2
are tipul int şi este corectă, rezultatul ei este 5. In limbajul C++ este posibilă încă o
formă de convertire a expresiilor, cu diagrama sintactică
tip(expresie)
De exemplu, conversia expresiei anterioare la tipul int se poate scrie
int(i + f)
Menţionăm că se pot face conversii în toate tipurile standard existente în limbaje.

2.8 Tablouri
Un tablou este o mulţime de elemente de acelaşi tip. Tablourile sunt tipuri structurate
simple. Instrucţiunea de declarare a unui tablou cu o dimensiune este următoarea
tip nume [ întreg] ;
unde întreg reprezintă numărul de elemente ale tabloului. Elementele tabloului sunt
nume[0], nume[1], …, nume[întreg – 1]. De exemplu instrucţiunea
int a[10];
declară un vector cu zece elemente de tip int. Elementele vectorului sunt a[0], a[1], …,
a[9]. Un tablou poate fi iniţializat la declararea sa scriind valorile elementelor între
accolade, {}, şi separate de virgule. Exemple de instrucţiuni ce declară tablouri şi le
atribuie valori
float x[3] = {1.32, -2.15, 4.45};
char c[4] = {‘a’, ‘b’, ‘c’, ‘x’};
In cazul în care lista de valori este mai scurtă decat numărul de elemente declarate,
ultimele elemente sunt iniţializate cu zero. In cazul iniţializării unui tablou, la declararea
lui putem omite numărul de elemente al tabloului. De exemplu, putem scrie
char x[] = {‘#’, ‘>’, ‘m’};
Compilatorul calculează dimensiunea tabloului din numărul de valori utilizate pentru
iniţializare. Instrucţiunea precedentă este echivalentă cu instrucţiunea
char x[3] = {‘#’, ‘>’, ‘m’};
Menţionăm că un vector de caractere poate fi iniţializat cu o constantă tip şir de caractere.
De exemplu, putem scrie
char s[] = “abc”;
Reamintim că o constantă şir de caractere este terminată printr-un octet 0, deci vectorul
declarat are 4 componente, ‘a’, ‘b’, ‘c’ şi 0. Instrucţiunea precedentă este echivalentă cu
char s[4] = “abc”;
sau cu instrucţiunea
char s[4] = {‘a’, ‘b’, ‘c’ , ‘\0’};
Un tablou poate avea oricâte dimensiuni. Diagrama sintactică a declaraţiei unui tablou
este următoarea

De exemplu, instrucţiunea
float b[7][3]
declară o matrice cu şapte linii şi trei coloane. Elementele matricei sunt
b[0][0] b[0][1] b[0][2]
… … …
b[6][0] b[6][1] b[6][2]
Un tablou cu mai multe dimensiuni poate fi de asemenea iniţializat la declararea sa. Fie
de definit matricea cu elemente întregi
1 2 5 
m = 
3 7 − 3
Instrucţiunea corespunzătoare este
int m[2][3] = {{1, 2, 5},{3, 7, -3}};
La utilizarea într-o expresie, indicii elementelor tablourilor pot fi orice expresii întregi.
Presupunem următoarele declaraţii de tablouri
double a[10], b;
int i, j, y[3][4];
Exemple de expresii ce conţin elemente de tablouri.
ai + b * y ij
(a[i]+b*y[i][j])/(cos(b)-sin(b))
cos( b) − sin( b)
e cos( b ) + y i , j +1 exp(cos(b)) + y[i][j + 1]

La utilizarea elementelor unui tablou, [] este un operator de selecţie ce are doi


operanzi: un nume de tablou şi un indice. El se aplică asupra unui nume de tablou şi
selectează un element al acelui tablou. De exemplu a[0] selectează primul element al
tabloului a, iar b[0] selectează prima linie a tabloului b. Aplicând încă o dată
operatorul de selecţie asupra lui b[0], de exemplu b[0][0] selectează primul element
din prima linie a lui b. Operatorul [] este asociativ la stânga.
In acelaşi mod, operatorul de apelare a unei funcţii (), aplicat asupra unui nume de
funcţie returnează valoarea calculată de funcţie. De exemplu cos(1.2) reprezintă
aplicarea operatorului () asupra numelui funcţiei cos.

Priorităţile şi asociativitatea operatorilor

Operator Asociativitate
[] () Asociativi la stânga
+ unar, - unar
*/% Asociativi la stânga
+- Asociativi la stânga

2.9 Instrucţiunea de atribuire

Operatorul de atribuire = atribuie o valoare unei variabile. Forma instrucţiunii de atribuire


este
variabilă = expresie;
De exemplu, următoarele instrucţiuni atribuie valori variabilelor
float x, y;
x = -1.34;
y = sin(x) + cos(x * x);
Limbajele C şi C++ au operatori speciali pentru scriera prescurtată a instrucţiunilor de
atribuire, +=, -=, *=, /= şi %=. Aceşti operatori se definesc astfel:
Considerăm o variabilă x şi o expresie e.
x op= e
este echivalent cu
x = x op e
Tabelul următor prezintă aceşti operatori

Operatori de atribuire
Instrucţiune Forma prescurtată
x=x+e x += e
x=x-e x -= e
x=x*e x *= e
x=x/e x /= e
x=x%e x %= e

Alţi operatori de acest tip vor fi prezentaţi ulterior. Menţionăm în final că un operator de
atribuire are doi operanzi şi ca rezultat valoarea operandului din stânga. Operanzii de
atribuire sunt asociativi la dreapta (se execută de la dreapta la stânga). Fie de exemplu
declaraţia
int x, y, z = 1;
Instrucţiunea
x = y = z;
atribuie variabilelor x şi y valoarea 1. Instrucţiunea
x += y += z;
atribuie variabilei y valoarea 2 şi variabilei z valoarea 3. De ce?

2.10 Prototipuri de funcţii. Biblioteci de prototipuri

Atunci când compilatorul întâlneşte un apel la funcţie, el trebuie să poată verifica


concordanţa între parametrii actuali şi cei formali şi are nevoie de tipul rezultatului
funcţiei pentru a face conversiile necesare evaluării expresiei. Pentru aceasta el are
nevoie de o definiţie a funcţiei în care apar tipurile parametrilor şi tipul rezultatului.
Această definiţie se numeşte şablon sau prototip şi are forma
tip nume(tip, tip, …, );
De exemplu, prototipul funcţiei sin este
double sin(double);
Limbajele C şi C++ au biblioteci standard cu prototipurile funcţiilor limbajului. Aceste
biblioteci sunt semnalate compilatorului cu directiva include
# include <nume_bibliotecă>
Aceste biblioteci sunt fişiere cu extensia h şi se numesc fişiere header.
De exemplu, biblioteca cu prototipurile funcţiilor matematice este <math.h>. Pentru a fi
semnalată compilatorului vom scrie instrucţiunea
# include <math.h>
Biblioteca cu funcţii intrare/ieşire tip C este <stdio.h>, biblioteca cu funcţii de prelucrat
şiruri tip C este <string.h>, biblioteca cu funcţii intrare/ieşire tip C++ este <iostream.h>,
etc. Putem defini propriile biblioteci cu prototipuri pe care să le semnalăm compilatorului
cu directive include. Numele propriilor biblioteci cu prototipuri sunt scrise între
ghilimele.
In limbajul C++ este posibil şi un nou stil de includere a bibliotecilor standard.
Bibliotecile specifice limbajului C sunt redefinite ca <cstdio>, <cmath>, <cstring>, etc.
In plus, toate funcţiile standard ale limbajelor C şi C++ sunt grupate într-un spaţiu de
nume denumit std. In consecinţă, putem semnala compilatorului bibliotecile standard ca
# include <nume_biblioteca>
using namespace std;
unde în directive include avem bibliotecile specifice limbajului C++.

2.11 Operaţii de intrare / ieşire

Orice aplicaţie are un director curent asociat şi fişiere standard de intrare şi ieşire.
Fişierul standard de intrare este tastatura iar cel de ieşire este ecranul. In program orice
fişier este asociat unui obiect numit stream. Există două tipuri de fişiere: text şi binare.
Fişierul text este este compus dintr-un şir de caractere grupate în linii. Liniile constau din
zero sau mai multe caractere plus un caracter ‘\n’. Fişierele standard sunt de tipul text.
Streamul de intrare asociat tastaturii are denumirea cin, streamul de ieşire asociat
ecranului se numeşte cout. Mai există alte doua streamuri cerr şi clog pentru scrierea
mesajelor de eroare.
Operatorul de scriere este <<. El inserează date în streamul cout. De exemplu, secvenţa
de instrucţiuni
int i = 123;
cout << “i = ” << i;
afişază pe ecran
i = 123
Pentru a despărţi textul afişat în linii, trebuie să scriem caracterul ‘\n’, care este predefinit
ca endl. De exemplu, pentru a afişa cele de mai sus pe două linii, vom scrie
cout << “i = “ << endl << i << endl;
sau
cout << “i = “ << ‘\n’ << i << ‘\n’;
sau, echivalent
cout << “i =” << endl
cout << i << endl;
Operatorul << poate scrie orice tip predefinit de date: int, float, şiruri de caractere, etc.
Operatorul de citire dintr-un stream este >>. El extrage date din stream. Operatorul >>
este urmat de numele variabilei ce va memora valoarea citită. De exemplu, secvenţa de
instrucţiuni
int a;
cin >> a;
va citi valoarea introdusă de la tastatură şi o va atribui variabilei a. Se pot citi oricâte date
din streamul cin. Instrucţiunea
cin >> a >> b;
este echivalent cu
cin >> a;
cin >> b;
Cele două valori introduce pentru exemplul de mai sus trebuie separate de spaţii , \t sau \n
(acesta din urmă este generat la apăsarea tastei Return).
Biblioteca de prototipuri pentru streamurile cin, cout, etc are numele <iostream.h>.
Pentru a modifica formatul de scriere sai citire a datelor putem utilize manipulatori.
Pentru scrierea sau citirea unui număr întreg în instrucţiunile cout sau cin în diferite baze
se utilizează manipulatorii
• hex pentru baza 16
• dec pentru baza 10
• oct pentru baza 8
La începerea execuţiei unui program implicită este baza 10. Pentru scrierea bazei se
uitlizează manipulatorii
• showbase pentru scrierea bazei
• noshowbase pentru a nu scrie baza
Exemplu. Fie instrucţiunea
int k = 20;
Tabloul următor prezintă exemple de utilizare a manipulatorilor.

cout << k; 20
cout << hex << k; 14
cout << oct << k; 24
cout << showbase << hex << k; 0x14
cout << showbase << oct << k; 024

In cazul numerelor reale avem manipulatorii


• fixed numărul este scris fără exponent
• scientific numărul este scris cu exponent
Exemplu. Fie instrucţiunea
float x = 122.63;
Tabloul următor prezintă exemple de utilizare a manipulatorilor.

cout << x; 122.63


cout << fixed << x; 122.63
cout << scientific << x; 1.226300e+002

Funcţiile următoare sunt apelate de operatorul de scriere <<


• setbase(int);
• setfill(char);
• setprecision(int);
• setw(int);
Funcţia setbase(int) indică baza în care va fi afişat un număr întreg, 8, 10 sau 16. Funcţia
setfill(char) indică un caracter cu care se umplu spaţiile unui camp. Valoarea implicită a
acestui manipulator este spaţiul. Funcţia setprecision(int) dă numărul de cifre cu care este
scris un număr real. Funcţia setw(int) dă dimensiunea campului în caractere pe care este
scris un număr. Valoarea implicită a acestui manipulator este 0. Dacă dimensiunea
campului nu este specificată, sau este prea mică, numărul este scris pe câte caractere este
necesar.
Exemplu. Fie instrucţiunea
int x = 23;
Tabloul următor prezintă exemple de utilizare a funcţiilor.

cout << setw(5) << x; 23


cout << x; 23
cout << setbase(16) << x: 17
cout << dec << setw(3) << setfill(‘*’) << x; *23

Exemplu. Fie instrucţiunea


float z = 12.64;
Tabloul următor prezintă exemple de utilizare a funcţiilor.

cout << setprecision(3) << z; 12.6


cout << setw(8) << z; 12.64
cout << setw(8) << setfill(‘*’) << z; ***12.64
cout << setw(15) << scientific << z; 1.264000e+001

Menţionăm că dimensiunea campului prescrisă de funcţia setw(int) se aplică doar


următorului număr de scris. Ceilalţi manipulatori răman la valoarea prescrisă pană sunt
modificaţi.

2.12 Funcţia main

Orice program scris în limbajele C sau C++ se compune din funcţii care se apelează unele
pe altele. Definiţia unei funcţii este
tip nume (lista de parametri)
{
instrucţiuni
}
Una din funcţii are numele main iar execuţia programului începe cu această funcţie.
Prototipul acestei funcţii este
int main();
Semnificaţia prototipului este următoarea. Funcţia main are ca rezultat o valoare întreagă
care este codul de terminare a programului şi nu are parametri. Corpul funcţiei este o
instrucţiune compusă, adică o secvenţă de instrucţiuni scrise între accolade, { şi }.
Programul poate conţine comentarii. Comentariile plasate între delimitatorii /* şi */ se pot
întinde pe mai multe linii. Comentariile ce încep cu caracterele // se întind pe o singură
linie. Putem scrie acum primul program care citeşte o valoare întreagă de la tastatură şi
afişează pătratul ei.

# include <iostream.h>
int main()
{
int i, j;
cout << “introduceti o valoare intreaga” << endl;
/* se citeste o valoare intreaga */
cin >> i;
cout << “valoarea introdusa este “ << i << endl;
/* se calculeaza patratul valorii citite */
j = i * i;
/* se scrie valoarea calculata */
cout << “patratul valorii este “ << j << endl;
return 0;
}

Menţionăm că funcţia main() returnează valoarea 0 cu instrucţiunea


return 0;
Putem include biblioteca cu prototipurile funcţiilor intrare/ieşie ca
# include <iostream>
using namespace std;
In biblioteca <iostream> prototipurile funcţiilor intrare/ieşire sunt grupate in spaţiul de
nume std.

2.13 Execuţia unui program

• Prima etapă este compilarea programului. In această etapă programul este


verificat pentru erori sintactice. Dacă programul nu conţine erori sintactice
compilatorul generează un program obiect traducând fiecare instrucţiune a
programului într-o serie de instrucţiuni elementare ale calculatorului.
• Etapa a doua este editarea legăturilor. In această etapă sunt ataşate programului
funcţiile din biblioteci. Atunci când compilatorul întalneşte un apel de funcţie (de
exemplu sin, cos, etc.), sau o operaţie intrare/ieşire, el generează doar o secvenţă
de apel la funcţie. Funcţiile respective sunt compilate în biblioteci speciale.
Programul editor de legături ataşează aceste funcţii programului obiect.
Programul generat de editorul de legături se numeşte program executabil. El este
un fişier cu extensia exe.
• In etapa a treia programul excutabil este încărcat în memorie şi executat.

2.14 Operatorul sizeof

Operatorul sizeof se aplică asupra unei expresii sau asupra unui tip şi are ca rezultat
numărul de octeţi de memorie utilizaţi. De exemplu, expresia
sizeof(int)
are ca rezultat valoarea 4. In cazul unui tablou rezultatul este numărul total de octeţi
ocupat de tablou.
Exemplu. Vom scrie un program care să afişeze numărul de octeţi utilizaţi pentru
memorarea tipurilor fundamentale. Pentru a afişa datele deplasate la stânga cu un număr
de spaţii vom scrie un caracter tab, ‘\t’.

# include <iostream.h>
int main()
{
float a[10], b;
cout << “Numarul de octeti utilizati:” << endl;
// scrie dimensiunea tipurilor standard
cout << ‘\tint: ’ << sizeof(int) << endl;
cout << ‘\tchar: ’ << sizeof(char) << endl;
cout << ‘\tfloat: ’ << sizeof(float) << endl;
cout << ‘\tdouble: ’ << sizeof(double) << endl;
// scrie dimensiunea unui vector
cout << ‘\tvectorul float a[10]: ’ << sizeof(a) << endl;
// scrie dimensiunea rezultatului unei expresii
cout << ‘\t expresie’ << sizeof(a[0]+ b) << ‘\n’;
}

Menţionăm că este echivalent dacă în instrucţiunea cout utilizăm caracterul ‘\n’, sau endl,
sau şirul de caractere “\n”.

2.14 Operatorii ++ şi - -

Operatorul ++ incrementează o variabilă întreagă cu unu, operatorul - - decrementează o


variabilă întreagă cu unu. Aceşti operatori pot fi prefix, adică putem scrie
++x, --x
sau postfix
x++, x- -
• Cazul operatorilor prefix, ++x, --x. Se incrementează sau decrementează valoarea
variabilei cu unu, valoarea incrementată sau decrementată fiind şi rezultatul
expresiei.
Exemplu. Fie declaraţia de variabile
int i = 1, x;
Instrucţiunea
x = ++i;
reprezintă scrierea prescurtată a secvenţei de instrucţiuni
i = i + 1;
x = i;
După execuţia instrucţiunii x = ++i variabilele au valorile i = 2 şi x = 2.
Exemplu. Fie declaraţia de variabile
int j = 3, k;
Instrucţiunea
k = --j;
reprezintă scrierea prescurtată a secvenţei de instrucţiuni
j = j – 1;
k = j;
După execuţia instrucţiunii variabilele au valorile j = 2 şi k = 2.
Exemplu. Fie următoarele declaraţii.
float a[4] = {1.2, -5, 8, 3};
float r;
int i = 1, k = 1;
Secvenţa de instrucţiuni
i = i +1;
r = a[i];
se poate scrie
r = a[++i];
Expresia ++i este evaluată la 2, iar r primeşte valoarea a[2] = 8.
Secvenţa de instrucţiuni
k = k – 1;
r = a[k];
se poate scrie
r = a[--k];
. Expresia --k este evaluată la 0 iar r primeşte valoarea a[0] = 1.2
• Cazul operatorilor postfix, x++, x--. Valoarea expresiei este chiar valoarea
variabilei (neincrementate sau decrementate). Se incrementează / decrementează
apoi variabila.
Exemplu. Fie cele două secvenţe de instrucţiuni de mai jos
int a, b;
b = 3;
a = b++;
Expresia
a = b++;
corespunde secvenţei de instrucţiuni
a = b;
b = b + 1;
In consecinţă, expresia b++ are valoarea 3 (valoarea neincrementată a
variabilei) şi apoi se incrementează b. Avem rezultatul
a=3
b=4
Exemplu. Fie cele două secvenţe de instrucţiuni de mai jos
int i; int j;
double x[4] = {2.4e1, 14.4, -3.1 0}; double x[4] = {2.4e1, 14.4, -3.1 0};
double d; double d;
i = 1; j = 1;
d = x[i++]; d = x[++j];
In primul caz instrucţiunea
d = x[i++];
corespunde secvenţei de instrucţiuni
d = x[i];
i = i + 1;
Expresia i++ are valoarea 1 şi apoi se incrementează i. Avem deci
d = 14.4
şi
i=2
In al doilea caz instrucţiunea
d = x[++j];
corespunde secvenţei de instrucţiuni
j = j + 1;
d = x[j];
In consecinţă expresia ++j are valoarea 2 şi avem
d = -3.1
şi
j=2
Alte exemple de utilizare a acestor operatori vor fi prezentate ulterior.

2.14 Operaţii cu numere întregi la nivel de bit

2.14.1 Operatori de depasare

Deplasarea la stânga, respectiv dreapta, a unui întreg cu un bit reprezintă înmulţirea,


respectiv împărţirea, acelui număr cu doi.
Operatorii de deplasare a unui număr întreg cu un număr de biţi sunt << pentru depasare
la stânga şi >> pentru depasare la dreapta. Expresia de deplasare a unui număr întreg are
forma

Rezultatul expresiei din stânga este configuraţia de biţi ce va fi deplasată. Expresia din
dreapta dă numărul de biţi cu care se face deplasarea. Deplasarea se face după regulile
deplasării numerelor binare cu semn. (La deplasarea la dreapta se propagă bitul de semn,
la deplasarea la stânga se adaugă zerouri). Dacă expresia de deplasat este de tipul
unsigned biţii adăugaţi sunt zerouri.
Exemple. Fie instrucţiunile de mai jos
int a = 0xff; int a = 0xff;
int b; int b;
b = a << 4; b = a >> 4;
Numărul a reprezentat pe patru octeţi are valoarea hexazecimală
a = 000000ff
Numărul deplasat la stânga cu 4 biţi este
00000ff0
iar deplasat la dreapta cu 4 biţi este
0000000f
Numărul 0xff convertit în zecimal este 255. Care este valoarea numerelor 0xf şi 0xff0 în
zecimal?.
Ca un alt exemplu, să definim constantele X, Y, Z şi R care să aibe valorile 1, 2, 4, 8,
folosind instrucţiunea enum.
enum {X = 1, Y = X << 1, Z = Y << 1, R = X << 3};
Limbajele C şi C++ au operatorii de atribuire <<= şi >>= care realizează operaţiile
următoare. Instrucţiunile
x = x << n
şi
x = x >> n
se scriu
x <<= n
şi respectiv
x >> n
Vom încheia acest paragraf cu un program care deplasează numere întregi şi le afişază
valoarea in zecimal şi hexazecimal. Pentru scrierea sau citirea unui număr întreg în
instrucţiunile cout sau cin în diferite baze se utilizează manipulatorii
• hex pentru baza 16
• dec pentru baza 10
• oct pentru baza 8
La începerea execuţiei unui program implicită este baza 10.
Vom exemplifica utilizarea operatorilor de deplasare şi vom afişa rezultatele în baza 16.

# include <iostream.h>
/* uitlizarea operatori de deplasare */
int main()
{
int a = 10;
cout << “a = “ << dec << a << “ “ << hex << a << endl;
// deplasează variabila a la stanga cu un bit
a = a << 1;
cout << “a deplasat la stanga cu 1 bit = “
<< dec << a << “ “ << hex << a << endl;
int b = 50;
cout << “b = “ << dec << b << “ “ << hex << b << endl;
// deplasează variabila b la dreapta cu 3 pozitii
b = b >> 3;
cout << “b deplasat la dreapta cu 3 biti = “
<< dec << b << “ “ << hex << b << endl;
return 0;
}

2.14.2 Operaţii logice la nivel de bit

Limbajele C şi C++ au următorii operatori pentru operaţii logice la nivel de bit


şi logic (and) notat &
sau logic (or) notat |
sau excusiv (xor) notat ^
complement faţă de unu notat ~
Aceşti operatori se definesc cu tabelele următoare

a b a&b a|b a^b


0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0

a ~a
0 1
1 0

Operatorii &, | ^ sunt operatori binary, ~ este operator unar. Expresiile cu operatori logici
la nivel de bit au formele următoare

Exemple. Fie următoarea secvenţă de program


int a, b, c, d, e;
a = 0xf000;
b = 0xabcd;
c = a & b;
d = a | b;
e = a ^ b;
Rezultatele sunt
c = 0xa000
d = 0xfbcd
e = 0x5bcd
In primul caz, pentru a calcula c = a & b avem de calculat
0xf & 0xa = 0xa
conform schemei de mai jos.
1 1 1 1 &
1 0 1 0
1 0 1 0
Pentru a calcula e = a ^ b trebuie să calculăm
0xf ^ 0xa = 0x5
conform schemei de mai jos
1 1 1 1 ^
1 0 1 0
0 1 0 1
Expresia ~a are valoarea 0x0fff. De ce?
Limbajele C şi C++ au operatorii &=, |= şi ^= pentru scrierea prescurtată a instrucţiunilor
de atribuire. De exemplu expresia
x=x&a
Se scrie prescurtat
x &= a
Menţionăm că operatorii <<, >>, &, | şi ~ sunt operatori aritmetici.
Exemplu. Fie variabila întreagă a = 0x6dbc. Vrem ca în variabila întreagă b să selectăm
ultimul octet al variabilei a şi apoi ultimii 10 biţi ai variabilei a. Selectarea unui număr de
biţi dintr-un şablon se face definind un şir de biţi numit mască şi efectuând operaţia &
între variabila iniţială şi mască.

# include <iostream.h>
int main()
{
int a = 0x6dbc, b;
cout << “a = “ << hex << a << endl;
// selecteaza ultimul octet
b = a & 0xff;
cout << “b = “ << hex << b << endl;
b = a & 0x3ff;
// selecteaza ultimii 10 biti
cout << “b = “ << hex << b << endl;
return 0;
}

Să se explice de ce în cazul al doilea masca este 0x3ff;

Cap. 3 Structuri de control fundamentale

3.1 Algoritme

Programele reprezintă formulări concrete ale unor algoritme ce prelucrează structuri de


date. Un algoritm este format dintr-un şir finit de acţiuni bine definite şi neambigue
pentru rezolvarea unei probleme. Fiecare acţiune trebuie să poată fi executată într-un
interval finit de timp.
Orice algoritm se poate descompune într-un număr finit de etape. El poate fi reprezentat
printr-un şir de acţiuni astfel încat efectul general al acestor acţiuni să conducă la
rezultatul dorit al calculului.
In cazul cel mai simplu un algoritm poate fi descompus într-un număr de acţiuni
secvenţiale care se reprezintă în felul următor
s1; s2; … sn;
Pentru executarea unei acţiuni în funcţie de îndeplinirea unei condiţii avem operatori
condiţionali
• Operatorul if cu formele
if condiţie s1;
sau
if condiţie s1; else s2;
condiţie este o expresie booleană care are valoarea advărat sau fals. Acţiunea s1
se execută când condiţie are valoarea adevărat.
• Operatorul switch este generalizarea operatorului if
switch(i)
i = 1: s1;
i = 2: s2;
………..
i = n: sn;
In funcţie de valoarea lui i se execută una dintre acţiunile s1, s2, …, sn.
Aceşti operatori se caracterizează prin faptul că au o singură intrare şi o singură ieşire.
Fiecare operator este interpretat în şirul de calcule ca o singură acţiune, indifferent de
conţinutul său. Operatorii anteriori sunt suficienţi pentru a descrie clasele de calcule ce se
pot descompune într-un număr cunoscut de acţiuni.
Pentru a descrie calculele repetate când numărul de acţiuni nu este cunoscut există
operatorii while şi do.
• Operatorul while are forma
while condiţie
s;
Acţiunea s se execută atata timp cat expresia booleană condiţie are valoarea
adevărat.
• Operatorul do are forma
do
s;
while condiţie
Si în acest caz acţiunea s se execută atâta timp cat condiţie are valoarea adevărat.

Menţionăm că în cazul operatorului do acţiunea s se execută cel puţin o dată, în


timp ce pentru operatorul while este posibil ca acţiunea s să nu se execute
niciodată.
• Operatorul for se utilizează atunci când numărul de execuţii ale unei acţiuni este
dinainte cunoscut. Operatorul are o variabilă de control ce se poate modifica la
fiecare iteraţie. Forma acestui operator este
for i = instrucţiune1; condiţie; instrucţiune2
s;
Operatorul include o instrucţiune1 ce specifică valoarea iniţială a variabilei de
control, şi o instrucţiune2 ce poate modifica valoarea variabilei de control după
fiecare iteraţie. Acţiunea s se execută atât timp cât expresia booleană condiţie are
valoarea adevărat. Operatorul for este echivalent cu următoarele instrucţiuni
instrucţiune1;
while(condiţie)
s;
instrucţiune2;
Menţionăm că testul condiţiei se face înainte de execuţia acţiunii s.
Operatorii prezentaţi se numesc structuri de control fundamentale. In construcţia unui
program structurile de control şi structurile de date sunt inseparabile. Structurile
fundamentale de date sunt
• Structuri simple: numere întregi, reale, caractere.
• Structuri complexe tablouri, fişiere.
Tablourile au un număr cunoscut de elemente. Ele se prelucrează de regulă cu operatorul
for. Fişierele secvenţiale au un număr de elemente necunoscut în avans. Ele se
prelucrează de regulă cu operatorul while.

3.2 Expresii relaţionale

Operatorii relaţionali ai limbajelor C şi C++ sunt


<, <=, >, >=
= =, !=
O expresie relaţională are următoarea formă

Rezultatul evaluării unei expresii relaţionale este fals sau adevărat. Priorităţile
operatorilor aritmetici sunt mai mari decăt ale operatorilor relaţionali.
Exemple. Fie următoarele instrucţiuni de atribuire
int a = 2, b = 3, c = 6;
In tabela de mai jos sunt prezentate exemple de expresii relaţionale şi rezultatul evaluării
lor.

Expresie relaţională Valoare


a*b >= c True
b+2 > a *c False
a+b = = 3 False
a != b True
b/a = = 1 True

Reamintim că în limbajul C++ valoarea true este reprezentată prin 1 iar valoarea false
prin 0. Fie următoarea instrucţiune de declarare a unei variabile de tip bool
bool r;
Putem avea următoarele instrucţiuni de atribuire
r = a + b = = c;
r = a – b >= 2;
r = a * a != -b;
Variabila r poate avea valori booleene, true sau false.

3.3 Expresii booleene

Operatorii booleeni ai limbajelor C şi C++ sunt


!
&&
| |
care reprezintă operatorii not, and şi respectiv or. Operatorul not este operator unar.
Aceşti operatori se definesc folosind tabelele de adevăr.

x y x && y x||y
false false false false
false true false True
true false false True
true true false True

x !x
false true
true false

Rezultatul evaluării unei expresii booleene este true sau false. In cursurile de logică
operatorii booleeni se notează astfel
not ¬
and ∧
or ∨
Exemple de expresii booleene şi scrierea lor sunt prezentate mai jos.
a ∧b a & &b
¬( a ∨ b) !( a || b)
a ∧b ∨ c a & &b || c
¬a ∨ ¬b ! a ||! b
a ∧ (b ∨ c) a & &( b || c )
Pentru scrierea simplă a expresiilor booleene sunt importante două teoreme numite
legile lui DeMorgan
¬( a ∨ b) = ¬a ∧ ¬b
¬( a ∧ b) = ¬a ∨ ¬b
care se demonstrează cu ajutorul tabelelor de adevăr.
In final menţionăm că operatorii booleeni and şi or sunt
• Comutativi
a ∨b = b ∨a
a ∧b = b ∧a
• asociativi la stânga
a ∨b ∨c = ( a ∨ b) ∨ c
a ∧b ∧ c = ( a ∧ b) ∧ c
• distributivi
a ∨ (b ∧ c) = ( a ∨ b) ∧ ( a ∨ c )
a ∧ (b ∨ c) = ( a ∧ b) ∨ ( a ∧ c )
In final prezentăm prioritatea (precedenţa) şi asociativitatea operatorilor
Operator asociativitate
[]() stânga
++ -- + - ! ~ sizeof dreapta
*/% stânga
+- stânga
<< >> stânga
< <= > >= stânga
= = != stânga
& stânga
^ stânga
| stânga
&& stânga
|| stânga
= += -= *= /= %= stânga

3.4 Operatorul do-while

Acest operator este definit de diagrama sintactică

do
instrucţiune
while(expresie);
Instrucţiunea se execută atâta timp cât expresia este diferită de zero. Instrucţiunea din
diagrama sintactică este o secvenţă de instrucţiuni.
Ca exemplu vom calcula valoarea 5!.
5
5!= ∏i
i =1
Vom utiliza o variabilă n pusă iniţial la valoarea 1 şi vom executa repetat instrucţiunea
n=n*i
pentru i luând valori de la 1 la 5. Programul pseudocod este următorul.

n = 1;
i=1
do
n = n * i;
i = i + 1;
while(i < 6)

Programul corespunzător este următorul

# include <iostream.h>
/* calculul valorii 5! */
int main()
{
int i, n;
i = 1;
n = 1;
do
n = n * i;
i = i + 1;
while(i < 6);
cout << “5! are valoarea “ << n << endl;
return 0;
}

Putem rescrie partea de calcul din program astfel

int i = 1, n = 1;
do
n *= i;
i++;
while(i < 6);

sau

int i = 1, n = 1;
do
n *=i++;
while(i < 6);

Reamintim că instrucţiunea
n *= i++;
este echivalentă cu instrucţiunile
n *= i;
i++;
conform modului de execuţie a operatorului postfix ++ : valoarea expresiei este chiar
valoarea variabilei i neincrementate, după care variabila i este incrementată.

3.5 Operatorul while

Forma operatorului while este cea din diagrama de mai jos


while(expresie)
instrucţiune
instrucţiunea se execută atâta timp cât expresia este diferită de zero. Instrucţiunea din
diagrama sintactică poate fi o instrucţiune compusă, formată dintr-o secvenţă de
instrucţiuni cuprinse între acolade, { şi }.
Vom exemplifica utilizarea acestui operator calculând suma elementelor unui vector a cu
4 componente. Elementele vectorului a au indicii 0, 1, 2 şi 3.
3
s = ∑ ai
i =0
Vom utilize o variabilă f de tip float pentru a calcula suma, care va fi pusă iniţial la
valoarea zero şi vom executa repetat instrucţiunea
s = s + ai
pentru i luând valori de la 0 la 3. Programul pseudocod este următorul.
s = 0;
i = 0;
while(i < 4)
s = s + a[i];
i = i + 1;
Programul este următorul

#include <iostream.h>
/* calculul sumei componentelor unui vector */
int main()
{
float a[4] = {2.34, -7.32, 2.5e-1, 73};
int i;
float s;
s = 0;
i = 0;
while(i < 4)
{
s = s + a[i];
i = i + 1;
}
cout << “suma componentelor este “ << s << endl;
return 0;
}

Putem rescrie partea de calcul din program astfel

s = 0;
i = 0;
while(i < 4)
{
s += a[i];
i++;
}

sau
s = 0;
i = 0;
while(i < 4)
{
s += a[i++];
}

3.6 Operatorul for

Operatorul for are forma


for(expresie1; expresie2; expresie3)
instrucţiune
Operatorul poate conţine variabile de control ce sunt modificate după fiecare iteraţie.
Operatorul for execută repetat o instrucţiune, atât timp cât expresie2 este diferită de zero.
expresie1 are rolul de a iniţializa variabile de control. expresie3 are rolul de a modifica
valoarea variabilelor de control. Instrucţiunea din diagrama sintactică poate fi o
instrucţiune compusă constand dintr-o secvenţă de instrucţiuni între acolade, { şi }.
Ca exemplu vom calcula valoarea expresiei e x + 2 * x pentru x cuprins între unu şi doi cu
pasul 0.2. Programul pseudocod este următorul

for i = 0; i <6; i = i + 1
x = 1 + 0.2 * i
y = ex + 2* x

Programul este următorul

#include <iostream.h>
# include <math.h>
int main()
{
int i;
float x, y
cout << “x” << ‘\t’ << “y” << endl;
for(i = 0; i < 6; i = i + 1)
{
x = 1 + 0.2 * i;
y = exp(x) + 2 * x + sqrt(x);
cout << x << ‘\t’ << y << endl;
}
return 0;
}

Vrem ca rezultatul să fie afişat în două coloane cu antetul x şi y. Pentru aceasta utilizăm
instrucţiunea
<< “x” << ‘\t’ << “y” << endl;
care scrie caracterele x şi y separate de caracterul ‘\t’ (tab). In program rezultatele sunt
scrise de asemenea separate de tab.
Menţionăm că instrucţiunea for se putea scrie astfel
for(i = 0; i < 6; i++)
sau
for(i = 0; i < 6; ++i)
Un alt mod de a calcula valorile expresiei de mai sus este următorul.

# include <iostream>
# include <math.h>
int main()
{
float x, y
cout << “x” << ‘\t’ << “y” << endl;
for(x = 1.0; x <= 2.0; x = x + 0.2)
{
y = exp(x) + 2 * x + sqrt(x);
cout << x << ‘\t’ << y << endl;
}
return 0;
}

Vom încheia acest paragraf cu un program care să calculeze suma a doi vectori.

# include <iostream.h>
/* calculul sumei a doi vectori */
int main()
{
float x[3] = {1.1e+1, -2.57, 13.2};
float y[3] = {-2.12, 13.5, 3.41};
float z[3];
for(int i = 0; i < 3; i++)
z[i] = x[i] + y[i];
for(int i = 0; i < 3; i++)
cout << z[i] << ‘\t’;
cout << ‘\n’;
return 0;
}

In final menţionăm instrucţiunile break şi continue. Instrucţiunea break produce ieşirea


imediată din instrucţiunile for, while sau do-while. Instrucţiunea continue trece la
următoarea iteraţie a instrucţiunii for, while sau do-while.

3.7 Operatorul if

Acest operator execută o anumită instrucţiune în funcţie dacă o anumită condiţie este
îndeplinită. Forma operatorului if este
if(expresie)
instrucţiune1;
else
instrucţiune2;
Dacă expresie este diferită de zero se execută instrucţiune1 altfel instrucţiune2.
Instrucţiunile din diagrama sintactică pot fi instrucţiuni compuse.
Vom exemplifica utilizarea acestui operator calculând suma componentelor pare şi
impare ale unui vector x cu şase elemente. Programul pseudocod este următorul

s1 = 0;
s2 = 0;
for i = 0; i < 6; i=i+1
if i % 2 = = 0
s1 = s1 + xi
else
s2 = s2 + xi
Programul este următorul

#include <iostream.h>
/* calculul sumei componentelor pare si impare ale unui vector */
int main()
{
float s1 = 0, s2 = 0;
int i;
float x[10] = {1, -12, 23.2, 0.44, 3.4, -2.3};
for(i = 0; i < 6; i = i + 1)
if(i % 2 = = 0)
s1 = s1 + x[i];
else
s2 = s2 + x[i];
cout << “suma elementelor pare este “ << s1 << endl
<< “suma elementelor impare este “ << s2 << endl;
return 0;
}

Menţionăm că o instrucţiune if poate conţine alte instrucţiuni if. De exemplu putem avea
instrucţiunea compusă

if (e1) if (e2)
s1
else
s2
else if(e3)
s3
else
s4
unde e1, e2, e3 sunt expresii booleene. Dacă expresia booleană e1 are valoarea adevărat,
(este diferită se zero), se execută instrucţiunea if(e2), în caz contrar se execută
instrucţiunea if(e3).
3.8 Operatorul ?

Acest operator este o formă simplificată a operatorului if. Forma lui este
expresie ? instrucţiune1 : instrucţiune2;
Dacă expresie are o valoare diferită de zero se execută instrucţiune1 altfel instrucţiune2.
Vom rescrie programul anterior astfel

#include <iostream.h>
/* calculul sumei componentelor pare si impare ale unui vector */
int main()
{
float s1 = 0, s2 = 0;
int i;
float x[10] = {1, -12, 23.2, 0.44, 3.4, -2.3};
for(i = 0; i < 6; i = i + 1)
i % 2 = = 0 ? s1 = s1 + x[i] : s2 = s2 + x[i];
cout << “suma elementelor pare este “ << s1 << endl
<< “suma elementelor impare este “ << s2 << endl;
return 0;
}

3.9 Operatorul switch

Acest operator execută o instrucţiune din mai multe posibile. Fiecare instrucţiune este
etichetată cu o constantă întreagă. Instrucţiunea conţine o expresie întreagă ce determină
ce etichetă trebuie aleasă.
switch(expresie)
{
case expint: instructiune
case expint: instructiune
….
case expint: instructiune
}
Una dintre etichete poate fi default. In acest caz, dacă expresia din instrucţiunea switch
nu are nici una din valorile etichetelor din instrucţiunile case, se trece la execuţia
instrucţiunilor cu eticheta default.
Instrucţiunea break este utilizată pentru ieşirea din instrucţiunea switch.
Exemplu. Vom citi două numere întregi şi un operator +, -, *, / sau % şi vom calcula
rezultatul operaţiei corespunzătoare.

#include <iostream.h>
int main()
{
int x, y;
char oper;
cout << “intoduceti doi intregi : ”;
cin >> x >> y;
cout << “introduceti un operator : “;
cin >> oper;
switch(oper)
{
case ‘+’ :
cout << x + y;
break;
case ‘-‘ :
cout << x – y;
break;
case ‘*’ :
cout << x * y;
break;
case ‘/’ :
cout << x / y;
break;
case ‘%’ :
cout << x % y;
break;
default :
cout << “eroare”;
break;
}
return 0;
}

Menţionăm că instrucţiunile corespunzătoare etichetelor sunt urmate de instrucţiunea


break ce asigură ieşirea din operatorul switch.

3.10 Operatorul ,

Operatorul , are următoarea formă


expresie1, expresie2
Rezultatul operatorului , este expresie2, rezultatul evaluării expresie1 se neglijază.
Operatorul , se poate utiliza oriunde limbajul admite o singură expresie, dar sunt necesare
două expresii.
Cap. 4 Funcţii

4.1 Definirea funcţiilor

Funcţiile se definesc într-un program atunci când un grup de instrucţiuni se utilizează în


mai multe locuri din program. Prototipul definiţiei unei funcţii este
tip nume (lista de parametri)
instrucţiune
Instrucţiunea este o instrucţiune compusă formată dintr-o secvenţă de instrucţiuni între
acolade. Lista de parametri are forma
tip1 arg1, tip2 arg2, …, tipn argn
unde tip1, tip2, …, tipn reprezintă tipurile parametrilor arg1, arg2, …, argn. Parametrii
din definiţia funcţiei se numesc parametri formali. O parte din parametri sunt variabile ce
vor fi prelucrate de funcţie (aceşti parametri se numesc parametri de intrare). O funcţie
poate calcula o valoare asociată numelui şi mai multe valori asociate unor parametri
(aceşti parametri se numesc parametri de ieşire). Tipul valorii asociate numelui este şi
tipul funcţiei. Dacă o funcţie nu are o valoare asociată numelui, tipul ei este void.
Asocierea unei valori calculate numelui funcţiei se face cu instrucţiunea return cu forma
următoare
return expresie;
unde expresie este valoarea calculată. Când tipul funcţiei este void, se utilizează
instrucţiunea
return;
Funcţiile sunt recursive, adică o funcţie se poate apela pe ea însăşi.
Apelarea unei funcţii se face utilizănd-o ca termen într-o expresie. Parametri de
apelare a funcţiei se numesc parametri actuali. Parametrii de intrare actuali ai unei
funcţii pot fi orice constante, variabile sau expresii. Parametrii actuali de ieşire sunt
variabile.
Exemplu. Vom scrie o funcţie care să calculeze suma componentelor unui vector cu
elemente numere reale. Parametrii funcţiei vor fi vectorul şi dimensiunea sa. Rezultatul
este o valoare reală ataşată numelui funcţiei.

#include <iostream.h>
/* funcţie ce calculeaza suma componentelor unui vector
Parametri de intrare
a – vector
n – dimensiunea vectorului a
*/
float suma(float a[], int n)
{
int i;
float s = 0;
// calculeaza suma componentelor
for (i = 0; i < n; i = i++)
s = s + a[i];
return s;
}
int main()
{
float x[5] = {11.3, -2.67, 0.34, -2.5, 14};
float z;
// calculeaza suma componentelor
z = suma(x, 5);
// scrie componentele vectorului
cout << “suma componentelor vectorului “ << endl;
for(i = 0, i < 5; i++)
cout << x[i] << “\t”;
cout << endl;
// scrie rezultatul
cout << “este “ << z << endl;
}

Componentele vectorului sunt scrise separate de caracterul tab pe un rând iar rezultatul pe
rândul următor. In exemplul anterior definiţia funcţiei este scrisă înainte de utilizarea ei în
funcţia main. Menţionăm că este posibil de a scrie definiţia unei funcţii după ce ea este
utilizată. In acest caz trebuie să scriem înainte de prima utilizare un şablon al funcţiei.
Şablonul unei funcţii are următoarea definiţie
tip nume(lista de parametri);
Pentru exemplul anterior şablonul poate fi
float suma(float a[], int n);
sau
float suma(float [], int);
In şablon putem să identificăm doar tipurile parametrilor.
Intr-un program putem avea mai multe funcţii cu acelaşi nume dar cu parametri
diferiţi sau cu un număr de parametri diferit. Acest lucru se numeşte
supraîncărcarea funcţiilor.

4.2 Pasarea parametrilor funcţiilor

La apelarea unei funcţii, parametrii actuali şi variabilele locale ale funcţiei sunt memorate
într-o stivă. Există două moduri de a pasa paremetrii unei funcţii: prin valoare şi prin
referinţă (adresă). In cazul pasării unui parametru prin valoare, în stivă se pune chiar
valoarea parametrului, în cazul pasării prin adresă în stivă se pune adresa parametrului.
• Cazul parametrilor pasaţi prin valoare. Dacă în corpul funcţiei modificăm aceşti
parametri, valoarea lor se modifică doar în stivă şi nu în programul apelant. In
consecinţă, parametrii pasaţi prin valoare nu pot fi parametri de ieşire ai
funcţiei.
• Cazul parametrilor pasaţi prin referinţă. Dacă în corpul funcţiei modificăm aceşti
parametri, valoarea lor se modifică în programul apelant (în stivă este adresa
acestor parametri). In consecinţă, pentru ca un parametru al unei funcţii să fie
parametru de ieşire, el trebuie pasat prin adresă
Pentru a vedea care este diferenţa între cele două moduri considerăm o funcţiei care să
permute valoarea a două variabile de tip întreg. O variantă necorectă este următoarea

void perm(int a, int b)


{
int c;
c = a;
a = b;
b = c;
return;
}

int main()
{
int x = 7, y = 12;
cout << “valoarile initiale x = “ << x << “ y = “ << y << endl;
perm(x, y);
cout << “valoarile permutate x = “ << x << “ y = “ << y << endl;
}

In ambele cazuri se tipăreşte aceeaşi valoare, x = 7 şi y = 12. Motivul este acela că


parametrii fiind pasaţi prin valoare, în funcţie instrucţiunile
a= b;
b = c;
modifică valoarea lor doar în stiva şi nu în programul apelant. In consecinţă, parametrii
pasaţi prin valoare nu pot fi parametri de ieşire ai funcţiei. Pentru ca un parametru al
unei funcţii să fie parametru de ieşire, el trebuie pasat prin referinţă (adresă). Există două
moduri de a pasa parametri prin adresă: utilizarea parametrilor tip referinţă şi tip pointer.
Referinţele şi variabilele tip pointer vor fi prezentate pe larg în paragraful următor. Vom
prezenta acum doar parametrii tip referinţă.
Fie un parametru de un tip T al unei funcţii. Referinţa la un astfel de parametru are tipul
T&. Dacă un parametru este tip referinţă, la apelarea funcţiei în stivă se va pune adresa
acestui parametru, şi atunci când este modificat în corpul funcţiei el este modificat în
programul apelant (este parametru de ieşire).
Varianta corectă a funcţiei care permută valorile a două variabile este următoarea

/* functie ce permute valoarea a doua variabile */


void perm(int& a, int& b)
{
int c;
c = a;
a = b;
b = c;
return;
}

Apelarea acestei funcţii se face astfel

int main()
{
int x = 7, y = 12;
cout << “valoarile initiale x = “ << x << “ y = “ << y << endl;
perm(x, y);
cout << “valoarile permutate x = “ << x << “ y = “ << y << endl;
}

In stivă se pun de această dată adresele variabilor x şi y deoarece parametri funcţiei sunt
de tip referinţă. Menţionăm că apelarea funcţiei se face utilizând pentru parametrul
referinţă o variabilă din programul apelant (nu o constantă sau expresie), în cazul nostru
perm(x,y)
Vom recapitula acum diferenţele între parametrii pasaţi prin valoare şi cei pasaţi prin
adresă (referinţă).
• Un parametru pasat prin valoare este un parametru de intrare. Valoarea lui actuală
este rezultatul evaluării unei constante, variabile sau expresii în programul
apelant.
• Un parametru tip referinţă este un parametru de ieşire. Argumentul pasat este o
variabilă din programul apelant.
Menţionăm că în cazul în care un parametru este un tablou, în stivă se pune adresa
primului element al tabloului. In consecinţă, parametri formali tablouri pot fi
parametri de ieşire.
Exemplu. Vo defini o funcţie ce calculează suma a doi vectori x şi y de numere reale de
dimensiune n. Tipul funcţiei este void. Vectorul sumă va fi z.

/*
Calculul sumei a doi vectori
Parametri de intrare
x – vector
y – vector
n – dimensiunea vectorilor x, y, z
Parametri de iesire
z – vector, z = x + y
Preconditii
Parametri de intrare sunt initializati
*/
void sumvect(double x[], double y[], double z[], int n)
{
int i;
for(i = 0; i < n; i++)
z[i] = x[i] + y[i];
}

Vom încheia acest paragraf definind o funcţie care să calculeze media şi dispersia unui şir
de numere x1 , x2 , xn . Valoarea medie a elementelor şirului este
1 n
m = ∑ xi
n i =1
iar dispersia
1 n
d= ∑ ( xi − m) 2
n − 1 i =1
Tipul funcţiei va fi void. Media şi dispersia vor fi asociate unor parametri de ieşire ai
funcţiei. Prototipul funcţiei va fi
void md(float x[], int n, float& m, float& d);
unde x este vectorul de numere de dimensiune n pentru care calculăm media şi dispersia.

#include <iostream.h>
/*
Calculul mediei si dispersiei componentelor unui vector
Parametri de intrare
x – vector
n – dimensiunea vectorului x
Parametri de iesire
m – media componentelor lui x
d – dispersia componentelor lui x
Preconditii
Parametrii de intrare sunt initializati
*/
void md(float x[], int n, float& m, float& d)
{
float s = 0;
int i;

// calculeaza media
for(i = 0; i < n; i++)
s = s + x[i];
m = s / n;

// calculeaza dispersia
float d = 0;
for(i = 0; i < n; i++)
d = d + (x[i] – m) * (x[i] – m);
d = d / (n – 1);
return;
}

int main()
{
float a[4] = {1.2e-1, -2.34, 1.5, 3.33};
float media, dispersia;
md(a, 4, media, dispersia);
cout << “ media = “ << media << “ dispersia = “ << dispersia << endl;
}

4.3 Recursivitatea

Funcţiile limbajelor C şi C++ sunt recursive. O funcţie se poate apela pe ea însăşi. Vom
exemplifica acest lucru cu o funcţie care să calculeze valoarea n! în variantă recursivă şi
nerecursivă.

Varianta nerecursivă Varianta recursivă

n
 1 n =1
n! = ∏i n! = 
i =1 n * (n −1)! n >1

// calculul factorialului // calculul factorialului


int fact(int n) int fact(int n)
{ {
int s = 1; int s;
for(int i = 1; i <= n; ++n) if(n = = 1)
s = s * i; s = 1;
return i; else
} s = n * fact(n – 1);
return s;
}

Vom prezenta stiva la apelarea variantei recursive pentru n = 3. Reamintim că parametrul


n şi variabila locală s sunt memoraţi în stivă. După apelul
fact(3)
stiva este

N=3 S=?

Deoarece n este diferit de unu, se execută instrucţiunea


S = n * fact(n – 1)
adică se apelează încă o dată funcţia, fact(2). Stiva este

N=3 S=? N=2 S=?

Se apelează încă o dată funcţia, fact(1), şi stiva este

N=3 S=? N=2 S=? N=1 S=1

După această ultimă apelare se ajunge la înstrucţiunea return, după care variabilele
corespunzând ultimei apelări a funcţiei se sterg din stivă. Stiva devine

N=3 S=? N=2 S=2

Din nou se ajunge la instrucţiunea return, variabilele corespunzând acestei apelări se


strerg din stivă

N=3 S=6

După care se obţine rezultatul final, valoarea 6.

4.4 Şabloane de funcţii

Şabloanele ne permit să definim funcţii generice în care putem defini tipuri generale ale
parametrilor şi a valorii returnate de funcţie. Definirea unui şablon de funcţie se face cu
instrucţiunea
template <typename identificator, … > declaraţie de funcţie
Să definim de exemplu o funcţie generică pentru calculul maximului a două variabile
/* functie generica ce calculeaza maximul a doua variabile */
template <typename T>
T maxval(T a, T b)
{
return (a > b ? a : b);
}
Apelarea unui şablon de funcţie se face astfel
nume_funcţie <tip>(parametri);
De exemplu, putem calcula maximul a două numere întregi sau reale astfel.

int main()
{
// maximul a doua numere intregi
int a = 15, x = 20, c;
c = maxval<int>(a, x);
cout << “ maximul dintre “ << a << “ si “ << b << “ este “ << c << endl;
// maximul a doua numere reale
float fa = 3.43, fb = -9.3;
cout << “maximul dintre “ << fa << “ si “ << fb << “ este “
<< maxval<float>(fa, fb) << endl;
}

Cap. 5 Pointeri şi referinţe

In orice program compilatorul alocă oricărei variabile definite în program o zonă de


memorie egală cu numărul de octeţi corespunzând tipului variabilei. In cazul unui tablou
se alocă memorie fiecărui component al tabloului. Fie de exemplu instrucţiunea
int a, b, x[3];
Compilatorul alocă zone de memorie variabilelor ca mai jos

Compilatorul crează o tabelă cu numele variabilelor şi adresele lor în memorie, de


exemplu
a 1000
b 1004
x 1008
O instrucţiune de atribuire de forma
a = b;
este executată astfel: se ia valoarea de la adresa variabilei a (în cazul nostru adresa 1000)
şi se pune la adresa variabilei b (în cazul nostru adresa 1004). Limbajele C şi C++ permit
definirea unor variabile ce conţin adrese ale altor variabile. Acestea au tipul pointer şi
referinţă. După cum vom vedea, aceste variabile se utilizează la transmiterea parametrilor
funcţiilor prin adresă, care pot fi parametric de ieşire ai funcţiilor. Pointerii sunt utili şi la
prelucrarea tablourilor.

5.1 Pointeri

Un pointer este o variabilă ce conţine adresa unei alte variabile. De exemplu, dacă n este
o variabilă de tip int, ce are valoarea 3, iar pn este o variabilă de tipul pointer la int, ce
conţine adresa lui n, putem reprezenta cele două variabile astfel

O variabilă de tip pointer se defineşte cu instrucţiunea


tip * nume;
De exemplu
int * pn;
defineşte o variabilă tip pointer la int ce poate conţine adresa unei variabile de tip int.
Pentru a calcula adresa unei variabile se utilizează operatorul &. Dacă x este o variabilă
oarecare, expresia &x este adresa lui x. Variabilele tip pointer pot fi iniţializate doar cu
adrese. De exemplu putem scrie
int n;
int * pn; // pn este de tipul pointer la int
pn = &n; // initializeaza pn cu adresa lui n
Pentru a obţine valoarea variabilei indicate de un pointer se utilizează operatorul * (numit
şi operator de adresare indirectă).
Fie de exemplu instrucţiunile
int k, n= 14;
int * pn, *pk;
Instrucţiunile
k = n;
şi
pn = &n;
k = *pn;
sunt echivalente, k primeşte valoarea 14. (Variabila pn a fost iniţializată în prealabil cu
adresa lui n). Instrucţiunile
k = n;
şi
pk = &k;
*pk = n;
sunt echivalente. In final, instrucţiunile
k = n;
şi
pk = &k;
pn = &n;
*pk = *pn;
sunt echivalente;
Exemplu. Fie următoarea secvenţă de instrucţiuni
float x;
float * y;
A doua instrucţiune declară că y poate conţine adresa unei variabile de tip float.
Instrucţiunile
x = 3.5;
şi
y = &x;
*y = 3.5;
sunt echivalente.
Exemplu. Vom scrie o funcţie care să permute valorile a două variabile ce vor fi
parametri de ieşire ai funcţiei. De aceea aceşti parametri vor fi variabile tip pointer.

void perm(int* a, int* b)


{
int c;
c = *a;
*a = *b;
*b = c;
}

Utilizarea acestei funcţii se face astfel

int main()
{
int x = 3, z = -4;
// scrie variabilele inainte de permutare
cout << “x = “ << x << “ y = “ << y << endl;
perm(&x, &y);
// scrie variabilele dupa permutare
cout << “x = “ << x << “ y = “ << y << endl;
}

Limbajul C nu are referinţe. In consecinţă, singurul mod de a declara parametri de


ieşire ai unei funcţii în limbajul C, este ca aceştia să fie de tip pointer.
Operatorii * şi & sunt asociativi la dreapta. Operatorii * şi & sunt inverşi.
Exemplu. Fie instrucţiunile
int a, b;
a = 7;
Instrucţiunile
b = a;
şi
b = *&a;
sunt echivalente.
Exemplu. Vom afişa valoarea unor variabile tip pointer.

#include <iostream.h>
int main()
{
int a, b;
// definim variabile tip pointer
int *pa, *pb;

pa = &a;
pb = &b;
// se scrie adresa variabilei a
cout << “ adresa variabilei a “ << &a << “ “ << pa << endl;
// se scrie valoarea variabilei b
b = 15;
cout << “ valoarea variabilei b “ << b << “ “ << *pb << endl;
}

Prima instrucţiune cout va afişa de două ori acceaşi adresă. A doua instrucţiune cout va
afişa de două ori aceeaşi valoare, 15.
Menţionăm că numele unui tablou este, prin definiţie, un pointer la primul element
al tabloului. Fie de exemplu instrucţiunile
float x[7], * pf;
Un pointer la tabloul x are valoarea &x[0] sau x . Putem deci scrie
pf = &x[0];
sau, echivalent,
pf = x;
Dacă scriem
pf = &x[3];
pf va conţine adresa celui de al patrulea element al tabloului. Legătura între tablouri şi
variabile tip pointer va fi prezentată pe larg în paragrafele următoare.
Exemplu. Să afişăm adresele elementelor unui vector de întregi.

# include <iostream.h>
int main()
{
int x[5];
for(int i = 0; i < 5; i++)
cout << “indice “ << i << “ adresa “ << &x[i] << endl;
return 0;
}

Exemplu. Fie instrucţiunile


int x, y;
int * a;
int ** b;
Instrucţiunea a doua declară că a poate conţine adresa unei variabile de tip int. A treia
instrucţiune declară că b poate conţine adresa unei variabile de tip int * (pointer la int).
Fie instrucţiunea
x = 10;
Instrucţiunile
y = x;
şi
a = &x;
y = * a;
şi respectiv
a = &x;
b = &a;
y = ** b;
sunt echivalente. Pentru a arăta aceasta, considerăm alocarea variabilelor x, y, a, b în
memorie. Adresele acestor variabile pot fi cele din tabela de mai jos

Variabila Adresa în memorie Conţinut


x 1000 Valoarea 10
y 1004 Valoarea 10
a 1008 Adresa 1000
b 1012 Adresa 1008

In cazul instrucţiunii
y = x;
la execuţia programului valoarea de la adresa variabilei x se depune la adresa variabilei y.
Ea este echivalentă cu instrucţiunile
a = &x;
y = * a;
Valoarea de la adresa a indică adresa unei alte variabile (x în cazul nostru, deoarece s-a
executat instrucţiunea a = &x). Se aplică operatorul * asupra variabilei a şi se obţine
conţinutul acestei variabile care este adresa lui x. Conţinutul de la această adresă se pune
la adresa lui y. Reamintim că operatorul * este asociativ la dreapta.
Instrucţiunea anterioară este echivalentă şi cu instrucţiunile
a = &x;
b = &a;
y = **b;
După execuţia instrucţiunii
a = &x;
variabila a conţine adresa lui x după cum s-a explicat mai înainte.
După excuţia instrucţiunii
b = &a;
variabila b conţine adresa lui a. Instrucţiunea y = **b se execută aplicând operatorul *
asupra variabilei b, rezultatul fiind valoarea variabilei b, adică adresa variabilei a. Se
aplică operatorul * asupra acestei valori (ceea ce este achivalent cu *a) şi se obţine
conţinutul lui a care este adresa variabilei x. Valoarea de la adresa lui x se depune în y.
Ca un ultim exemplu considerăm următoarele instrucţiuni
int x, y;
int *a;
int **aa;
int ***aaa;
int ****aaaa;
Aceste instrucţiuni declară că a poate conţine o adresă de variabilă tip int, aa poate
conţine o adresă de tip int *, aaa poate conţine o adresă de tip int **, etc. Vom iniţializa
pe x şi variabilele tip pointer astfel:
x = 10;
a = &x;
aa = &a;
aaa = &aa;
aaaa = &aaa;
Instrucţiunile
y = x;
y = *a;
y = **aa;
y = ***aaa;
y = ****aaa;
produc acelaşi rezultat, adică
y = 10;
Instrucţiunile
cout << x
cout << *a
cout << **aa
cout << ***aaa
cout << ****aaaa;
produc afişarea aceleiaşi valori
x = 10
La tipărirea valorilor variabilelor de tip pointer, se tipăresc adresele conţinute de acestea
în hexazecimal. Instrucţiunile
cout << &x << ‘\t’ << a << endl;
cout << &a << ‘\t’ << aa << endl;
cout << &aa << ‘\t’ << aaa << endl;
cout << &aaa << ‘\t’ << aaaa << endl;
afişază respectiv următoarele valori:
adresa lui x
adresa lui a
adresa lui aa
adresa lui aaa
Considerăm următoarele instrucţiuni
int x, y = 4;
x = y;
Operatorul = aplicat asupra celor doi operanzi, în cazul nostru x şi y, ia valoarea de la
adresa corespunzând variabilei din stânga şi o memorează şi o memorează la adresa
corespunzătoare variabilei din dreapta. In consecinţă, în partea stângă a semnului egal
putem utiliza valorile unor variabile tip pointer.
Considerăm următoarele instrucţiuni
int x, y;
int * a;
a = &y;
x = 10;
*a = x;
Instrucţiunea
cout << “x = “ << x << “ y = “ << y << endl;
va afişa valorile
x = 10 y = 10
Modul de excuţie a acestor instrucţiuni este următorul: instrucţiunea
a = &y;
memorează în variabila a adresa lui y. Instrucţiunea
*a = x;
se execută în felul următor: operatorul * are o prioritate mai mare decât operatorul =, în
consecinţă se aplică operatorul * asupra variabilei a şi se obţine adresa lui y. Se aplică
apoi operatorul = asupra adresi lui y (şi asupra adresei lui x) şi y primeşte valoarea 10. In
consecinţă, instrucţiunile
a = &y;
*a = x;
sunt echivalente cu instrucţiunea
y = x;
Fie de exemplu următoarele instrucţiuni:
float x, y;
float * a;
float ** aa;
float *** aaa;
float **** aaaa;
Aceste instrucţiuni declară că a poate conţine adresa unei variabile tip float, aa poate
conţine adresa unei variabile de tip float *, etc. Fie instrucţiunile
a = &x;
aa = &a;
aaa = &aa;
aaaa = &aaa;
Fie acum instrucţiunea
y = 3.45e-1;
Instrucţiunile
*a = y;
**aa = y;
***aaa = y;
****aaaa = y;
au acelaşi effect, variabila x primeşte valoarea variabilei y. De exemplu
**aa = y;
aplică de două ori operatorul *. Ea se execută astfel:
se aplică operatorul * asupra variabilei aa şi se obţine conţinutul acesteia, adică adresa
variabilei a. Se aplică încă o dată operatorul * asupra rezultatului şi se obţine adresa
variabilei x. In final operatorul = atribuie variabilei x valoarea lui y.
Orice variabilă tip pointer trebuie iniţializată înainte de a fi utilizată. De exemplu,
următoarea secvenţă de instrucţiuni nu este corectă
int *pn;
*pn = 5;
deoarece variabila pn nu a fost iniţializată. O secvenţă corectă este
int *pn;
int x;
pn = &x;
*pn = 5;
In acest caz variabila pn a fost iniţializată cu adresa unei variabile de tip întreg.

5.2 Referinţe

O referinţă (sau adresă) este un alt nume pentru o variabilă. Instrucţiunea de


declarare a unei referinţe este
Tip& nume_referinţă = nume_variabilă
unde
Tip este tipul variabilei nume_variabilă
nume_referinţă este numele variabilei referinţă (adresă).
Variabila nume_variabilă trebuie să fie declarată înainte şi să aibe tipul Tip. De
exemplu
int& rx = x;
declară pe rx ca fiind o referinţă a lui x (variabila x de tip întreg a fost declarată anterior).
Numele x şi rx sunt două nume diferite pentru aceeaşi variabilă. Ele au totdeauna aceeaşi
valoare. Pentru a verifica acest lucru vom considera următorul exemplu.

int main()
{
float f = 12.8;
float& fr = f;
cout << “f = “ << f << “ fr = “ << fr << endl;
fr = fr + 25.3;
cout << “f = “ << f << “ fr = “ << fr << endl;
f = f – 3.23;
cout << “f = “ << f << “ fr = “ << fr << endl;
}
După fiecare instrucţiune de scriere, valorile f şi fr vor fi aceleaşi. După cum vom vedea
în paragraful următor, dacă un parametru formal al unei funcţii este de tip referinţă, el
poate fi un parametru de ieşire.
O referinţă nu este o variabilă separată. Fie următorul program în care scriem adresa
unei variabile şi a referinţei la variabilă.

int main()
{
double dbl = 23.44.
double& dbref = dbl;
// tipareste adresele lui dbl si dblref
cout << “adresa lui dbl = “ << &dbl
<< “ adresa lui dblref = “ << &dblref << endl;
double& db2 = dbl;
double& db3 = dbref;
cout << “&dbl = “ << &dbl << “ &db2 = “ << db2
<< “ &db3 = “ << db3 << “ & dbref = “ << dbref << endl;
}

In toate cazurile se va tipări aceeaşi valoare.


Vom încheia acest paragraf definind o funcţie care să calculeze valoarea medie şi
dispersia unui şir de numere reale. Formulele corespunzătoare au fost prezentate anterior.
Funcţia va avea ca parametri de intrare vectorul x şi dimensiunea kui n, iar ca parametri
de ieşire valorile calculate, media şi dispersia. Funcţia va avea tipul void.

void md(float x[], int n, float& m, float& d)


{
// calculeaza suma componentelor
float s = 0;
int i;
for(i = 0; i < n; i++)
s = s + x[i];
s = s / n;
m = s;
// calculeaza dispersia
float ds = 0;
for(i = 0; i < n; i++)
ds = ds + (x[i] – s) * (x[i] – s);
d = s / (n – 1);
}

int main()
{
float a[3] = {1.22, -3.76, 16.02};
float med, disp;
md(a, 3, med, disp);
cout << “media = “ << med << endl
<< “dispersia = “ << disp << endl;
}

5.3 Parametri funcţiilor

La apelarea unei funcţii, parametrii acesteia se pun într-o stivă, împreună cu variabilele
locale ale funcţiei. După tipul parametrului, în stivă se pune valoarea sau adresa
parametrului. Considerăm de exemplu următoarea funcţie care adună două numere reale:

float funad(float a, float b)


{
float c;
c = a + b;
return c;
}

Considerăm o instrucţiune ce apelează această funcţie


x = funad(10.3, 20.44);
La apelarea funcţiei în stivă se alocă spaţiu pentru parametrii funcţiei, a şi b, şi pentru
variabila locală c
a=10.3 b=20.44 c
In acest caz, dacă modificăm un parametru, de exemplu a, valoarea parametrului se
modifică doar în stivă, şi nu în programul apelant. In acest caz parametrii funcţiei sunt
doar parametri de intrare. Pentru ca parametri unei funcţii să fie parametri de ieşire, ei
trebuie să fie de tip pointer sau referinţă. In acest caz dacă se modifică valoarea
parametrului în funcţie, modificarea valorii se face în programul apelant. Vom rescrie
funcţia anterioară astefel încât rezultatul să fie asociat unui parametru. Nu vom mai avea
o valoare calculată asociată numelui, deci tipul funcţiei va fi void.

void funad1(float a, float b, float * c)


{
float r;
r = a + b;
*c = r;
return;
}

Apelarea acestei funcţii se face astfel:


float x, y, rez;
x = 1.23;
y = -34.56;
funad1(x, y, &rez);
O altă posibilitate este de a defini o variabilă tip pointer care să conţină adresa variabilei
rez.
float * m;
m = &rez;
funad1(x, y, m);
In acest caz stiva va fi următoarea.

A = 1.23 B = -34.56 Adresa lui c

5.4 Pointeri la funcţii

O funcţie are un punct de intrare care este linia ei de definiţie. Acest punct de intrare este
o adresă de memorie care poate fi atribuită unei variabile tip pointer şi care poate fi
utilizată la apelarea funcţiei. In programul care apelează funcţia trebuie să definim o
variabilă pointer de tipul funcţiei respective. Considerăm şablonul definiţiei unei funcţii
Tip nume(lista de parametri);
Tipul unei variabile pointer la această funcţie este
Tip (*ident) (lista de parametri);
unde tip şi lista de parametri din variabila pointer corespund cu tip şi lista de parametri
din şablonul definiţia funcţiei.
Exemplu. Fie o funcţie f ce calculează suma a două numere întregi x, y.

int f(int x, int y)


{
return x + y;
}

Şablonul acestei funcţii este


int f(int, int);
Menţionăm că în şablon putem specifica şi numele parametrilor. Numele parametrilor din
şablon pot fi diferite de cele din definiţia funcţiei. Putem declara ca şablon pentru funcţia
anterioară
int f(int a, int b);
Tipul unei variabile pointer la această funcţie este
int (*ident)(int , int);
unde ident este numele variabilei tip pointer. Apelarea unei funcţii folosind o variabilă de
tip pointer se face atribuind variabilei pointer ca valoare numele funcţiei şi aplicând
opoeratorul () asupra variabilei tip pointer.
Vom apela acum funcţia f de mai sus direct şi folosind o variabilă tip pointer.

int main()
{
int a = 5, b = 7, sm;
// apelarea directa a funcţiei
sm = f(a, b);
cout << “suma numerelor “ << a << “ si “ << b << “ este “ sm << endl;
// prt este o variabila pointer la functia f
int (*ptr)(int, int);
// se initializeaza ptr
prt = f;
// se apeleaza functia f prin pointer
sm = (*ptr)(a, b);
cout << “suma numerelor “ << a << “ si “ << b << “ este “ sm << endl;
return 0;
}

In consecinţă, apelarea unei funcţii se poate face astfel


• scriind numele funcţiei urmat în paranteze de parametri actuali ca termen într-o
expresie. (aplicând asupra numelui funcţiei operatorul ()).
• Atribuind unei variabile tip pointer ca valoare adresa punctului de intrare în
funcţie. Adresa punctului de intrare este chiar numele funcţiei. Apelarea se face
aplicând operatorul * asupra variabilei tip pointer.

5.5 Declararea variabilelor tip pointer la funcţie

O instrucţiune de declarare a unui tip pointer la funcţie poate conţine următorii operatori,
scrişi aici în ordinea priorităţilor
1. [], ()
2. *, const
3. tipuri de date
Operatorii [] şi () sunt asociativi la stânga. Operatorii * şi const sunt asociativi la dreapta.
Instrucţiunea poate conţine şi un identificator care este numele variabilei. Interpretarea
şabloanelor se face considerând operatorii în ordinea priorităţii lor, [] reprezintă tablouri,
() reprezintă funcţii, * reprezintă un pointer, etc.
Exemplu. Fie şablonul
float *f();
Interpretarea lui se face astfel:

float *f() f este


float * () funcţie care returnează
float * pointer la o valoare
float tip float

f este o funcţie ce returnează un pointer de tip float.


Exemplu. Fie şablonul
float (*pf) ();
Interpretarea lui se face astfel:
float (*pf)() pf este
float (*) () pointer la
float () funcţie care returnează o valoare
float tip float

pf este pointer la o funcţie ce returnează o valoare de tip float.


Exemplu.
int (*r)(int, int);
r este un pointer la o funcţie cu doi parametri de tip int, int şi care returnează o valoare de
tip int.
Exemplu. Fie următoarea instrucţiune
const char * pc = “abcd”;
Reamintim că şirul de caractere “abcd” este un vector cu 5 componente tip caracter, ‘a’,
‘b’, ‘c’, ‘d’ şi ‘\0’ (şirurile sunt terminate prin zero). Prin această instrucţiune
compilatorul generează un vector cu 5 componente tip character şi atribuie acestui vector
variabiler tip pointer pc. Interpretarea tipului variabilei pc se face astfel:

const char * pc pc este


const char * pointer la
const char constant
char char

Această instrucţiune declară un pointer la un vector constant de caractere (am ţinut cont
că operatorii * şi const se citesc de la dreapta la stânga). In consecinţă, nu putem scrie
pc[3] = ‘c’;
deoarece şirul de caractere este constant, dar putem scrie
pc = “ghij”;
adică pointerul poate fi iniţializat cu o altă valoare.
Exemplu. Fie acum instrucţiunea
char * const cp = “abcd”;
Interpretarea ei se face astfel:

char * const cp cp este


char * const constant
char * pointer la
char char

Instrucţiunea declară un pointer constant la un vector de caractere. In consecinţă putem


modifica vectorul, de exemplu putem scrie
cp[3] = ‘c’;
dar nu putem scrie
cp = “ghij”;
deoarece pointerul este o constantă.
Exemplu. Considerăm şablonul
char (*(*f())[])();
Interpretarea lui se face astfel:

char (*(*f())[])() f este


char (*(*())[])() funcţie ce returnează
char (*(*)[])() pointer la
char (*[])() Vector de
char (*)() pointeri la
char () funcţie ce returneaza o valoare
char tip char

f este o funcţie ce returnează un pointer la un vector de pointeri la o funcţie ce returnează


o valoare de tip char.

5.6 Pointeri şi tablouri unidimensionale

Prin definiţie, o variabilă tip tablou conţine adresa primului element al tabloului
(elementul cu indicele zero al tabloului). Variabila este un pointer constant la primul
element al tabloului. Fie de exemplu instrucţiunea
char buffer[20];
Variabila buffer conţine adresa primului element al tabloului (adresa elementului
buffer[0]). In consecinţă, această valoare poate fi atribuită unei variabile tip pointer la
tipul elementelor tabloului. Dacă scriem
char * px;
px = buffer;
variabila px conţine adresa variabilei buffer[0] şi, în consecinţă,
*px
este valoarea variabilei buffer[0].
La execuţia unui program, numele unui tablou este convertit la un pointer la primul
element al tabloului. Prin definiţie, în cazul unui tablou x, scrierile
x[i]
şi
*(x + i)
sunt echivalente.
Exemplu. Fie un vector cu 5 componente tip float şi un vector z cu 3 componente întregi.
Se cere să scriem componentele vectorilor. Un program posibil este următorul

#include <iostream.h>
int main ()
{
float x[5] = {1.23, 6.89, -8.5e2, 0, 9.01};
int z[3] = {-11, 2, 4};
int i;
// scrie componentele vectorului x
for(i = 0; i < 5; i++)
cout << ‘\t’ << x[i];
cout << endl;
// scrie componentele vectorului z
for(i = 0; i < 3; i++)
cout << ‘\t’ << *(z + i);
cout << endl;
}
Programul poate fi rescris astfel. Definim o variabilă tip pointer la tipul float ce va
conţine pe rand adresele componentelor x vectorului şi o variabilă tip pointer la tipul int
pentru vectorul z. Variabilele vor fi initializate cu adresele vectorilor.

# include <iostream.h>
int main()
{
float x[5] = {1.23, 6.89, -8.5e2, 0, 9.01};
int z[3] = {-11, 2, 4};
int i;
float * px;
int * pz;
px = x;
pz = z;
// scrie componentele vectorului x
for(i = 0; i < 5; i++)
cout << ‘\t’ << *(px + i)
cout << endl;
// scrie componentele vectorului z
for(i = 0; i < 5; i++)
cout << ‘\t’ << pz[i];
cout << endl;
return 0;
}

Operaţiile cu variabilele tip pointer sunt adunarea şi scăderea unor valori întregi. La
adunarea sau scăderea unei valori întregi dintr-o variabilă tip pointer, la variabilă se
adună sau se scade valoarea respectivă înmulţită cu dimensiunea în octeţi a tipului
variabilei (unu pentru tipul char, patru pentru tipul int sau float, etc.) astfel încât variabila
conţine adresa unui alt element de tablou. Fie din nou instrucţiunile
char buffer[20];
char * px;
px = buffer;
Expresia px + 1 este adresa variabilei buffer[1], iar *(px + 1) este chiar valoarea
componentei buffer[1], px + i este adresa variabilei buffer[i] iar *(px + i) este chiar
valoarea componentei buffer[i]. Putem deci să rescriem programul precedent astfel

# include <iostream.h>
int main()
{
float x[5] = {1.23, 6.89, -8.5e2, 0, 9.01};
int i;
float * px;
px = x;
// scrie componentele vectorului x
for(i = 0; i < 5; i++)
{
cout << ‘\t’ << *px;
px = px + 1;
}
cout << endl;
return 0;
}

Echivalent, instrucţiunea de scriere poate fi

for(i = 0; i < 5; i++)


{
cout << ‘\t’ << *px++;
}

Pentru a vedea că ultima formă este corectă reamintim modul de execuţie a operatorului
postfix ++. Instrucţiunea
*px++
este echivalentă cu instrucţiunile
*px;
px++;
In expresie se utilizează valoarea neincrementată a variabilei, după care variabila este
incrementată. In consecinţă, se utilizează expresia *px după care variabila px este
incrementată. Vom incheia acest paragraph cu un program care să scrie adresele
componentelor unui vector utilizand o variabilă tip pointer

# include <iostream.h>
int main()
{
float x[5] = {1.23, 6.89, -8.5e2, 0, 9.01};
int i;
float * px;
px = x;
// scrie adresele componentele vectorului x
cout << “adresele componentelor vectorului” << endl;
for(i = 0; i < 5; i++)
{
cout << i << ‘\t’ << px << endl;
px = px + 1;
}
return 0;

5.7 Siruri tip C


Şirurile tip C sunt vectori de caractere ASCII la care ultimul caracter este ‘\0’.
Bibliotecile standard ale limbajelor C şi C++ au numeroase funcţii pentru lucrul cu şiruri
tip C. Câteva dintre aceste funcţii, ale căror prototipuri se află în biblioteca <string.h>,
sunt prezentate în cele ce urmează.
Funcţia
char * strcpy(char * s1, const char * s2);
copiază şirul s2 în s1, inclusiv caracterul ‘\0’. Funcţia returnează adresa şirului s1.
Parametrul s2 este declarat de tip const, adică funcţia nu modifică şirul s2.
Funcţia
int strcmp(const char * s1, const char * s2);
compară cele două şiruri, caracter cu caracter, până la primul caracter diferit. Rezultatul
este un număr < 0, = 0, sau > 0, după cum sunt caracterele diferite comparate. Exemple
de utilizare a funcţiei strcmp sunt prezentate în tabelul de mai jos

strcmp(“abc”, “bcd”) <0 ‘a’ < ‘b’


strcmp(“xyz”, “xyz”) =0 şirurile sunt egale
strcmp(“abcd”, “abc”) >0 şirul “abcd” este mai lung

Funcţia
size_t strlen(const char * s);
calculează lungimea şirului s (numărul de caractere ce preced ‘\0’).
Funcţia
char * strchr(const char * s, int c);
caută prima apariţie a caracterul c (convertit la char) în şirul s. Funcţia returnează un
pointer la caracterul găsit sau NULL în caz contrar.
Următoarele funcţii manipulează caractere tip ASCII. Prototipurile lor se află în
biblioteca <ctype.h>.

Funcţie Descriere
int isalnum (int c); Test dacă un caracter este alfanumeric
int isalpha (int c); Test dacă un caracter este alfabetic
int isdigit (int c); Test dacă un caracter este o cifră zecimală
int isxdigit(int c); Test dacă un caracter este o cifră hexazecimală
int islower (int c); Test dacă un caracter este literă mică
int isspace (int c); Test dacă un caracter este spaţiu (‘ ‘, ‘\n’, ‘\t’)
int isupper (int c); Test dacă un caracter este literă mare

Funcţiile au un rezultat diferit de zero dacă argumentul este conform descrierii funcţiei.
Următoarele funcţii convertesc literele mari în litere mici şi invers.

Funcţie Descriere
int tolower(int c); Converteşte în litere mici
int toupper(int c); Converteşte în litere mari

Vom exemplifica utilizarea acestor funcţii tipărind caracterele alfabetice, alfanumerice,


cifrele, literele mici şi mari ale codului ASCII. Vom utiliza constanta predefinită
UCHR_MAX din biblioteca <limits.h> care dă valoarea maximă a unui obiect de tipul
unsigned char (această valoare este 255). Vom construi o funcţie care generează toate
caracterele codului ASCII (toate numerele de la 0 la 255) şi le vom tipări în fiecare caz
caracterele pentru care funcţiile de mai sus returnează o valoare pozitivă (valoarea
adevărat). Funcţia pe care o vom construi va avea ca parametru un pointer de tipul
funcţiilor de mai sus. Prototipul acestor funcţii este
int nume(int);
iar un pointer la o asemenea funcţie are tipul
int (* fn)(int);

#include <stdlib.h>
#include <iostream.h>
void prtcars(const char * nume, int (*fn)(int))
{
char c;
// scrie numele functiei
cout << nume << “ : “;
/* genereaza toate caracterele si scrie pe cele pentru
care functia are valoarea adevarat */
for(c = 0; c < UCHR_MAX; ++c)
if((*fn)(c))
cout << c;
cout << endl;
}
int main()
{
ptrcars(“isdigit”, &isdigit);
ptrcars(“islower”, &islower);
ptrcars(“isupper”, &isupper);
ptrcars(“isalpha”, &isalpha);
ptrcars(“isalnum”, &isalnum);
ptrcars(“isxdigit”, isxdigit);
}

Exerciţiu. Să se scrie un program care să convertească literele mici ale unui şir în litere
mari.
Vom exemplifica utilizarea pointerilor la tablouri unidimensionale construind o funcţie
care copiază un şir în altul (ca şi funcţia standard strcpy). Prototipul ei va fi
void copy(char * s1, char * s2);
unde şirul s2 este sursa iar şirul s1 este destinaţia.
O primă variantă este

void copy(char * s1, char * s2)


{
int len = strlen(s2);
for(i = 0; i < len + 1; i++)
s1[i] = s2[i];

O altă variantă este

void copy(char * s1, char * s2)


{
int i;
for(i = 0; (s1[i] = s2[i]) != ‘\0’; i++)
;
}

Condiţia care se testează pentru execuţia instrucţiunii for este


(s1[i]=s2[i]) != ‘\0’;
Ea este testată astfel. Se execută instrucţiunea de atribuire
s1[i] = s2[i]
după care rezultatul s1[i] este comparat cu valoarea ‘\0’, care este caracterul de terminare
al şirului s2. Reamintim că operatorul de atribuire are ca rezultat valoarea atribuită.
O altă variantă utilizează pointeri şi faptul că şirurile tip C sunt terminate prin zero.

void copy(char * s1, char * s2)


{
while(*s1 = *s2)
{
s1++;
s2++;
}
}

Expresia testată pentru execuţia instrucţiunii while este


*s1 = *s2
Rezultatul evaluării expresiei este valoarea *s1 (un caracter copiat în şirul s1).
Instrucţiunea while se execută pană când caracterul ‘\0’ al şirului s2 este copiat în s1.
In final putem scrie varianta

void copy(char * s1, char * s2)


{
while(*s1++ = *s2++)
;
}

care este o scriere condensată a variantei anterioare.


Alte funcţii ce prelucrează şiruri de caractere sunt
int atoi(char * s);
double atof(char * s);
care convertesc un şir de caractere într-un număr întreg şi respectiv real. Prototipurile
acestor funcţii se află în biblioteca <stdlib.h>. Un exemplu de utilizare a acestor funcţii
poate fi următorul. Fie două şiruri ce conţin numere. Vom converti aceste şiruri în
numere şi vom efectua produsul lor.

# include <stdlib.h>
# include <iostream.h>
int main()
{
char * s1 = “-123”;
char * s2 = “1.22e-1”;
int x;
double y;
// scrie sirurile
cout << “sirul s1 : ” << s1 << endl
<< “sirul s2 : ” << s2 << endl;
// converteste sirurile in numere
x = atoi(s1);
y = atof(s2);
// scrie numerele
cout << “x = “ << x << “ y = “ << y << endl;
cout << “x * y = “ << x * y << endl;
}

5.8 Pointeri şi tablouri multidimensionale

Tablourile se definesc conform urmăroarei diagrame sintactice

Elementele tablourilor sunt memorate pe linii. De exemplu, tabloul


int x[2][3];
este compus din elementele
x[0][0], x[0][1], …, x[1][0], …x[1][2]
El este considerat ca un vector cu 2 elemente, x[0] şi x[1], fiecare element fiind un vector
cu trei elemente de tip int. Operatorul [] selectează un element al unui tablou (operator de
selecţie). El este un operator binar, operandul stâng fiind un tablou, operandul drept fiind
un indice. De exemplu, x[0] selectează primul element din x care este vectorul
x[0][0], x[0][1], x[0][2]
Aplicând operatorul de selecţie asupra lui x[0], selectăm un element al acestuia, de
exemplu x[0][1]. Reamintim că operatorul de selecţie este asociativ la stânga. Indicii unui
element de tablou pot fi orice expresii întregi pozitive.
Exemplu. Să construim o funcţii care să citească matrice cu două linii şi două coloane.
Funcţia poate fi următoarea
void rdmat(float a[2][2])
{
int i, j;
for(i = 0; i < 2; i++)
for(j = 0; j < 2; j++)
cin >> a[i][j];
}

Numele unui tablou este prin definiţie un pointer la primul element al tabloului.
Elementele tabloului pot fi selectate folosind operatorul *. Fie de exemplu tabloul
double x[3];
După cum am spus anterior, prin definiţie, expresiile
x[i]
şi
*(x+i)
sunt echivalente.
Considerăm tabloul
float a[2][3];
Expresia
a[i][j]
este echivalentă cu
(a[i])[j]
care, conform celor de mai sus, este echivalentă cu
*((a[i]) + j)
care este echivalentă cu expresia
*(*(a + i) +j)
Ca exemplu de utilizare a pointerilor la tablouri bidimensionale să construim o funcţie
care să scrie elementele unei matrice cu două linii şi două coloane. Funcţia poate fi

void wrmat(float x[2][2])


{
int i, j;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 2; j++)
cout << a[i][j] << “ “;
cout << endl;
}

Utilizand cele de mai sus putem rescrie funcţia ca

void wrmat(float x[2][2])


{
int i, j;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 2; j++)
cout << *(*(a + i) +j) << “ “;
cout << endl;
}

Reamintim că
a[i]
selectează un element al tabloului a, aici o linie, deci
*(a + i)
selectează o linie a tabloului a.

5.9 Parametrii funcţiei main. Parametrii liniei de comandă

Prototipul funcţiei main() utilizat pană acum este


Int main();
Funcţia main poate avea următoarul prototip
int main(int argc, char * argv[]);
Considerăm al doilea parametru din această definiţie
char * argv[]

char * argv [] argv este


char * [] vector de
char * pointeri la
char char

argv reprezină un vector de pointeri la character, iar argc este dimensiunea acesui vector.
Pointerii sunt adresele unor şiruri de caractere ce corespund argumentelor liniei de
comandă. Primul şir de caractere este chiar numele programului. Ca exemplu fie un
program ce tipăreşte argumentele liniei de comandă.

int main(int argc, char * argv[])


{
int i;
for(i = 0; i < argc; ++i)
cout << argv[i] << endl;
}

Un mod de a furniza parametri liniei de comandă este de a rula programul într-o fereastră
DOS, de la linia de comandă
 aplicatie.exe parametru1 parametru2 … parametrun
Alt mod este de a introduce aceşti parametri într-o cutie de dialog a mediul de
programare.

Cap. 6 Fişiere tip C

In limbajele C şi C++ fişierele sunt considerate ca un şir ordonat de octeţi. Există două
tipuri de fişiere, text şi binare.
• Un fişier tip text este un şir ordonat de caractere grupate în linii. Fiecare linie
constă din zero sau mai multe caractere plus un caracter ‘\n’ (return).
• Un fişier binar este un şir ordonat de octeţi.
Menţionăm că fiecare fişier pe disc are o etichetă ce conţine numele fişierului, adresa de
început şi adresa de sfarşit a fişierului.
In program fiecare fişier este asociat unui stream. Un stream este asociat unui fişier prin
operaţia de deschidere a fişierului. Pentru prelucrare, un fişier se deschide în citire. Când
se crează un fişier el se deschide în scriere. La deschiderea unui fişier în scriere se crează
o etichetă corespunzătoare acestui fişier şi se completeză numele fişierului şi adresa de
început. La deschiderea în citire sistemul de operare caută pe disc fişierul cu numele
specificat şi memorează adresele de început şi de sfarşit ale fişierului. Un stream este
disociat de un fişier prin operaţia de închidere a fişierului. La închiderea unui fişier în
creare se completează eticheta fişierului cu adresa ultimului octet al fişierului.
Fişierele binare au un indicator de poziţie ce dă adresa următorului octet de citit sau scris.
Acest indicator este iniţial pus la zero şi este actualizat de operaţiile de citire sau scriere.
Un fişier poate avea o zonă de memorie asociată (buffer) în care se citesc date din fişier
sau din care se scriu date în fişier.
Limbajele C şi C++ au tipul de structură FILE ce poate înregistra toate informaţiile
necesare pentru controlul unul stream, inclusiv:
• Numele fişierului
• Indicatorul de poziţie al fişierului
• Indicator de eroare poziţionat dacă a apărut o eroare intrare/ieşire
• Indicatorul de sfârşit de fişier, eof, (end of file)
Prototipurile funcţiilor ce efectuează operaţii intrare/ieşire se află în biblioteca
<stdio.h>. Funcţiile importante pentru prelucrarea fişierelor sunt următoarele.
• FILE* fopen(const char * filename, const char * mode);
Această funcţie deschide un fişier. Ea crează o structură tip FILE cu informaţii despre
fişier. Parametrii acestei funcţii sunt
1. filename - un pointer la şirul de caractere ce dă numele fişierului.
2. mode - un pointer la un şir de caractere ce dă modul de deschidere.
Parametrul mode poate avea următoarele valori:
“r” – fişier text deschis în citire
“w” - fişier text deschis în scriere
“rb” – fişier binar deschis în citire
“wb” - fişier binar deschis în scriere

• int fclose(FILE * stream); Funcţia închide un fişier. Ea returnează valoarea zero


dacă operaţia a avut succes.
• int feof(FILE * stream); Funcţia returnează o valoare diferită de zero dacă s-a
detectat sfârşitul unui fişier în timpul operaţiei de citire precedente.
• int remove(char * filename); şterge fişierul cu filename. Funcţia returnează o
valoare diferită de zero dacă operaţia avut succes.
La lansarea în execuţie a unui program există trei streamuri standard tip text deschise:
• stdin – fişierul standard de intrare asociat tastaturii
• stdout - fişierul standard de ieşire asociat ecranului
• stderr - fişierul standard de ieşire pentru scrierea mesajelor de eroare
Exemplu. La prelucrarea oricărui fişier text în citire vom avea următoarele instrucţiuni

FILE * fp;
fp = fopen(“numefisier”, “r”);
// prelucreaza fisierul
fclose(fp);
Menţionăm că la citirea secvenţială a unui fişier, după fiecare operaţie de citire
trebuie să testăm indicatorul de sfarşit al fişierului. La întalnirea sfarşitului de fişier
funcţiile de citire returnează o valoare specifică. Citirea secvenţială a unui fişier se
face cu instrucţiunea while ce va testa acest indicator.

6.1 Fişiere tip text

In cazul fişierelor tip text avem două tipuri de funcţii pentru operaţiile de intrare / ieşire:
• funcţii pentru intrări / ieşiri cu format la care operaţiile de citire / scriere se fac
sub controlul unui format
• funcţii care citesc / scriu caractere

6.1.1 Funcţii intrare / ieşire cu format

Aceste funcţii scriu sau citesc valori în sau din fişiere pe disc, fişierele standard sau şiruri
de caractere. Pentru scriere, aceste funcţii sunt:
int fprintf(FILE * stream, const char * format, argumente);
int printf( const char * format, argumente);
int sprintf(char * s, const char * format, argumente);
Pentru citire, funcţiile sunt
int fscanf(FILE * stream, const char * format, argumente);
int scanf( const char * format, argumente);
int sscanf(char * s, const char * format, argumente);
Parametrul format este un şir de caractere compus din specificatori de conversie definiţi
de % şi alte caractere. Specificatorii de conversie sunt
%d – întreg cu semn în baza 10
%i – întreg cu semn
%o – întreg în baza 8
%u – întreg în baza 10 fară semn
%x – întreg în baza 16 cu semn
%c – caracter
%s – şir de caractere
%f – număr real
%e – număr real cu exponent
După % poate urma un număr întreg care dă lungimea minimă a câmpului în cazul
instrucţiunii printf şi lungimea maximă a campului în cazul instrucţiunii scanf. La
scrierea numerelor cu specificatorul %f putem specifica şi numărul de cifre ale părţii
subunitare. De exemplu specificatorul %7f descrie un număr real ce ocupă 7 poziţii, iar
specificatorul %7.3f descrie un număr real ce ocupă 7 poziţii din care 3 sunt pentru partea
subunitară.
Exemplu. Vom scrie un program care calculează valoarea unei expresii
1 +cos( 2 x ) + 2 sin( x )
x +ln x
pentru valori ale lui x cuprinse între 1 şi 2 cu pasul 0.2. Valorile expresiei vor fi scrise pe
ecran şi într-un fişier cu numele rez.txt. Specificatorii de format vor fi %4f pentru
variabila x şi %6f pentru expresie. Valoarea lui x va ocupa 4 caractere iar cea a lui z 6
caractere. Valorile vor fi separate de un caracter tab, ‘\t’. Formatul se încheie cu un
caracter ‘\n’.

#include <stdio.h>
#include <math.h>
int main void()
{
FILE * fp;
fp = fopen(“rez.txt”, “w”);
int i;
float x, z;
// scrie antetul
printf(“x\tf(x) \n”);
fprintf(fp, “x\tf(x) \n”);
// calculeaza valorile expresiei si scrie aceste valori in fisiere
for(i = 0; i < 6; i++)
{
z = (1 + cos(2 * x) + 2 * sin(x)) / (x + log(fabs(x)));
printf(“ %4f \t %6f \n”, x, z);
fprintf(fp, “ %4.1f \t %6.2f \n”, x, z);
x = x + 0.2;
}
fclose(fp);
return 0;
}

In programul anterior puteam defini un şir de caractere cu specificatorii de format


char * format = “ %4f \t %6f \n”;
şi apoi instrucţiunile de scriere sunt
printf(format, x, z);
fprintf(fp, format, x, z);
Exemplu. Să citim un număr hexazecimal de la tastatură şi să-l scriem în format zecimal
şi hexazecimal pe ecran.

#include<stdio.h>
int main()
{
int i;
printf(“introduceti un numar hexazecimal\n”);
scanf(“%x”, &i);
printf(“\n numarul zecimal %d\n numarul hexazecimal%x\n”, i, i);
}

Menţionăm că parametrii funcţiei scanf sunt de tip pointer (sunt parametri de ieşire). In
consecinţă, la apelarea funcţiei scanf în program utilizăm adresa variabilei i.
scanf(“%x”, &i);
Exemplu. Consideram un vector cu 5 componente numere reale. Vom afişa componentele
vectorului sub forma
Element Valoare
x[0] …..
x[1] …..

# include <stdio.h>
int main()
{
double x[5] = {1.23, -23, 0.98, 4.12, 5.1};
int i;
printf(“Element\tValoare”);
// scrie componentele vectorului
for(i = 0; i < 5; i++)
printf(“x[%d] \t\t%d\t”, i, x[i]);
return 0;
}

6.1.2 Funcţii intrare / ieşire tip caracter

Funcţia
int fgetc(FILE * stream);
citeşte un character din fişierul de intrare şi avansează indicatorul de poziţionare al
fişierului cu valoarea unu. Dacă se întâlneşte sfârşitul de fişier, funcţia returnează
valoarea EOF care este o constantă predefinită în biblioteca <stdio.h>.
Funcţia
int getchar();
citeşte un character din streamul stdin. Ea este echivalentă cu funcţia fgetc(stdin);
Funcţia
int fputc(int c, FILE * stream);
scrie caracterul c în streamul specificat de stream. Funcţia returnează caracterul scris sau
constanta EOF în caz de eroare.
Funcţia
int putchar(int c);
scrie caracterul c in streamul stdout.
Funcţia
int ungetc(int c, FILE * stream);
pune caracterul c în streamul de intrare.
Următoarele funcţii citesc sau scriu linii din fişiere tip text.
Funcţia
char * fgets(char * s, int n, FILE * stream);
citeşte cel mult n – 1 caractere în vectorul s. Ea se opreşte dacă întâlneşte caracterul ‘\n’
sau sfârşitul de fişier. După ultimul caracter citit se adaugă caracterul ‘\0’ în vectorul s.
Funcţia returnează adresa vectorului s. La întâlnirea sfârşitului de fişier sau la o eroare
funcţia retuurnează valoarea NULL.
Funcţia
int fputs(const char * s, FILE * stream);
scrie şirul de caractere s (terminat cu ‘\0’) în fluxul stream. Caracterul ‘\0’ nu este scris.
Funcţia
int puts(char * s);
scrie şirul de caractere s (terminat cu ‘\0’) în fluxul stdout. Caracterul ‘\0’ nu este scris.
Funcţia scrie şi caracterul ‘\n’ după ce a scris şirul s.
Reamintim că funcţia fputs nu scrie caracterul ‘\n’ în fişier, în timp ce funcţia puts scrie
caracterul ‘\n’ în fişierul stdout după şir. In consecinţă, pentru a afişa câte un şir de
caractere pe rand, la scrierea în fişierul stdout cu funcţia fputs vom scrie
fputs(x, stdout);
fputs(“\n”, stdout);

Schema principială de citire a unui fişier secvenţial este următoarea. Presupunem că


utilizăm o funcţie read(file) ce citeşte câte un bloc de date din fişier. Operaţia de citire
poziţionează indicatorul de sfarşit de fişier la valoarea true dacă în cursul operaţiei de
citire s-a detectat sfarştul fişierului.

read(file)
while(! eof(file))
{
prelucreaza blocul citit
read(file)
}

In cazul citirii caracterelor de la tastatură, sfarşitul de fişier este indicat prin CTRL+Z.
Exemplu. Vom exemplifica utilizarea acestor funcţii cu un program care să calculeze
dimensiunea în octeţi a unui fişier. Programul va citi numele unui fişier de la tastatură, şi
va deschide fişierul în citire. Apoi se va citi câte un caracter pană la întalnirea sfarşitului
de fişier şi se vor număra octeţii citiţi. Citirea secvenţială a fişierului se va face cu
instrucţiunea while.

/* calculul dimensiunii unui fisier */


#include <stdio.h>
int main()
{
char name[64];
FILE* file;
char car;
int nb = 0;

printf("introduceti numele fisierului\n");


scanf("%s", name);
// deschide fisierul in citire
file = fopen(name, "r");
// test daca fisierul exista
if(file == NULL)
{
printf("nume de fisier eronat\n");
return 0;
}
// citeste cate un caracter până la sfarsitul fisierului
// citeste un caracter
car = fgetc(file);
while(car != EOF)
{
// numara caracterul citit
nb = nb + 1;
// citeste un caracter
car = fgetc(file);
}
fclose(file);
printf("fisierul %s contine %d octeti\n", name, nb);
return 0;
}

Exemplu. Vom face un program care să copieze un fişier existent în altul. Fie un fişier cu
numele file1.txt în directorul curent. Vom copia acest fişier în fişierul cu numele file2.txt
tot în directorul curent. Vom deschide cele două fişiere şi vom testa dacă operaţia a avut
succes. Funcţia fopen are ca rezultat valoarea NULL dacă operaţia nu a avut succes. Apoi
se citeşte câte un caracter din primul fişier şi se scrie în al doilea.

# include <stdio.h>
int main ()
{
FILE * fin, * fout;
char nume1[64], nume2[64];
// citeste numele primului fisier
printf(“introduceti numele fisierului ce va fi copiat\n”);
scanf(“%s”, nume1);
fin = fopen(nume1, "r");
if(fin == NULL)
{
printf("fisierul %s nu exista\n", nume1);
return 0;
}
// citeste numele noului fisier
printf(“introduceti numele noului fisier\n”);
scanf(“%s”, nume2);
fout = fopen(nume2, "w");
if(fout == NULL)
{
printf("fisierul %s nu se poate crea", nume2);
return 0;
}
// copiaza fisierul
int c;
c = fgetc(fin);
while(c != EOF)
{
fputc(c, fout);
c = fgetc(fin);
}
fclose(fin);
fclose(fout);
return 0;
}

Instrucţiunile ce citesc şi scriu din fişiere pot fi rescrise ca

int c;
while((c = fgetc(fin)) != EOF)
fputc(c, fout);

6.2 Fişiere binare

Pentru scrierea şi citirea de date din fişiere binare există funcţiile fread şi fwrite. Aceste
funcţii pot citi sau scrie unul sau mai multe blocuri de date.
Funcţia
size_t fread(void * ptr, size_t size, size_t nmb, FILE * stream);
citeşte în vectorul ptr cel mult nmb blocuri de dimensiune size din fişierul stream. Funcţia
returnează numărul efectiv de blocuri citite. Indicatorul de poziţie al fişierului este
avansat cu numărul de octeţi citiţi. In cazul întâlnirii sfarşitului de fişier, funcţia
returnează valoarea 0.
Funcţia
size_t fwrite(const void * ptr, size_t size, size_t nmb, FILE * stream);
scrie în vectorul ptr cel mult nmb blocuri de dimensiune size în fişierul stream. Funcţia
returnează numărul efectiv de blocuri scrise. Indicatorul de poziţie al fişierului este
avansat cu numărul de octeţi scrişi.
Pentru citirea elementelor în ordine aleatoare există posibilitatea de a modifica
indicatorul de poziţie al fişierului cu funcţia
int fseek (FILE * stream, long int offset, int whence);
Noua valoare a indicatorului fişierului este obţinută adăugând valoarea offset la poziţia
specificată de whence care poate fi
SEEK_SET începutul fişierului
SEEK_CUR poziţia curentă a indicatorului
SEEK_END sfârşitul fişierului
După o instrucţiune fseek, următoarea instrucţiune poate fi o operaţie de citire sau scriere.
Funcţia
long ftell(FILE* stream);
are ca rezultat valoarea indicatorului de poziţie al fişierului.
Exemplu. Vom crea un fişier binar pe care îl citim apoi secvenţial. Vom scrie în fişier 10
blocuri de câte 15 octeţi fiecare. Blocurile vor consta din şiruri de câte 14 caractere,
primul bloc caractere ‘0’, al doilea bloc caractere ‘1’, etc. Citirea secvenţială se va face
cu instrucţiunea while. După fiecare citire testăm rezultatul funcţiei fread. Un rezultat
zero al acestei funcţii semnifică întâlnirea sfârşitului de fişier. Menţionăm că putem
genera caracterele ‘0’, ‘1’, etc., o expresie de forma
‘0’ + i
unde i ia valorile 0, 1, 2, …

#include <stdio.h>
int main()
{
FILE * fil;
int i, j;
char x[15];
// deschide fisierul in creare
fil = fopen(“fil.txt”, “wb”);
if(fil == NULL)
{
printf(“fisierul nu se poate crea\n”);
return 0;
}
for(i = 0; i < 10; i++)
{
// creaza un bloc
for(j = 0; j < 14; ++j)
x[j] = ‘0’ + i ;
x[14] = 0;
// scrie blocul in fisier
fwrite(x, 15, 1, fil);
}
// inchide fisierul
fclose(fil);
// deschide fisierul in citire
fil = fopen(“fil.txt”, “rb”);
if(fil == NULL)
{
printf(“fisierul nu se poate deschide\n”);
return 0;
}
int xct;
// citeste un sir
xct = fread(x, 15, 1, fil);
while(xct != 0)
{
// scrie sirul pe ecran (in streamul stdout)
printf(“%s\n”, x);
// citeste un sir
xct = fread(x, 15, 1, fil);
}
// inchide fisierul
fclose(fil);
}
Exemplu. Vom crea un fişier binar în care vom scrie 15 blocuri de câte 10 caractere şi
apoi vom citi blocurile pare. Blocurile vor conţine şiruri de caractere de forma
abcde…
bcdef…
cdefg…
generate cu o expresie de forma
‘a’ + i + j
unde i şi j iau valorile 0, 1, 2, …

#include <stdio.h>
int main()
{
FILE * fil;
int i, j;
char x[10];
// deschide fisierul in creare
fil = fopen(“fil”, “wb”);
if(fil == NULL)
{
printf("fisierul nu se poate crea\n");
return 0;
}
for(i = 0; i < 15; i++)
{
// creaza un bloc
for(j = 0; j < 9; ++j)
x[j] = ‘a’ + i + j;
x[9] = 0;
// scrie blocul in fisier
fwrite(x, 10, 1, fil);
// scrie sirul pe ecran (in fluxul stdout)
puts(x);
}
// inchide fisierul
fclose(fil);
// deschide fisierul in citire
fil = fopen(“fil”, “rb”);
if(fil == NULL)
{
printf("fisierul nu se poate citi\n");
return 0;
}
printf("fisierul citit\n");
// citeste sirurile pare
for(i = 0; i < 15; i += 2)
{
// pozitioneaza indicatorul fisierului
fseek(fil, (long)(i * 10), SEEK_SET);
// citeste un sir
fread(x, 10, 1, fil);
// scrie sirul pe ecran (in fluxul stdout)
fputs(x, stdout);
fputs(“\n”, stdout);
}
// inchide fisierul
fclose(fil);
}

Menţionăm că puteam avansa indicatorul fişierului la sfarşitul ciclului for cu 10 octeţi.

// avanseaza indicatorul fisierului cu 10


fseek(fil, (long)(10), SEEK_CUR);

Cap. 7 Structuri tip C

O structură este un tip de date definit de utilizator. O structură este o colecţie de date de
tipuri diferite. Datele dintr-o structură vor fi numite componente sau câmpuri. Definirea
unei structuri se face cu instrucţiunea
struct nume {liste de declaraţii de tip };
unde nume este noul tip de date.
De exemplu, o structură poate defini tipul numere complexe
struct complex {
float real;
float imag;
};
Menţionăm că o structură poate conţine componente de orice tip, chiar şi alte structuri.
Putem defini apoi variabile corespunzând acestui nou tip cu o instrucţiune
struct nume listă de variabile;
De exemplu, într-un program putem defini numere complexe a şi b
struct complex a, b;
O structură poate fi iniţializată la declarare. Putem scrie
complex n = {1.1, -2.34};
Adresarea unui element al unei structuri se face cu operatorul .
nume.membru
Putem da valori variabilelor definite anterior
a.real = 1.0;
a.imag = -1.2 + sin(0.3);
Putem utilize valorile componentelor unei structuri în expresii
float x;
x = a.real * a.imag;
Pentru a defini funcţii ce au ca argumente sau rezultat structuri în limbajul C,
definim un nou tip de date corespunzand structurii cu instrucţiunea typedef. Pentru
exemplificare vom defini (cu instrucţiunea typedef) un tip numit vector ce va fi o
structură ce conţine două numere reale.

# include <stdio.h>
typedef struct
{
float compa;
float compb;
} vector;

Vom construi o funcţie care să adune doi vectori. Funcţia va avea ca parametri două
structuri şi ca rezultat o structură. Vom declara variabile de acest tip în funcţia main şi în
funcţia ce adună vectorii.

vector addcmp(vector x, vector y)


{
vector c;
c.compa = x.compa + y.compa;
c.compb = x.compb + y.compb;
return c;
}

int main()
{
vector cx, cy, cz;
cx.compa = 1;
cx.compb = 1;
cy.compa = 0;
cy.compb = 1;
cz = addcmp(cx, cy);
printf(“suma este (%f , %f) \n”, cz.compa ,cz.compb);
}

La fel ca şi în cazul variabilelor putem defini variabile tip pointer la o structură, ce vor
conţine adresa unei structuri. Instrucţiunea de definire a unui pointer la o structură este
struct nume * identificator;
sau
numetip * identificator;
dacă am definit un tip de structură cu instrucţiunea typedef.
unde nume este numele structurii iar identificator este numele variabilei tip pointer.
De exemplu,
struct complex * pc;
defineşte un pointer de tip struct complex. Variabila tip pointer poate fi iniţializată ca
orice variabilă tip pointer, folosind operatorul de calcul al adresei &
pc = &a;
In cazul unui pointer p la o structură, un camp al structurii este adresat astfel
(*p).membru
Prin definiţie, această scriere se prescurtează
p->membru
Operatorii . şi -> au aceeaşi prioritate ca operatorii () şi [].
Exemplu. Fie instrucţiunile
complex y;
struct complex * py;
py = &y;
Putem iniţializa structura y, utilizand pointerul py
py->real = -23.4;
py->imag = 1.2;
Putem iniţializa direct componentele structurii
y.imag = 1.24;
y.real = 0.23;
Putem utiliza valorile variabileleor definite de structura y.
float fm = pc->imag;
Sunt corecte şi scrierile
(*pc).real = cos(1.23);
double d = (*pc).imag + (*pc).real;
Exemplu. Vom defini un tip de structură numcmp ce descrie numerele complexe cu
instrucţiunea typedef. Vom iniţializa o variabilă de acest tip şi vom scrie valorile ei pe
ecran folosind un pointer la structură.

# include <stdio.h>
typedef struct
{
float real;
float imag;
} numcmp;
int main ()
{
numcmp cr = {1.1, -1.2};
numcmp * pv;
pv = &cr;
printf("(%f , %f)\n", pv->real, pv->imag);
}

Vom prezenta un exemplu de utilizare a operatorilor de incrementare ++ în cazul


pointerilor la structuri. Fie structura
struct strx {
int c;
float d;
};
Fie o variabilă tip strx şi o variabilă tip pointer la structura strx;
struct strx a;
struct strx *b;
Vom iniţializa pe b cu adresa lui a.
b = &a;
şi elementul c al structurii la valoarea zero.
b->c = 0;
Putem incrementa pe c astfel:
b->c = b-> + 1;
b->c += 1;
(*b).c += 1;
++(*b).c;
++b->c;
Operatorul ++ are o prioritate mai mică decât -> şi . astfel încât ultimele expresii sunt
++((*b).c);
++(b->c);

Cap. 8 Clase

Pentru a rezolva o problemă trebuie să creăm un model al problemei. Procesul de


modelare se numeste abstractizare. In acest proces trebuie să definim
• datele afectate
• operaţiile identificate de problemă.
Abstractizarea este structurarea problemei în entităţi definind datele şi operaţiile asociate
acestor entităţi. Tipurile de date abstracte pe care le definim au următoarele proprietăţi
• definesc o structura a datelor
• datele sunt accesibile prin operaţii bine definite. Setul de operaţii definite se
numeşte interfaţă. Operaţiile interfeţei sunt singurul mecanism de acces la
structura de date.
Definiţiile pe care le vom utilize sunt următoarele
• Clasa este o reprezentare a unui tip de date abstracte. Ea asigură detaliile de
implementare pentru structura de date şi pentru operaţii. Clasa defineşte atribute si
metode care implementează structura de date şi operaţiile tipului abstract.
Instanţele clasei se numesc obiecte. O clasa defineşte proprietăţile şi comportarea
unei mulţimi de obiecte. Intr-o clasă există o parte de definire a datelor şi
operaţiilor şi o parte de implementare. Partea de definire se numeşte interfaţă.
• Obiect. Un obiect este o instanţa a unei clase (a unui tip abstract). El este unic
identificat prin nume şi defineşte o stare reprezentată de valorile atributelor la un
moment dat. Comportarea unui obiect este definită de metodele ce se pot aplica
asupra lui.
• Mesaje. Un program este o multime de obiecte create, distruse şi care
interacţionează. Interacţiunea este bazată pe mesaje trimise de la un obiect la altul
cerand destinatarului sa aplice o metodă asupra lui (apelarea unei functii). Un
mesaj este o cerere către un obiect ca el să invoce una din metodele lui. Mesajul
conţine
- numele metodei
- argumentele metodei
• Metoda. O metoda este asociată unei clase. Un obiect invoca o metodă ca reacţie
la primirea unui mesaj.
Clasa este un tip de date definit de utilizator. Clasa defineşte variabile şi funcţii ce
prelucrează aceste variabile. Funcţiile definite de clasă se numesc metode sau funcţii
membre ale clasei. Variabilele clasei se numesc câmpuri sau atribute sau proprietăţi.
Definiţia unei clase se numeşte interfaţă.
Există o notaţie pentru tipurile de date abstracte pe care le definim

In programe creăm variabile de tipul unor clase care se numesc obiecte. Vom spune că
obiectele sunt instanţe ale unor clase. Obiectele unei clase sunt create şi iniţializate de
funcţii membre ale clasei special definite pentru acest scop numite constructori.
Obiectele sunt distruse când nu mai sunt necesare de funcţii membre ale clasei numite
destructori. Valorile proprietăţilor dau starea obiectului la un moment dat.
Datele şi funcţiile membre ale clasei pot fi publice, private sau protejate. Membri
publici ai clasei pot fi utilizaţi de orice funcţie din program. Membri privaţi ai clasei pot
fi utilizaţi doar de funcţiile membre ale clasei. Membri protejaţi vor fi prezentaţi ulterior.
Implicit, toate datele şi funcţiile unei clase sunt private.
Principiile programării orientate obiect sunt următoarele
• Primul principiu al programării orientate obiect este încapsularea. Acesta cere
combinarea datelor şi metodelor într-o singură structură de date. Acest principiu
cere ascunderea structurii de date şi asigurarea unei interfeţe pentru adresarea
datelor. Exemplu. Numere complexe. Un număr complex este o pereche ordonată
de numere reale. Pentru a înmulţi două numere complexe trebuie să adresăm
structura şi să adunăm produse ale părţile reale si cele imaginare. Definind o
operaţie mult încapsulăm detaliile şi înmulţim două numere complexe fără să ne
intereseze cum se face operaţia în detaliu.
• Moştenirea. Acest principiu cere definirea unei clase generale ce conţine
caracteristicile comune ale mai multor elemente. Apoi această clasă este
moştenită de alte clase particulare, fiecare adaugă doar elementele proprii. Clasa
care este moştenită se numeşte clasă de bază, iar clasele care moştenesc se
numesc clase derivate.
Polimorfismul. Avem funcţii cu acelaşi nume în clasa de bază şi în clasele derivate.
Funcţiile din clasele derivate redefinesc operaţiile necesare claselor derivate. Când
apelăm funcţia respectivă se apelează versiunea corespunzătoare tipului obiectului.

8.1 Definirea unei clase

8.1.1 Definirea unei clase

Diagrama sintactică a definiţiei unei clase este

Clasele sunt definite utilizand cuvantul cheie class. Specificatorii de acces sunt public,
protected şi private, identificator este numele clasei, iar membru este o declaraţie de
funcţie membru sau dată a clasei. Implicit toţi membri clasei sunt privaţi. Ca exemplu
vom defini o clasă care reprezintă numere raţionale ca un cât a două numere întregi.

Definiţia clasei este următoarea

class Ratio
{
private:
int num, den;
public:
void assign(int, int);
double convert();
void invert();
void print();
};
Numele clasei este Ratio. Definiţia clasei este inclusă între accolade { şi }, urmate de ;.
Date membre ale clasei sunt num şi den şi ele vor memora numărătorul şi numitorul
numărului raţional descris de clasă. Ele sunt de tip int şi sunt declarate private. In acest
fel ele vor fi accesibile doar funcţiilor membre ale clasei. Interzicerea accesului din
afara clasei este un principiu al programării orientate obiect numit ascunderea
informaţiilor. Funcţiile membre ale clasei sunt assign(), ce atribuie valori numărului
raţional, convert(), ce dă valoarea numărului, invert(), ce inversează numărul şi print(), ce
scrie numărul ca un cât de două numere întregi. Ele sunt declarate publice şi vor putea fi
utilizate de obiecte în afara clasei. Funcţiile membre pot fi definite în interiorul clasei sau
în afara ei. Funcţiile definite în interiorul clasei sunt funcţii inline. Compilatorul
generează direct textul lor în program, şi nu un apel la funcţie.
In limbajul C++ avem posibilitatea de a grupa anumite nume în spaţii de nume.
Prin definiţie, datele şi funcţiile unei clase constituie un spaţiu de nume ce are
numele clasei. Operatorul de rezoluţie :: arată că un nume de dată sau de funcţie
aparţine unui spaţiu de nume. Operatorul de rezoluţie :: se utilizează pentru a
defini o funcţie membru a clasei în afara clasei.
Implementarea clasei este următoarea

void Ratio::assign(int numval, int denumval)


{
num = numval;
den = denval;
}

double Ratio::convert()
{
return double(num) / den;
}

void Ratio::invert()
{
int temp;
temp = num ;
num = den;
den = temp;
}

void Ratio::print()
{
cout << num << “/” << den;
}

Apelul unei funcţii a clasei de către un obiect se face conform următoarei diagrame
sintactice
Utilizarea unui câmp al un obiect se face conform următoarei diagrame sintactice

Vom exemplifica utilizarea clasei definind un număr rational şi calculând inversul lui.

int main()
{
// creaza un obiect x de tipul Ratio
Ratio x;
x.assign(2, 7);
// scrie x
cout << “x = “ ;
x.print();
cout << “ = “ << x.convert() << endl;
// inverseaza numarul
x.invert();
cout << “1/x = “ <<;
x.print();
cout << endl;
}

Declararea unui obiect se face în acelaşi fel cu declararea tipurilor standard. Vom
spune că un obiect este o instanţă a unei clase. Orice obiect are propriile variabile.
In funcţia main(), x este declarat ca un obiect de tipul Ratio (x este o instanţă a clasei
Ratio). El are cele două data membre num şi den şi poate apela funcţiile membre assign(),
invert() şi print() care au fost declarate publice în definiţia clasei.

Acesta este un principiu al programării orientate obiect. Nu utilizăm variabile şi funcţii


globale, ci fiecare obiect are propriile lui date şi funcţii.
Menţionăm că în funcţia main() nu puteam scrie o instrucţiune
cout << x.num << “ “ << x.den << endl;
deoarece variabilele clasei num şi den sunt declarate private. Ele pot fi utilizate doar de
funcţiile clasei şi nu de obiecte de tipul clasei. Pentru a putea utiliza datele clasei de către
obiecte ca mai sus, ele ar trebui declarate publice.
Menţionăm că în limbajul C++ putem defini clase şi cu cuvântul cheie struct. In acest
caz toţi membrii clasei sunt declaraţi implicit ca publici. Spre deosebire de limbajul C, în
limbajul C++ cuvantul cheie struct defineşte clase.

8.1.2 Pointerul this

Considerăm instrucţiunile în care un obiect apelează o funcţie membru a clasei, de


exemplu
x.print();
Funcţiile clasei au un parametru implicit un pointer de tipul clasei. In acest fel aceste
funcţii pot adresa variabilele obiectului (în cazul nostru ale obiectului x). Fiecare obiect
are o variabilă implicită , numită this care este un pointer de tipul clasei ce conţine adresa
obiectului. Atunci când un obiect apelează o funcţie a clasei, parametrul implicit al
funcţiei primeşte ca valoare pointerul this. Pointerul this poate fi utilizat în orice funcţie
membră a clasei pentru a apela variabile sau funcţii membre. Ca exemplu vom rescrie
funcţia invert() ca să utilizeze pointerul this la apelarea variabilelor num şi den.

void Ratio::invert()
{
int temp;
temp = this->num;
this->num = this->den;
this->den = temp;
}

8.1.3 Spaţii de nume

In limbajul C++ putem defini spaţii de nume. Un spaţiu de nume ne permite să grupăm o
mulţime de clase, obiecte şi funcţii globale sub un nume. Definiţia unei clase crează
automat un spaţiu cu numele clasei ce cuprinde toate variabilele şi funcţiile membre ale
clasei. Declararea unui spaţiu de nume se face astfel
namespace identificator
{
// declaraţii de clase, funcţii, obiecte
}
De exemplu, putem defini următorul spaţiu

namespace general
{
int a, b;
}

Pentru a adresa variabilele din afara spaţiului se utilizează operatorul de rezoluţie ::


De exemplu, scriem
general::a
Directiva using namespace asociază un spaţiu de nume cu un anumit nivel al
programului, astfel încât obiectele şi funcţiile din acel spaţiu sunt accesibile direct ca şi
când ar fi fost definite ca globale. Această directivă are forma
using namespace identificator;
Toate clasele, obiectele şi funcţiile din bibliotecile C++ standard sunt definite în
spaţiul std. In consecinţă, obiectul cout corespunzător streamului de ieşire standard poate
fi utilizat astfel
• Folosind directiva using namespace
# include <iostream>
using namespace std;
……………………………………
cout << endl;
• Utilizând numele spaţiului
# include <iostream>
……………………………………
cout std::cout << std::endl;
• Utilizând stilul clasic tip C în care toate clasele, obiectele şi funcţiile din
bibliotecile standard sunt definite într-un spaţiu global.
# include <iostream.h>
……………………………………..
cout << endl;
Menţionăm că la utilizarea stilului clasic tip C se utilizează bibliotecile standard cu
extensia h, de exemplu <stdio.h>, <iostream.h>, <math.h>, etc. La utilizarea cu spaţii de
nume se utilizează bibliotecile paralele, fără extensia h, de exemplu <cstdio>,
<iostream>, <cmath>, etc.

8.2 Constructori şi destructori

8.2.1 Constructori

In general obiectele trebuie să iniţializeze variabilele lor la declararea obiectului. In


exemplul precedent iniţializarea s-a făcut cu funcţia assign(). Este mai natural ca
iniţializarea să se facă la declararea obiectului. Pentru aceasta se definesc funcţii
constructor ale clasei. Un constructor este o funcţie membru a clasei apelată automat
atunci când obiectul este declarat. Constructorul are acelaşi nume cu al clasei şi nu
are nici un tip.
Clasa precedentă poate avea un constructor definit ca mai jos.

#include <iostream>
using namespace std;
class Ratio
{
private:
int num, den;
public:
void assign(int, int);
double convert();
void invert();
void print();
Ratio(int, int);
};

Vom prezenta în continuare doar definiţia constructorului, restul definiţiilor fiind cele
anterioare.

Ratio::Ratio(int a, int b)
{
num = a;
den = b;
}

Vom exemplifica utilizarea acestei clase definind două obiecte şi afişând valorile lor.

int main()
{
Ratio x(-2, 12), y(4, 5);
cout << “x = “;
x.print();
cout << endl;
cout << “y = “;
y.print();
cout << endl;
return 0;
}

Orice clasă are cel puţin doi constructori. Ei sunt identificaţi de declaraţia lor
X(); // constructorul implicit
X(const X&); // constructorul copiere
unde X numele clasei.
Constructorul implicit nu are parametri. El este apelat ori de câte ori declarăm un
obiect cu instrucţiunea
X obiect;
Dacă nu definim niciun constructor, constructorul implicit este definit de
compilator.
Constructorul copiere este apelat atunci când un obiect este copiat. De exemplu
instrucţiunea
X a(b);
declară un obiect a de tipul X şi îi atribuie ca valoare obiectul b. Dacă nu definim un
constructor copiere, el este definit de compilator. Menţionăm cuvântul cheie const
din definiţia constructorului copiere. El interzice modificarea argumentului în
timpul copierii.
Orice clasă include un operator implicit de atribuire = care are ca parametru un
obiect de tipul clasei.
Exemplu. Fie din nou clasa Ratio în care definim un constructor copiere, un constructor
fără parametri, şi un constructor cu parametri.

# include <iostream>
using namespace std;
class Ratio
{
public:
Ratio(int, int);
Ratio(const Ratio& x);
Ratio();
void print();
void invert();
private:
int num;
int den;
};

Vom prezenta doar definiţiile constructorilor, definiţiile funcţiilor print() şi invert() sunt
cele anterioare.

Ratio::Ratio(const Ratio& x)
{
num = x.num;
den = x.den;
}
Ratio:: Ratio()
{
num = 0;
den = 1;
}
Ratio::Ratio(int x, int y)
{
num = x;
den = y;
}
int main()
{
Ratio x(12, 25);
Ratio z;
Ratio y(x); // se apeleaza constructorul copiere
x.print();
y.print();
z = x;
z.print();
}

Constructorul copiere este apelat ori de câte ori


• un obiect este copiat
• un obiect este pasat prin valoare unei funcţii (obiectul este copiat în stivă)
• un obiect este returnat ca valoare de o funcţie

Exemplu. Presupunem clasa Ratio definită anterior. Fie o funcţie globală f care are ca
parametru un obiect de tip Ratio (un număr raţional) şi ca rezultat un obiect de tip Ratio
care este inversul parametrului funcţiei. Definitia funcţiei este următoarea

Ratio f(Ratio r) // se apeleaza constructorul copiere


{
Ratio s = r; // se apeleaza constructorul copiere
s.invert();
return s; // se apeleaza constructorul copiere
}
int main()
{
Ratio x(11, 22);
Ratio y(x);
Ratio z;
// scrie numarul x
x.print();
z = f(y);
// scrie numărul z
z.print();
return 0;
}

Constructorul copiere este apelat


1) atunci când se declară obiectul
Ratio y(x);
2) când se apelează funcţia
z = f(y);
Parametrul funcţiei f (r în definiţia funcţiei) este pasat prin valoare, deci în stivă se pune o
copie a obiectului creată cu constructorul copiere.
3) când se execută instrucţiunea
return s;
obiectul s se copiază în stivă utilizand constructorul copiere.
Menţionăm că definiţia funcţiei f putea fi
Ratio f(const Ratio r);
deoarece funcţia nu modifică parametrul r.
Putem defini tablouri de obiecte în mod obişnuit, de exemplu
Ratio x[20];
Pentru aceasta, clasa trebuie să definească un constructor fără parametri. Tabloul
definit poate fi prelucrat, de exemplu putem avea instrucţiunile
Ratio a;
a = x[5];

8.2.2 Destructori

Destructorul unei clase este apelat automat când un obiect este distrus. Fiecare clasă are
un singur destructor. Destructorul nu are tip şi nici parametri. Diagrama sintactică a
destructorului este următoarea
~ nume_clasa(){/* corpul destructorului*/}
Dacă el nu este definit explicit, compilatorul generează unul. Pentru a vedea cum sunt
apelaţi constructorii şi destructorul fie următoarea clasă în care constructorul şi
destructorul scriu un mesaj.

class Ratio
{
public:
Ratio() {cout << “obiectul este creat” << endl;}
~Ratio() {cout << “obiectul este distrus” << endl;}
private:
int num;
int den;
};

Fie două blocuri de instrucţiuni în funcţia main. In fiecare bloc creăm câte un obiect.

int main()
{
{
cout << “incepe blocul 1” << endl;
Ratio x;
cout << “iesire blocul 1” << endl;
}
{
cout << “incepe blocul 2” << endl;
Ratio y;
cout << “sfarşit blocul 2” << endl;
}
}

Mesaje le scrise vor fi:

incepe blocul 1
obiectul este creat
iesire blocul 1
obiectul este distrus

incepe blocul 2
obiectul este creat
iesire blocul 2
obiectul este distrus

Menţionăm că toate variabilele locale unei funcţii sau unui bloc sunt create intr-o stivă la
intrarea în funcţie sau bloc şi sunt şterse din stivă la ieşirea din funcţie sau bloc. Stiva se
numeşte heap.

8.3 Funcţii prietene

In general avem nevoie să utilizăm şi să modificăm datele unui obiect în timpul execuţiei
programului. Dacă datele sunt declarate private, nu putem face acest lucru direct. De
aceea se definesc funcţii care să modifice şi să furnizeze valorile datelor unui obiect. In
cazul clasei Ratio putem defini funcţii membre ale clasei
int getnum();
int getden();
void setnum(int);
void setden(int);
Aceste funcţii se numesc funcţii de acces şi de regulă sunt publice.
Definiţiile acestor funcţii pot fi

int Ratio::getnum()
{
return num;
}

void Ratio::setnum(int x)
{
num = x;
}

Exemplu. Vom defini o funcţie globală care să adune două numere raţionale. Prototipul
funcţiei va fi
Ratio sum(const Ratio x, const Ratio y);
Parametrii x şi y sunt cele două numere raţionale ce sunt adunate. Deoarece obiectele nu
vor fi modificate, ele au fost declarate const.
a c ad + bc
Considerăm două numere rationale, r = şi s = . Suma lor va fi t = . In
b d bd
implementarea funcţiei nu putem folosi direct variabilele num şi den, deoarece ele sunt
declarate private în definiţia funcţiei. Vom utiliza funcţiile getnum() şi getden() pentru a
obţine valoarea lor. Implementarea funcţiei este următoarea

Ratio sum(Ratio x, Ratio y)


{
int a = x.getnum();
int b = x.getden();
int c = y.getnum();
int d = y.getden();
Ratio temp(a * d + b * c, b * d);
return temp;

O altă soluţie este următoarea. Pentru ca o funcţie externă să aibe acces la membri
privaţi ai clasei, ea trebuie declarată în definiţia clasei ca funcţie prietenă, friend.
Diagrama sintactică a definiţiei unei funcţii prietene este
friend tip nume_funcţie ( parametri);
Noua definiţie a clasei Ratio este

class Ratio
{
public:
Ratio(int, int);
Ratio(const Ratio& x);
Ratio();
void print();
void invert();
friend Ratio sum(Ratio x, Ratio y)
private:
int num;
int den;
};

In această definiţie funcţia sum a fost declarată prietenă, deci poate utiliza variabilele
private ale clasei. Implementarea funcţiei sum este următoarea

Ratio sum(Ratio x, Ratio y)


{
Ratio temp(x.num * y.den + x.den * y.num, x.den * y.den);
return temp;

8.4 Fişierele standard C++

Limbajul C++ defineşte clasele istream şi ostream pentru operaţii intrare şi ieşire cu
fişierele standard, tastatura şi ecranul. Streamul cin este un obiect de tipul istream, iar
streamurile cout, cerr şi clog sunt obiecte de tipul ostream. Aceste obiecte sunt definite în
bibliotecile <iostream.h> şi <iostream>.
Clasa istream defineşte operatorul de citire >> şi funcţii ce citesc caractere ASCII şi
şiruri tip C. Funcţiile
int get();
istream& get(char&);
citesc un character. Funcţiile
istream& get(char* s, int size, char ch);
istream& getline(char* s, int size, char ch);
citeşte cel mult size - 1 caractere în vectorul s. Citirea se opreşte după citirea a size - 1
caractere sau la întalnirea caracterului ch. Valoarea implicită a caracterului ch este ‘\n’.
Clasa ostream defineşte operatorul de scriere << şi funcţia
ostream& put(char);
ce scrie un character.
Exemplu. Fie instrucţiunea
char c;
Instrucţiunile
cin.get(c);
şi
c = cin.get();
citesc un caracter de la tastatură în variabila c.
Menţionăm că operatorul >> este un operator binar. Operandul stâng este un obiect tip
istream, oparandul drept este o variabilă, rezultatul este o referinţă la un obiect tip
istream. Ţinând cont de prototipul funcţiei get
istream& get(char&);
este posibil să scriem
char c;
int x;
cin.get(c) >> x;
deoarece operandul stâng este o referinţă tip istream, rezultatul evaluării funcţiei
get(char&).
Toate operaţiile de scriere de până acum în streamul cout nu au utilizat un format. Este
posibil de a preciza formatul informaţiei scrise în felul următor
• putem insera spaţii sau caracterul ‘\t’
cout << s << “ “ << ‘\t’ << x << endl;
• putem insera manipulatori definiţi în biblioteca <iomanip> sau <iomanip.h>.
Implicit, orice valoare este scrisă pe un număr de caractere specificat sau pe
numărul de caractere necesare. De exemplu, numărul întreg 123 este totdeauna
scris pe cel puţin 3 caractere. Funcţia
setw(int w);
dă dimensiunea câmpului în care este scrisă valoarea. Dimensiunea implicită a
câmpului este 0. Funcţia
setfill(char c);
dă caracterul c cu care sunt umplute spaţiile libere. Valoarea implicită este spaţiu.
Exemplu. Fie variabila
int k = 192;
scrisă cu diverse dimensiuni ale campului şi caractere de umplere.

Instrucţiunea de scriere Rezultat


cout << "[" << setw(6) << k << "]" << endl; [ 192]
cout << "[" << setw(6) << setfill('*') << k << "]" << endl; [***192]

Funcţia setw() care dă dimensiunea câmpului este valabilă doar pentru câmpul
următor.
Funcţia
setprecision(int p);
dă numărul p de caractere pe care este scris un număr real, tip float sau double.
Exemple. Fie variabila
float x = 3.1459;
scrisă cu diferite precizii. De remarcat că valoarea scrisă este rotunjită.

Instrucţiunea de scriere Rezultat


cout << pi << endl; 3.1459
cout << setprecision(3) << pi << endl; 3.15
cout << setprecision(4) << pi << endl; 3.146
cout << setprecision(6) << pi << endl; 3.1459

Clasa ios defineşte următoarii specificatori


dec - pentru baza 10
hex - pentru baza 16
oct - pentru baza 8
showbase pentru specificarea bazei
Specificatorul showbase scrie 0x pentru numere în baza 16 şi 0 pentru numere în
baza 8. Exemple. Fie variabila
int k = 29;
scrisă în baze diferite

Instrucţiunea de scriere Rezultat


cout << hex << i; 13
cout << hex << showbase << i; 0x13
cout << oct << i; 35
cout << oct << showbase << i; 035

Specificatorii următori specifică modul de cadrare a valorii scrise


left - cadrare la stânga
right - cadrare la dreapta
Specificatorii următori specifică modul de scriere a numerelor reale
fixed - scriere fără exponent
scientific - scriere cu exponent
Exemple. Fie instrucţiunea
float f = 24.35;
Instrucţiunea de scriere Rezultat
cout << fixed << f << endl; 24.35
cout << scientific << f << endl; 2.435e+001

Cap. 9 Supraîncărcarea operatorilor

Operatorii limbajului C++ sunt automat definiţi pentru tipurile fundamentale: int, double,
char, etc. Când definim o clasă nouă, creăm un nou tip. Operatorii limbajului pot fi
definiţi şi pentru tipurile nou create. Această operaţie se numeşte supraîncărcarea
operatorilor (operator overloading). Pentru a supraîncarca un operator definim o
functie de forma
tip operator semn (parametri) {/* corpul funcţiei */}
unde semn este operatorul dorit +,-, *, / , [], (), <<, >>, =, etc. Operatorii supraîncărcaţi
au aceeaşi prioritate ca cei originali şi acelaşi număr de parametri. Menţionăm că orice
operator supraîncărcat poate fi definit ca funcţie membră a clasei sau ca funcţie globală.
Exemplu. Să supraîncărcăm operatorul + pentru clasa Ratio, care să adune două numere
raţionale. Operatorul va fi definit ca o funcţie globală.

Ratio operator+(Ratio x, Ratio y)


{
int a = x.getnum();
int b = x.getden();
int c = y.getnum();
int d = y.getden();
Ratio temp(a * d + b * c, b * d);
return temp;
}

Se va compara această funcţie cu funcţia sum definită anterior. Funcţia are doi parametri,
numerele de adunat deoarece este o funcţie globală. Putem utiliza funcţia definită astfel

int main()
{
Ratio x(2, 3), y(3, -5), z;
z = x + y;
z.print();
return 0;
}

Putem defini operatorul ca funcţie prietenă a clasei. In definiţia clasei vom scrie
instrucţiunea
friend Ratio operator+ (Ratio, Ratio);
In acest caz definiţia funcţiei este

Ratio operator+(Ratio x, Ratio y)


{
Ratio temp(x.num * y.den + x.den * y.num, x.den * y.den);
return temp;
}

Al doilea mod de a defini operatorul + este ca funcţie membră a clasei. O funcţie membră
a clasei este apelată de un obiect. Fie a, b, c obiecte de tip Ratio. Putem scrie
c = a.operator+(b);
sau, echivalent
c = a + b;
De accea, atunci când operatorul este o funcţie membră a clasei, funcţia corespunzătoare
are un singur parametru, operandul drept, şi este apelată de operandul stâng. Definiţia
clasei Ratio cu operatorul + este

class Ratio
{
public:
Ratio(int, int);
Ratio(const Ratio& x);
Ratio();
void print();
void invert();
Ratio operator+(Ratio); // functia are un singur parametru
private:
int num;
int den;
};

Implementarea funcţiei este

Ratio Ratio::operator+(Ratio x)
{
Ratio temp(num * x.den + den * x.num, num * x.den);
return temp;
}

Exemplu. Fie instrucţiunea


Ratio a(1, 3), b(4, -3), c;
Instrucţiunile
c = a + b;
şi
c = a.operator+(b);
sunt echivalente.

9.1 Supraîncărcarea operatorului de atribuire

Considerăm clasa definită anterior în care definim constructorul implicit, constructorul


copiere şi operatorul de atribuire. Menţionăm că operatorul de atribuire este automat
definit pentru orice clasă. Supraîncărcarea lui aici este doar un exerciţiu. O definiţie
incorectă a operatorului de atribuire este următoarea

# include <iostream>
using namespace std;
class Ratio
{
private:
int num, den;
public:
Ratio();
Ratio(const Ratio&);
void operator = (const Ratio&);
};

O implementare a definiţei incorecte a operatorului de atribuire supraîncărcat este


următoarea

void Ratio::operator = (const Ratio& r)


{
num = r.num;
den = r.den;
}

Ea copiază obiectul r în obiectul ce apelează operatorul. Fie de exemplu instrucţiunea


Ratio x, y, z;
Cu această definiţie putem scrie
x = z;
y = z;
dar nu putem scrie
x = y = z;
deoarece rezultatul funcţiei este void.
Pentru a vedea care este definiţia corectă a operatorului de atribuire considerăm
instrucţiunea
int x, y, z = 2;
Limbajul permite o instrucţiune de atribuire de forma
x = y = z = 2;
Operatorul = este asociativ la dreapta şi are ca rezultat valoarea atribuită operandului
stâng. Prima dată se execută instrucţiunea
z = 2;
apoi
y = z;
etc. Putem scrie instrucţiunea de mai sus
f(x, f(y, f(z, 2)));
In consecinţă, operatorul trebuie să aibe ca rezultat o referinţă de acelaşi tip cu valoarea
pe care o atribuie (tipul operandului stâng). In acest fel operatorul este asociativ la
dreapta. Definiţia corectă a operatorului de atribuire al clasei Ratio este
Ratio& operator = (const Ratio & );
Definiţia clasei va fi

class Ratio
{
private:
int num, den;
public:
Ratio();
Ratio(const Ratio&);
Ratio& operator = (const Ratio&);
}

Prototipul operatorului de atribuire al unei clase T este


T& operator = (const T&);
Operatorul are ca rezultat o referinţă la obiectul ce apelează operatorul. Pentru aceasta se
utilizează pointerul this. Implementarea corectă a operatorului de atribuire supraîncărcat
este următoarea.

Ratio& Ratio::operator = (const Ratio& r)


{
// obiectul ce a apelat operatorul primeste valoarea obiectului r
num = r.num;
den = r.den;
// rezultatul funcţiei este referinta obiectului ce a apelat operatorul
return *this;
}

Putem scrie acum


Ratio x, y, z(2, 3);
x = y = z;

9.2 Supraîncărcarea operatorilor aritmetici

Presupunem că vrem să definim o clasă pentru lucrul cu numere complexe. Vrem să


putem efectua operatii cu numere complexe la fel ca şi cu tipurile fundamentale, de
exemplu să definim numere complexe
Complex a(1.0, -1.0), b(2.0, -3), c;
şi să scriem pentru adunare
c = a + b;
Limbajul ne dă posibilitatea de a defini funcţii care să efectueze operaţiile standard +,-,
etc., pentru operanzi ce sunt obiecte ale unor clase. Acest lucru se numeşte
supraîncărcarea operatorilor (operator overloading). Un operator poate fi definit ca o
funcţie globală sau ca o funcţie membru a clasei. Vom exemplifica supraîncărcarea
operatorului + pentru o clasă ce descrie numere complexe. O reprezentare a clasei este
cea de mai jos.

Exemplu. Supraîncărcarea operatorului + pentru clasa Complex în cazul în cazul în care


operatorul este o funcţie globală. Funcţia are numele operator+ şi doi parametri de tip
Complex, numerele ce se adună. Tipul funcţiei este Complex. Definiţia funcţiei este
Complex operator+(Complex, Complex);
Considerăm definiţia clasei Complex de mai jos.

class Complex
{
private:
float real;
float imag;
public:
Complex();
Complex (float, float);
};

Problema scrierii funcţiei este aceea că datele clasei Complex sunt declarate private şi nu
pot fi utilizate direct de funcţie, care este globală. O soluţie este de a scrie două funcţii
membre ale clasei, de exemplu
float getreal();
float getimag();
care au ca rezultat variabilele real şi imag ale clasei. Această soluţie rămane ca exerciţiu.
O altă soluţie este următoarea. Pentru ca o funcţie externă să aibe acces la membrii
privaţi ai clasei, ea trebuie declarată în definiţia clasei ca funcţie prietenă, friend.
Diagrama sintactică a definiţiei unei funcţii prietene este
friend tip nume_funcţie ( parametri);
In consecinţă, definiţia clasei Complex va fi

class Complex
{
private:
float real;
float imag;
public:
Complex();
Complex (float, float);
friend Complex operator+(Complex, Complex);
};

Definiţia funcţiei va fi cea de mai jos.

Complex operator+(Complex& x, Complex& y)


{
Complex temp;
temp.real = x.real + y.real;
temp.imag = x.imag + y.imag;
return temp;
}

Trebuie ca în definiţia clasei să includem un constructor implicit, fără parametri


Complex();
Constructorul implicit nu mai este generat automat de compilator deoarece am definit un
constructor. Constructorul implicit este apelat la declararea obiectului temp
Complex temp;
în funcţia operator+.
Exemplu. Supraîncărcarea operatorului + pentru clasa Complex în cazul în care
operatorul este definit ca o funcţie membră a clasei, al cărei nume este operator+. O
funcţie membru a clasei este apelată de un obiect. In consecinţă, vom scrie
c = a.operator+(b);
sau, echivalent
c = a + b;
De accea, atunci când operatorul este o funcţie membră a clasei, funcţia corespunzătoare
are un singur parametru, operandul drept, şi este apelată de operandul stâng. Fie definiţia
clasei Complex în care definim şi funcţia operator+

class Complex
{
private:
float real;
float imag;
public:
Complex();
Complex (float, float);
Complex operator+ (Complex);
};

Implementarea constructorilor este

Complex::Complex(float x, float y)
{
real = x;
imag = y;
}

Complex::Complex()
{
real = 0;
imag = 0;
}

Implementarea operatorul + este următoarea.

Complex Complex::operator+(Complex p)
{
Complex temp;
temp.real = real + p.real;
temp.imag = imag + p.imag;
return temp;
}

Trebuie ca în definiţia clasei să includem un constructor implicit, fără parametri


Complex();
Constructorul implicit nu mai este generat automat de compilator deoarece am definit un
constructor. Constructorul implicit este apelat la declararea obiectului temp
Complex temp;
în funcţia operator+.
Exerciţiu. Să se scrie implementarea operatorilor de scădere, -, înmulţire, * şi împărţire, /
a două numere complexe.

9.3 Supraîncărcarea operatorilor << şi >>

Operatorul << este numit operator de inserţie, el insereaza caractere într-un stream.
Operatorul >> este numit operator de extracţie, el extrage caractere dintr-un stream.
Toate functiile de inserţie au forma

ostream& operator << (ostream& stream, tip obiect)


{
// corpul functiei
return stream;
}

Primul parametru este o referinţă la streamul de ieşire. Al doilea este obiectul ce trebuie
inserat. Ultima instrucţiune este
return stream;
Exemplu. Operatorul de inserţie << pentru clasa Complex. In definiţia clasei definim
funcţia
friend ostream& operator << (ostream& stream, Complex x);
Implementarea funcţiei este

ostream& operator << (ostream& stream, Complex x)


{
stream << "(" << x.real << "," << x.imag << ")";
return stream;
}

Exemplu. Operatorul de extracţie >> este supraîncărcat astfel


friend istream& operator >> (istream& stream, Complex& x);
Implementarea lui este următoarea

istream& operator >> (istream& stream, Complex& x)


{
stream >> x.real >> x.imag;
return stream;
}

Când un operator este membru al unei clase, operandul stâng (transmis prin this) este cel
care apeleaza operatorul. Operatorii << şi >> pe care i-am definit nu pot fi membri ai
clasei deoarece operandul stâng ostream, respectiv istream nu este un membru al clasei.
In consecinţă, aceşti operatori vor fi funcţii externe. Reamintim că pentru ca o funcţie
externă să aibe acces la membrii privaţi ai clasei, ea trebuie declarată în definiţia
clasei ca funcţie prietenă friend. Diagrama sintactică a definiţiei unei funcţii prietene
este
friend tip nume_funcţie ( parametri);
Definiţia clasei este

class Complex
{
private:
float real;
float imag;
public:
Complex (float, float);
Complex operator+ (Complex);
friend ostream& operator << (ostream&, Complex&);
friend istream& operator >> (istream&, Complex&);
}

Putem utiliza operatorii definiţi astfel

int main()
{
Complex a(1.0, 1.0);
Complex b(0.0, 1.0);
Complex c;
c = a + b;
// echivalent putem scrie
c = a.operator+(b);
cout << c << endl;
return 0;
}

Trebuie ca în definiţia clasei să includem un constructor implicit, fără parametri, de


exemplu

Complex() {real = 0; imag = 0;}

Constructorul implicit nu mai este generat automat de compilator deoarece am definit un


constructor. Constructorul implicit este apelat la declararea obiectului temp
Complex temp;
în funcţia operator+ şi în funcţia main(). Este de dorit ca acest constructor să iniţializeze
variabilele obiectului.
Orice clasă include un operator implicit de atribuire = care are ca parametru un
obiect de tipul clasei.
Cap. 10 Moştenirea

10.1 Pointeri la obiecte. Operatorii new şi delete

O clasă, odată definită, este un tip valid. Putem defini variabile tip pointer la clase la fel
cu pointeri la orice tip. O variabilă tip pointer la o clasă poate fi iniţializată cu adresa unui
obiect iar datele şi funcţiile acelui obiect pot apelate utilizând pointerul.
Instrucţiunea de definire a unui pointer la o clasă este
tip * identificator;
unde tip este numele clasei iar identificator este numele variabilei tip pointer.
Variabila tip pointer poate fi iniţializată ca orice variabilă tip pointer, folosind operatorul
de calcul al adresei &.
Fie p un pointer la un obiect. Un câmp al obiectului este adresat astfel
(*p).membru
Prin definiţie, această scriere se prescurtează
p->membru
O funcţie membră a clasei se apelează astfel
(*p).funcţie(parametri);
Prin definiţie, această scriere se prescurtează astfel
p->funcţie(parametri);
Operatorii . şi -> au aceeaşi prioritate ca operatorii () şi [].
Exemplu. Fie o clasă ce defineşte un dreptunghi. Clasa are ca variabile lungimea şi
lăţimea dreptunghiului, funcţii de acces la variabilele clasei, şi o funcţie care calculează
aria.

class Rectangle
{
private:
float h;
float b;
public:
float aria();
Rectangle(float, float);
float geth();
float getb();
void seth(float);
void setb(float);
};

Rectangle::Rectangle(float x, float y)
{
h = x;
b = y;
}

float Rectangle::aria()
{
return h * b;
}

float Rectangle::geth()
{
return h;
}

void Rectangle::seth(float x)
{
h = x;
}

Funcţiile getb() şi setb() se definesc la fel ca geth() şi seth().


Vom apela funcţiile clasei direct şi prin pointer.

int main()
{
Rectangle d1(1.2, 4.5);
// defineste un pointer la obiect
Rectangle * pd1;
pd1 = &d1;
// apeleaza functiile direct
cout << “inaltimea = “ << d1.geth() << endl
<< “latimea = “ << d1.getb() <<
<< “aria = “<< d1.aria() << endl;
// apeleaza functiile prin pointer
cout << “inaltimea = “ << pd1->geth() << endl
<< “latimea = “ << pd1->getb() <<
<< “aria = “<< pd1->aria() << endl;
}

Există trei moduri de a crea obiecte.


• Obiecte globale declarate la nivelul programului, în afara oricărei funcţii. Ele sunt
create la inceputul execuţiei programului şi distruse la sfârşitul execuţiei.
• Obiecte locale, declarate în funcţii sau în blocuri din funcţii, între { şi } Ele sunt
create la intrarea în bloc şi distruse la ieşirea din bloc.
• Obiecte create în memorie cu operatorul new. Ele trebuie distruse cu operatorul
delete când nu mai sunt necesare. Ele sunt create într-o zonă specială de memorie
denumită heap.
Operatorul new crează un obiect în memorie apeland constructorul şi furnizează adresa
obiectului nou creat, adică un pointer. Diagrama sintactică este
ptr = new tip (listă de parametri);
tip este numele unei clase iar ptr este variabilă tip pointer. Operatorul delete are forma
delete ptr;
unde ptr este variabila tip pointer cu adresa obiectului.
Exemplu. Vom considera aceeaşi clasă Rectangle, vom crea un obiect, (dreptunghi), cu
operatorul new şi vom scrie dimensiunile şi aria lui.

int main()
{
Rectangle * ptr;
// creaza obiectul cu operatorul new
ptr = new Rectangle(2.5, 17.1);
// functii apelate prin pointer
cout << “inaltimea = “ << ptr->geth() << endl
<< “latimea = “ << ptr->getb() <<
<< “aria = “<< ptr->aria() << endl;
// distruge obiectul
delete ptr;
return 0;
}

Putem crea vectori de obiecte cu operatorul new. Pentru aceasta clasa trebuie să aibă un
constructor fără parametri. De exemplu, putem crea un vector cu zece obiecte de tip
Complex
Complex * vect;
vect = new Complex[10];
Acest vector se şterge cu operatorul delete cu forma
delete [] vect;

10.2 Moştenirea

Moştenirea este un concept al programării cu obiecte. Moştenirea permite să creăm


clase care sunt derivate din alte clase existente, astfel încât ele includ unii dintre
membri claselor părinte, variabile şi funcţii. Clasa derivată moşteneşte membrii
clasei părinte, funcţii şi variabile. In acest fel putem construi programe complexe
din obiecte simple. Clasa care este moştenită se numeşte clasă de bază, iar clasele
care moştenesc se numesc clase derivate. Clasa de bază conţine caracteristicile
comune ale mai multor elemente. Clasele care moştenesc sunt clase particulare ce
adaugă doar elementele proprii.
Diagrama sintactică a definirii unei clase derivate este

class nume_clasa_derivata : acces nume_clasa_baza


{
// definitia clasei derivate
};

In această definiţie cuvântul cheie acces poate fi public, protected sau private.
Semnificaţia acestui termen va fi prezentată ulterior.
Exemplu. Vrem să definim două clase Rectangle şi Triangle care să descrie dreptunghiuri
şi respective triunghiuri. Clasele au în comun două variabile ce conţin baza şi înalţimea şi
o funcţie ce dă valori acestor variabile. Vom defini deci o clasă de bază numită Polygon
ce are două variabile h şi b, înălţimea şi lăţimea, declarate aici cu acces protejat,
(protected), un constructor fără parametri, o funcţie setval() ce dă valori acestor variabile
şi o funcţie print() ce afişază valorile variabilelor.
class Polygon
{
protected:
float h, b;
public:
void setval(float x, float y){h = x; b = y;}
Polygon() {h = 0; b = 0;}
void print() {cout << “b = “ << b << “,” << “ h = “, << h << endl;}
};

Vom defini cele două clase care descriu triunghiuri şi dreptunghiuri derivate din clasa
Polygon. Clasele vor avea o funcţie ce calculează aria. O reprezentare grafică a acestei
dependenţe este cea de mai jos.

Definiţia claselor derivate este următoarea.

class Rectangle : public Polygon


{
public:
float area{return h * b;}
};
class Triangle : public Polygon
{
public:
float area{return h * b / 2;}
};

Un exemplu de utilizare a acestor clase în care calculăm aria unui triunghi şi a unui
dreptunghi este următorul.

int main()
{
Rectangle drpt;
Triangle trgh;
drpt.setval(1.3, 4.4);
trgh.setval(2.3, 12);
cout << “triunghi” << endl;
cout << trgh.area() << endl;
cout << “dreptunghi” << endl;
cout << drpt.area() << endl;
}

Am definit variabilele clasei de bază h şi b de tipul protected. In acest caz ele pot fi
utilizate în clasele derivate, dar nu pot fi utilizate de obiecte. De exemplu, nu putem scrie
instrucţiunea
cout << trgh.h << endl;
Pentru a afişa valorile variabilelor b şi h am definit funcţia print().
Vom prezenta acum semnificaţia cuvântului cheie acces din definiţia clasei derivate. Fie
definiţia unei clase

class Baza
{
public:
int x;
protected:
int y;
}

şi fie o clasă derivată cu specificatorul de acces public

class Deriv : public Baza


{
/* declaratii din clasa Deriv */
}
In acest caz variabilele x şi y au în clasa Deriv specificatorul de acces definit în clasa
Baza, respectiv public şi protected şi pot fi utilizate de funcţiile definite în clasa Deriv,
dar numai variabila x poate fi utilizată de obiectele de tipul Deriv.
Considerăm clasa derivată definită cu specificatorul de acces protected

class Deriv : protected Baza


{
/* declaratii din clasa Deriv */
}

In acest caz variabilele x şi y au în clasa Deriv specificatorul de acces protected. Ele pot
fi utilizate de funcţiile definite în clasa Deriv, dar nu pot fi utilizată de obiecte de tipul
Deriv.
Considerăm clasa derivată definită cu specificatorul de acces private

class Deriv : private Baza


{
/* declaratii din clasa Deriv */
}

In acest caz toate variabilele x si y din clasa Baza au în clasa Deriv specificatorul de
acces private.
Menţionăm că datele şi funcţiile declarate private în clasa de bază nu pot fi utilizate
în clasele derivate sau de obiecte de tipul claselor derivate.
Specificatorul de acces din definiţia clasei derivate dă nivelul minim de acces pentru
membrii moşteniţi din clasa de bază. La utilizarea specificatorului de acces public, clasa
derivată moşteneşte toţi membrii clasei de bază cu nivelul de acces avut în clasa de bază.
Putem sumariza acum accesul la variabilele unui obiect în funcţie de specificatorii lor

specificatorul de acces al variabilei


Public protected private
Membri ai aceleiaşi clase da da da
Membri claselor derivate da da nu
Nemembri da nu nu

Exemplu. Fie clasele X şi Y definite după cum urmează


class X
{
public:
int x;
protected:
int y;
private:
int z;
};
class Y : public X
{
public:
int w;
};

Fie două obiecte definite astfel


X a;
Y b;
Câmpurile celor două obiecte, a şi b sunt cele de mai jos.

Obiectul a are cele trei câmpuri din declaraţia clasei, x, y, z cu specificatorii respectiv
public, protected, private. Obiectul b are câmpurile x şi w cu specificatorul de acces
public şi y cu specificatorul protected. Câmpul z nu este moştenit de obiectul b.
O clasă derivată moşteneşte toţi membri clasei de bază exceptând constructorii,
destructorul şi operatorul =. Deşi constructorii nu sunt moşteniţi, constructorul implicit şi
destructorul sunt totdeauna apelaţi când un obiect de tipul clasei derivate este creat sau
distrus. In constructorul clasei derivate se apelează la început constructorul implicit al
clasei de bază. Dacă este nevoie, putem apela explicit un constructor al clasei de bază
pentru iniţializarea variabilelor. Apelarea sa se face astfel

constructor_clasa_derivata(lista de parametri)
: constructor_clasa_de_baza (lista de parametri)
{
/* definitia constructorului clasei derivate */
}

Exemplu. Să definim o clasă numită Point care descrie un punct pe ecran prin
coordonatele sale şi o clasă Pixel ce descrie un pixel şi moşteneşte din clasa Point
coordonatele pixelului. Clasa Point va avea două câmpuri de tip întreg, x şi y, ce sunt
coordonatele punctului pe ecran, doi constructori şi o funcţie clear() ce pune la valoarea
zero cele două coordinate.
class Point
{
public:
int x, y;
void clear();
Point(int, int);
Point(){x = 0; y = 0;}
void print();
};

void Point::clear()
{
x = 0;
y = 0;
}
Point::Point(int p1, int p2)
{
x = p1;
y = p2;
}
void Point::print()
{
cout << “ x = “ << x << “,” << “ y = “ << y << endl;
}

Când un obiect dintr-o clasă derivată este creat sau distrus, constructorul implicit
(fără parametri) şi destructorul clasei de bază sunt totdeauna apelaţi. De aceea am
definit şi constructorul implicit în definiţia clasei Point.
Vrem să definim acum o clasă Pixel care să descrie un pixel pe ecran. Un pixel este
caracterizat de coordonatele sale şi de culoare. Culoarea va fi un câmp de tip întreg. Clasa
Pixel va moşteni coordonatele punctului din clasa Point. Funcţia clear() va pune la
valoarea zero coordonatele şi culoarea obiectului tip Pixel.
class Pixel : public Point
{
public:
int color;
void clear();
Pixel(int, int, int);
void print();
};

In definiţia constructorului clasei Pixel vom apela constructorul cu parametri al clasei


Point.

Pixel::Pixel(int a, int b, int c) : Point(a, b)


{
color = c;
}

void Pixel::clear()
{
Point::clear();
color = 0;
}

void Pixel::print()
{
Point::print();
cout << “culoarea : “ << color << endl;
}
Menţionăm că nu puteam defini funcţia clear() a clasei Pixel astfel

void Pixel::clear()
{
clear();
color = 0;
}

deoarece ar fi însemnat o apelare recursivă. Un exemplu de utilizare a clasei Pixel este


prezentat mai jos.

int main()
{
Pixel p(1, 2, 15);
p.print();
return 0;
}

10.3 Funcţii virtuale. Polimorfism

Considerăm exemplul anterior în care, pentru a apela funcţia area() prin pointer pentru
cele două obiecte am definit două variabile pointer de tipuri diferite. Este posibil ca un
pointer la clasa de bază să conţină adresa unui obiect al unei clase derivate.
Exemplu. Fie clasele Point şi Pixel definite anterior. Vom crea obiecte de tip Point şi
Pixel şi vom scrie valorile lor utilizând o variabilă pointer de tip Point.

int main()
{
// defineste un pointer de tip Point
Point * prt;
// creaza un obiect de tip Point
Point p(12, 240);
// atribuie variabilei prt adresa obiectului p de tip Point
prt = &p;
// afisaza coordonatele utilizand variabila pointer
prt->print();
/* creaza un obiect de tip Pixel */
Pixel px(15, 124, 5);
/* atribuie variabilei prt adresa obiectului px de tip Pixel */
prt = &px;
/* afisaza coordonatele utilizand variabila pointer */
prt->print();
return 0;
}
Menţionăm că ambele instrucţiuni
prt->print();
apelează funcţia print() a clasei Point, chiar dacă ne aşteptăm ca în cazul al doilea să
se apeleze funcţia print() a clasei Pixel.
Funcţia apelată este determinată de tipul variabilei pointer, nu de tipul obiectului.
Putem face ca funcţia apelată să fie determinată de tipul obiectului şi nu de tipul
variabilei pointer definind funcţii virtuale.
Exemplu. Vom redefini clasele Point şi Pixel astfel. Funcţie clear(), ce pune la valoarea
zero variabilele, şi funcţia print() ce afişază valoarea variabilelor vor fi declarate virtuale.
Definiţia clasei Point este următoarea

class Point
{
private:
int x, y;
virtual void clear();
virtual void print();
Point(int, int);
Point(){x = 0; y = 0;}
};

Definiţiile funcţiilor sunt cele anterioare. Ele vor fi repetate aici.

void Point::clear()
{
x = 0;
y = 0;
}
Point::Point(int p1, int p2)
{
x = p1;
y = p2;
}
void Point::print()
{
cout << “x = “ << x << “,” << “ y= “ << y << endl;
}

Clasa Pixel moşteneşte clasa Point. Constructorul ei apelează constructorul cu parametri


al clasei Point, iar funcţiile clear() şi print() apelează funcţiile cu acelaşi nume din clasa
Point.

class Pixel : public Point


{
private:
int color;
virtual void print();
virtual void clear();
Pixel(int, int, int);
};

Definiţiile funcţiilor sunt cele anterioare.

Pixel::Pixel(int a, int b, int c) : Point(a, b)


{
color = c;
}

void clear()
{
Point::clear();
color = 0;
}

void Pixel::print()
{
Point::print();
cout “ culoare = “ << p.color << endl;
}

In funcţia main() vom crea obiecte de tip Point şi apoi Pixel. Vom scrie datele din aceste
obiecte apelând funcţia print() printr-un pointer la clasa de bază.

int main()
{
Point * ptr;
// creaza un obiect de tipul Point
Point p(2, 7);
// atribuie variabilei ptr adresa obiectului p de tip Point
ptr = &p;
// afisaza coordonatele punctului
ptr->print();
// creaza un obiect de tipul Pixel
Pixel px(1, 2, 15);
// atribuie variabilei ptr adresa obiectului px de tip Pixel
ptr = &px;
// afisaza coordonatele si culoarea pixelului
prt->print();
return 0;
}

Prima instrucţiune
ptr->print();
apelează funcţia print() a clasei Point. A doua instrucţiune
ptr->print();
apelează funcţia print() a clasei Pixel.
Vom spune că apelul de funcţie
ptr->print();
este polimorfic, deoarece se modifică după natura obiectului indicat de pointer. Orice
clasă ce defineşte sau moşteneşte funcţii virtuale se numeşte polimorfă.
Exemplu. Vrem să definim clase care să conţină informaţii despre angajaţii unei
intreprinderi, numele şi departamentul, în cazul tuturor angajaţilor, iar în cazul
managerilor şi poziţia. Aceste informaţii vor fi şiruri de caractere. Va trebui să avem
două tipuri de obiecte, unul ce va conţine numele şi departamentul, şi altul ce va conţine
numele, departamentul şi poziţia. Pentru aceasta vom defini o clasă de bază numită
Person ce conţine numele unei persoane (un şir de caractere) şi o funcţie ce afişază
numele.

class Person
{
protected:
char * name;
public:
Person(char * s)
{ name = new char[strlen(s) + 1]; strcpy(name, s);}
virtual void print()
{cout << “name : “ << name << “\n”;}
};

Vom defini o clasă ce moşteneşte clasa Person, clasa Hired cu date despre angajaţi şi o
clasă numită Manager cu date despre manageri ce moşteneşte din clasa Hired conform
diagramei de mai jos.

class Hired : public Person


{
protected:
char * dept;
public:
Hired(char * s, char * d) : Person(s)
{dept = new [strlen(d) + 1]; strcpy(dept, d);}
virtual void print()
{Person::print(); cout << “ department : “ << dept << “\n”; }
};
class Manager : public Hired
{
protected:
char * position;
public:
Manager(char * s, char * d, char * p) : Hired(s, d)
{position = new [strlen(p) + 1]; strcpy(position, p);}
virtual void print()
{Hired::print();cout << “ position : “ << position << “\n”; }
};

Ca exemplu de utilizare a claselor create vom defini obiecte de cele două tipuri.

int main()
{
Hired x1(“Alex”, “proiectare”);
Manager m1(“Bob”, “proiectare”, “sef”);
Person * ps;
// scrie datele pentru x1
ps = &x1;
ps->print();
// scrie datele despre m1
ps = & m1;
ps->print();
return 0;
}
10.4 Date şi funcţii statice

10.4.1 Date statice

Obiectele de tipul unei clase au propriile variabile ce conţin valori ce dau starea fiecărui
obiect. Uneori este necesar să existe o variabilă a clasei într-un singur exemplar pentru
toate obiectele. Acest lucru se poate realiza simplu definind acea variabilă de tip static.
Definirea se face scriind cuvântul cheie static la începutul declaraţiei variabilei. Datele
de tip static iniţializate în afara clasei. Datele statice sunt la fel ca şi variabilele globale,
dar în spaţiul de nume al clasei.

class A
{
static int x;
};
int A::x = 0;

Datele statice există chiar dacă nu există nici un obiect de tipul clasei. Pentru exemplul de
mai sus putem utiliza variabila statică x ca A::x sau A.x sau, dacă există obiecte de tipul
clasei, ca nume_obiect.x
O aplicaţie a datelor statice este aceea de a număra obiectele existente de tipul clasei.
Exemplu. Vom defini o clasă cu o variabilă statică ce va număra obiectele existente de
tipul clasei. Constructorul adună o unitate, iar destructorul scade o unitate din variabila
statică.

# include <iostream>
using namespace std;

class X
{
public:
static int nb;
X(){nb ++;}
~X(){nb--;}
};

int X::nb = 0;

In funcţia main() vom crea obiecte şi vom afişa numărul lor.

int main()
{
X a, b;
cout << “exista “ << X.nb << “ obiecte” << endl;
{
X c, d;
cout << “exista “ << X.nb << “ obiecte” << endl;
}
cout << “exista “ << X::nb << “ obiecte” << endl;
return 0;
}

Rezultate afişate vor fi


exista 2 obiecte
exista 4 obiecte
exista 2 obiecte
După instrucţiunea
X a, b;
există două obiecte, a şi b. După instrucţiunea
X c, d;
există patru obiecte. După ieşirea din blocul interior există două obiecte, obiectele c şi d
fiind distruse. Menţionăm că putem adresa variabila nb ca X.nb sau X::nb.

10.4.2 Funcţii statice

Funcţiile statice sunt la fel ca şi funcţiile globale. Ele pot prelucra doar datele statice ale
clasei. Ele pot fi apelate utilizând numele clasei sau al unui obiect de tipul clasei.
Definirea unei funcţii statice se face scriind cuvantul cheie static înaintea definiţiei
funcţiei. Un exemplu de utilizare a funcţiilor statice este tehnica de programare
ClassFactory. Ea constă în următoarele. Fie o clasă de bază şi două clase derivate din ea,
Deriv1 şi Deriv2. Vrem să definim o funcţie care să aibe ca rezultat un obiect de tipul
Deriv1 sau Deriv2 în funcţie de valoarea unui parametru.

Exemplu. Vom defini o clasă cu o metodă ce calculează suma a două numere şi o clasă cu
o metodă ce calculează produsul a două numere. In ambele cazuri metoda se va numi
oper(). Ele vor moşteni o clasă ce memorează cele două numere. Clasa de bază are
următoarea definiţie

# include <iostream>
using namespace std;

class Base
{
protected:
float a, b;
public:
Base() {a = 0; b = 0;}
Base(float, float);
virtual float oper(){return 0;}
};

Base::Base(float x, float y)
{
a = x;
b = y;
}

Clasele derivate definesc funcţia oper() ce efectuează adunarea sau produsul a două
numere. Ele au următoarele definiţii

class Add : public Base


{
public:
Add(float x, float y) : Base(x, y) {}
float oper(){return a + b;}
};

class Mul : public Base


{
public:
Mul(float x, float y) : Base(x, y) {}
float oper() {return a * b;}
};

Vom defini o clasă cu o metodă statică ce crează un obiect de tip Add sau Mul în funcţie
de valoarea unui parametru şi are ca rezultat un pointer de tipul clasei de bază (Base).

class clf
{
public:
static Base * operfactory(float, float, char);
};

Base * clf::operfactory(float x, float y, char c)


{
if(c == '+')
return new Add(x, y);
else
return new Mul(x, y);
};

Funcţia main() crează un obiect de tipul Add şi memorează adresa lui într-un pointer de
tipul clasei de bază. Apoi utilizează obiectul la calculul sumei a două numere.

int main()
{
float z;
Base * base;
clf cf;
// creaza un obiect care sa calculeze suma a doua numere
base = cf.operfactory(1.2, -2.35, '+');
// calculeaza suma numerelor
z = base->oper();
cout << z << endl;
return 0;
}

Cap. 11 Fişiere tip C++

Limbajul C++ defineşte clasele istream şi ostream pentru operaţii intrare şi ieşire cu
fişierele standard, tastatura şi ecranul. Obiectul cin este o instanţă a clasei istream, iar
cout o instanţă a clasei ostream. Pentru lucrul cu fişiere de date limbajul C++ defineşte
următoarele clase
• ofstream pentru operaţii de scriere de fişiere
• ifstream pentru operaţii de citire din fişiere
• fstream pentru operaţii de citire şi scriere a fişierelor
Aceste clase moştenesc din clasele istream şi ostream. Definiţiile acestor clase se găsesc
în biblioteca <fstream>. O serie de constante utilizate de aceste clase sunt definite în
clasa ios. Pentru prelucrarea unui fişier se crează un obiect instanţă a uneia din clasele de
mai sus care este denumit stream. Diagrama moştenirii acestor clase este cea de mai jos.
Funcţiile membre importante ale claselor sunt
• funcţia membră open() cu prototipul
open(char * filename, int mode);
asociază obiectul cu fişierul. Parametrul filename este un şir de caractere cu numele
fişierului. Al doilea parametru este opţional şi dă modul de deschidere. El poate avea
valorile
ios::in - deschidere în citire
ios::out – deschidere în scriere
ios::binary – fişier binary
Aceşti parametri se pot combina folosind operatorul | . De exemplu, pentru un fişier
binar deschis în scriere vom avea
ios::binary | ios:out
iar pentru deschiderea unui fişier binar în citire
ios::binary | ios::in
Acest al doilea parametru este opţional pentru obiecte de tipul ifstream şi ofstream care
sunt automat deschise în citire şi respectiv scriere.
• funcţia
close();
închide un fişier.
• funcţia
eof()
are valoarea adevărat dacă s-a detectat sfârşitul fişierului
• funcţia
is_open()
are valoarea adevărat dacă fişierul este deschis

11.1 Fişiere text

Există două tipuri de operaţii intrare/ ieşire pentru fişiere tip text
• funcţii pentru intrări / ieşiri cu format
• funcţii ce scriu / citesc caractere

11.1.1 Funcţii intrare / ieşire cu format

Fişierele tip text se prelucrează în acelaşi mod ca fişierele standard de intrare şi ieşire, cin
şi cout. Operatorii de citire şi scriere sunt >> şi <<.
Exemplu. Vom calcula valoarea expresiei
cos 2 ( x) + x
e= + sin( x)
1+ x
pentru x cuprins în intervalul [0, 2] cu pasul 0.2. Vom scrie valorile calculate într-un
fişier cu numele rez.txt.

# include <fstream>
# include <iostream>
# include <cmath>
using namespace std;
int main()
{
char * filename = “rez.txt”;
ofstream fil1;
fil1.open(filename);
if(!fil1.is_open)
{
cout << “ Nu se poate crea fisierul “ << filename << endl;
return 0;
}
// creaza fisierul
int i;
double x, e;
for(i = 0; i < 11; i++)
{
// calculeaza expresia si scrie in fisier
x = i * 0.2;
e = (cos(x) * cos(x) + x) / (1.0 + fabs(x)) + sin(x);
fil1 << x << ‘\t’ << e << endl;
}
fil1.close();
// citeste fisierul creat si afisaza rezulatele
ifstream fil2;
fil2.open(filename);
if(!fil2.is_open())
{
cout << “ nu se poate deschide fisierul “ << filename << endl;
return 0;
}
// scrie antetul
cout << "x" << '\t' << "e" << endl;
fil2 >> x >> e;
while(!fil2.eof())
{
cout << x << ‘\t ‘ << e << endl;
fil2 >> x >> e;
}
fil2.close();
return 0;
}

11.1.2 Funcţii intrare / ieşire tip caracter

Clasele istream şi ifstream au următoarele funcţii membre pentru citirea caracterelor.


Funcţia
get(char&);
citeşte un caracter, iar funcţia
getline(char * bloc, int size);
citeşte cel mult size caractere în vectorul bloc.
Clasele ostream şi ofstream au funcţia membră
put(char);
ce scrie un caracter.
Exemplu. Vom copia un fişier în altul şi vom calcula lungimea fişierului.

# include <iostream>
# include <fstream>
using namespace std;
int main()
{
char filename [24];
cout << “Introduceti numele fisierului de copiat” << endl;
cin >> filename;
ifstream fila(filename);
if(!fila.is_open())
{
cout << endl << “Fisierul “ << filename << “ nu exista “ << endl;
return 0;
}
cout << endl << “Introduceti numele noului fisier” << endl;
cin >> filename;
ofstream filb(filename);
if(!filb.is_open())
{
cout << “Fisierul “ << filename << “ nu se poate crea” << endl;
return 0;
}
char car;
int nl = 0;
fila.get(car);
while(!fila.eof())
{
filb.put(car);
nl++;
fila.get(car);
}
fila.close();
filb.close();
cout << "fisierul " << filename << “ are ” << nl << " caractere" << endl;
return 0;
}

Menţionăm că expresia
fila.get(car)
are valoarea fals la întâlnirea sfarşitului de fişier. In consecinţă, puteam copia fişierul
astfel
while(fila.get(car))
{
filb.put(car);
nl++;
}

11.2 Fişiere binare

Scrierea şi citirea datelor din fişierele binare se face cu funcţiile


write(char * block, int size);
read(char * block, int size);
Primul parametru este adresa unui vector de caractere de unde sunt scrise datele sau unde
sunt citite datele. Al doilea parametru dă numărul de caractere de citit sau de scris.
Fişierele au indicatori interni care dau adresa următorului octet de citit sau de scris.
Voloarea acestor indicatori este dată de funcţiile
tellg();
pentru indicatorul următorului octet de citit.
tellp();
pentru indicatorul următorului octet de scris.
Indicatorii de poziţie sunt modificaţi de funcţiile
seekg(int offset, int direction);
pentru indicatorul de citire şi respectiv
seekp(int offset, int direction);
pentru indicatorul de scriere. Parametrul offset dă valoarea cu care se modifică
indicatorul. Parametrul direction are valorile
ios::beg – relativă la începutul fişierului
ios::end – relativă la sfarşitul fişierului
ios::cur – relativă la poziţia curentă
Exemplu. Vom crea un fişier binar cu 10 blocuri de câte 10 caractere fiecare, după care
citim fişierul şi îl afişăm pe ecran. Primul bloc va conţine cractere ‘0’, al doilea bloc
caractere ‘1’, etc. Citirea fişierului se face după schema cunoscută, testând sfarşitul de
fişier după fiecare citire.
# include <iostream>
# include <fstream>
using namespace std;
int main()
{
char filename[] = “fis.txt”;
char x[11];
ofstream fila;
// creaza fisierul
fila.open(filename, ios::out / ios::binary);
if(!fila.is_open())
{
cout << “ Nu se poate crea fisierul “ << filename << endl;
return 0;
}
int i, j;
// creaza fisierul
for(i = 0; i < 10; i++)
{
// creaza un bloc
for(j = 0; j < 10; j++)
x[j] = ‘0’ + i;
x[10] = 0;
// scrie blocul in fisier
fila.write(x, 11);
}
fila.close();
ifstream filb;
// citeste si afisaza fisierul
filb.open(filename, ios::binary / ios::in);
if(!filb.is_open())
{
cout << “Nu se poate citi fisierul “ << filename << endl;
return 0;
}
filb.read(x, 11);
while(!filb.eof())
{
cout << x << endl;
filb.read(x, 11);
}
filb.close();
return 0;
}
Exemplu. Vom crea un fişier binar format din 26 de blocuri conţinând şiruri de 10
caractere fiecare. Vom citi apoi fiecare al patrulea bloc modificând indicatorul de citire.
Primul bloc va conţine caractere ‘a’, al doilea bloc va conţine caractere ‘b’, etc.

# include <iostream>
# include <fstream>
using namespace std;
int main()
{
char filename[] = “fil.txt”;
char x[11];
ofstream filx;
// creaza fisierul
filx.open(filename, ios::out | ios::binary);
if(!filx.is_open())
{
cout << “ Nu se poate crea fisierul “ << filename << endl;
return 0;
}
int i, j;
for(i = 0; i < 26; i++)
{
// creaza un bloc
for(j = 0; j < 10; j++)
x[j] = ‘a’ + i;
x[10] = 0;
// scrie blocul in fisier
filx.write(x, 11);
}
filx.close();
ifstream fily;
// citeste si afisaza fisierul
fily.open(filename, ios::binary | ios::in);
if(!fily.is_open())
{
cout << “Nu se poate citi fisierul “ << filename << endl;
return 0;
}
// citeste si afisaza fisierul
for(i = 0; i < 26; i = i + 4)
{
// modifica indicatorul fisierului
fily.seekg(i * 11, ios::beg);
fily.read(x, 11);
cout << x << endl;
}
fily.close();
return 0;
}

Cap. 12 Siruri tip C++

Limbajul C++ defineşte clasa string pentru lucrul cu şiruri. Spre deosebire de şirurile tip
C care sunt terminate printr-un octet cu valoarea zero, obiectele acestei clase au un câmp
ce conţine lungimea şirului. Clasa string este definită în fişierul header <string>. Clasa
are următorii constructori:
• constructorul implicit care crează un şir vid
string();
• constructor copiere cu argument şir tip C sau şir tip string
string(const string&);
string(const char *);
Operatorul = are ca argument drept un şir tip C sau şir tip string.
Exemple de definiri de obiecte tip string
string x = "abcd";
string y(x);
string z = x;
string x1("abc");
Clasa implementează operatorii + şi +=(concatenarea şirurilor).
Exemple.
string a = x + “xyz”;
a += “abc”;
Clasa defineşte operatori de comparare, <, <=, >, >= , = = şi != ce au ca rezultat valorile
true sau false.
Exemple.
if(x < x1)
cout << x1;
else
cout << x;
Funcţii membre importante ale clasei sunt
int length(); – dă lungimea şirului
int size(); – dă lungimea şirului
const char * c_str(); - converteşte şirul într-un şir tip C
bool empty(); - are valoarea true dacă şirul este vid
Exemplu. Să copiem un şir C++ într-un şir tip C.
char c[100];
string str(“abcd”);
strcpy(c, str.c_str());
Clasa are operatorul de selecţie [] ce poate selecta un caracter din şir s-au atribui o
valoare unui caracter din şir.
Exemplu. Să scriem un şir, caracter cu caracter.
string s = “abcd”;
for(j = 0; j < s.length(); j++)
cout << s[j];
Operatorii << şi >> scriu şi citesc şiruri tip string.
Vom prezenta acum funcţii de căutare de subşiruri şi de modificare a şirurilor.
Fie un obiect tip string. Funcţia find() cu prototipul
int find(char * substring);
dă indicele primei apariţii a şirului substring în şirul respectiv. Dacă şirul substring nu
există în şir funcţia are ca rezultat lungimea şirului.
Exemple. Fie şirul
string str = “abcdefg”;
Instrucţiunea
cout << str.find(“cd”) << endl;
afişază valoarea 2. (indicele caracterului ‘c’ este 2). Instrucţiunea
cout << str.find(“xyz”) << endl;
afişază valoarea 7 (lungimea şirului).
Funcţia erase şterge un subşir din şir. Ea are prototipul
erase(int index, int size)
Funcţia şterge size caractere începând cu caracterul cu indicele index.
Exemplu. Fie şirul
string str = “ABCD*FGHIJK”;
Instrucţiunea
str.erase(4, 2);
şterge două caractere începând cu caracterul ‘*’ ce are indicele 4. Noul şir este
“ABCDGHIJK”
Funcţia replace înlocuieşte un subşir cu altul. Ea are prototipurile
replace(int index, int size, char * sir);
replace(int index, int size, string sir);
şi înlocuieşte subşirul de lungime size ce începe cu caracterul index cu subşirul sir.
Exemple. Fie şirul
string str = “ABCDGHIJK”;
Instrucţiunea
str.replace(5, 2, “xyz”);
înlocuieşte subşirul “HI” cu subşirul “xyz”. Noul şir este
“ABCDGxyzJK”
Fie şirul
string s2 = “abc”;
Instrucţiunea
str.replace(5, 3, s2);
înlocuieşte subşirul “xyz” cu şirul “abc”. Noul şir este
“ABCDGabcJK”
Exemplu. Vom rescrie un exemplu anterior în care am utilizat şiruri tip C şi vom utiliza
acum şiruri tip string. Vom defini o clasă de bază numită Person ce conţine numele unei
persoane (un şir de caractere) şi o funcţie ce afişază acest şir (numele persoanei).

class Person
{
protected:
string name;
public:
Person(string s)
{ name = s;}
virtual void print()
{cout << “name : “ << name << “\n”;}
};

Vom defini o clasă ce moşteneşte clasa Person, clasa Hired cu date despre angajaţi şi o
clasă numită Manager cu date despre manageri ce moşteneşte din clasa Hired.

class Hired : public Person


{
protected:
string dept;
public:
Hired(string s, string d) : Person(s)
{dept = d;}
virtual void print()
{Person::print(); cout << “ department : “ << dept << “\n”; }
};

class Manager : public Hired


{
protected:
string position;
public:
Manager(string s, string d, string p) : Hired(s, d)
{position = p;}
virtual void print()
{Hired::print(); cout << “ position : “ << position << “\n”; }
};

Vom exemplifica acum claselor create.

int main()
{
Hired x1(“Alex”, “proiectare”);
Manager m1(“Bob”, “proiectare”, “sef”);
Person * ps;
// scrie datele pentru x1
ps = &x1;
ps->print();
// scrie datele despre m1
ps = & m1;
ps->print();
return 0;
}

Reamintim că funcţia print() este virtuală, astfel încât apelul


ps->print();
apelează funcţia print() din clasa corespunzătoare tipului obiectului, producând rezultatul
corect. Menţionăm de asemenea că instrucţiunea
Hired x1(“Alex”, “proiectare”);
este corectă deoarece constructorul copiere al clasei string are ca argument un şir de tip C
sau de tip C++.
Cap 13. Tratarea excepţiilor

13.1. Excepţii

In timpul execuţiei programului pot apare diverse erori. Un tip de erori sunt cele asociate
operaţiilor intrare/ ieşire : încercăm să citim un fişier inexistent, vrem să creăm un fişier
pe disc dar discul este plin, etc. Al tip de erori sunt cele legate de operaţiile aritmetice :
împărţirea prin zero, indici eronaţi, depăşirea gamei de reprezentare a numerelor, etc.
Limbajul C++ are un mecanism de tratare a excepţiilor ce permite ca atunci când apare o
eroare (o excepţie) programul să apeleze automat o funcţie de tratare a erorii. Această
funcţie are ca parametru un obiect cu informaţii despre eroare. Tratarea excepţiilor se
face cu instrucţiunile try, catch şi throw. Instrucţiunea pe care o urmărim pentru apariţia
unei excepţii trebuie inclusă într-un bloc try. Dacă apare o excepţie (o eroare), ea este
lansată cu instrucţiunea throw care generează un obiect cu informaţii despre eroare şi
este prinsă cu instrucţiunea catch care are ca parametru obiectul cu informaţii despre
eroare generat de instrucţiunea throw. Forma generală a instrucţiunilor try şi catch este
următoarea
try
{
// bloc cu instrucţiuni
}
catch(tip1 arg)
{
// prelucreaza exceptia
}
catch(tip2 arg)
{
// prelucreaza exceptia
}
……………
catch(tipn arg)
{
// prelucreaza exceptia
}

Tipurile argumentelor din instrucţiunile catch trebuie să fie diferite. Atunci când
apare o excepţie trebuie să executăm instrucţiunea throw cu forma
throw obiect;
Excepţia este un obiect cu informaţii despre eroare generat de instrucţiunea throw. Ea
poate fi un obiect simplu de tip int, char, double, etc., sau un obiect instanţă a unei clase
definită de programator. Intr-un bloc pot apare mai multe tipuri de excepţii. Fiecare tip de
excepţie este tratată de o anumită instrucţiune catch. Ea se determină după argumentului,
care este tipul obiectului generat de instrucţiunea throw.
Exemplu. Tratarea unei excepţii împărţire prin zero.

# include <iostream>
using namespace std;

int main()
{
int x = 2, y = 0, z;
try
{
if(y == 0)
throw y;
z = x / y;
cout << "x = " << x << " y = " << y << " x / y = " << z << endl;
}
catch(int a)
{
cout << "divide by zero" << endl;
cout << "line : " << __LINE__ << endl;
}
return 0;
}

Limbajul defineşte două constante utile în cazul excepţiilor. Constanta __LINE__


conţine numărul liniei curente compilate, iar constanta __FILE__ conţine numele
fişierului curent compilat.
Blocul catch încearcă repararea erorii apărute. Dacă nu este posibil acest lucru, se pot
utiliza funcţiile exit() sau abort() pentru a termina programul.
Pentru a prinde o excepţie trebuie ca tipul obiectului lansat de instrucţiunea throw să
coincidă cu tipul obiectului specificat într-o instrucţiune catch, astfel excepţia nu este
prinsă.

Preluarea tuturor excepţiilor

Dacă dorim ca un singur bloc catch să preia toate excepţiile unui bloc try vom scrie
blocul catch astfel
catch(…)
{
// trateaza exceptiile
}

13.2 Excepţii lansate de funcţii

Intr-un bloc try pot exista apeluri la multe funcţii şi orice funcţie poate lansa excepţii.
Excepţiile lansate de o funcţie sunt precizate la definirea funcţiei în felul următor

tip nume_functie(lista de parametric) throw (lista de tipuri)


{
// corpul functiei
}
Exemplu. Vom defini o funcţie ce calculează câtul a două numere întregi şi lansează o
excepţie de tip string dacă numitorul este zero. Tipul excepţiei lansate de funcţie este
precizat în linia de definiţie a funcţiei şi este un şir tip string ce conţine un mesaj.

# include <iostream>
# include <string>
using namespace std;

int fun(int x, int y) throw(string)


{
if(y = = 0)
throw string(“impartire prin zero”);
else
return x / y;
}

int main()
{
int a = 10, b = 0, c;
try
{
// functie ce poate lansa o exceptie
c = fun(a, b);
cout << a << “/” << b << “=” << c << endl;
}
catch(string mesaj)
{
cout << mesaj << endl;
}
return 0;
}

Menţionăm că funcţia ce lansează o excepţie este apelată într-un bloc try, iar excepţia
este prinsă în blocul catch corespunzător.

13.3 Excepţii standard

Limbajul C++ defineşte o clasă de bază special proiectată pentru a declara obiecte care să
fie lansate de instrucţiunea throw. Clasa se numeşte exception iar prototipul ei se află în
biblioteca <exeception>. Clasa defineşte un constructor implicit, un constructor cu
parametru şir de caractere şi unul copiere, operatorul = şi funcţia virtuală what() cu
prototipul
virtual char * what();

care are ca rezultat un mesaj despre excepţie.


Exempul. Vom rescrie exemplul anterior utilizând clasa exception.

# include <iostream>
# include <exception>
using namespace std;

int fun(int x, int y) throw(exception)


{
if(y = = 0)
throw exception(“impartire prin zero”);
else
return x / y;
}

int main()
{
int a = 10, b = 0, c;
try
{
// functie ce poate lansa o exceptie
c = fun(a, b);
cout << a << “/” << b << “=” << c << endl;
}
catch(exception exm)
{
cout << exm.what() << endl;
}
return 0;
}

Cap. 14 Aplicaţii

14.1 Funcţii de timp

Limbajele C şi C++ au funcţii predefinite pentru a obţine timpul. Prototipurile acestor


funcţii se găsesc în bibliotecile <time.h> sau <ctime>. Timpul este măsurat în
milisecunde de la data de 1 Ianuarie 1970 GMT. Tipul variabilelor ce memorează timpul
este long int şi este redefinit ca time_t.
typedef long time_t; /* time value */
Funcţia standard ce completează această variabilă este
time_t time(time_t * timeptr);
Funcţia time are ca rezultat timpul în parametrul timeptr şi ca valoare.
Exemplu.
time_t t1, t2;
t1 = time(&t2);
Variabilele t1 şi t2 primesc ca valoare timpul curent.
Funcţia ctime converteşte timpul unei variabile time_t într-un şir de caractere.
char* ctime(time_t * timeptr);
Exemplu. Instrucţiunea
cout << ctime(&t1) << endl;
afişază timpul din variabila t1.
Structura predefinită tm conţine următoarele informaţii
struct tm {
int tm_sec; /* seconds after the minute - [0,59] */
int tm_min; /* minutes after the hour - [0,59] */
int tm_hour; /* hours since midnight - [0,23] */
int tm_mday; /* day of the month - [1,31] */
int tm_mon; /* months since January - [0,11] */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday - [0,6] */
int tm_yday; /* days since January 1 - [0,365] */
int tm_isdst; /* daylight savings time flag */
};
Funcţia
struct tm * localtime(time_t * timeptr);
converteşte timpul unei variabile time_t în structura tm.
Exemplu.
time_t t1;
struct tm t2;
struct tm * ptrt;
t1 = time(NULL);
ptrt = localtime(&t1);
t2 = * ptrt;
cout << t2.tm_hour << ":" << t2.tm_min << ":" << t2.tm_sec << endl;
Funcţia
time_t mktime(struct tm * t);
converteşte timpul dintr-o variabilă tm într-o structură time_t.
Funcţia
struct tm * gmtime(const time_t * timeptr);
are ca rezultat un pointer la o structură tm ce conţine timpul GMT.

14.2 Fire de execuţie

Programele de până acum au fost procese cu un singur fir de execuţie (thread). Este
posibil să cream programe cu mai multe fire de execuţie simultan. Fiecare fir execută o
anumită funcţie cu prototipul
void f(void *);
Prototipurile funcţiilor pentru lucrul cu fire de execuţie se află în biblioteca <process.h>.
Limbajul C defineşte următoarele funcţii pentru lucrul cu fire de execuţie
• funcţia
unsigned long _beginthread(void (*f) (void *), unsigned int stack, void * arglist);
lansează în execuţie un fir. Primul parametru este un pointer la funcţia ce va fi executată
de fir. Al doilea parametru este dimensiunea unei stive ce poate fi utilizată de fir. Ultimul
parametru este o listă de argumente pasată firului.
• funcţia
_sleep(int ms);
suspendă execuţia firului de aşteptare pentru un număr de milisecunde dat de parametrul
ms.
Exemplu. Vom face un program care să lanseze în execuţie două fire. Funcţiile executate
de fire vor executa un ciclu în care se scrie un mesaj şi apoi se pune firul în aşteptare un
număr de milisecunde. In exemplul nostru dimensiunea stivei firelor de execuţie va fi
zero iar lista de parametri pasaţi firelor va fi NULL.

# include <stdio.h>
# include <stdlib.h>
# include <process.h>

// functie executata de un fir


void fthr1(void * arg)
{
int i;
for(i = 0; i < 5; i++)
{
printf("thread1\n");
_sleep(1000);
}
return;
}

// functie executata de un fir


void fthr2(void * arg)
{
int i;
for(i = 0; i < 5; i++)
{
printf("thread2\n");
_sleep(500);
}
return;
}

int main()
{
_beginthread(&fthr1, 0, NULL);
_beginthread(&fthr2, 0, NULL);
return 0;
}

Cap. 15 Biblioteca de şabloane standard

In activitatea de programare apar frecvent anumite structuri de date: liste simplu sau
dublu înlănţuite, cozi, stive, mulţimi, vectori, etc. Limbajul C++ are o bibliotecă cu clase
special proiectate pentru crearea şi manipularea acestor structuri. Aceste clase sunt clase
generice care au ca parametru tipul obiectelor manipulate. Biblioteca de şabloane
standard are trei componente
• Containere. Un container este o structură de date ce conţine alte obiecte. Aceste
obiecte se numesc elementele containerului. Exemple de containere sunt vectorii
şi listele.
• Iteratori. Iteratorii permit parcurgerea elementelor unui container
• Algoritme. Algoritmele sunt funcţii ce se aplică asupra elementelor unui
container, de exemplu sortarea elementelor unei liste sau ale unui vector.

15.1 Clase generice

Clasele din biblioteca de şabloane standard sunt clase generice în care tipurile datelor şi
funcţiilor sunt parametri. O clasă generică se defineşte cu instrucţiunea template cu forma

template <class T1, class T2, …, class Tn>


class nume_clasa
{
// definitia clasei
};

In această definiţie T1, T2, …, Tn sunt tipuri ce se pot utiliza la declararea de obiecte de
tipul clasei generice. Un obiect de tipul unei clase generice se declară cu următoarea
diagramă sintactică

nume_clasa <tip1, tip2, …, tipn> nume_obiect;

Exemplu. Să definim o clasă generică X ce poate calcula pătratul unui număr întreg sau
real.

# include <iostream>
using namespace std;

template <class T>


class X
{
private:
T a;
public:
X(T b) {a = b;}
T square() {return a * a;}
T geta() {return a;}
};

Să definim obiecte de tipul X ce conţin elemente de tip int sau double utilizând clasa
generică X.

int main()
{
// crează un obiect cu şablonul <int>
X<int> n(2);
cout << "patratul valorii" << n.geta() << " este " << n.square() << endl;
// crează un obiect cu şablonul <double>
X<double> d(3.14);
cout << "patratul valorii" << d.geta() << " este " << d.square() << endl;
return 0;
}

Biblioteca de şabloane standard are trei componente


• containere. Ele sunt obiecte ce memorează alte obiecte. Exemple de containere
sunt vectori, liste, cozi, stive.
• iteratori sunt obiecte ce permit parcurgerea elementelor unui container
• algoritmi ce implementează operaţii asupra elementelor containerelor (de exemplu
sortarea elementelor unui vector)
In cele ce urmează vom prezenta vectorii şi listele.

15.2 Vectori

Clasa generică vector implementează vectori cu elemente de un anumit tip. Un obiect de


tip vector are un număr iniţial de componente şi dimensiunea lui creşte dacă este nevoie.
Clasa vector are ca parametru tipul componentelor vectorului.
Clasa defineşte următorii constructori
• constructorul implicit (fără parametri)
vector();
crează un vector vid
• constructorul copiere
vector(vector p);
crează un vector în care copiază elementele unui vector p care este parametru
Fie T tipul componentelor vectorului (parametrul clasei generice). Clasa defineşte
următoarele funcţii
• void push_back(T&); adaugă un element la sfârşitul vectorului
• void pop_back(); ştrege ultimul element al vectorului
• int size(); dă numărul de componente ale vectorului
• bool empty(); are valoarea true dacă vectorul este vid
• operatorul [] şi funcţia T& at(int) selectează un element al vectorului
• T& front(); are ca rezultat primul component al vectorului
• T& back();are ca rezultat ultimul component al vectorului
Menţionăm că un vector poate avea două sau mai multe elemente egale.
Exemplu. Să creăm şi să listăm un vector cu componente întregi.

# include <iostream>
# include <vector>
using namespace std;
int main()
{
// definim un vector cu componente intregi
vector <int> v;
// adauga 4 elemente
v.push_back(12);
v.push_back(-5);
v.push_back(7);
v.push_back(13);
// afisaza componentele vectorului
int k;
for(k = 0; k < v.size(); k++)
cout << v[k] << endl;
return 0;
}

Menţionăm că expresia v[k] putea fi înlocuită cu expresia v.at(k).


La definirea obiectului v, vectorul are alocate un număr implicit de componente

După execuţia instrucţiunilor push_back() vectorul este următorul

şi are dimensiunea 4. Menţionăm că vectorul are un element fictiv înaintea primului


element şi altul după ultimul element.

Iteratori

Pentru parcurgerea componentelor unui container (vector, listă, etc.) se pot utiliza
iteratori. Un iterator este asemenea unui pointer la un element al containerului.
Clasa iterator defineşte următorii operatori
• operatorii ++ şi - - modifică iteratorul la următoarea / precedenta poziţie din
container
• operatorii relaţionali = =, !=, <, <=, >, >= permit compararea a doi iteratori
• operatorul de adresare indirectă * permite adresarea / modificarea valorii de la
poziţia curentă a containerului
Un iterator poate fi iniţializat la orice poziţie iniţială din container. Vom menţiona că
există două tipuri de iteratori ce permit parcurgerea vectorilor în sens direct şi respectiv în
sens invers.

Parcugerea unui vector în sens direct

Clasa iterator este o clasă internă a containerului (în cazul nostru a clasei vector). Un
obiect de tipul acestei clase se defineşte astfel
vector<tip>::iterator nume_obiect;
Clasa vector defineşte funcţiile
begin();
end();
ce au ca rezultat un iterator ce indică primul element al vectorului şi respectiv elementul
fictiv primul ce urmează după ultimul element al vectorului. Pentru vectorul anterior
avem situaţia din figura de mai jos

Parcurgerea unui vector în sens invers

Clasa reverse_iterator este o clasă internă a containerului (în cazul nostru a clasei vector).
Un obiect de tipul acestei clase se defineşte astfel
vector<tip>::reverse_iterator nume_obiect;
Clasa vector defineşte funcţiile
rbegin();
rend();
ce au ca rezultat un iterator ce indică ultimul element al vectorului şi respectiv elementul
fictiv ce precede primul element al vectorului. Pentru vectorul anterior avem situaţia de
mai jos

Exemplu. Să parcurgem vectorul anterior utilizând iteratori.

# include <iostream>
# include <vector>
using namespace std;
int main()
{
// definim un vector cu componente intregi
vector <int> v;
// se adauga 4 elemente
v.push_back(12);
v.push_back(-5);
v.push_back(7);
v.push_back(13);
// afisaza componentele vectorului utilizand iteratori
vector <int>::iterator it;
for(it = v.begin(); it != v.end(); it++)
cout << *it << endl;
// afisaza conponentele vectorului in ordine inverse utilizand iteratori
vector<int>::reverse_iterator iter1;
for(iter1 = v.rbegin(); iter1 != v.rend(); iter1++)
cout << *iter1 << endl;
return 0;
}

Putem reprezenta parcurgerea vectorului ca mai jos

Sortarea componentelor unui vector

Biblioteca de şabloane standard defineşte funcţii de sortare a componentelor unui vector.


Aceste funcţii sunt definite în biblioteca <algorithm>. Prima dintre acestea, funcţia sort()
are următorul prototip
sort(iter1, iter2);
unde iter1 şi iter2 sunt iteratori ce dau primul element din vector şi ultimul element din
vector ce vor fi sortaţi. Prototipul funcţiei sort() este definit în biblioteca <algorithm>.
Exemplu. Vom crea un vector cu componente tip string şi îl vom sorta.

# include <iostream>
# include <vector>
# include <algorithm>
# include <string>
using namespace std;

int main()
{
int i;
vector<string> vst;
vst.push_back("abc");
vst.push_back("rst");
vst.push_back("ccd");
vst.push_back("mnp");
// afisaza vectorul
cout << "vectorul initial" << endl;
for(i = 0; i < vst.size(); i++)
cout << vst.at(i) << endl;
// sorteaza vectorul
sort(vst.begin(), vst.end());
// afisaza vectorul
cout << "vectorul sortat" << endl;
for(i = 0; i < vst.size(); i++)
cout << vst.at(i) << endl;
return 0;
}

A doua funcţie sort() are următorul prototip


sort(iter1, iter2, comp);
unde iter1 şi iter2 sunt iteratori ce dau primul element din vector şi ultimul element din
vector ce vor fi sortaţi iar comp este o funcţie cu prototipul
bool comp(T x, T y);
Parametri x şi y sunt două elemente de tipul T al componentelor vectorului. Funcţia sort
permută elementele x şi y dacă funcţia comp are rezultatul false. Prototipul funcţiei sort()
este definit în biblioteca <algorithm>.
Exemplu. Vom crea un vector cu componente întregi şi îl vom sorta în ordine crescătoare
şi apoi descrescătoare. Vom defini două funcţii de comparare pentru cele două cazuri

# include <iostream>
# include <vector>
# include <algorithm>
using namespace std;

/*
funcţie de comparare pentru sortarea elementelor
vectorului in ordine crescatoare
*/
bool comp(int k, int n)
{
return k < n;
}

/*
funcţie de comparare pentru sortarea elementelor
vectorului in ordine descrescatoare
*/

bool comp2(int k, int n)


{
return k > n;
}

int main()
{
vector<int> v;
// creaza vectorul
v.push_back(12);
v.push_back(-3);
v.push_back(4);
v.push_back(12);
// afisaza vectorul
cout << “Vectorul initial” << endl;
int i;
for(i = 0; i < v.size(); i++)
// cout << v[i] << endl;
cout << v.at(i) << endl;
// afisaza vectorul cu iterator
vector<int>::iterator iter;
cout << "Vectorul initial\n";
for(iter = v.begin(); iter != v.end(); iter++)
cout << *iter << endl;
// sorteaza vectorul in ordine crescatoare
sort(v.begin(), v.end(), comp);
// afisaza vectorul sortat
cout << "Vectorul sortat in ordine crescatoare\n";
for(i = 0; i < v.size(); i++)
cout << v[i] << endl;
// sorteaza vectorul in ordine descrescatoare
sort(v.begin(), v.end(), comp2);
// afisaza vectorul sortat
cout << "Vectorul sortat in ordine descrescatoare\n";
for(i = 0; i < v.size(); i++)
cout << v[i] << endl;
return 0;
}

15.3 Liste

Clasa generică list implementează o listă dublu înlănţuită, adică o listă ce poate fi
parcursă în ambele sensuri. Lista poate avea oricâte elemente de acelaşi tip. Prototipul
clasei este definit în biblioteca <list>. Clasa defineşte următorii constructori
• constructorul implicit
list();
defineşte o listă vidă
• constructorul
list(list p);
crează o listă în care copiază elementele listei p
Fie T tipul elementelor listei. Funcţiile definite de clasa list sunt următoarele.
• void push_back(T&); adaugă un element la sfârşitul listei
• void push_front(T&); adaugă un element la începutul listei
• void pop_front(); şterge primul element din listă
• void pop_back();şterge ultimul element din listă
• T& front(); are ca rezultat primul element din listă
• T& back();are ca rezultat ultimul element din listă
15.3.1 Parcurgerea listelor

Pentru parcurgerea componetelor unei liste se pot utiliza iteratori. Un iterator este un
pointer la un element al listei.

Parcurgerea listelor în ordine directă

Pentru parcurgerea unei liste în ordine directă se utilizează clasa iterator care este o clasă
internă a clasei list. Un obiect de tipul acestei clase se defineşte astfel
list<tip>::iterator nume_obiect;
Operaţiile implementate de clasa iterator sunt cele prezentate în paragraful anterior.
Pentru parcurgerea directă a listelor clasa list defineşte funcţiile
begin();
end();
ce au ca rezultat un iterator ce indică primul element al listei şi respectiv ultimul element
al listei.

Parcurgerea listelor în ordine inversă

Pentru parcurgerea inversă a listelor se utilizează clasa reverse_iterator care este tot o
clasă internă a clasei list. Un obiect de tipul acestei clase se defineşte ca
list<tip>::reverse_iterator nume_obiect;
Operaţiile implementate de clasa iterator sunt cele prezentate în paragraful anterior.
Clasa list defineşte funcţiile
rbegin();
rend();
ce au ca rezultat un iterator pentru parcurgerea în sens invers a listei ce indică ultimul
element al listei şi respectiv primul element al listei.

15.3.2 Sortarea listelor

Pentru sortarea în ordine directă şi inversă a listelor clasa list defineşte funcţiile
void sort();
void reverse();
ce sorteză elementele listei în ordine directă şi inversă.
Exemplu. Vom crea o listă cu elemente întregi, o vom sorta ascendant şi descendent şi
vom afişa elementele listei.

# include <iostream>
# include <list>
using namespace std;

int main()
{
list <int> ls;
// adauga elemente la sfarsitul si inceputul listei
ls.push_back(11);
ls.push_back(7);
ls.push_front(4);
ls.push_front(12);
// parcurgerea listei
cout << "lista initiala\n";
list<int>::iterator iter;
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << endl;
// sortarea elementelor
ls.sort();
// parcurgerea listei sortate
cout << "lista sortata\n";
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << endl;
// sortarea elementelor in ordine inversa
ls.reverse();
// parcurgerea listei sortate
cout << "lista sortata in ordine inversa\n";
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << endl;
// parcurgerea listei
cout << "parcurgerea listei in ordine inversa\n";
list<int>::reverse_iterator iter1;
for(iter1 = ls.rbegin(); iter1 != ls.rend(); iter1++)
cout << *iter1 << endl;
return 0;
}

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