CPP

1
1 Constante, variabile şi expresii .................................................................................... 5
1.1 Tipuri fundamentale ............................................................................................. 5
1.2 Variabile ............................................................................................................... 5
1.3 Modificatori de tip ................................................................................................ 6
1.4 Operatorul typedef ................................................................................................ 7
1.5 Constante .............................................................................................................. 7
1.6 Constante cu nume ............................................................................................... 9
1.7 Expresii aritmetice .............................................................................................. 11
1.8 Tablouri .............................................................................................................. 13
1.9 Instrucţiunea de atribuire .................................................................................... 15
1.10 Prototipuri de funcţii. Biblioteci de prototipuri ............................................... 16
1.11 Operaţii de intrare / ieşire ................................................................................ 17
1.12 Funcţia main .................................................................................................... 21
1.13 Execuţia unui program .................................................................................... 22
1.14 Operatorul sizeof ............................................................................................. 22
1.15 Operatorii ++ şi - - ........................................................................................... 24
1.16 Operaţii cu numere întregi la nivel de bit ........................................................ 27
1.16.1 Operatori de deplasare ............................................................................... 27
1.16.2 Operaţii logice la nivel de bit .................................................................... 28
2 Structuri de control fundamentale ............................................................................. 32
2.1 Algoritme ........................................................................................................... 32
2.2 Expresii relaţionale ............................................................................................ 33
2.3 Expresii booleene .............................................................................................. 35
2.4 Operatorul if ...................................................................................................... 36
2.5 Operatorul switch .............................................................................................. 39
2.6 Operatorul ? ....................................................................................................... 40
2.7 Operatorul while ................................................................................................ 40
2.8 Operatorul do-while .......................................................................................... 43
2.9 Operatorul for .................................................................................................... 44
2.10 Operatorul , ...................................................................................................... 49
3 Funcţii ........................................................................................................................ 51
3.1 Funcţii standard .................................................................................................. 51
3.1.1 Funcţii C standard de manipulare a caracterelor ......................................... 51
3.2 Definirea funcţiilor ............................................................................................. 54
3.3 Prototipuri de funcţii .......................................................................................... 56
3.4 Compilarea separată a funcţiilor ......................................................................... 57
3.5 Funcţii cu parametri tablouri ............................................................................. 58
3.6 Supraîncărcarea funcţiilor .................................................................................. 64
3.7 Transmiterea parametrilor către funcţii .............................................................. 66
3.8 Recursivitatea ..................................................................................................... 71
3.9 Funcţii generice .................................................................................................. 72
4 Pointeri şi referinţe .................................................................................................... 74
4.1 Pointeri ............................................................................................................... 74
4.1.1 Declararea variabilelor tip pointer ............................................................... 75
4.2 Referinţe ............................................................................................................. 79
4.3 Pointeri la funcţii ................................................................................................ 82
4.4 Interpretarea instrucţiunilor ce conţin pointeri ................................................... 83
4.5 Pointeri şi tablouri unidimensionale ................................................................... 85
4.6 Poineri şi şiruri tip C .......................................................................................... 89
2
4.7 Pointeri şi tablouri multidimensionale .............................................................. 92
4.8 Parametrii funcţiei main. Parametrii liniei de comandă ..................................... 97
4.9 Alocarea dinamică a memoriei .......................................................................... 97
5 Fişiere tip C ............................................................................................................. 106
5.1 Fişiere tip text ................................................................................................... 107
5.1.1 Funcţii intrare / ieşire cu format ................................................................ 108
5.1.2 Funcţii intrare / ieşire tip caracter .............................................................. 112
5.2 Fişiere text tip şir de caractere .......................................................................... 115
5.3 Fişiere binare .................................................................................................... 117
6 Structuri tip C şi uniuni ........................................................................................... 122
6.1 Structuri ............................................................................................................ 122
6.2 Uniuni ............................................................................................................... 125
7 Clase ....................................................................................................................... 127
7.1 Definirea unei clase .......................................................................................... 128
7.1.1 Definirea unei clase ................................................................................... 128
7.1.2 Pointerul this .............................................................................................. 133
7.1.3 Spaţii de nume ........................................................................................... 133
7.2 Constructori şi destructori ................................................................................ 134
7.2.1 Constructori ............................................................................................... 134
7.2.2 Destructori ................................................................................................. 138
7.3 Funcţii prietene ................................................................................................. 140
7.4 Determinarea tipului unei expresii ................................................................... 142
8 Siruri tip C++ ........................................................................................................... 144
8.1 Clasa string ....................................................................................................... 144
9 Supraîncărcarea operatorilor ................................................................................... 148
9.1 Supraîncărcarea operatorilor aritmetici ............................................................ 148
9.2 Supraîncărcarea operatorului de atribuire ........................................................ 150
9.3 Supraîncărcarea operatorilor << şi >> .............................................................. 152
10 Moştenirea ............................................................................................................. 155
10.1 Pointeri la obiecte. Operatorii new şi delete .................................................. 155
10.2 Moştenirea .................................................................................................... 160
10.2.1 Definirea unei clase derivate ................................................................... 160
10.2.2 Specificatorii de acces ............................................................................. 162
10.3 Funcţii virtuale. Polimorfism ........................................................................ 168
10.4 Destructori virtuali ........................................................................................ 175
10.5 Date şi funcţii statice ..................................................................................... 177
10.5.1 Date statice .............................................................................................. 177
10.5.2 Funcţii statice .......................................................................................... 178
11 Fişiere tip C++ ....................................................................................................... 181
11.1 Fişiere text ..................................................................................................... 182
11.1.1 Funcţii intrare / ieşire cu format .............................................................. 182
11.1.2 Funcţii intrare / ieşire tip caracter ............................................................ 183
11.2 Fişiere binare ................................................................................................. 186
11.3 Fişiere text tip string ...................................................................................... 189
12 Tratarea excepţiilor ................................................................................................ 191
12.1 Excepţii .......................................................................................................... 191
12.2 Excepţii lansate de funcţii ............................................................................. 193
12.3 Excepţii standard ........................................................................................... 194
12.4 Excepţii intrare / ieşire .................................................................................. 196
13 Aplicaţii ................................................................................................................ 199
3
13.1 Funcţii de timp ............................................................................................... 199
13.2 Fire de execuţie ............................................................................................. 200
13.3 Funcţia system ............................................................................................... 202
14 Biblioteca de şabloane standard ............................................................................ 204
14.1 Clase generice ................................................................................................ 204
14.2 Containere, iteratori şi algoritme generice ................................................... 206
14.3 Vectori ........................................................................................................... 207
14.3.1 Parcurgerea vectorilor cu iteratori ........................................................... 209
14.3.2 Stergerea şi inserarea de elemente .......................................................... 211
14.3.3 Sortarea componentelor unui vector ........................................................ 213
14.3.4 Căutarea unui element într-un vector ...................................................... 215
14.3.5 Copierea unui container. Iteratori pentru streamuri ................................ 217
14.4 Liste ............................................................................................................... 219
14.4.1 Parcurgerea listelor .................................................................................. 219
14.4.2 Sortarea listelor ........................................................................................ 220
14.4.3 Inversarea ordinii elementelor listelor ..................................................... 221
14.4.4 Inserarea şi ştergerea elementelor din liste .............................................. 222
14.4.5 Copierea listelor ...................................................................................... 224
14.5 Parcurgerea şirurilor tip string cu iteratori .................................................... 225
14.6 Numere complexe .......................................................................................... 226
Anexa 1. Reprezentarea informaţiilor ....................................................................... 229
A.1 Reprezentarea numerelor întregi în sistemul binar ......................................... 229
A.2 Deplasarea numerelor binare cu semn ........................................................... 233
A.3 Reprezentarea numerelor reale ....................................................................... 234
A.4 Reprezentarea caracterelor ............................................................................. 236
Anexa 2. Priorităţile şi asociativitatea operatorilor ................................................... 238
Anexa 3. Tipul complex în limbajul C ...................................................................... 239
4
Partea I Programarea procedurală
1 Constante, variabile şi expresii
1.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. De exemplu, operaţiile tipului real sunt + - * /, pentru tipul întreg se
adaugă restul împărţirii a două numere întregi, simbolizat %. 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 Semnificaţie
Dimensiune
în octeţi
Domeniu de
valori
Operaţii
int Numere întregi 4
[-2
31
… 2
31
)
+-*/%
float
Numere reale în virgulă
mobilă scurtă
4 ) 10 10 (
38 38
 − +-*/
double
Numere reale în virgulă
mobilă lungă
8 ) 10 10 (
307 307
 − +-*/
char
Caractere în cod ASCII şi
numere întregi pe un octet
1
[-2
7
… 2
7
)
+-*/%
wchar_t
Numere pozitive şi caractere
UNICODE pe doi octeţi
2
[0 … 2
16
)
+-*/%
bool Valoare booleană 1 false,true
and, or,
not
Pentru fiecare tip există constante predefinite ce dau limitele minimă ş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.
1.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.
De exemplu abc şi Abc sunt două nume de variabile diferite. 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.
5
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). O alt
mod de iniţializare a unei variabile la declararea tipului ei este dat de diagrama de mai
jos
Valoarea cu care se iniţializează variabila este expresia din paranteze. De exemplu,
instrucţiunea
double d (1.2 – cos(1.7));
declară o variabilă de tip double şi o iniţializează la valoarea 1.2 – cos(1.7);
Menţionăm că tipul bool nu este definit în limbajul C.
1.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. De exemplu, tipul unsigned int are domeniul
de valori [0 … 2
31
),

iar tipul unsigned char are domeniul de valori [0 … 2
8
) ,
• signed. Valorile variabilelor sunt numere cu semn. Primul bit este bit de semn,
6
• short. Acest modificator se aplică tipului int. De exemplu, tipul short int are
domeniul de valori [-2
15
… 2
15
),

iar tipul unsigned short int are domeniul de
valori [0 …2
16
). Tipul wchar_t este tipul unsigned short int predefinit.
• long
1.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 cu instrucţiunea
typedef char bool
1.5 Constante
Tipurile de constante din limbaj corespund tipurilor de date.
Constante întregi
Constantele întregi sunt numere întregi scrise în bazele 8, 10 sau 16.
• constantele zecimale sunt numere întregi reprezentate în baza 10. Ele nu pot
începe cu cifra 0. Exemple de constante zecimale sunt :
275, -325, +12
• constante hexazecimale sunt numere întregi reprezentate în baza 16. Ele încep
cu 0x sau 0X. Cifrele hexazecimale sunt 0, …, 9 şi A … F sau a … f. Exemple
de constante hexazecimale :
0x1E sau 0x1e sau 0X1e sau 0X1E care reprezintă valoarea zecimală
30
-0x2f sau -0x2F, sau 0x02F reprezintă valoarea zecimală -47.
• constante octale sunt numere întregi reprezentate în baza 8. Ele încep
obligatoriu cu cifra 0. Exemple de constante octale :
016 reprezintă numărul zecimal 14 (să se arate că (14)
10
= (16)
8
)
-014 reprezintă numărul zecimal -12 (să se arate că (-12)
10
= (-14)
8
).
Constante reale
Constantele reale sunt numere reale în simplă sau dublă precizie. Partea subunitară
este separată de cea întreagă prin punct zecimal.
• constante reale în virgulă mobilă scurtă. 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
7
• constante reale în virgulă mobilă lungă. Se scriu după aceleaşi reguli ca şi
constantele reale în virgulă mobilă scurtă şi sunt urmate de 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 caracter
• constante de tip caracter reprezintă un caracter al codului ASCII pe un octet.
Caracterele se scriu î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.
Caracterul 1 în codul ASCII este
‘1’ sau echivalent, 0x31 sau 49.
Un alt mod de a defini constante tip caracter 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 ‘\x61’ şi respective ‘\x41’. Caracterul ‘1’
se reprezintă ca ‘\x31’. Caracterele speciale ale codului ASCII se reprezintă
folosind secvenţa de evitare
‘\n’ CR
‘\r’ LF
‘\t’ tab orizontal
‘\”’ ghilimele
‘\’’ apostrof
‘\\’ backslash
‘\?’ semnul intrebării
‘\0’ null (valoarea 0 pe un octet)
• 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. Sirul anterior se reprezintă
ca
a b c d \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.
8
• constante tip caracter UNICODE corespund tipului wchar_t. Ele se definesc
sub forma L’\xhhhh’ unde h reprezintă o cifră hexazecimală.
Constante booleene
constante booleene sunt true şi false şi se reprezintă prin valorile unu şi respective
zero.
1.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 constante întregi sau un tip întreg şi valorile
asociate acelui tip. Instrucţiunea enum de definire a unor constante întregi are
forma
Instrucţiunea enum de declarare a unui tip întreg şi a valorilor asociate lui are
forma
De exemplu,
enum CLRX {aaa, bbb, ccc};
defineşte tipul CLRX şi trei constante aaa, bbb, ccc aferente acestui tip, prima
are valoarea zero şi fiecare constantă următoare are o valoare mărită cu unu :
aaa = 0
bbb = 1
ccc = 2
9
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 tipul cstval şi constantele de acest tip
ppp = 2
dd = 3
cc = -1
nn = 14
Instructiunea
enum BAZA {BIN = 2, HEX = 16, OCT = 8, DEC = 10};
defineşte tipul BAZA şi constantele aferente
BIN = 2
HEX = 16
OCT = 8
DEC = 10
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. Orice constantă definită cu
instrucţiunea enum poate fi utilizată ca orice constantă întreagă.
• utilizarea cuvântului cheie const. O asemenea instrucţiune are forma
diagramelor sintactice de mai jos

unde tip este un tip al limbajului iar valoare este o expresie constantă. De
exemplu, instrucţiunile :
const int a = 7, n(-2);
const char b = ‘a’;
const float c = 2.5e-2 * 4.14;
const double x = sin(0.2) + cos(1.5);
const float z = log(12.5) / 2.3, y(2 + ln(1.7));
definesc constantele a, n, b, c, x, z şi y, ce au valorile specificate în
instrucţiune.
10
1.7 Expresii aritmetice
Expresiile aritmetice sunt formate din constante, variabile şi funcţii. Operatorii sunt +
- * şi / pentru operanzi reali, iar î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
19 % 4 = 3 19 / 4 = 4
19 % (-4) = 3 19 / (-4) = -4
-19 % 4 = -3 -19 / 4 = -4
-19%(-4) = -3 -19 / (-4) = 4
La schimbarea semnului unui operand, se poate schimba doar semnul câtului şi
restului, şi nu valoarea lor absolută.
Pentru gruparea termenilor se folosesc paranteze rotunde, ( şi ). De exemplu, expresia
b a
b a
+

se scrie ca
(a – b) / (a + b)
iar expresia
2
2
2
* *
m
x c x b a
+
+ +
se scrie ca
(a + b * x + c * x * x) / (2 + m*m)
Expresia
b
c
c
b
a
+
se scrie ca
a / (b / c + c / b)
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 1
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.
11
x
x b x a
+
+
75 . 2
) sin( * ) ( cos *
2
(a*cos(x)*cos(x)+b*sin(x))/(2.75+fabs(x))
5
2x x
e e

+
(exp(x)+exp(-2*x))/5
) cos( 1 ln ) 2 log( x x + + +
log10(fabs(x)+2)+log(fabs(1+cos(x)))
5 . 3
) (
+
+
a
y x pow(x+y, a+3.5)
Menţionăm că, în expresii putem folosi orice fel de constante întregi, reale sau
caracter. 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 caracter sunt convertite în numărul întreg corespunzător.
Evaluarea expresiilor aritmetice se face ţinând cont de priorităţile operatorilor şi de
asociativitatea lor.
Prioritatea operatorilor Asociativitate
+ unar, - unar la dreapta
* / % la stânga
+ - 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
De exemplu, expresia 8 / 4 / 2 este interpretată ca (8 / 4) / 2 şi are rezultatul 1.
Expresia
x / y * z
este interpretată ca
(x / y ) * z
adică operatorii / şi * ce au aceeaşi prioritate se execută de la stânga la dreapta.
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 compilatorul face aceste conversii sunt următoarele:
• tipul float se converteşte la double,
• tipurile char şi short int se convertesc la int,
12
• 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.
Există două forme ale expresiilor de conversie a tipurilor.
Prima formă a expresiei de conversie de tip 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
int i = 7 ;
double = 3.47 ;
expresia
(i + f) % 2
nu este corectă deoarece expresia in parantezele rotunde 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 forma
tip(expresie)
De exemplu, expresia anterioară se poate scrie
int(i + f) % 2
Menţionăm că se pot face conversii între toate tipurile standard existente în limbaje.
Conversia de la un tip real la un tip întreg se face prin trunchiere, valoarea 3.71
convertită la int este 3, iar valoarea -3.4 convertită la int este -3.
1.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 [ număr întreg] ;
unde număr întreg reprezintă numărul de elemente ale tabloului. Elementele tabloului
sunt
nume[0], nume[1], …, nume[număr î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].
Dimensiunea unui tablou este fixată la declararea sa şi nu se poate schimba.
Un tablou poate fi iniţializat la declararea sa scriind valorile elementelor între acolade,
{}, şi separate de virgule. Exemple de instrucţiuni ce declară tablouri şi le atribuie
valori
double x[3] = {1.32, -2.15, 4.45};
13
char c[4] = {‘a’, ‘b’, ‘c’, ‘x’};
Ultimul tablou este reprezentat în memorie astfel
a b c x
In cazul în care lista de valori este mai scurtă decât 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’ si este reprezentat în memorie
ca
‘a’ ‘b

‘c’ ‘\0’
Instrucţiunea precedentă este echivalentă cu oricare dintre instrucţiunile următoare:
char s[4] = “abc”;
char s[4] = {‘a’, ‘b’, ‘c’ , ‘\0’};
char s[4] = {0x61, 0x62, 0x63, 0};
char s[4] = {97, 98, 99, 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]
Elementele tablourilor sunt memorate pe linii. Un tablou cu mai multe dimensiuni
poate fi de asemenea iniţializat la declararea sa. Fie de definit matricea m cu elemente
întregi, pozitive
1
]
1

¸


·
3 7 3
5 2 1
m
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
14
double a[10], b;
int i, j, y[3][4];
Următoarele expresii ce conţin elemente de tablouri :
) sin( ) cos(
*
b b
y b a
ij i

+
(a[i]+b*y[i][j])/(cos(b)-sin(b))
1 ,
) cos(
+
+
j i
b
y e
exp(cos(b)) + y[i][j + 1]
2
3 2 −
+
n jk
b a
a[j][k] + b[2 * n – 3] * b[2 * n – 3]
23 12
* y x
x[1][2] * y[2][3]
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
[] () la stânga
+ unar, - unar, (tip) la dreapta
* / % la stânga
+ - la stânga
1.9 Instrucţiunea de atribuire
O operaţie definită pentru toate tipurile fundamentale este atribuirea. 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 x , y şi z .
float x, y, z;
x = -1.34;
y = sin(x) + cos(x * x);
z = (x + y) / (y + cos(x) * cos(x));
Menţionăm că, la atribuire, expresia din partea dreaptă este convertită în tipul
variabilei din stânga. Fie de exemplu instrucţiunile
int a;
double x = 2.7;
a = x;
Variabila întreagă a primeste valoarea 2.
Limbajele C şi C++ au operatori speciali pentru scrierea prescurtată a instrucţiunilor
de atribuire, +=, -=, *=, /= şi %=. Aceşti operatori se definesc astfel:
15
Considerăm o variabilă x şi o expresie e. Instrucţiunea
x op= e;
este echivalentă cu
x = x op e;
De exemplu, instrucţiunea
x = x + cos(y) ;
se scrie prescurtat
x += cos(y) ;
Tabelul următor prezintă aceşti operatori
Forma prescurtată a operatorilor 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 instrucţiunea
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?
1.10 Prototipuri de funcţii. Biblioteci de prototipuri
Atunci când compilatorul întâlneşte un apel la o 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 este
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 numefuncţie(tip, tip, …, );
De exemplu, funcţia sin are un singur parametru de tip double,iar rezultatul este de tip
double; prototipul funcţiei sin este
double sin(double);
Limbajele C şi C++ au biblioteci standard cu prototipurile funcţiilor limbajului. De
asemenea, utilizatorul poate defini biblioteci cu prototipuri. Toate aceste biblioteci
sunt semnalate compilatorului cu directiva include cu forma
# include nume_bibliotecă
Aceste biblioteci sunt fişiere numite fişiere header sau antet. Directiva include este
diferită în cazul limbajului C de cea din limbajul C++.
In cazul limbajului C fişierele header au extensia h. De exemplu, biblioteca cu
prototipurile funcţiilor intrare/ieşire tip C este stdio.h, biblioteca cu prototipurile
funcţiilor matematice este math.h, biblioteca cu prototipurile funcţiilor de prelucrat
16
şiruri tip C este string.h, etc. In consecinţă, în cazul unui program în limbajul C vom
semnala compilatorului aceste biblioteci cu directivele
# include <stdio.h>
# include <math.h>
In limbajul C++ fişierele header nu au extensie. De exemplu, biblioteca cu
prototipurile funcţiilor intrare/ieşire tip C++ este iostream. La utilizarea în limbajul
C++, 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 iostream şi math astfel
# include <iostream>
# include <cmath>
using namespace std;
Putem defini propriile biblioteci cu prototipuri pe care să le semnalăm compilatorului
cu directiva include. Numele propriilor biblioteci cu prototipuri sunt scrise între
ghilimele.
1.11 Operaţii de intrare / ieşire
Orice aplicaţie ce rulează pe un calculator 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. Ele vor fi prezentate detaliat într-un capitol
ulterior. Fişierele standard asociate unei aplicaţii sunt de tipul text. Fişierul text este
este compus dintr-un şir de caractere grupate în linii. Liniile constau din zero sau mai
multe caractere urmate de un caracter ‘\n’. Streamul de intrare asociat tastaturii are
denumirea cin, streamul de ieşire asociat ecranului se numeşte cout. Mai există alte
două streamuri cerr şi clog pentru scrierea mesajelor de eroare.
Operatorul de scriere a datelor
Operatorul de scriere a datelor este <<. El inserează date în streamul de ieşire. De
exemplu, secvenţa de instrucţiuni
int i = 123;
cout << “i = ” << i;
afişază pe ecran
i = 123
Operatorul << inserează în stream valoarea expresiei din dreapta sa. De exemplu,
expresia
<< “i= “
inserează în stream şirul de caractere i= , expresia
<< i
inserează în stream valoarea variabilei i, 123, etc.
Pentru a afişa valori pe mai multe linii, trebuie ca după fiecare linie să scriem
caracterul ‘\n’, care este predefinit ca endl. De exemplu, fie instrucţiunile
int j = -7;
double x = 1 + sin(0.2);
Pentru a afişa valorile variabilelor j si x pe două linii, vom scrie
cout << “j = “ << j << endl << “x = “ << x << endl;
sau
cout << “j = “ << j << ‘\n’ << “x = “ << x << ‘\n’;
sau
cout << “j =” << j << “\n” << “x = “ << x << “\n”;
17
sau, echivalent, cu doua instrucţiuni cout
cout << “j =” << j << endl;
cout << “x = “ << x << endl;
Operatorul << poate scrie orice tip predefinit de date: int, float, şiruri de caractere, etc.
Operatorul de citire a datelor
Operatorul de citire dintr-un stream este >>. El extrage date din streamul de intrare şi
le atribuie unor variabile. Operatorul >> este urmat de numele variabilei ce va
memora valoarea citită. De exemplu, secvenţa de instrucţiuni
int a;
cin >> a;
va citi o valoare introdusă de la tastatură şi o va atribui variabilei a (expresia >>a
citeşte o valoare de la tastatură şi o atribuie variabilei a). O instrucţiune cin poate citi
oricâte date din stream. Fie instrucţiunea
int a, b ;
Instrucţiunea
cin >> a >> b;
citeşte două valori de la tastatură şi le atribuie variabilelor a şi b. Ea poate fi scrisă ca
cin >> a;
cin >> b;
Valorile introduse de la tastatură sunt analizate şi atribuite variabilelor de către
instrucţiunea cin. Pentru fiecare variabilă se procedează astfel :
• se citesc şi se ignoră toate caracterele spaţiu, ‘\t’ sau ‘\n’ (acesta din urmă este
generat la apăsarea tastei Return),
• se citesc următoarele caractere corespunzând tipului variabilei sau până la
întâlnirea unui caracter spaţiu, ‘\t’ sau ‘\n’ şi valoarea citită se atribuie
variabilei.
Fie de exemplu valorile 25 şi 17 de citit pentru variabilele de tip întreg a şi b cu una
din instrucţiunile precedente. Ele pot fi introduse de la tastatură separate de un spaţiu
ca
Şirul introdus este analizat astfel :
• se citesc toate caracterele spaţiu, ‘\t’ sau ‘\n’ până la prima cifra întâlnită şi se
ignoră (în acest caz primul caracter este diferit de spaţiu, ‘\t’ sau ‘\n’),
• se citesc apoi toate caracterele până la primul caracter diferit de cifră, deoarece
trebuie citit un număr întreg, şi rezultă numărul 25.
Se procedează la fel pentru al doilea număr :
• se citeşte şi se ignoră spaţiul,
• se citesc cifrele până la primul caracter diferit de cifră şi rezultă numărul 17.
Aceleaşi valori pot fi introduce ca
Fie de iniţializat prin citire o variabilă x tip caracter la valoarea ‘A’. Variabila este
definită cu instrucţiunea
char x;
Instrucţiunea de citire este
cin >> x;
Putem introduce caracterul A astfel:
18
sau
Citirea caracterului se face după regula de mai sus : se citesc şi se ignoră toate
caracterele spaţiu, ‘\t’ sau ‘\n’ şi se citeşte un caracter care este atribuit variabilei x.
Valorile introduse de la tastatură sunt analizate şi atribuite variabilelor de către
instrucţiunea cin doar după introducerea unui caracter ‘\n’.
Reamintim că biblioteca de prototipuri pentru streamurile cin, cout, etc are numele
iostream. Pentru a modifica formatul de scriere sau citire a datelor putem utiliza
manipulatori definiţi în biblioteca iomanip. 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 baza 10 este implicită.
Fie următorul exemplu
int a ;
cin >> hex >> a ;
cout << a << endl;
Presupunem introdus de la tastatură şirul de caractere de mai jos
Valoarea atribuită prin citire variabilei a este 27. De ce ?
In următorul exemplu vom citi numărul negativ -44 în baza 16.
int i ;
cin >> hex >> i ;
Numărul poate fi introdus ca în tabelul de mai jos.
Valoarea afişată este 27. Pentru a afişa valoarea variabilei a în baza 16 trebuie să
scriem
cout << hex << a << endl ;
Pentru afişarea bazei se utilizează 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 pentru a afişa pe
ecran valoarea variabilei k.
cout << k; 20
cout << hex << k; 14
cout << showbase << hex << k; 0x14
cout << oct << k; 24
cout << showbase << oct << k; 024
In cazul numerelor reale avem manipulatorii:
19
• 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 pentru a afişa pe
ecran valoarea variabilei x.
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)
• setw(int)
• setfill(char)
• setprecision(int)
Funcţia setbase(int) indică baza în care va fi afişat un număr întreg, 8, 10 sau 16.
Funcţia setw(int) dă dimensiunea câmpului în caractere pe care este scris un număr.
Valoarea implicită a acestui manipulator este 0. Dacă dimensiunea câmpului nu este
specificată, sau este prea mică, numărul este scris pe câte caractere este necesar.
Funcţia setfill(char) indică un caracter cu care se umplu spaţiile libere ale unui câmp.
Valoarea implicită a acestui caracter este spaţiul. Funcţia setprecision(int) dă numărul
de cifre cu care este scris un număr real.
Exemplu. Fie instrucţiunile
int x = 23;
double pi(3.1459);
Tabloul următor prezintă exemple de utilizare a funcţiilor operatorului <<.
cout << setw(5) << x; 23
cout << x; 23
cout << setbase(16) << x: 17
cout << dec << setw(3) << setfill(‘*’) << x; *23
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
Exemplu. Fie instrucţiunea
float z = 12.64;
Tabelul următor prezintă exemple de utilizare a funcţiilor anterioare la afişarea
variabilei z cu instrucţiunea cout.
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
Manipulatorii următori specifică modul de cadrare al valorilor scrise :
• left - cadrare la stânga
20
• right – cadrare la dreapta
Menţionăm că dimensiunea câmpului prescrisă de funcţia setw(int) se aplică
doar următorului număr de scris. Ceilalţi manipulatori rămân la valoarea
prescrisă până sunt modificaţi.
Exemplu. Fie instrucţiunile
int x ;
cin >> x ;
cout << hex << x << endl ;
cout << x << endl ;
Valoarea variabilei x este afişată în baza 16 cu ambele instrucţiuni, deoarece
manipulatorul de scriere a bazei are valoarea hex. Pentru a afişa din nou variabilele
întregi în baza 10 trebuie să scriem o instrucţiune
cout << dec << x << endl ;
1.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 numefunctie (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 acestui prototip este următoarea. Funcţia main are ca rezultat o valoare
întreagă şi nu are parametri. Corpul funcţiei este o instrucţiune compusă, adică o
secvenţă de instrucţiuni scrise între acolade, { şi }. Corpul funcţiei conţine şi o
instrucţiune return ce termină execuţia funcţiei şi transmite în programul appelant
valoarea calculată de funcţie.
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.
// program care se citeste o valoare intreaga si calculeaza patratul ei
# include <iostream>
using namespace std;
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 afisaza valoarea calculata */
cout << “patratul valorii este “ << j << endl;
return 0;
21
}
Menţionăm că funcţia main() returnează valoarea 0 cu instrucţiunea
return 0;
Limbajul C++ defineşte constanta EXIT_SUCCESS ce are valoarea zero. In
consecinţă, putem scrie
return EXIT_SUCCESS;
Rezultatul rulării programului este cel de mai jos.
Pentru claritatea programelor, limbaajul defineşte şi constanta EXIT_FAILURE ce
are valoarea unu.
1.13 Execuţia unui program
Reamintim etapele de execuţie a 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. Fiecare
fişier sursă este compilat separat. Din fiecare fişier sursă rezultă un fişier
obiect.
• Etapa a doua este editarea legăturilor. In această etapă sunt ataşate
programului funcţiile din biblioteci. Atunci când compilatorul întâlneş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
precompilate în biblioteci speciale şi programul editor de legături ataşează
aceste funcţii programului obiect. Editorul de legături generează un program
executabil din toate fişierele obiect. El este un fişier cu extensia exe.
• In etapa a treia programul excutabil este încărcat în memorie şi executat.
1.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. Există două forme ale acestui operator
sizeof (tip)
sizeof (expresie)
De exemplu, expresia
sizeof(int)
are ca rezultat valoarea 4, expresia
sizeof (char)
are valoarea 1, etc.
In cazul unui tablou rezultatul este numărul total de octeţi ocupat de tablou. Fie
instrucţiunea
double a[5];
Expresia
22
sizeof(a)
are valoarea 40 iar expresia
sizeof(a) / sizeof(a[0])
dă numărul elementelor tabloului a.
In cazul cand operandul este o expresie, operatorul sizeof dă numărul de octeţi ocupat
de rezultatul expresiei. Fie de exemplu instrucţiunile
int x;
double m;
Expresia
sizeof(x + m)
are valoarea 8, deoarece tipul expresiei este double.
Exemplu. Vom scrie un program care să afişeze numărul de octeţi utilizaţi pentru
memorarea tipurilor fundamentale. Vrem ca rezultatele să fie afişate pe ecran astfel:
Numarul de octeti utilizati
int : ....
char : ....
float : ....
double : ….
vectorul float a[10] : ….
expresia a[0]+b : ....
Pentru a afişa datele deplasate la stânga cu un număr de spaţii vom scrie un caracter
tab, ‘\t’. Programul este următorul :
// dimensiunea tipurilor standard
# include <iostream>
using namespace std;
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 << “\texpresia a[0]+b: ” << sizeof(a[0]+ b) << ‘\n’;
}
Rezultatul rulării programului este prezentat mai jos.
23
Menţionăm că este echivalent dacă în instrucţiunea cout utilizăm caracterul ‘\n’, sau
endl, sau şirul de caractere “\n”. Şirului de caractere “\tint: “ duce la scrierea
caracterului ‘\t’ şi a şirului ”int: “. Instrucţiunea
cout << ”\tint: “ << sizeof(int) << endl;
putea fi scrisă ca
cout << ‘\t’ << ”int: “ << sizeof(int) << endl;
1.15 Operatorii ++ şi - -
Operatorul ++ incrementează o variabilă întreagă cu valoarea unu, operatorul - -
decrementează o variabilă întreagă cu unu. Aceşti operatori pot fi :
• prefix
++x, --x
• postfix
x++, x- -
Expresiile ++x şi x++ reprezintă instrucţiunea
x = x + 1
iar expresiile - -x şi x- - reprezintă instrucţiunea
x = x – 1
Cazul operatorilor prefix, ++x, --x
O expresie de forma
f(++x);
este scrierea prescurtată a expresiilor:
x = x + 1;
f(x);
In acelaşi fel, expresia
f(--x);
reprezintă scrierea prescurtată a expresiilor:
x = x – 1;
f(x);
Modul de execuţie a expresiei este următorul:
1. se incrementează sau decrementează valoarea variabilei x cu unu,
2. valoarea incrementată sau decrementată se utilizează mai departe în calcule.
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 secventa de instrucţiuni
24
int i = 1;
cout << “i = “ << i << endl;
cout << “i = “ << ++i << endl;
cout << “i = “ << i << endl;
Valorile afişate vor fi
i = 1
i = 2
i = 2
deoarece a doua instructiune cout este echivalentă cu instrucţiunile
cout << “i = “;
i = i + 1;
cout << i << endl;
Exemplu. Fie următoarele declaraţii.
double a[4] = {1.2, -5, 8, 3};
double 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--
O expresia de forma
f(x++);
reprezintă scrierea prescurtată a expresiilor
f(x);
x = x + 1;
In acelaşi mod, expresia
f(x--);
reprezintă scrierea prescurtată a expresiilor:
f(x);
x = x – 1;
Modul de execuţie a expresiei este următorul :
1. se utilizează în calcule valoarea variabilei x (neincrementate sau
nedecrementate),
2. se incrementează / decrementează apoi variabila x.
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 :
25
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 instructiunile :
int j = 1;
cout << “j = “ << j << endl;
cout << “j = “ << j++ << endl;
cout << “j = “ << j << endl;
Valorile afişate vor fi :
j = 1
j = 1
j = 2
deoarece a doua instrucţiune cout este echivalentă cu instrucţiunile :
cout << “j = “ << j;
j = j + 1;
cout << endl;
Exemplu. Fie secvenţa de instrucţiuni de mai jos :
int i;
double x[4] = {2.4e1, 14.4, -3.1 0};
double d;
i = 1;
d = x[i++];
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
Fie acum secvenţa de instrucţiuni următoare :
int j;
double x[4] = {2.4e1, 14.4, -3.1 0};
double d;
j = 1;
d = x[++j];
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
26
1.16 Operaţii cu numere întregi la nivel de bit
1.16.1 Operatori de deplasare
Deplasarea la stânga a unui întreg cu un bit reprezintă înmulţirea acelui număr cu doi.
Deplasarea la dreapta a unui întreg cu un bit reprezintă câtul împărţirii acelui număr
cu doi. De exemplu, prin deplasarea numărului 7 la dreapta cu un bit se obţine
rezultatul 3. Operatorii de deplasare a unui număr întreg cu un număr de biţi sunt <<
pentru deplasare la stânga şi >> pentru deplasare la dreapta. Expresia de deplasare a
unui număr întreg are forma
Rezultatul expresiei din stânga este numărul întreg 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, vezi anexa. (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. Operatorii de deplasare, << şi >>
sunt operatori aritmetici.
Exemple. Fie instrucţiunea
int x = 7 >> 1;
Variabila x primeşte valoarea 3 (câtul înpărţirii lui 7 la 2 este 3).
Fie instrucţiunile de mai jos
int a = 0xff; int y = 0xff;
int b; int c;
b = a << 4; c = y >> 4;
Numărul a, reprezentat pe patru octeţi, are valoarea hexazecimală
a = 000000ff
Numărul a deplasat la stânga cu 4 biţi este
00000ff0
Numărul y 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?
Fie două variabile întregi, i si j şi secvenţa de instrucţiuni :
i = 7;
j = i >> 1;
Variabila j va primi valoarea 3. Reamintim că 7 / 2 = 3.
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 >>= pentru scrierea prescurtată a
instrucţ iunilor ce conţin operatorii << şi >>. Instrucţiunea
x = x << n ;
se scrie prescurtat
x <<= n ;
iar instrucţiunea
x = x >> n ;
se scrie prescurtat
27
x >>= n ;
Vom încheia acest paragraf cu un program care deplasează numere întregi şi afişază
valoarea lor în zecimal şi hexazecimal. Reamintim că, 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 baza 10 este implicită.
Vom exemplifica utilizarea operatorilor de deplasare deplasând două variabile întregi
la stânga şi la dreapta cu un bit şi apoi cu trei biţi şi vom afişa rezultatele în bazele 10
şi 16.
# include <iostream>
using namespace std;
/* uitlizarea operatorilor de deplasare */
int main()
{
int x = 10;
cout << “x = “ << dec << x << “ “ << hex << showbase << x << endl;
// deplasează variabila a la stanga cu un bit
x = x << 1;
cout << “x deplasat la stanga cu 1 bit = “
<< dec << x << “ “ << hex << showbase << x << endl;
int b = 50;
cout << “b = “ << dec << b << “ “ << hex << showbase << b << endl;
// deplasează variabila b la dreapta cu 3 pozitii
b = b >> 3;
cout << “b deplasat la dreapta cu 3 biti = “
<< dec << b << “ “ << hex << showbase << b << endl;
return 0;
}
Rezultatele rulării programului sunt prezentate în figura de mai jos.
1.16.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 de adevăr următoare
28
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 binari. Expresiile cu aceşti operatori logici au forma
următoare
Operaţiile sunt aplicate asupra fiecărei perechi de biţi din cei doi operanzi. Operatorul
~ este operator unar. Expresiile cu acest operator au forma următoare
Operatorul ~ complementează fiecare bit din expresia întreagă. Operatorii &, | şi ~
sunt operatori aritmetici.
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, la calculul valorii c = a & b, vom exemplifica doar calculul expresiei
0xf & 0xa = 0xa
Rezultatul se obţine conform calculului de mai jos.
0 1 0 1
0 1 0 1
& 1 1 1 1
Pentru calculul valorii e = a ^ b vom calcula expresia
0xf ^ 0xa = 0x5
Rezultatul se obţine conform calculului de mai jos
1 0 1 0
0 1 0 1
^ 1 1 1 1
Pentru calculul valorii d = a | b vom calcula expresia
0xf | 0xa = 0xf
29
Rezultatul se obţine conform calculului de mai jos
1 1 1 1 |
1 0 1 0
------------------
1 1 1 1
Expresia ~a are valoarea 0x0fff. De ce?
Limbajele C şi C++ au operatorii &=, |= şi ^= pentru scrierea prescurtată a
instrucţiunilor de atribuire ce conţin operatori &, | şi ^. De exemplu expresia
x = x & a ;
Se scrie prescurtat
x &= a ;
Pentru a selecta sau modifica anumiţi biţi dintr-un număr întreg se defineşte un şir de
biţi numit mască ce conţine 1 pe poziţiile biţilor ce trebuie selectaţi sau modificaţi.
• Pentru selectarea biţilor se efectuează operaţia & între numărul iniţial şi
mască. De exemplu, pentru a selecta biţii unei cifre hexazecimale vom utiliza
ca mască valorile 0x1, 0x2, 0x4 şi 0x8. Pentru a selecta cifrele octale dintr-un
număr întreg vom utiliza ca mască valorile 07, 070, 0700, etc. Pentru a selecta
cifrele hexazecimale dintr-un număr întreg vom utiliza ca mască valorile 0xf,
0xf0, 0xf00, etc.
• Pentru inversarea biţilor se efectuează operaţia ^ între număr şi mască.
• Pentru a pune biţii din număr la valoarea 1 se execută operaţia | între număr şi
mască.
• Pentru a pune biţii din număr la valoarea 0 se execuţă operaţia & între număr
şi ~mască.
Exemplu. Fie variabila întreagă a = 0x6dbc. Vrem ca în variabila întreagă b să
selectăm ultimul octet, apoi ultimii 10 biţi şi în final primii 4 biţi ai variabilei a.
Inversarea unor biţi din variabila a este prezentată în finalul exemplului.
// operatii logice la nivel de bit
# include <iostream>
using namespace std;
int main()
{
int a = 0x6dbc, b;
cout << “a = “ << hex << showbase << a << endl;
// selecteaza ultimul octet din a
b = a & 0xff;
cout << “ultimul octet din a = “ << hex << showbase << b << endl;
b = a & 0x3ff;
// selecteaza ultimii 10 biti din a
cout << “ultimii 10 biti din a = “ << hex << showbase << b << endl;
// selectează primii patru biti din a
b = a & 0xf000;
cout << “primii 4 biti din a = “ << hex << showbase << b << endl;
// pastram primii opt biti si inversam ultimii opt biti din a
b = a ^ 0x00ff;
cout << “ultimii 8 biti inversati din a = “ << hex << showbase << b << endl;
return 0;
}
30
Rezultatul rulării programului este cel de mai jos.
Să se explice de ce, în cazul al doilea, masca este 0x3ff;
31
2 Structuri de control fundamentale
2.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 încât 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, indiferent 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ă atâta timp cât expresia booleană condiţie are valoarea
adevărat.
• Operatorul do are forma
do
s;
while (condiţie)
In acest caz acţiunea s se execută atâta timp cât condiţie are valoarea adevărat.
32
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.
In toţi operatorii de mai sus, acţiunea este o instrucţiune simplă sau o
instrucţiune compusă (un bloc). In program, o instrucţiune compusă (un bloc),
este un grup de instrucţiuni inclus între acolade, { şi }. Intr-un bloc putem defini
variabile locale, al căror domeniu de existenţă este limitat la acel bloc.
Variabilele locale sunt create la intrarea în bloc şi sunt şterse la ieşirea din bloc.
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.
Exemplu. Algoritmul de calcul pentru n! care este definit ca

·
·
n
i
i n
1
!
s = 1;
for i = 1; i <= n; i = i + 1
s = s * i;
2.2 Expresii relaţionale
O operaţie definită pentru tipurile de date fundamentale este compararea.
Operatorii relaţionali ai limbajelor C şi C++ sunt, în ordinea priorităţilor
<, <=, >, >=
= =, !=
O expresie relaţională are următoarea formă
33
Rezultatul evaluării unei expresii relaţionale este fals sau adevărat (false sau true).
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. La scrierea valorii unei variabile booleene valoarea afişată este 0 sau 1, după
cum variabila are valoarea false, respectiv true. Pentru afişarea valorii booleene ca
true, respectiv false se utilizează manipulatorul boolalpha.
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 valorile true sau false.
Un exemplu de program este cel de mai jos.
#include <iostream>
using namespace std;
int main()
{
int a = 3, b = -2, c = 5;
bool r;
cout << " a = " << a << " b = " << b << " c = " << c << endl;
r = a + b == c;
cout << "a + b == c " << boolalpha << r << endl;
r = a - b >= 2;
cout << "a - b >= 2 " << boolalpha << r << endl;
r = a * a != - b;
cout << "a * a != - b " << boolalpha << r << endl;
return EXIT_SUCCESS;
}
34
Rezultatul rulării programului este prezentat în continuare.
In final reamintim că operatorul = este operator de atribuire, iar operatorul = = este
operator de comparare. Instrucţiunea
a = b
atribuie variabilei a valoarea variabilei b, iar expresia
a = = b
este o expresia relaţională care are ca rezultat valoarea true sau valoarea false.
2.3 Expresii booleene
Operatorii booleeni ai limbajelor C şi C++ sunt, în ordinea priorităţilor
!
&&
| |
care reprezintă operatorii nu (not), şi (and), respectiv sau (or). Operatorul nu 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:
nu (not) ¬
şi (and) ˄
sau (or) ˅
Exemple de expresii booleene şi scrierea lor sunt prezentate mai jos :
) || &( & ) (
||! !
|| & &
) || ( ! ) (
& &
c b a c b a
b a b a
c b a c b a
b a b a
b a b a
∨ ∧
¬ ∨ ¬
∨ ∧
∨ ¬

Pentru scrierea simplă a expresiilor booleene sunt importante două teoreme
numite legile lui DeMorgan
35
b a b a
b a b a
¬ ∨ ¬ · ∧ ¬
¬ ∧ ¬ · ∨ ¬
) (
) (
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
c b a c b a
c b a c b a
∧ ∧ · ∧ ∧
∨ ∨ · ∨ ∨
) (
) (
• distributivi
) ( ) ( ) (
) ( ) ( ) (
c a b a c b a
c a b a c b a
∧ ∨ ∧ · ∨ ∧
∨ ∧ ∨ · ∧ ∨
In final prezentăm prioritatea (precedenţa) şi asociativitatea operatorilor
Operator Asociativitate
[ ] ( ) stânga
++ -- + - ! ~ sizeof (tip) dreapta
* / % stânga
+ - stânga
<< >> stânga
< <= > >= stânga
= = != stânga
& stânga
^ stânga
| stânga
&& stânga
| | stânga
= += -= *= /= %= <<= >>= &= |= ^= stânga
2.4 Operatorul if
Acest operator execută o anumită instrucţiune în funcţie dacă o anumită condiţie este
îndeplinită sau nu. Forma operatorului if este
if(condiţie)
S1;
else
S2;
Modul de execuţie al operatorului if este următorul:
1. se evaluează condiţia,
2. dacă valoarea condiţiei este diferită de zero se execută instrucţiunea S1 altfel
instrucţiunea S2.
Operatorul if poate avea şi o formă simplificată
if(expresie)
S;
în care se execută instrucţiunea S când condiţia are valoare diferită de zero.
36
Ca prim exemplu vom scrie un program care să testeze dacă un întreg este divizibil cu
altul. Cei doi întregi se citesc de la tastatură. Pentru a testa dacă un întreg n este
divizibil cu un alt întreg d, vom calcula expresia n % d, restul împărţirii lui n la d.
# include <iostream>
using namespace std;
int main()
{
int n, d;
cout << “introduceti doi intregi : “;
cin >> n >> d;
if(n % d == 0)
cout << n << “ este divizibil cu “ << d << endl;
else
cout << n << “ nu este divizibil cu “ << d << endl;
return 0;
}
Un exemplu de execuţie a programului este dat mai jos.
Un alt exemplu este calculul maximului a două numere întregi citite de la tastatură.
// calculul maximului a doua numere intregi
# include <iostream>
using namespace std;
int main()
{
int n, m;
cout << “introduceti doi intregi : “ << endl;
cin >> n >> m;
cout << “ maximul dintre “ << m << “ si “ << n << “ este ”;
if(n > m)
cout << n << endl;
else
cout << m << endl;
return 0;
}
Un exemplu de rulare a programului este prezentat mai jos.
37
Instrucţiunile din expresia operatorului if pot fi instrucţiuni simple sau
instrucţiuni compuse. O instrucţiune compusă, (un bloc), este un grup de
instrucţiuni cuprinse între acolade, { şi }.
Exemplu. Ordonarea descrescătoare a două numere întregi citite de la tastatură.

// ordonarea descrescatoare a două numere intregi citite de la tastatura
# include <iostream>
using namespace std;
int main()
{
int x, y;
cout << “introduceti doi intregi : “;
cin >> x >> y;
if(x < y)
{
int temp = x;
x = y;
y = temp;
}
cout << «numerele ordonate descrescator : « << x << “,” << y << endl;
return 0;
}
Un exemplu de execuţie a programului este cel de mai jos.
In acest exemplu, instrucţiunile ce permută variabilele x şi y constituie un bloc,
executat când x < y. Blocul defineşte variabila locală temp.
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).
38
2.5 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ă. Forma operatorului switch este următoarea
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.
Pentru ieşirea din instrucţiunea switch se utilizează instrucţiunea break.
Exemplu. Vom citi două numere întregi în două variabile x şi y şi un operator +, -, *, /
sau % într-o variabilă tip char şi vom calcula rezultatul operaţiei corespunzătoare.
Programul este următorul.
// calculator cu numere intregi
#include <iostream>
using namespace std;
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 << oper << y << "=" << x + y;
break;
case ‘-‘ :
cout << x << oper << y << "=" << x – y;
break;
case ‘*’ :
cout << x << oper << y << "=" << x * y;
break;
case ‘/’ :
cout << x << oper << y << "=" << x / y;
break;
case ‘%’ :
cout << x << oper << y << "=" << x % y;
break;
default :
39
cout << “eroare”;
break;
}
cout << endl;
return 0;
}
Menţionăm că instrucţiunile corespunzătoare etichetelor sunt urmate de instrucţiunea
break ce asigură ieşirea din operatorul switch. Rezultatul rulării programului este cel
de mai jos.
2.6 Operatorul ?
Acest operator este o formă simplificată a operatorului if. Forma lui este
expresie ? S1 : S2;
Dacă expresie are o valoare diferită de zero, se execută instrucţiunea S1, altfel
instrucţiunea S2.
Vom rezolva din nou problema divizibilităţii a două numere.
# include <iostream>
using namespace std;
int main()
{
int k, m;
cout << “intoduceti doi intregi : ”;
cin >> k >> m;
cout << k << ((k % m)? " nu ": "") << " este divizibil cu " << m
<< endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Instrucţiunea
((k % m)? " nu ": "")
are ca rezultat şirul de caractere “ nu “ când k nu divide m sau şirul “” în caz contrar.
2.7 Operatorul while
Operatorul while execută repetat un grup de instrucţiuni. Forma operatorului while
este
while (condiţie)
S;
40
instrucţiunea S se execută atâta timp cât condiţia este diferită de zero (are valoarea
adevărat). Instrucţiunea S poate fi o instrucţiune simplă sau o instrucţiune
compusă, (un bloc), formată dintr-un grup de instrucţiuni, cuprinse între
acolade, { şi }.
Vom exemplifica utilizarea acestui operator calculând suma elementelor unui vector a
cu patru componente. Elementele vectorului a au indicii 0, 1, 2 şi 3.

·
·
3
0 i
i
a s
Vom utiliza 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
i
a s s + ·
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>
using namespace std;
/* 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;
}
Instrucţiunile
s = s + a[i];
i = i + 1;
constituie un bloc ce este executat repetat, cât timp i < 4.
Putem rescrie partea de calcul din program astfel
s = 0;
i = 0;
while(i < 4)
{
s += a[i];
41
i++;
}
sau
s = 0;
i = 0;
while(i < 4)
{
s += a[i++];
}
In următorul exemplu vom calcula media unui şir de numere reale citite de la
tastatură. Fie n variabila întreagă în care vom citi lungimea şirului de numere, i o
variabilă în care numărăm valorile citite şi x o variabilă în care citim câte un număr.
Variabila suma va conţine suma numerelor deja citite. Programul pseudocod este
următorul.
i = 1;
suma = 0;
read n;
while (i <= n)
{
read x;
suma = suma + x;
i = i + 1;
}
media = suma / n;
write media;
Programul corespunzător este următorul
// calculul mediei unui sir de numere reale citite de la tastatura
# include <iostream>
using namespace std;
int main()
{
int n, i = 1;
double suma = 0, media, x;
cout << "cate numere ? ";
cin >> n;
// calculul sumelor partiale
while(i <= n)
{
cin >> x;
suma = suma + x;
i = i + 1;
}
// calculul mediei
media = suma / n;
42
cout << "media este : " << media << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
2.8 Operatorul do-while
Operatorul do-while este tot un operator repetitiv. Acest operator are forma
do
S
while (condiţie);
Instrucţiunea S se execută atâta timp cât condiţia este diferită de zero (are valoarea
adevărat). Instrucţiunea S poate fi o instrucţiune simplă sau o instrucţiune
compusă, (un bloc), formată dintr-un şir de instrucţiuni între acolade, { şi }.
Exemplu. Vom calcula valoarea 5! care se defineşte ca

·
·
5
1
! 5
i
i
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>
using namespace std;
/* calculul valorii 5! */
int main()
{
int i, n;
i = 1;
n = 1;
do
43
{
n = n * i;
i = i + 1;
}
while(i < 6);
cout << “5! are valoarea “ << n << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Menţionăm că, deoarece dorim să executăm repetat mai multe instrucţiuni cu
instrucţiunea do-while
n = n * i;
i = i + 1;
scriem aceste instrucţiuni ca o instrucţiune compusă, între acolade, { şi }.
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ă.
2.9 Operatorul for
Operatorul for permite execuţia repetată a unui grup de instrucţiuni cu modificarea
unei variabile de control după fiecare iteraţie. Operatorul for are forma
for(expresie1; condiţie; expresie3)
44
S
expresie1 are rolul de a iniţializa variabila de control. Operatorul for execută repetat
instrucţiunea S, atât timp cât condiţie este diferită de zero. expresie3 are rolul de a
modifica valoarea variabilei de control după fiecare execuţie. Instrucţiunea S,
executată repetat, poate fi o instrucţiune simplă sau o instrucţiune compusă, (un
bloc), ce constă dintr-un grup de instrucţiuni între acolade, { şi }.
Exemplu. Vom calcula valoarea expresiei x x e
x
+ +2 pentru x cuprins între 1 şi 2
cu pasul 0.2. O primă variantă de program pseudocod este următorul
for (i = 0; i < 6; i = i + 1)
{
x = 1 + 0.2 * i
y = x x e
x
+ +2
}
Vrem ca rezultatul să fie afişat în două coloane cu antetul x şi y, ca mai jos.
x y
…. ….
…. ….
Programul este următorul
# include <iostream>
# include <cmath>
# include <iomanip>
using namespace std;
int main()
{
int i;
double x, y;
cout << setw(4) << "x" << '\t' << setw(5) << "y" << endl;
for(i = 0; i < 6; i = i + 1)
{
x = 1 + 0.2 * i;
y = exp(x) + 2 * x + sqrt(x);
cout << setw(4) << x << '\t' << setw(5) << y << endl;
}
return 0;
}
Pentru a scrie antetul tabelului cu valori am utilizat instrucţiunea
cout << setw(4) << "x" << '\t' << setw(5) << "y" << endl;
care scrie şirurile de caractere x şi y pe câte 4, respectiv 5 coloane, separate de
caracterul ‘\t’ (tab). In program rezultatele sunt scrise de asemenea separate de tab.
Pentru a prescrie numărul de coloane al unui câmp se utilizează funcţia setw() din
biblioteca <iomanip>. Rezultatul rulării programului este cel de mai jos.
45
Instrucţiunile dintre acolade formează instrucţiunea compusă executată repetat de
instrucţiunea for.
Menţionăm că instrucţiunea for se putea scrie astfel
for(i = 0; i < 6; i++)
sau
for(i = 0; i < 6; ++i)
Este posibil să definim variabila de control chiar în instrucţiunea for, în
expresie1 din definiţia instrucţiunii for. De exemplu, în loc de instrucţiunile
int i;
for (i = 0; i < 6; i++)
putem scrie
for(int i = 0; i < 6; i++)
Variabilele x şi y sunt utilizate doar în instrucţiunea compusă executată de for. Ele pot
fi definite în interiorul acestei instrucţiuni compuse (bloc). In acest fel programul este
mai clar.
int main()
{
cout << setw(4) << "x" << '\t' << setw(5) << "y" << endl;
for(int i = 0; i < 6; i = i + 1)
{
double x, y;
x = 1 + 0.2 * i;
y = exp(x) + 2 * x + sqrt(x);
cout << setw(4) << x << '\t' << setw(5) << y << endl;
}
return 0;
}
Reamintim că, variabilele definite într-un bloc există doar în acel bloc. Ele sunt
create la intrarea în bloc şi şterse la ieşirea din bloc.
Un alt mod de a calcula valorile expresiei de mai sus este de a utiliza instrucţiunea
for(x = 1; x <= 2; x = x + 0.2)
care modifică pe x de la 1 la 2 cu pasul 0.2. Programul este următorul
# include <iostream>
# include <cmath>
# include <iomanip>
using namespace std;
int main()
{
double x;
46
cout << setw(4) << "x" << '\t' << setw(5) << "y" << endl;
for(x = 1; x <= 2; x = x + 0.2)
{
double y;
y = exp(x) + 2 * x + sqrt(x);
cout << setw(4) << x << '\t' << setw(5) << y << endl;
}
return 0;
}
In loc de instrucţiunile
double x;
for (x = 1; x <= 2; x = x + 0.2)
putem scrie
for (double x = 1; x <= 2; x = x + 0.2)
In acest caz, variabila de control definită în instrucţiunea for există doar în interiorul
instrucţiunii for. Ea poate fi utilizată doar în instrucţiunile executate repetat de for.
Exemplul următor calculează suma componentelor pare şi impare ale unui vector x cu
şase elemente. Programul pseudocod pentru calculul sumelor este următorul
s1 = 0;
s2 = 0;
for( i = 0; i < 6; i=i+1)
if (i % 2 = = 0)
s1 = s1 +
i
x
else
s2 = s2 +
i
x
Elementul
i
x
este adunat la suma componentelor pare s1 sau impare s2 după cum
expresia i % 2 are valoarea zero sau nu.
Programul este următorul.
#include <iostream>
using namespace std;
/* calculul sumei componentelor pare si impare ale unui vector */
int main()
{
float s1 = 0, s2 = 0;
float x[6] ;
cout << "Dati componentele vectorului"<< endl;
for(int i = 0; i < 6; i = i + 1)
{
cin >> x[i];
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;
47
return 0;
}
Rezultatul rulării programului este cel de mai jos.
In acest program, variabila i poate fi utilizată doar în instrucţiunile executate repetat
de for, între { şi }.
Vom încheia acest paragraf cu un program care să calculeze suma a doi vectori cu
câte trei componente reale fiecare, iniţializaţi la definirea lor în program.
/* calculul sumei a doi vectori */
# include <iostream.h>
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, do-while sau switch. Instrucţiunea
continue trece la următoarea iteraţie a instrucţiunii for, while sau do-while.
Exemplu. Vom citi n numere reale de la tastatură şi vom calcula media celor pozitive.
La fiecare iteraţie a instrucţiunii for, vom citi câte un număr şi vom testa dacă este
negativ. Dacă da, vom trece la următoarea iteraţie cu instrucţiunea continue, în caz
contrar vom prelucra numărul citit.

#include <iostream>
using namespace std;
int main()
{
int n, m = 0, cnt;
float x, sum = 0, media;
cout <<"media numerelor pozitive" << endl;
cout << "cate numere ? ";
cin >> n;
48
for(cnt = 1; cnt <= n; ++cnt)
{
cout << "dati un numar : ";
cin >> x;
if(x < 0)
continue;
sum = sum + x;
m = m + 1;
}
media = sum / m;
cout << "media = " << media << endl;
return 0;
}
Rezultatul rulării programului este prezentat mai jos.
2.10 Operatorul ,
Operatorul , are următoarea formă
expresie1, expresie2
Rezultatul operatorului este expresie2, rezultatul evaluării expresie1 se neglijază.
Operatorul , se poate utiliza oriunde în mod normal se utilizează o singură expresie,
dar sunt necesare două expresii. Un exemplu este instrucţiunea for, când este necesar
să iniţializăm două expresii, exp1 şi exp2.
for(exp1, exp2; condiţie; exp3)
Exemplu. Vom testa dacă un cuvânt este palindrom (este acelaşi cuvânt citit în ambele
sensuri). In program vom citi un şir de caractere într-un vector de caractere str. Fie n
numărul de caractere citite. Vom compara literele din prima jumătate a cuvântului cu
cele din a doua jumătate, utilizând două variabile, una ce creşte de la zero la (n - 1) /
2 , iar alta ce scade de la (n – 1). Vom iniţializa cele două variabile în instrucţiunea for
cu operatorul ,. Pentru a calcula lungimea unui şir de caractere vom utilize funcţia
strlen() cu prototipul
int strlen(char[]);
din biblioteca <cstring> . Programul este următorul:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char str[80];
int cntup, cntdn, n, nst;
bool pn = true;
49
cout << "introduceti un cuvant " << endl;
cin >> str;
n = strlen(str);
nst = (n - 1) / 2;
// initializarea a doua variabile cu operatorul ,
for(cntup = 0, cntdn = n - 1; cntup <= nst; cntup++, cntdn--)
if(str[cntup] != str[cntdn])
pn = false;
if(pn)
cout << str << " este palindrom" << endl;
else
cout << str << " nu este palindrom" << endl;
return 0;
}
Rezultatul rulării programului este prezentat mai jos.
50
3 Funcţii
Limbajele de programare au funcţii predefinite ce corespund funcţiile matematice
uzuale : sin, cos, exp, log, log10, etc.
In plus, limbajele de programare permit definirea de funcţii care să realizeze anumite
calcule. Atunci când un grup de instrucţiuni se utilizează în mai multe locuri din
program, aceste instrucţiuni se plasează într-o funcţie care este apelată unde este
necesară. In acest fel programele mari pot fi formate din module. Descompunerea
unui program în module are avantaje. Funcţiile pot fi grupate în biblioteci şi utilizate
în diverse programe. Funcţiile pot fi testate separat şi programele sunt mai clare.
Un program constă din una sau mai multe funcţii. Una dintre funcţii are numele main
şi execuţia programului începe cu această funcţie.
3.1 Funcţii standard
Limbajele C şi C++ au multe funcţii standard, matematice, de prelucrare a
caracterelor, etc. Mai înainte am prezentat cele mai utile funcţii matematice. Vom
prezenta funcţiile de manipulare a caracterelor.
3.1.1 Funcţii C standard de manipulare a caracterelor
Funcţiile ce manipulează caractere tip ASCII sunt un exemplu de funcţii standard ale
limbajelor C şi C++. In cazul programelor în limbajul C++, prototipurile acestor
funcţii sunt definite în bibliotecile <cstdlib>, <cctype> şi <cstring>, în cazul
limbajului C prototipurile acestor funcţii sunt definite în bibliotecile <stdlib.h>,
<ctype.h> şi <string.h>.
Funcţiile următoare testează tipul unui caracter. Prototipurile lor se află în bibliotecile
<cctype> şi <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 isupper (int c); Test dacă un caracter este literă mare
int isspace (int c); Test dacă un caracter este spaţiu (‘ ‘, ‘\n’, ‘\t’)
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
Exemple de utilizare a acestor funcţii sunt prezentate în tabelul de mai jos.
tolower(‘A’) ‘a’
tolower (‘a’) ‘a’
tolower (‘*’) ‘*’
51
O funcţie utilă la prelucrarea şirurilor este strlen() care dă lungimea unui şir de
caractere, fără caracterul ‘\0’ terminal. Prototipul acestei funcţii este
int strlen(char[]);
şi se găseşte în bibliotecile <cstring> şi <string.h>.
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 "abc" < "bcd" deoarece ‘a’ < ‘b’
strcmp(“xyz”, “xyz”) = 0 şirurile sunt egale
strcmp(“abcd”, “abc”) > 0 şirul “abcd” este mai lung
Prototipul acestei funcţii se găseşte în bibliotecile <cstring> şi <string.h>.
Vom scrie o funcţie care să numere literele mici, literele mari şi cifrele dintr-un şir
citit de la tastatură. Programul este următorul.
# include <iostream>
# include <cstring>
# include <cctype>
using namespace std;
// calculul numarului de litere mici, litere mari si cifre dintr-un sir
int main()
{
char x[50];
int i, n, cnt;
cout << “introduceti un sir”;
cin >> x;
// calculeaza lungimea sirului
n = strlen(x);
// numara literele mici din sir
cnt = 0;
for(i = 0; i < n; i++)
if(islower(x[i]))
cnt = cnt + 1;
cout << “sirul contine : “ << cnt << “ litere mici” << endl;
// numara literele mari din sir
cnt = 0;
for(i = 0; i < n; i++)
if(isupper(x[i]))
cnt++;
cout << “sirul contine : “ << cnt << “ litere mari” << endl;
// numara cifrele din sir
cnt = 0;
for(i = 0; i < n; i++)
if(isdigit(x[i]))
cnt = cnt + 1;
cout << “sirul contine : “ << cnt << “ cifre” << endl;
return 0;
52
}
Rezultatul rulării programului este următorul.
Funcţia strlen() are un parametru şi calculează o valoare. Apelarea ei se face scriind
numele funcţiei urmat în paranteze de argumentul funcţiei într-o expresie. Funcţia
strlen() este apelată în membrul drept al unei instrucţiuni de atribuire. Apelarea
funcţiei şi transmiterea valorii calculate de funcţie în punctual din program unde este
apelată este prezentată schematic mai jos.
Exerciţiu. Să se scrie un program care să convertească literele mici ale unui şir citit de
la tastatură în litere mari.
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 bibliotecile <cstdlib> şi <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 <cstdlib>
# include <iostream>
using namespace std;
int main()
{
char s1[] = “-123”;
char s2[] = “1.22e-1”;
int x;
double y, z;
// scrie sirurile
cout << “sirul s1 : ” << s1 << endl
<< “sirul s2 : ” << s2 << endl;
// converteste sirurile in numere
x = atoi(s1);
y = atof(s2);
z = x * y;
53
// scrie numerele si produsul lor
cout << “x = “ << x << “ y = “ << y << endl;
cout << “x * y = “ << z << endl;
return 0 ;
}
Rezultatul rulării programului este cel de mai jos.
3.2 Definirea funcţiilor
O funcţie calculează o valoare pe baza valorilor argumentelor sale, furnizate de un
program care apelează funcţia. Orice funcţie are un nume şi parametri sau argumente.
Definiţia unei funcţii este
tip numefuncţie (lista de parametri)
{
corpul funcţiei
}
Prima linie conţine :
• tip este tipul valorii calculate de funcţie (se mai numeşte tipul funcţiei),
• numefuncţie este numele funcţiei ales de programator,
• lista de parametri (argumente ale funcţiei) 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.
Corpul funcţiei este format dintr-o secvenţă de instrucţiuni între acolade, { şi }, ce
descriu calculele efectuate de funcţie. Valoarea calculată de funcţie este transmisă în
punctul de apelare a funcţiei cu instrucţiunea return ce are forma
return expresie;
Tipul acestei expresii trebuie să fie acelaşi cu tipul funcţiei. Instrucţiunea return
termină totodată şi execuţia funcţiei.
Exemplu. Să definim o funcţie care să calculeze suma a două numere reale de tip
double.
Numele funcţiei va fi suma, parametrii ei vor fi două variabile de tip double, x şi y,
valoarea calculată de funcţie va fi de tip double. In corpul funcţiei vom defini o
variabilă de tip double, z, în care vom calcula rezultatul.
/* functie ce calculeaza suma a doua numere reale */
double suma (double x, double y)
{
double z ;
z = x + y ;
return z ;
}
54
Parametri x şi y din definiţia funcţiei suma sunt parametri de intrare, ei conţin valori
ce sunt utilizate de funcţie în calcule.
Variabilele definite într-o funcţie există doar în timpul execuţiei funcţiei. Din această
cauză, ele se numesc variabile locale. Variabila z din funcţia de mai sus este locală.
Variabilele nu pot fi utilizate în afara funcţiei.
Apelarea unei funcţii se face scriind numele funcţiei urmat de lista de parametrii
inclusă în paranteze, ca termen într-o expresie. Parametrii din listă sunt separaţi
de virgule. Parametrii de apelare a funcţiei se numesc parametri actuali sau
argumente. Parametrii de intrare actuali ai unei funcţii pot fi orice constante, variabile
sau expresii.
Funcţiile sunt recursive, adică o funcţie se poate apela pe ea însăşi.
Vom utiliza funcţia definită mai sus, în cadrul funcţiei main pentru calculul sumei a
două numere reale, citite de la tastatură. Vom defini două variabile de tip double, a şi
b, ce vor conţine valorile citite de la tastatură şi o variabilă c de tip double, ce va
conţine rezultatul. a, b şi c sunt variabile locale ale funcţiei main.
int main()
{
double a, b ;
cout << " a = " ; cin >> a ;
cout << " b = " ; cin >> b ;
double c ;
c = suma(a, b) ;
cout << " a + b = " << c << endl ;
return 0 ;
}
Rezultatul rulării programului este sel de mai jos.
Funcţia suma este utilizată în membrul drept al unei instrucţiuni de atribuire, deoarece
calculează o valoare asociată numelui. Ea este apelată în instrucţiunea
c = suma(a, b);
Reamintim că parametrii x şi y din definiţia funcţiei sunt parametrii de intrare. In
consecinţă, putem apela funcţia suma cu parametrii orice constante, variabile sau
expresii, de exemplu
c = suma(1.73, -2.22);
sau
c = suma(1.73 * 2.5 + a, a – b + 2);
Menţionăm că numele parametrilor actuali (din instrucţiunea de apelare a funcţiei) pot
fi diferite de cele ale parametrilor formali din definiţia funcţiei.
Pasarea parametrilor către funcţii se face prin intermediul unei stive. La apelarea
funcţiei, se alocă spaţiu într-o stivă pentru parametrii funcţiei şi pentru variabilele
locale ale funcţiei. In spaţiul alocat pentru parametrii funcţiei se pun valorile acestor
parametri. Funcţia poate utiliza valorile acestor parametrii în calcule. Valoarea
calculată de funcţie se transmite în punctul de apelare de către instrucţiunea return.
55
Menţionăm că este posibil ca o funcţie să nu calculeze nici o valoare. In acest caz
tipul ei este void, iar instrucţiunea return are forma
return;
Menţionăm că, în general, o funcţie poate calcula mai multe valori care să fie
transmise în programul apelant, asociate unor parametri. In acest caz o parte din
parametrii funcţiei sunt parametri de intrare şi ei conţin valorile ce vor fi prelucrate de
funcţie, ceilalţi parametri vor conţine valorile calculate de funcţie ,ce sunt transmise în
programul apelant şi vor fi numiţi parametri de ieşire. Modul de definire a
parametrilor de ieşire va fi arătat ulterior.
3.3 Prototipuri de funcţii
La întâlnirea unei instrucţiuni în care se apelează o funcţie, compilatorul trebuie să
cunoască prototipul funcţiei adică numărul şi tipurile parametrilor şi tipul rezultatului
(tipul valorii calculate de funcţie). Compilatorul utilizează numărul şi tipurile
parametrilor pentru a verifica dacă funcţia este apelată corect. Tipul rezultatului este
utilizat pentru a converti rezultatul funcţiei dacă funcţia este un termen al unei
expresii. In cazul în care funcţia este definită în program înainte a fi apelată,
compilatorul cunoaşte informaţiile necesare din linia de definiţie a funcţiei. Acesta
este cazul programului de mai sus. Este posibil ca într-un program să definim funcţii
şi după ce ele au fost utilizate. In acest caz compilatorul nu cunoaşte prototipul
funcţiei în momentul apelării ei şi este obligatoriu să definim prototipul funcţiei
înainte de a o utiliza, astfel încât compilatorul poate verifica dacă apelarea funcţiei
este corectă (tipul şi numărul parametrilor, etc.).. Prototipul unei funcţii are
următoarea definiţie
tip nume(lista de parametri);
In prototip putem să identificăm doar tipurile parametrilor.
In cazul funcţiei suma definită anterior prototipul este
double suma(double, double);
sau
double suma(double x, double y) ;
De remarcat că prototipul unei funcţii este chiar linia de definiţiei a funcţiei
urmată de caracterul ;.
Prototipurile funcţiilor matematice standard sunt definite în bibliotecile
standard. De exemplu, biblioteca <iostream> conţine prototipurile funcţiilor intrare /
ieşire pentru fişierele standard ale sistemului (tastatura şi monitorul), biblioteca
<cmath> conţine prototipurile funcţiilor matematice, etc. Aceste biblioteci sunt
semnalate compilatorului cu directiva # include.
Exemplu. Vom defini o funcţia max() care să calculeze maximul a două numere de tip
double. Definiţia funcţie max va fi scrisă după definiţia funcţiei main(). In acest caz
trebuie să definim prototipul funcţiei max() înainte de definiţia funcţiei main().
#include <iostream>
using namespace std;
// prototipul functiei max(). Functia max() este apelata
// in functia main(), dar este definite ulterior
double max (double, double) ;
// definitia functiei main()
56
int main()
{
double a, b, c ;
cout << " a = " ; cin >> a ;
cout << " b = " ; cin >> b ;
c = max(a, b) ;
cout << " max(a, b) = " << c << endl ;
return 0 ;
}
// definitia functiei max()
double max (double x, double y)
{
if( x > y)
return x ;
else
return y ;
}
Rezultatul rulării programului este cel de mai jos.
De remarcat că, o funcţie, poate avea mai multe instrucţiuni return.
3.4 Compilarea separată a funcţiilor
Funcţiile definite de programator pot fi scrise în fişiere separate. Aceste fişiere vor fi
compilate separat. Avantajul compilării separate a funcţiilor este acela că ele pot fi
testate separat, înainte de a fi utilizate. Funcţiile definite în fişiere separate sunt
semnalate compilatorului, în fişierele în care sunt utilizate cu directive include, la fel
ca şi funcţiile standard. Forma directivei include este
#include “nume fisier”
Numele fişierului este scris aici între ghilimele. De regulă, aceste fişiere au extensia h.
Programul anterior este rescris cu funcţia max() definită în fişierul max.h. Cele două
fişiere sunt prezentate mai jos.
Fişierul main.cpp
#include <iostream>
using namespace std;
#include "max.h"
int main(int argc, char *argv[])
{
double a, b, c ;
57
cout << " a = " ; cin >> a ;
cout << " b = " ; cin >> b ;
c = max(a, b) ;
cout << " max(a, b) = " << c << endl ;
return 0;
}
Fişierul max.h
// definitia functiei max()
double max (double x, double y)
{
if( x > y)
return x ;
else
return y ;
}
3.5 Funcţii cu parametri tablouri
Funcţiile pot avea ca parametri tablouri, vectori, matrice, etc. Am spus mai înainte că
pasarea parametrilor către funcţii se face prin intermediul unei stive. Prin definiţie,
atunci când un parametru este un tablou, în stivă se pune adresa tabloului. In
consecinţă, parametrul formal tablou este un parametru de ieşire. Dacă elementele
tabloului sunt modificate în funcţie, modificarea apare şi în programul ce a apelat
funcţia.
In linia de definiţie a unei funcţii, parametrii formali tablouri au forma
tip numetablou [dim1] [dim2] … [dimn]
unde tip este tipul elementelor, iar dim1, dim2, …, dimn sunt numere, dimensiunile
tabloului. Fie de exemplu o funcţie cu numele fun cu doi parametri formali, o matrice
A cu două linii şi trei coloane cu elemente tip double şi un vector X cu şapte
componente întregi iar rezultatul funcţiei de tip double. Linia de definiţie a funcţiei
are forma
double fun (double A[2] [3], int X[7])
La apelarea unei funcţii cu parametri tablouri, parametrul actual tablou este numele
tabloului, fără paranteze sau indici.
La utilizarea unui element al unui tablou, se calculează adresa acestui element relativă
la începutul tabloului. Fie de exemplu matricea A[2] [3] de mai sus. Elementele
matricei au indicii din tabelul de mai jos.
(0, 0) (0, 1) (0, 2)
(1, 0) (1, 1) (1, 2)
In memorie, elementele matricei sunt memorate pe linii, ca mai jos.
(0, 0) (0, 1) (0, 2) (1, 0) (1, 1) (1, 2)
Poziţia primului element a
00
este 0, poziţia elementului a
01
este 1, etc. Poziţia
elementului a
ij
este dată de formula
i * 3 + j
58
De exemplu, a
10
ocupă poziţia 3, a
12
ocupă poziţia 5, a
01
ocupă poziţia 1. In general, la
o matrice cu n linii şi m coloane, poziţia elementului a
ij
este
i * m + j
Se vede că, în această formulă, nu intervine prima dimensiune a tabloului. In
consecinţă, în linia de definiţie a unei funcţii, parametrii formali tablouri au şi forma
tip numetablou [] [dim2] … [dimn]
în care prima dimensiune nu se specifică. De exemplu, linia de definiţie a funcţie din
exemplul de mai sus este
double fun (double A[] [3], int X[])
Vom considera pentru început parametrii formali vectori. Un parametru formal vector
are forma
tip numevector[]
sau
tip numevector[dim]
unde dim este numărul de componente ale vectorului.
Exemplu. Vom scrie o funcţie care să calculeze suma componentelor unui vector a cu
elemente numere reale de tip float. Parametrii funcţiei vor fi vectorul a şi dimensiunea
sa. Valoarea calculată va fi ataşată numelui funcţiei. Vom presupune că, la apelarea
funcţiei elementele vectorului sunt iniţializate. După cum am spus mai sus, parametrul
formal vector are forma
float a[]
Definiţia funcţiei va fi precedată de un comentariu ce descrie parametrii funcţiei, (de
intrare, de ieşire), condiţii asupra parametrilor de intrare, valoarea calculată de funcţie,
etc.
Definiţia funcţiei este următoarea
/* funcţie ce calculeaza suma componentelor unui vector
float suma(float a[], int n);
Parametri de intrare
a – vector
n – dimensiunea vectorului a
Iesire
suma – suma componentelor vectorului a
Preconditie :
elementele vectorului a sunt initializate
*/
float suma(float a[], int n)
{
int i;
float s = 0;
// calculeaza suma componentelor
for (i = 0; i < n; i = i + 1)
s = s + a[i];
return s;
}
O funcţie, odată scrisă, trebuie testată. Un program simplu ce testează funcţia de mai
sus este următorul.
59
/* testarea functiei suma */
int main()
{
float x[5] = {11.3, -2.67, 0.34, -2.5, 14};
float z;
int i;
// 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;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Funcţia este apelată cu instrucţiunea
z = suma(x, 5);
După cum am spus mai sus, când un parametru formal al unei funcţii este vector, la
apelarea funcţiei parametrul actual este numele vectorului, fără indici sau paranteze
drepte.
Componentele vectorului sunt afişate pe ecran 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. Reamintim că, este posibil de a scrie definiţia
unei funcţii după funcţia main(). In acest caz, înainte de definiţia funcţiei main()
trebuie să scriem prototipul funcţiei, cum s-a arătat mai sus.
Exemplu. Căutarea binară într-o listă ordonată crescător. Fie un vector x cu n
elemente reale ordonate crescător. Vrem să testăm dacă valoarea z se află printre
componentele vectorului. Fie indicii ls şi ld domeniului de căutare în vector. Iniţial ls
= 0 şi ld =n-1. Metoda căutării binare constă în testarea elementului de la mijloc al
vectorului cu indicele i=(ls+ld)/2 şi restrângerea domeniului de căutare dacă valoarea
z nu a fost găsită. Domeniul de căutare este restrâns astfel:
dacă x[i] < z atunci ls = i + 1
dacă x[i] > z atunci ld = i – 1
La fel ca la funcţia precedentă, definiţia funcţiei este precedată de un comentariu ce
descie funcţia, parametrii, condiţiile asupra parametrilor, etc.
Funcţia ce implementează acest algoritm este următoarea
/*
Cautarea binara
60
int find (double x[], int n, double z)
Parametri de intrare :
x - vector cu elemente sortate crescator
n - dimensiunea lui x
z - valoarea cautata
find = indicele elementului z in lista daca exista,
= -1 daca z nu exista in lista.
Preconditie :
x[0] <= x[1] <= ... <= x[n - 1]
*/
int find (double x[], int n, double z)
{
int ls, ld, i;
ls = 0;
ld = n - 1;
while(ls <= ld)
{
i = (ls + ld) / 2;
if(x[i] == z)
return i;
if(x[i] < z)
ls = i +1;
else
ld = i - 1;
}
return -1;
}
Un program de testare a funcţiei este următorul. Reamintim că, la apelarea funcţiei,
parametrul vector este scris ca numele tabloului, fără indici sau paranteze drepte.
int main()
{
double a[6] = {-2.35, 1.77, 3.14, 4.2, 5.12, 7.09};
double z;
int k;
z = 4.2;
k = find(a, 6, z);
if(k < 6)
cout << "elementul " << z << " are rangul " << k << endl;
else
cout << "elementul " << z << " nu este in lista" << endl;
return 0;
}
Tabela de mai jos prezintă iteraţiile algoritmului pentru z = 4.2.
iteraţia ls ld i x[i]
61
1 0 5 2 3.14
2 3 5 4 5.12
3 3 3 3 4.2
Rezultatul rulării programului este cel de mai jos.
Reamintim că primul element din vector are indicele zero.
O funcţie poate avea ca parametri şi matrice, etc. In acest caz, în definiţia funcţiei
parametrul trebuie să conţină şi numărul de linii şi de coloane al matricei, ca în
exemplul de mai jos.
Vom încheia acest paragraf cu un program ce defineşte funcţii pentru lucrul cu
matrice:
• funcţie ce citeşte o matrice de la tastatură,
• o funcţie ce scrie elementele unei matrice pe ecran,
• o funcţie ce calculează suma a două matrice.
Matricele pot avea oricâte linii şi de coloane, ce vor fi prescrise în program cu două
directive define. Fiecare funcţie este precedată de un comentariu ce descrie parametrii
şi operaţia realizată de funcţie.
#include <iostream>
#include <iomanip>
# define NLIN 2
# define NCOL 2
using namespace std;
/*
funcţie ce citeste elementele unei matrice
void rdmat(double a[NLIN][NCOL])
Parametrii :
NLIN – numarul de linii
NCOL – numarul de coloane
Paramertii de iesire:
a – matrice cu NLIN linii si NCOL coloane
Elementele matricei se introduc pe linii.
*/
void rdmat(double a[NLIN][NCOL])
{
int i, j;
for(i = 0; i < NLIN; i++)
for(j = 0; j < NCOL; j++)
cin >> a[i][j];
return;
}
62
/*
functie ce afisaza elementele unei matrice pe linii
Parametrii de intrare:
NLIN – numarul de linii
NCOL – numarul de coloane
a – matrice cu NLIN linii si NCOL coloane
*/
void wrmat(double a[NLIN][NCOL])
{
int i, j;
for(i = 0; i < NLIN; i++){
for(j = 0; j < NCOL; j++)
cout << setw(6) << a[i][j] << " ";
cout << endl;
}
return;
}
/*
functie ce aduna doua matrice cu elemente tip double
Parametrii de intrare:
a – matrice de intrare cu NLIN linii şi NCOL coloane
b – matrice de intrare cu NLIN linii şi NCOL coloane
Parametrii de iesire
c – matricea suma cu NLIN linii şi NCOL coloane
Preconditie
Matricele a si b sunt initializate.
*/
void addmat(double a[NLIN][NCOL], double b[NLIN][NCOL],
double c[NLIN][NCOL])
{
int i, j;
for(i = 0; i < NLIN; i++)
for(j = 0; j < NCOL; j++)
c[i][j] = a[i][j] + b[i][j];
return;
}
/*
program pentru testarea functiilor
*/
int main(int argc, char *argv[])
{
double x[NLIN][NCOL], y[NLIN][NCOL], z[NLIN][NCOL];
// citeste elementele matricelor x si y
cout << "introduceti prima matrice pe linii" << endl;
rdmat(x);
cout << "introduceti a doua matrice pe linii" << endl;
rdmat(y);
63
addmat(x, y, z);
cout << "matricea suma" << endl;
wrmat(z);
return 0;
}
Rezultatul rulării programului este prezentat mai jos.
3.6 Supraîncărcarea funcţiilor
Intr-un program putem defini mai multe funcţii cu acelaşi nume dar cu
parametri de tipuri diferite sau cu un număr de parametri diferit (prototipuri
diferite). Acest lucru se numeşte supraîncărcarea funcţiilor.
Vom exemplifica supraîncărcarea funcţiilor definind două funcţii cu acelaşi nume,
min, ce calculează minimul a două, respective trei numere întregi. Variabilele ce
conţin aceşti întregi vor fi parametri de intrare pentru funcţii.
# include <iostream>
using namespace std;
// calculul minimului a doua numere intregi
int min(int x, int y)
{
return (x < y? x: y);
}
// calculul minimului a trei numere intregi
int min(int a, int b, int c)
{
int x = a < b? a: b;
if(x < c)
return x;
else
return c;
}
int main()
{
int a = 29, b = 47, c = -32;
cout << "min " << a << "," << b << " este " << min(a, b) << endl;
cout << "min " << a << "," << b << ", " << c << " este "
64
<< min(a, b, c) << endl;
return 0;
}
Rezultatul programului este cel de mai jos.
Vom exemplifica utilizarea prototipurilor definind cele două funcţii cu numele min ce
calculează minimul a două, respectiv trei numere reale după funcţia main. Vom
declara mai întâi prototipurile celor două funcţii şi vom defini funcţia main.
# include <iostream>
using namespace std;
// defineste prototipurile celor doua functii min()
double min(double, double);
double min(double, double, double);
// functia main()
int main()
{
double a = 68.29, b = -77.3, c = 32.5;
cout << "min " << a << "," << b << " este " << min(a, b) << endl;
cout << "min " << a << "," << b << ", " << c << " este "
<< min(a, b, c) << endl;
return 0;
}
Vom defini acum cele două funcţii.
// calculul minimului a doua numere reale
double min(double x, double y)
{
return (x < y? x: y);
}
// calculul minimului a trei numere reale
double min(double a, double b, double c)
{
double x = min(a, b);
return (x < c? x : c);
}
Menţionăm că prototipurile celor două funcţii puteau fi scrise respectiv
double min(double a, double b);
65
double min(double a, double b, double c);
3.7 Transmiterea parametrilor către funcţii
La apelarea unei funcţii, parametrii actuali şi variabilele locale ale funcţiei sunt
memorate într-o stivă. Există două moduri de a transmite parametrii către funcţii: prin
valoare şi prin referinţă (adresă). In cazul transmiterii unui parametru prin valoare, în
stivă se pune chiar valoarea parametrului, în cazul transmiterii prin adresă în stivă se
pune adresa parametrului.
• Cazul parametrilor transmiş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 transmişi prin valoare sunt parametri de
intrare (nu pot fi parametri de ieşire ai funcţiei),
• Cazul parametrilor transmişi prin adresă (referinţă). Dacă în corpul funcţiei
modificăm aceşti parametri, valoarea lor se modifică în programul apelant
(deoarece în stivă este adresa acestor parametri). In consecinţă, pentru ca un
parametru al unei funcţii să fie parametru de ieşire, el trebuie transmis
prin adresă (referinţă).
Vom rezuma proprietăţile celor două moduri de transmitere a parametrilor către
funcţii, în tabelul de mai jos.
In definiţia funcţiei
Parametri transmişi prin valoare Parametri transmişi prin adresă
Sunt parametri de intrare pentru funcţie Sunt parametri de ieşire pentru funcţie
Parametrul este o copie a argumentului Parametrul este adresa argumentului
Funcţia nu poate modifica parametrul Funcţia poate modifica parametrul
La apelarea funcţiei
Parametri transmişi prin valoare Parametri transmişi prin adresă
Argumentul este o constantă, variabilă
sau expresie
Argumentul este o variabilă
In general o funcţie poate calcula mai multe valori. Una dintre valori este transmisă la
punctul de apelare de instrucţiunea return. Celelalte valori vor fi asociate unor
parametri de ieşire, care vor fi obigatoriu transmişi prin adresă.
Definirea parametrilor tip referinţă (adresă)
Există două moduri de a defini parametri transmişi prin adresă:
• utilizarea parametrilor tip referinţă,
• utilizare parametrilor tip pointer.
Referinţele şi variabilele tip pointer vor fi prezentate pe larg în capitolul 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&.
De exemplu, parametrul formal int x, este transmis funcţiei prin valoare, parametrul
formal int& x este transmis prin adresă.
66
Dacă un parametru al funcţiei este tip referinţă, la apelarea funcţiei se va pune în stivă
adresa acestui parametru, şi atunci când acest parametru este modificat în corpul
funcţiei el este modificat direct în programul apelant (este parametru de ieşire).
Pentru a vedea care este diferenţa între cele două moduri de transmitere a parametrilor
considerăm o funcţiei care modifică valoarile parametrilor săi. Primul parametru va fi
transmis prin valoare iar celălalt prin adresă.
// functie ce modifica valorile parametrilor
// primul parametru este transmis prin valoare, al doilea prin adresa
# include <iostream>
using namespace std;
void f(int x, int& y)
{
x = 52;
y = 65;
return;
}
int main()
{
int a = 20, b = 30;
cout << “a = “ << a << “ , b = “ << b << endl;
f(a, b);
cout << “a = “ << a << “ , b = “ << b << endl;
f(2*a + 3, b);
cout << “a = “ << a << “ , b = “ << b << endl;
return 0;
}
Rezultate afişate sunt cele de mai jos.
Menţionăm că primul parametru se modifică doar în stivă şi nu în programul apelant,
doarece nu este transmis prin referinţă.
Valorile variabilelor a şi b înainte de prima apelare a funcţiei sunt
La prima apelare a funcţiei stiva este
După prima execuţie a funcţiei valorile variabilelor a şi b în program sunt
67
Menţionăm că valoarea variabilei a nu se modifică, deoarece primul parametru al
funcţiei este transmis prin valoare.
Exemplu. Să construim o funcţiei care să permute valoarea a două variabile de tip
întreg. Funcţia va avea doi parametri care la apelare au valorile variabilelor ce trebuie
permutate, iar la ieşirea din funcţie trebuie să aibe valorile permutate.
/* functie ce permute valoarea a doua variabile */
void perm(int& a, int& b)
{
int c;
c = a;
a = b;
b = c;
return;
}
O funcţie main() ce testează funcţia scrisă este următoarea.
int main()
{
int x = 7, y = 12;
cout << “valorile initiale x = “ << x << “ y = “ << y << endl;
perm(x, y);
cout << “valorile permutate x = “ << x << “ y = “ << y << endl;
}
Testarea acestei funcţii produce rezultatele de mai jos.
In stivă se pun adresele variabilor x şi y deoarece parametrii funcţiei sunt de tip
referinţă. Menţionăm că apelarea funcţiei se face utilizând pentru parametrii referinţă
nişte variabile din programul apelant (nu constante sau expresii), î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 transmis 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 transmis
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
68
pot fi parametri de ieşire, modificarea unui element al tabloului în funcţie
produce modificarea lui în programul ce a apelat funcţia.
Exemplu. Vom defini o funcţie ce calculează suma a doi vectori x şi y de numere
reale de dimensiune n. Vectorul sumă va fi z. Tipul funcţiei este void deoarece toate
valorile calculate de funcţie sunt asociate unor parametrii de ieşire.
/*
Calculul sumei a doi vectori
void sumvect(double x[], double y[], double z[], int n) ;
Parametrii de intrare :
x – vector
y – vector
n – dimensiunea vectorilor x, y, z
Parametrii de iesire :
z – vector, z = x + y
Preconditii :
Parametrii 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];
return;
}
Un program ce testează funcţia de mai sus este următorul
int main()
{
double a[3] = {1.29, -3.15, 6.92}, b[3] = {0.73, 5.25, -3.21};
double c[3];
sumvect(a, b, c, 3);
cout << "vectorul suma" << endl;
for(int i = 0; i < 3; i++)
cout << c[i] << " ";
cout << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Vom încheia acest paragraf definind o funcţie care să calculeze media şi dispersia
unui şir de numere n
x x x  , ,
2 1 . Valoarea medie a elementelor şirului este
69

·
·
n
i
i
x
n
m
1
1
iar dispersia

·


·
n
i
i
m x
n
d
1
2
) (
1
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 m şi
dispersia d. Deoarece valorile calculate, m şi d sunt asociate unor parametri, funcţia
are tipul void.
#include <iostream>
using namespace std;
/*
Calculul mediei si dispersiei componentelor unui vector
void md(float x[], int n, float& m, float& d);
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
d = 0;
for(i = 0; i < n; i++)
d = d + (x[i] – m) * (x[i] – m);
d = d / (n – 1);
return;
}
Un program ce testează funcţia de mai sus este următorul.
int main()
{
float a[4] = {1.2e-1, -2.34, 1.5, 3.33};
70
float media, dispersia;
md(a, 4, media, dispersia);
cout << “ media = “ << media << “ dispersia = “ << dispersia << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
3.8 Recursivitatea
Funcţiile limbajelor C şi C++ sunt recursive, adică 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
i
i n
1
!
¹
'
¹
> −
·
·
1 )! 1 ( *
1 1
!
n n n
n
n
// calculul factorialului
int fact(int n)
{
int s = 1;
for(int i = 1; i <= n; ++n)
s = s * i;
return s;
}
// calculul factorialului
int fact(int n)
{
int s;
if(n = = 1)
s = 1;
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 de
n=3 s=? n=2 s=?
Se apelează încă o dată funcţia, fact(1), şi stiva devine
n=3 s=? n=2 s=? n=1 s=1
71
După această ultimă apelare se ajunge la instrucţ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.
3.9 Funcţii generice
In limbajul C++ putem să definim funcţii generice în care putem defini tipuri generale
pentru parametri şi valoarea returnată de funcţie. Definirea unei funcţii generice se
face cu instrucţiunea
template <typename identificator, 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 unei funcţii generice se face astfel
nume_funcţie <tip, 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 “ << x << “ 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;
return;
}
Rezultatul rulării programului este cel de mai jos.
72
Exerciţiu. Să se definească o clasă generică ce permută două variabile de acelaşi tip.

73
4 Pointeri şi referinţe
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[2], c;
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
c 1016
Menţionăm că unitatea centrală a calculatoarelor are un registru acumulator utilizat în
calcule. O instrucţiune de atribuire de forma
a = b ;
este tradusă de compilator astfel : se încarcă în registrul acumulator valoarea de la
adresa variabilei b (în cazul nostru adresa 1004) şi se memorează conţinutul
acumulatorului la adresa variabilei a (în cazul nostru adresa 1000). O instrucţiune de
atribuire de forma
a = b + c;
este tradusă de compilator astfel: se încarcă în registrul acumulator valoarea de la
adresa variabilei b (în cazul nostru adresa 1004) se adună la registru valoarea de la
adresa variabilei c (în cazul nostru adresa 1016), şi se memorează conţinutul
registrului acumulator la adresa variabilei a (în cazul nostru adresa 1000). In
programul generat de compilator nu există nume de variabile, ci doar adrese.
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ă; parametrii transmişi prin
adresă pot fi parametri de ieşire ai funcţiilor. Pointerii sunt utilizaţi şi la prelucrarea
tablourilor.
4.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
74
4.1.1 Declararea variabilelor tip pointer
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.
Instrucţiunea
int u, *pv;
defineşte o variabilă u de tip int şi o variabilă pv de tip pointer la 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
Variabilele de tip pointer pot fi iniţializate la declararea lor. Putem scrie de exemplu
int n, *pn = &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;
Imaginea memoriei este următoarea
Tabela cu adrese creată de compilator poate fi
k 2000
n 2004
pn 2008
pk 2012
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). Imaginea memoriei după execuţia ultimelor două instrucţiuni este
14 14 adresa lui n (2004)
k n pn pk
75
Reamintim modul de execuţie al instrucţiunii
k = n;
valoarea de la adresa 2004 (adresa lui n) se memorează la adresa 2000 (adresa lui k).
Instrucţiunea
k = *pn ;
se execută astfel : valoarea de la adresa 2008 (adresa lui pn) este adresa operandului,
în cazul nostru 2004, vezi tabela de mai sus. Valoarea de la adresa 2004 se
memorează la adresa 2000 (adresa lui k).
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;
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ă, ea nu conţine adresa unei variabile de
tip int. 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.
Variabilele tip pointer pot fi utilizate în expresii aritmetice în locul variabilelor
aritmetice a căror adresă o conţin. De exemplu, secvenţa de instrucţiuni
int u, v, w;
u = 3;
v = 2 * (u + 5);
w = 2 * ( 3 + u + 5);
poate fi scrisă cu variabile tip pointer ca
int u, v, w;
int * pu;
u = 3;
pu = &u;
v = 2 * (*pu + 5);
w = 2 * (3 + *pu + 5)
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.
Exemplu. Vom scrie o funcţie care să permute valorile a două variabile ce vor fi
parametri de ieşire ai funcţiei. Aceşti parametri vor fi variabile tip pointer.
76
// functie ce permuta valorile a doua variabile
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, y = -4;
// scrie variabilele inainte de permutare
cout << “ valori initiale : “ << “x = “ << x << “ y = “ << y << endl;
perm(&x, &y);
// scrie variabilele dupa permutare
cout << “ valori permutate : “ << “x = “ << x << “ y = “ << y << endl;
return 0;
}
Rularea programului produce rezultatele de mai jos.
La apelarea funcţiei perm(), stiva este următoarea:
In consecinţă, instrucţiunile din funcţie
*a = *b;
*b = c;
modifică valorile parametrilor (variabilele x şi y) în funcţia main.
Operatorii * şi & sunt unari şi asociativi la dreapta. Operatorii * şi & au aceeaşi
prioritate cu operatorii ++ -- + - ! ~ sizeof (tip), vezi Anexa 2. Operatorii * şi &
sunt inverşi.
Exemplu. Fie instrucţiunile
int a, b;
a = 7;
Instrucţiunile
b = a;
şi
b = *&a;
sunt echivalente.
77
Exemplu. In programul de mai jos definim două variabile de tip int, a şi b şi două
variabile de tip pointer la tipul int, pa şi pb. Iniţializăm variabila pa cu adresa
variabilei a şi afişăm adresa variabilei a şi valoarea variabilei pa, ele trebuie să
coincidă. Iniţializăm variabila pb cu adresa variabilei b şi apoi dăm o valoare
variabilei b. Afişăm variabila b şi valoarea variabilei a cărei adresă este memorată în
pb. Cele două valori trebuie să coincidă.
#include <iostream>
using namespace std;
int main()
{
int a, b;
// definim variabile tip pointer
int *pa, *pb;
// initializam variabilele tip pointer
pa = &a;
pb = &b;
// se scrie adresa variabilei a si valoarea variabilei pa
cout << “ adresa variabilei a : “ << &a << “ , “ << pa << endl;
// se scrie valoarea variabilei b si valoarea variabilei cu adresa in pb
b = 15;
cout << “ valoarea variabilei b : “ << b << “ , “ << *pb << endl;
return 0 ;
}
Rezultatul programului este cel de mai jos.
Prima instrucţiune cout afişază de două ori aceeaşi adresă. A doua instrucţiune cout
afişază de două ori aceeaşi valoare, 15.
Menţionăm că numele unui tablou este, prin definiţie, un pointer constant la
primul element al tabloului. Fie de exemplu instrucţiunile
float x[7], * pf, z;
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 x. Instrucţiunile
z = x[3];
şi
pf = &x[3] ;
z = *pf;
sunt echivalente. Fie pc o variabilă de tip pointer la char. Ea poate primi ca valoare
adresa unui vector de tip char (şir de caractere). Fie instrucţiunile
78
char *pc, y[7] ;
Putem scrie
pc = y ;
sau putem iniţializa pe pc cu adresa unui şir constant
pc = “ abcd “ ;
Putem să definim de exemplu un pointer şi să-l iniţializăm în aceeaşi instrucţiune
astfel
char * ax = “aceg“;
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 numere întregi.
# include <iostream>
using namespace std;
int main()
{
int x[5];
for(int i = 0; i < 5; i++)
cout << “x[“ << i << “] adresa : “ << &x[i] << endl;
return 0;
}
Rezultatul programului este cel de mai jos. Adresele se modifică cu 4 octeţi. De ce ?
4.2 Referinţe
O referinţă (sau adresă) este un alt nume pentru o variabilă. Fie T tipul unei
variabile şi fie instrucţiunea ce defineşte o variabilă
T nume_variabilă;
Instrucţiunea de definire a unei referinţe este
T& nume_referinţă = nume_variabilă
unde
nume_referinţă este numele variabilei referinţă (adresă).
Variabila nume_variabilă trebuie să fie declarată înainte şi să aibe tipul T. De
exemplu instrucţiunile
int x;
int& rx = x;
declară pe rx ca fiind o referinţă a lui x (este obligatoriu ca variabila x de tip întreg a
fost declarată anterior). Secvenţa anterioară se poate scrie
int x, &rx = x;
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 în
care scriem valoarea unei variabile şi a referinţei corespunzătoare.
79
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;
return 0;
}
Rezultatele rulării programului sunt cele de mai jos. După fiecare instrucţiune de
scriere, valorile f şi fr vor fi aceleaşi.
După cum am văzut în paragraful anterior, 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ă. Variabila şi referinţa au aceeaşi adresă.
Pentru a verifica acest lucru, în următorul program vom afişa adresa unei variabile şi a
referinţei la această variabilă. Vom defini o variabilă de tip double dbl, două variabile
tip referinţă dbref şi dbref2 la dbl şi o altă variabilă tip referinţă dbref3 ce va fi
iniţializată cu valoarea lui dbref.
int main()
{
double dbl = 23.44;
double& dbref = dbl;
double& dbref2 = dbl;
// scrie adresele variabilelor dbl si dblref
cout << "adresa lui dbl = " << &dbl << endl
<< "adresa lui dbref = " << &dbref << endl;
// scrie adresele variabilelor dbl si dbref2
cout << "adresa lui dbl = " << &dbl << endl
<< "adresa lui dbref2 = " << &dbref2 << endl;
double& dbref3 = dbref;
// scrie adresele variabilelor dbref si dbref3
cout << "adresa lui dbref = " << &dbref << endl
<< "adresa lui dbref3 = " << &dbref3 << endl;
return 0;
}
In toate cazurile se tipăreşte aceeaşi valoare după cum se vede mai jos.
80
Vom încheia acest paragraf definind o funcţie care implementează căutarea binară.
Algoritmul a fost prezentat anterior. Funcţia va avea ca parametri de intrare vectorul
x, cu elementele sortate crescător, dimensiunea n a acestui vector şi valoarea z care
este căutată printre elementele lui x. Indicele elementului în listă este parametrul de
ieşire k. Funcţia va avea tipul void.
/*
Cautarea binara
void find(double x[], int n, double z, int& k);
Parametri de intrare :
x - vector cu elemente sortate crescator
n - dimensiunea lui x
z - valoarea cautata
Parametri de iesire :
k - indicele elementului z in lista.
Preconditie :
x[0] <= x[1] <= ... <= x[n - 1]
*/
void find(double x[], int n, double z, int& k)
{
int ls, ld;
ls = 0;
ld = n - 1;
while(ls <= ld)
{
k = (ls + ld) / 2;
if(x[k] == z)
return;
if(x[k] < z)
ls = k +1;
else
ld = k - 1;
}
k = n;
return;
}
Un program ce testează funcţia este următorul
int main()
{
double a[6] = {-2.35, 1.77, 3.14, 4.2, 5.12, 7.09};
double z;
int k;
81
z = 3.14;
find(a, 6, z, k);
if(k < 6)
cout << "elementul " << z << " are rangul " << k << endl;
else
cout << "elementul " << z << " nu este in lista" << endl;
return 0;
}
Recapitulăm în tabelul de mai jos prototipuri posibile pentru funcţia find şi modul de
apelare a funcţiei.
Prototip Apelare
int find(double x[], int n, double z); k = find(a, n, z);
void find(double x[], int n, double z, int& k) ; find(a, n, z, k);
4.3 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);
O variabilă de tip pointer la această funcţie are definiţia
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, iar ident este numele variabilei pointer.
Exemplu. Fie o funcţie f care calculează suma a două numere întregi x, y. Definiţia
funcţiei este următoarea
int f(int x, int y)
{
return x + y;
}
Sablonul funcţiei este
int f(int, int);
In şablon trebuie să specificăm tipul parametrilor, ca mai sus. In şablon putem
specifica şi numele parametrilor. Numele parametrilor din şablon pot fi diferite de
cele din definiţia funcţiei. De exemplu, şablonul funcţiei anterioare putea fi declarat
ca
int f(int a, int b);
O variabilă de tip 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 astfel:
• se atribuie variabilei pointer ca valoare numele funcţiei,
• se aplică operatorul () asupra adresei conţinută în variabila tip pointer, ca în
exemplul de mai jos.
82
Vom apela acum funcţia f definită 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;
// se defineste variabila ptr, pointer la functia f
int (*ptr)(int, int);
// se initializeaza variabila 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 în două feluri:
• se scrie numele funcţiei, urmat în paranteze de parametri actuali, ca termen
într-o expresie (se aplicând operatorul () asupra numelui funcţiei),
• se defineşte o variabilă de tip pointer la funcţie ; se atribuie acestei variabile ca
valoare adresa punctului de intrare în funcţie (adresa punctului de intrare este
chiar numele funcţiei) ; apelarea se face scriind variabila de tip pointer, urmată
în paranteze de parametrii funcţiei, ca termen într-o expresie (aplicând
operatorul () asupra adresei conţinută în variabila tip pointer).
4.4 Interpretarea instrucţiunilor ce conţin pointeri
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 al
cărei tip este definit de şablon. Interpretarea şabloanelor se face considerând
operatorii în ordinea priorităţii lor, [] reprezintă un tablou, () reprezintă o funcţie, *
reprezintă un pointer, etc. Fiecare operator se înlocuieşte cu o expresie :
1. () – funcţie care returnează,
2. [] – tablou de,
3. * - pointer la,
4. const – constant,
5. tip – tip.
Se începe cu identificatorul din şablon.
Exemplu. Fie şablonul
float *f();
Interpretarea lui se face astfel:
float *f() f este
float * () funcţie care returnează
83
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:
pf este pointer la o funcţie ce returnează o valoare de tip float. Operatorii ()
consecutivi din linia a doua s-au citit de la stânga la dreapta.
Exemplu. Sablonul
int (*r) (int, int);
se interpretează ca mai sus :
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 caracter şi atribuie adresa
acestui vector variabilei 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’;
float (*pf)() pf este
float (*) () pointer la
float () funcţie care returnează o valoare
float tip float
84
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.
4.5 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 tip tablou 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. Fie instrucţiunile :
char buffer[20] ;
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. Ele reprezintă valoarea variabilei x[i].
La fel, scrierile
&x[i]
şi
x + i
sunt echivalente. Ele reprezintă adresa variabilei x[i].
Vom rescrie funcţia ce permută două variabile întregi ca mai jos. Funcţia va avea ca
parametri doi vectori cu câte o componentă.
// functie ce permute valorile a doua variabile intregi
void perm(int a[], int b[])
{
85
int c;
c = a[0];
a[0] = b[0];
b[0] = c;
return;
}
Funcţia main() ce apelează funcţia perm() este următoarea.
int main()
{
int x[1] = {3}, y[1] = {-4};
// scrie variabilele inainte de permutare
cout << " valori initiale : " << "x = " << x[0] << " y = " << y[0] <<
endl;
perm(x, y);
// scrie variabilele dupa permutare
cout <<" valori permutate : " << "x = "<< x[0]<<" y = "<<y[0] <<
endl;
return 0;
}
Tabelul de mai jos arată cele două variante ale funcţiei ce permută două variabile, cea
de la începutul capitolului, cu pointeri şi cea de mai sus, cu vectori.
void perm(int* a, int* b)
{
int c ;
c = *a ;
*a = *b ;
*b = c ;
return;
}
void perm(int a[], int b[])
{
int c;
c = a[0];
a[0] = b[0];
b[0] = c;
return;
}
Deoarece instrucţiunile :
*a
şi
a[0]
sunt echivalente, cele două funcţii sunt identice.
Exemplu. Fie x un vector cu 5 componente tip float şi un vector z cu 3 componente
întregi. Se cere să afişăm componentele vectorilor. Vom face acest lucru utilizând
elementele vectorului x[i] şi pointeri *(z + i). Un program posibil este următorul.
#include <iostream>
using namespace std;
int main ()
{
float x[5] = {1.23, 6.89, -8.5e2, 0, 9.01};
int z[3] = {-11, 2, 4};
int i;
86
// 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;
return 0 ;
}
Rezultatul rulării programului este cel de mai jos
Programul poate fi rescris astfel. Definim o variabilă px de tip pointer la tipul float ce
va conţine pe rând adresele componentelor vectorului x, şi o variabilă pz de tip pointer
la tipul int pentru vectorul z. Variabilele vor fi initializate cu adresele vectorilor.
# include <iostream>
using namespace std;
int main()
{
float x[5] = {1.23, 6.89, -8.5e2, 0, 9.01};
int z[3] = {-11, 2, 4};
int i;
float * px;
px = x;
// scrie componentele vectorului x
for(i = 0; i < 5; i++)
cout << ‘\t’ << *(px + i) ;
cout << endl;
int * pz;
pz = z;
// scrie componentele vectorului z
for(i = 0; i < 3; i++)
cout << ‘\t’ << pz[i];
cout << endl;
return 0;
}
Programul de mai sus are rolul de a arăta echivalenţa între pointeri şi tablouri.
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 tip pointer conţine adresa unui alt element de tablou. Fie din nou
instrucţiunile :
87
char buffer[20];
char * px;
px = buffer;
Expresia px este adresa elementului buffer[0], iar *px este chiar valoarea elementului
buffer[0]. Expresia px + 1 este adresa variabilei buffer[1], iar *(px + 1) este chiar
valoarea componentei buffer[1]. In general, px + i este adresa variabilei buffer[i] iar
*(px + i) este chiar valoarea componentei buffer[i]. Pentru a aduna sau scădea o
unitate dintr-o variabilă de tip pointer se pot folosi operatorii ++ şi --.
Putem să rescriem programul precedent astfel :
# include <iostream>
using namespace std;
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 for în care scriem 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 paragraf cu un program care să scrie adresele
componentelor unui vector utilizând o variabilă tip pointer
# include <iostream>
using namespace std;
int main()
{
88
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 << “x[” << i << “]\t” << px << endl;
px = px + 1;
}
return 0;
}
Rezultatul rulării programului este cel de mai jos. După cum se observă, adresele
cresc cu 4 octeţi. De ce ?.
4.6 Poineri şi şiruri 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 şi caractere ASCII. Câteva dintre aceste funcţii, ale căror prototipuri se
află în bibliotecile <cstring> sau <string.h>, au fost prezentate mai înainte şi sunt
repetate aici.
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.
Funcţia
size_t strlen(const char * s);
calculează lungimea şirului s (numărul de caractere ce preced ‘\0’).
Tipul size_t este tipul unsigned int.
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. Valoarea NULL este predefinită
în limbajele C şi C++.
89
Vom exemplifica acum utilizarea funcţiilor de manipulare a caracterelor ASCII
tipărind caracterele alfabetice, alfanumerice, cifrele, literele mici şi mari ale codului
ASCII.
Aceste funcţii au fost prezentate anterior şi prototipurile lor din bibliotecile <cctype>
sau <ctype.h> sunt repetate aici.
Funcţii de manipulare a caracterelor ASCII
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
Vom utiliza constanta predefinită UCHAR_MAX din biblioteca <climits> sau
<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 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);
Caracterele vor fi generate cu o instrucţiune for a cărei variabilă de control are tipul
unsigned char. Definiţia funcţiei este următoarea.
#include <cstdlib>
#include <climits>
#include <iostream>
using namespace std;
/*
functie care afisaza caractere ASCII
Parametri de intrare
nume – numele functiei, sir de caractere
fn – pointer la functie
*/
void prtcars(const char * nume, int (*fn)(int))
{
unsigned char c;
// scrie numele functiei
cout << nume << “ : “;
/* genereaza toate caracterele si scrie pe cele pentru
care functia are valoarea adevarat */
for(c = 0; c < UCHAR_MAX; ++c)
90
if((*fn)(c))
cout << c;
cout << endl;
return;
}
Următorul program testează această funcţie.
int main()
{
prtcars(“isdigit”, &isdigit);
prtcars(“islower”, &islower);
prtcars(“isupper”, &isupper);
prtcars(“isalpha”, &isalpha);
prtcars(“isalnum”, &isalnum);
prtcars(“isxdigit”, &isxdigit);
return 0;
}
Rezultatul rulării programului este cel de mai jos.
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 cea în care copiem un vector de caractere în alt vector.
void copy(char * s1, char * s2)
{
int i;
int len = strlen(s2);
for(i = 0; i < len + 1; i++)
s1[i] = s2[i];
return;
}
Reamintim că, o variabilă tip tablou este un pointer constant la primul element al
tabloului.
O altă variantă este
void copy(char * s1, char * s2)
{
int i;
91
for(i = 0; (s1[i] = s2[i]) != ‘\0’; i++)
;
return;
}
Condiţia care se testează pentru execuţia instrucţiunii for este
(s1[i]=s2[i]) != ‘\0’;
Ea este executată 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ă, în cazul nostru s1[i].
O altă variantă utilizează pointeri în locul elementelor de tablouri şi faptul că şirurile
tip C sunt terminate prin zero.
void copy(char * s1, char * s2)
{
while(*s1 = *s2)
{
s1++;
s2++;
}
return;
}
Expresia testată de către instrucţiunea while este
*s1 = *s2
Rezultatul evaluării expresiei este valoarea *s1 (un caracter copiat în şirul s1).
Instrucţiunea while se execută până când această valoare este 0 (până când caracterul
‘\0’ al şirului s2 este copiat în s1).
In final putem scrie varianta următoare.
void copy(char * s1, char * s2)
{
while(*s1++ = *s2++)
;
return;
}
care este o scriere condensată a variantei anterioare.
4.7 Pointeri şi tablouri multidimensionale
Tablourile se definesc conform urmăroarei diagrame sintactice
92
Elementele tablourilor sunt memorate pe linii. De exemplu, tabloul
int x[2][3];
este compus din elementele :
x[0][0], x[0][1], x[0][2], x[1][0], x[1][1], 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 (este 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.
Numele unui tablou este o variabilă de tip pointer ce conţine adresa primului element
al matricei. In cazul matricei de mai sus x conţine adresa elementului x[0][0].
Elementele tabloului pot fi selectate folosind operatorul *.
Fie de exemplu tabloul
double a[3];
După cum am spus anterior, prin definiţie, expresiile
a[i]
şi
*(a + i)
sunt echivalente.
Aplicăm această regulă pentru tabloul de mai sus
int x[2][3];
Expresia
x[i][j]
este echivalentă cu
(x[i])[j]
care, conform celor de mai sus, este echivalentă cu
*((x[i]) + j)
care este echivalentă cu expresia
*(*(x + i) + j)
Reamintim că
x[i]
selectează un element al tabloului x, aici o linie, deci, echivalent
*(x + i)
selectează o linie a tabloului x.
Vom prezenta un exemplu de utilizare a pointerilor la tablouri cu două dimensiuni.
Fie o matrice x cu două linii şi trei coloane, cu elemente de tip întreg. Vom afişa
pentru început adresele celor două linii ale matricei, cu trei programe echivalente.
După cum am spus mai sus, cele două linii ale matricei sunt vectori cu trei
componente, iar adresele lor sunt x[0] şi x[1], sau, echivalent, *(x + 0) şi *(x + 1).
Prima variantă a programului este cea de mai jos.
#include <iostream>
using namespace std;
int main()
93
{
int x[2][3];
int i;
// afisarea adreselor liniilor matricei x
for(i = 0; i < 2; i++)
cout << "adresa lui x[" << i << "] = " << x[i] << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
După cum se observă, adresa lui x[1] este mai mare cu 12 octeţi (0xc) decât adresa lui
x[0]. De ce?
A doua variantă afişază adresele liniilor matricei x utilizând expresia echivalentă a lui
x[i], care este *(x + i).
#include <iostream>
using namespace std;
int main()
{
int x[2][3];
int i;
// afisarea adreselor liniilor matricei x
for(i = 0; i < 2; i++)
cout << "adresa lui x[" << i << "] = " << *(x + i) << endl;
return 0;
}
A treia variantă a programului este următoarea. Reamintim că linia matricei este un
vector cu elemente întregi. Definim o variabilă y de tip pointer la întreg
int * y;
In program, ea va primi ca valoare adresa liniei matricei x (adresa unui vector cu
elemente întregi).
#include <iostream>
using namespace std;
int main()
{
int x[2][3];
int i;
int * y;
94
for(i = 0; i < 2; i++)
{
y = x[i];
cout << "adresa lui x[" << i << "] = " << y << endl;
}
return 0;
}
Menţionăm că, în locul instrucţiunii
y = x[i] ;
se putea scrie
y = *(x + i) ;
Vom afişa acum adresele elementelor matricei x cu două programe echivalente.
Adresele elementelor primei linii sunt x[0] + 0, x[0] + 1, x[0] + 2. Adresele
elementelor limiei a doua sunt x[1] + 0, x[1] + 1, x[1] + 2. In general, adresa
elementului x[i][j] al unei matrice x este
x[i] + j
Prima variantă a programului este următoarea
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
int x[2][3];
int i, j;
// afisarea adreselor elementelor matricei x
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
{
cout << "adresa lui x[" << i << "][" << j << "] = " << x[i] + j << endl;
}
}
return 0;
}
Rezultatul rulării programului este cel de mai jos. După cum se observă, adresele
cresc cu patru octeţi. De ce ?
O altă variantă a programului este cea de mai jos. In această variantă, expresia x[i] + j
este înlocuită cu expresia *(x + i) + j.
95
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
int x[2][3];
int i, j;
// afisarea adreselor elementelor matricei x
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
{
cout << "adresa lui x[" << i << "][" << j << "] = " << *(x + i) + j << endl;
}
}
return 0;
}
In final prezentăm un program ce afişază componentele matricei x. Elementul x[i][j]
al matricei x este, conform regulilor de mai sus,
*(*(x + i) + j)
Elementele matricei sunt afişate pe câte trei coloane, fiecare element este urmat de
două spaţii. Pentru a prescrie dimensiunea câmpului utilizăm în instrucţiunea cout
funcţia setw(4).
#include <iostream>
#include <iomanip>
using namespace std;
int main(int argc, char *argv[])
{
int x[2][3] = {{1, 2, 3}, {-2, -4, -6}};
int i, j;
// afisarea elementelor matricei x
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
cout << setw(4) << *(*(x + i) + j) << " ";
cout << endl;
}
return 0;
}
Rezultatul programului este afişat mai jos.
96
4.8 Parametrii funcţiei main. Parametrii liniei de comandă
Prototipul funcţiei main() utilizat până acum este
int main();
Funcţia main poate avea şi următorul prototip
int main(int argc, char * argv[]);
Considerăm al doilea parametru din această definiţie
char * argv[]
El este interpretat astfel
char * argv [] argv este
char * [] vector de
char * pointeri la
char char
argv reprezină un vector de pointeri la caracter, 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;
return 0;
}
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 casetă de dialog a mediul de
programare.
4.9 Alocarea dinamică a memoriei
Alocarea memoriei unei variabile scalare sau tablou se face astfel :
• de către compilator, la declararea unei variabile de un tip oarecare
• cu funcţia malloc()
• cu operatorul new
Alocarea memoriei unei variabile cu funcţia malloc() sau operatorul new se face la
execuţia programului.
In programele în care alocăm memorie cu funcţia malloc() sau cu operatorul new
trebuie să definim o variabilă pointer de un tip corespunzător ce va conţine adresa
memoriei alocate.
Alocarea dinamică a memoriei cu funcţia malloc()
97
Alocarea de memorie se poate face cu funcţia malloc() cu prototipul
void * malloc(int size);
unde size este numărul de octeţi de alocat. Tipul funcţiei este void*, adică un pointer
de tip nespecificat, şi el trebuie convertit în pointer la tipul alocat. Eliberarea
memoriei alocate se face cu funcţia
void free(p);
unde p este o variabilă tip pointer ce conţine adresa zonei de memorie ce va fi
eliberate. Prototipul acestor funcţii se află în biblioteca <cstdlib> pentru limbajul C+
+, iar în cazul programelor C, numele bibliotecii este <stdlib.h>.
Exemplu. Vom aloca un vector cu 10 componente întregi, îl vom iniţializa şi îl vom
scrie pe ecran. In program definim o variabilă v de tip pointer la întreg ce va conţine
adresa memoriei alocate de funcţia malloc(). După alocare, vectorul poate fi utilizat
pentru calcule. La sfârşitul programului, memoria alocată trebuie eliberată cu funcţia
free().
# include <iostream>
# include <cstdlib>
using namespace std;
int main()
{
int j;
int * v = (int *) malloc(10 * sizeof(int));
for(j = 0; j < 10; j++)
{
// *(v + j) = 2 * j;
v[j] = 2 * j;
}
for(j = 0; j < 10; j++)
{
cout << *(v + j) << “ “;
// cout << v[j] << “ “;
}
cout << endl;
free(v); // elibereaza memoria
return 0;
}
Instrucţiunea
int * v = (int *) malloc(10 * sizeof(int));
alocă un vector cu 10 componente de tipul int. Ea are acelaşi efect cu instrucţiunea
int v[10];
dar vectorul v[10] este alocat de compilator şi nu poate fi şters, în timp ce vectorul
v[10], alocat cu funcţia malloc, poate fi şters. Instrucţiunea converteşte pointerul
furnizat de funcţia malloc() la tipul (int *).
Rezultatul rulării programului este cel de mai jos.
98
In continuare vom arăta cum se alocă memorie pentru o matrice. Pentru a determina
tipul variabilei pointer ce va conţine memoria alocată cu funcţia malloc(), vom analiza
cazul unei matrice x cu elemente întregi cu două linii şi trei coloane. Definiţia acestei
matrice este
int x[2][3] ;
Matricea x este formată din doi vectori, x[0] şi x[1], ce corespund celor două linii ale
matricei. Vectorul x[0] de exemplu, conţine elementele x[0][0], x[0][1], x[0][2]. Tipul
vectorilor x[0] şi x[1] este, aşa cum am spus mai sus
int *
Matricea x constă deci dintr-un vector cu două componente al căror tip este int *, deci
tipul variabilei x va fi
int **
Prezentăm mai jos un program în care am definit matricea
int x[2][3] = {{1, 2, 3}, {-4, -5, -6}};
Vom afişa adresele liniilor matricei, adresele elementelor matricei şi elementele
matricei utilizând o variabilă tip pinter.
Definim un vector y cu două elemente de tipul int * ce va fi iniţializat cu adresele
celor două linii ale matricei x, x[0] şi x[1] ;
int * y[2];
y[0] = x[0];
y[1] = x[1];
Definim în final o variabilă z de tip pointer ce va fi iniţializat cu adresa vectorului y.
Tipul variabilei z va fi int **
int ** z ;
Iniţializarea variabilei z cu adresa vectorului y se face cu instrucţiunea
z = y;
In continuare prezentăm secvenţe din program în care utilizăm variabila z pentru a
afişa adresele liniilor matricei, adresele elementelor matricei şi valorile elementelor
matricei x.
Vom afişa adresele liniilor matricei x cu cele două expresii echivalente, z[i] şi *(z + i)
// afisaza adresele liniilor matricei x
cout << "adresele liniilor matricei" << endl;
for(i = 0; i < 2; i++)
{
cout << "adresa lui x[" << i << "] : " << z[i] << endl;
}
// afisaza adresele liniilor matricei x
cout << "adresele liniilor matricei" << endl;
for(i = 0; i < 2; i++)
{
cout << "adresa lui x[" << i << "] : " << *(z + i) << endl;
}
Afişăm apoi adresele elementelor matricei x cu cele două expresii echivalente, z[i] + j
şi *(z + i) + j
99
// afisaza adresele elementelor matricei
cout << "adresele elementelor matricei" << endl;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
cout << "adresa lui x[" << i << "][" << j << "] : "
<< z[i] + j << endl;
}
// afisaza adresele elementelor matricei
cout << "adresele elementelor matricei" << endl;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
cout << "adresa lui x[" << i << "][" << j << "] : "
<< *(z + i) + j << endl;
}
In final, afişăm elementele matricei x pe linii, cu cele două expresii echivalente, z[i][j]
şi *(*(z + i) + j
// afisaza elementele matricei x pe linii
cout << "elementele matricei" << endl;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
cout << setw(3) << z[i][j] << " ";
cout << endl;
}
// afisaza elementele matricei x pe linii
cout << "elementele matricei" << endl;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
cout << setw(3) << *(*(z + i) + j) << " ";
cout << endl;
}
Programul complet este cel de mai jos.
#include <iostream>
#include <iomanip>
using namespace std;
int main(int argc, char *argv[])
{
int x[2][3] = {{1, 2, 3}, {-4, -5, -6}};
int * y[2];
int ** z;
100

z = y;

y[0] = x[0];
y[1] = x[1];
int i, j;
// afisaza adresele liniilor matricei x
cout << "adresele liniilor matricei" << endl;
for(i = 0; i < 2; i++)
{
cout << "adresa lui x[" << i << "] : " << z[i] << endl;
}
// afisaza adresele liniilor matricei x
cout << "adresele liniilor matricei" << endl;
for(i = 0; i < 2; i++)
{
cout << "adresa lui x[" << i << "] : " << *(z + i) << endl;
}
// afisaza adresele elementelor matricei x
cout << "adresele elementelor matricei" << endl;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
cout << "adresa lui x[" << i << "][" << j << "] : "
<< z[i] + j << endl;
}
// afisaza adresele elementelor matricei x
cout << "adresele elementelor matricei" << endl;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
cout << "adresa lui x[" << i << "][" << j << "] : "
<< *(z + i) + j << endl;
}
// afisaza elementele matricei x pe linii
cout << "elementele matricei" << endl;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
cout << setw(3) << z[i][j] << " ";
cout << endl;
}
// afisaza elementele matricei x pe linii
cout << "elementele matricei" << endl;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
101
cout << setw(3) << *(*(z + i) + j) << " ";
cout << endl;
}
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Exemplu. Vom aloca memorie pentru o matrice cu elemente întregi cu nlin linii şi
ncol coloane, unde nlin şi ncol se vor citi de la tastatură. Alocarea se face astfel. După
cum am spus mai sus, o matrice este compusă din vectori corespunzători liniilor.
Fiecare linie a matricei este un vector cu componente de tip int, deci tipul vectorului
liniei este int*. Se alocă mai întâi un vector de dimensiune nlin ce va conţine elemente
de tip int*. Variabila z ce conţine adresa acestui vector are tipul int **. Apoi, fiecare
element al acestui vector, primeşte ca valoare adresa unui vector de ncol întregi. In
final se atribuie valori componentelor matricei
z[i][j] = i * j ;
Programul este următorul
# include <iostream>
# include <cstdlib>
using namespace std;
int main()
{
int i, j;
int nlin, ncol;
cout << "introduceti # de coloane "; cin >> ncol;
cout << "introduceti # de linii "; cin >> nlin;
// aloca un vector de dimensiune nlin de pointeri de tip int *
102
int ** z = (int **) malloc(nlin * sizeof(int *));
// aloca pentru fiecare linie un vector de ncol intregi
for(i = 0; i < nlin; i++)
z[i] = (int*) malloc(ncol * sizeof(int));
// atribuie valori componentelor matricei
for(i = 0; i < nlin; i++)
for(j = 0; j < ncol; j++)
z[i][j] = i * j;
for(i = 0; i < nlin; i++)
{
for(j = 0; j < ncol; j++)
cout << z[i][j] << '\t';
cout << endl;
}
return 0;
}
Prima funcţie malloc() alocă memorie pentru un vector cu nlin elemente de tipul int*.
A doua funcţie malloc() alocă memorie pentru un vector cu ncol elemente de tipul int.
Rezultatul rulării programului este arătat mai jos.
Alocarea dinamică a memoriei cu operatorul new
Limbajul C++ permite alocarea memoriei necesară unei variabile cu operatorul new
cu formele:
• pentru alocarea de memorie pentru un scalar
new tip
• pentru alocarea de memorie pentru un vector cu un număr de componente dat
de expresie întreagă
new tip[expresie întreagă]
De exemplu, următoarele instrucţiuni alocă memorie pentru o variabilă scalară
double * ptr;
ptr = new double;
*ptr = 1.7;
Operatorul new alocă memoria corespunzătoare tipului de variabilă şi are ca rezultat
adresa zonei de memorie alocată. Primele două instrucţiuni puteau fi scrise
double *ptr = new double;
Memoria alocată cu operatorul new poate fi eliberată cu operatorul delete cu formele
• pentru eliberarea memoriei allocate unei variabile scalare
delete prt;
• pentru eliberarea memoriei alocate unui vector
delete [] ptr;
Variabila ptr conţine adresa memoriei alocată mai înainte cu new.
Operatorii new şi delete sunt utili la operaţii cu vectori.
Exemplu. Instrucţiunea
103
float *pf = new float[10];
alocă un vector de 10 componente de tip float, iat variabila pf conţine adresa
vectorului. Un vector alocat cu new poate fi şters cu operatorul delete astfel
delete [] pf;
Exemplu. Vom aloca un vector cu 10 componente întregi, îl vom iniţializa şi vom
afişa pe ecran componentele lui.
int main()
{
int * v = new int[10];
int j;
for(j = 0; j < 10; j++)
{
*(v + j) = j;
// v[j] = j;
}
for(j = 0; j < 10; j++)
{
cout << v[j] << “ “;
// cout << *(v + j) << “ “;
}
cout << endl;
delete [] v;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Instrucţiunea
int * v = new int[10];
are acelaşi efect cu instrucţiunea
int x[10];
dar vectorul v[10] alocat cu new poate fi şters, vectorul v[10] alocat de compilator nu
poate fi şters.
Exemplu. Vom aloca o matrice cu nlin linii şi ncol coloane, cu elemente întregi, unde
nlin şi ncol se vor citi de la tastatură. Pentru tipul variabilelor utilizate în program vezi
exemplu anterior. Elementele matricei vor primi aceleaşi valori ca în exemplul
anterior.
# include <iostream>
# include <cstdlib>
using namespace std;
int main()
{
104
int i, j;
int ** v;
int nlin, ncol;
cout << "introduceti # de coloane "; cin >> ncol;
cout << "introduceti # de linii "; cin >> nlin;
v = new int * [nlin];
for(i = 0; i < nlin; i++)
v[i] = new int[ncol];
for(i = 0; i < nlin; i++)
for(j = 0; j < ncol; j++)
v[i][j] = i * j;
for(i = 0; i < nlin; i++)
{
for(j = 0; j < ncol; j++)
cout << v[i][j] << '\t';
cout << endl;
}
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Instrucţiunea
v = new int * [nlin];
alocă memorie pentru un vector cu nlin elemente de tipul int*. La fel ca mai înainte
variabila tip pointer v are tipul int**.
105
5 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, urmate de un caracter ‘\n’.
• 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 sfârşit a fişierului.
In program, fiecare fişier este asociat unui obiect numit 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. La deschiderea unui fişier în citire,
sistemul de operare caută pe disc fişierul cu numele specificat şi memorează adresele
de început şi de sfârşit ale fişierului în obiectul stream asociat.
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 în obiectul stream asociat.
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 de pe disc a fişierului cu
adresa ultimului octet al fişierului.
Fişierele 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), poziţionat la valoare true dacă
în timpul operaţiei de citire s-a ajuns la sfârşitul fişierului.
Prototipurile funcţiilor intrare / ieşire pentru fişierele tip C sunt memorate în
fişiere header şi trebuie semnalate compilatorului cu directive include.
Prototipurile funcţiilor ce efectuează operaţii intrare/ieşire în limbajul C se află
în biblioteca <stdio.h>. Pentru limbajul C++ numele fişierului header este
redefinit ca <cstdio>. Reamintim cele două forme ale directivei include:
• în cazul programelor C, directiva include conţine numele fişierului antet
(header), inclusiv extensia h. Forma directivei include este
#include <stdio.h>
• în cazul programelor C++, directiva include conţine doar numele
fişierului antet (header). Forma directivei include este
#include <cstdio>
In exemplele următoare vom utiliza stilul C.
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 şi are ca rezultat adresa ei. Parametrii acestei funcţii sunt
106
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 numele 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. Schema prelucrării unui fişier text în citire este următoarea
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 dacă s-a ajuns la sfârşitul fişierului. La întâlnirea sfârşitului de
fişier indicatorul de sfârşit de fişier este poziţionat la o valoare diferită de zero
iar funcţiile de citire returnează o valoare specifică (constanta EOF, predefinită
în biblioteca <stdio.h>). Citirea secvenţială a unui fişier se face cu instrucţiunea
while ce va testa indicatorul de sfârşit de fişier cu funcţia feof(), sau dacă funcţia
de citire a returnat valoarea EOF.
In cazul citirii datelor de la tastatură, (din fişierul stdin), sfârşitul de fişier este
indicat prin Ctrl+Z. După aceasta se apasă tasta return.
Fiecare aplicaţie are un director curent asociat. Atunci când parametrul
filename din instrucţiunea fopen() conţine doar numele fişierului, fişierul se află
în directorul curent. Pentru a prelucra fişiere în alte directoare, trebuie ca
parametrul filename să conţină şi calea, absolută sau relativă spre fişier.
5.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.
107
5.1.1 Funcţii intrare / ieşire cu format
Aceste funcţii scriu / citesc valori în / din fişiere pe disc, fişierele standard sau fişiere
tip şir de caractere (fişiere memorate în vectori tip char). 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);
Funcţiile au ca rezultat numărul de octeţi scrişi. In cazul unei erori intrare / ieşire
funcţiile au ca rezultat un număr negativ.
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);
Funcţiile au ca rezultat numărul de valori citite. In cazul unei erori intrare / ieşire
funcţiile au ca rezultat un număr negativ.
• funcţiile de scriere / citire pentru fişiere pe disc sunt fprintf şi fscanf,
• funcţiile de scriere / citire pentru fişierele standard stdout / stdin sunt printf şi
scanf,
• funcţiile de scriere / citire pentru fişierele tip şir de caractere sunt sprintf şi
sscanf. In cazul fişierelor şir de caractere, parametrul s este un şir tip C în care
se scriu sau din care se citesc valorile variabilelor.
Menţionăm că, în cazul instrucţiunilor scanf, argumentele sunt parametri de
ieşire, deci de tip pointer.
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 tip float
%lf – număr real tip double
%e – număr real cu exponent
%le- număr real tip double cu exponent
%p – pointer
După % poate urma un număr întreg care dă lungimea minimă a câmpului în cazul
instrucţiunii printf şi lungimea maximă a câmpului î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ă.
108
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);
}
Rezultatul programului este cel de mai jos.
Reamintim 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. Să citim două numere reale de tip float de la tastatură şi să afişăm suma lor.
Programul este următorul
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
float a, b, c;
printf("Dati doua numere reale\n");
scanf("%f %f", &a, &b);
c = a + b;
printf("a = %f, b = %f, a + b = %f\n", a, b, c);
return 0;
}
Rezultatul programului este cel de mai jos.
109
In cazul instrucţiunilor printf specificatorii de conversie dau formatul de scriere a
variabilelor, în timp ce restul caracterelor dn format sunt scrise în fişier. De exemplu,
instrucţiunea
printf(“\n numarul zecimal : %d\n numarul hexazecimal : %x\n”, i, i);
are ca rezultat scrierea caracterului ‘\n’, (trecerea la un nouă linie), a şirului “numarul
zecimal” şi a valorii variabilei i, etc.
Exemplu. Să definim o funcţie ce calculează suma unui şir de numere reale tip double
citite de la tastatură. Numărul termenilor din şir va fi citit tot de la tastatură.
Programul este dat în continuare. Numerele reale tip double se citesc şi se afişază cu
specificatorul de conversie tip %lf.
#include <stdio.h>
#include <stdlib.h>
double sumnum()
{
int i, n;
double s, x;
printf("cate numere ?\n");
scanf("%d", &n);
s = 0;
for(i = 1; i <=n; i++)
{
scanf("%lf", &x);
s = s + x;
}
return s;
}

int main(int argc, char *argv[])
{
double rez;
rez = sumnum();
printf(" suma numerelor = %lf\n", rez);
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Exemplu. Vom scrie un program care calculează valoarea unei expresii
x x
x x
ln
) sin( 2 ) 2 cos( 1
+
+ +
110
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 %4.1f pentru
variabila x şi %6.2f pentru expresie. Valoarea lui x va ocupa 4 caractere, cu un
caracter pentru partea subunitară, cea a lui z ocupă 6 caractere, două pentru partea
subunitară. Valorile vor fi separate de un caracter tab, ‘\t’. Formatul se va încheia cu
un caracter ‘\n’.
#include <stdio.h>
#include <math.h>
int main ()
{
FILE * fp;
fp = fopen("rez.txt", "w");
int i;
float x, z;
// scrie antetul pe ecran si in fisier
printf(" x \t f(x) \n");
fprintf(fp, " x \t f(x) \n");
x = 1.0;
// 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(" %4.1f \t %6.2f \n", x, z);
fprintf(fp, " %4.1f \t %6.2f \n", x, z);
x = x + 0.2;
}
fclose(fp);
return 0;
}
Rezultatul rulării programului este cel de mai jos.
In programul anterior puteam defini un şir de caractere cu specificatorii de format
char * format = “ %4.1f \t %6.2f \n”;
sau
char format[] = “ %4.1f \t %6.2f \n”;
şi instrucţiunile de scriere devin
printf(format, x, z);
fprintf(fp, format, x, z);
Exemplu. Consideram un vector cu 5 componente numere reale. Vom afişa
componentele vectorului sub forma
111
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;
}
5.1.2 Funcţii intrare / ieşire tip caracter
Fişierele text sunt formate din linii ce conţin zero sau mai multe caractere, urmate de
caracterul ‘\n’. Pentru prelucrarea acestor fişiere este util să putem citi sau scrie
caractere şi linii, adică şiruri de caractere terminate cu caracterul ‘\n’.
Fişierele text au indicator de poziţionare ce conţine numărul următorului caracter de
scris sau de citit. Acest indicator are valoarea zero la deschiderea fişierului şi apoi este
modificat de instrucţiunile de citire sau scriere a fişierului.
• funcţia
int fgetc(FILE * stream);
citeşte un caracter 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 caracter din streamul stdin. Ea este echivalentă cu funcţia fgetc(stdin);
• funcţia
int fputc(int c, FILE * stream);
scrie caracterul c în fişierul 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. Caracterul ‘\n’ este introdus în şirul citit. 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 returnează
valoarea NULL.
112
• 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 rând, 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 sfârşit de fişier la o valoare diferită de zero dacă în cursul
operaţiei de citire s-a detectat sfârştul fişierului şi acest lucru este detectat cu funcţia
feof().
read(file)
while(! feof(file))
{
// prelucreaza blocul citit
read(file)
}
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. Funcţia fopen are ca rezultat valoarea NULL dacă
operaţia nu a avut succes. Apoi se va citi câte un caracter pană la întâlnirea sfârş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");
113
return EXIT_FAILURE;
}
// 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 EXIT_SUCCESS;
}
Rezultatul rulării programului este cel de mai jos.
Un alt mod de a rezolva această problemă va fi arătat ulterior.
Exemplu. Vom face un program care să copieze un fişier existent în altul. Fie un fişier
în directorul curent. Vom copia acest fişier într-un alt fişier tot în directorul curent.
Numele celor două fişiere se vor citi de la tastatură. Vom deschide cele două fişiere şi
vom testa dacă operaţia a avut success, utilizând faptul că funcţia fopen are ca rezultat
valoarea NULL dacă operaţia nu a avut succes. Apoi se citeşte repetat câte un caracter
din primul fişier şi se scrie în al doilea, până la întâlnirea sfârşitului primului fişier.
Citirea secvenţială a primului fişier se va face cu instrucţiunea while.
# 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 EXIT_FAILURE;
}
// citeste numele noului fisier
printf(“introduceti numele noului fisier\n”);
114
scanf(“%s”, nume2);
fout = fopen(nume2, "w");
if(fout == NULL)
{
printf("fisierul %s nu se poate crea", nume2);
return EXIT_FAILURE;
}
// copiaza fisierul
int c;
c = fgetc(fin);
while(c != EOF)
{
fputc(c, fout);
c = fgetc(fin);
}
fclose(fin);
fclose(fout);
return EXIT_SUCCESS;
}
Rezultatul rulării programului este cel de mai jos.
Instrucţiunile ce citesc şi scriu din fişiere pot fi rescrise ca
int c;
while((c = fgetc(fin)) != EOF)
fputc(c, fout);
5.2 Fişiere text tip şir de caractere
Limbajul oferă posibilitatea de a lucra cu fişiere tip text memorate într-un şir tip C.
Instrucţiunile de scriere / citire sunt:
int sprintf(char * s, const char * format, argumente);
int sscanf(char * s, const char * format, argumente);
Parametrul s este şirul de caractere tip C în care se scriu sau din care se citesc datele.
Parametrul format este un şir de caractere compus din specificatori de conversie
definiţi de % şi alte caractere. Asemenea fişiere sunt utile la conversii ale valorilor
numerice în şiruri de caractere şi invers.
Exemplu. Fie doi vectori tip char ce conţin şirurile “104” şi “1.23e-1”. Vom converti
aceste şiruri în două variabile tip int şi double, vom face suma lor şi o vom scrie pe
ecran.
#include <stdio.h>
int main()
{
115
char x[] = "104";
char y[] = "1.23e-1";
int i;
double z, r;
sscanf(x, "%d", &i);
sscanf(y, "%lf", &z);
printf("i = %d \n", i);
printf("y = %lf \n", z);
r = z + i;
printf("i + y = %lf\n", r);
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Reamintim că la citirea unei variabile tip double se utilizează specificatorul de
conversie %lf.
Exemplu. Fie variabilele
int x = 10;
double d = -2.34;
char c[] = "abc";
Vom scrie valorile acestor variabile, separate de spaţii, într-un fişier tip şir de
caractere
char s[100] ;
Vom apoi citi valorile din vectorul s şi vom iniţializa alte variabile
int x1;
double d1;
char c1[10];
şi vom scrie valorile lor pe ecran.
# include <stdio.h>
int main()
{
char s[100];
int x = 10;
double d = -2.34;
char c[] = "abc";
// scrie pe ecran variabilele initiale
printf("x = %d d = %f c = %s\n", x, d, c);
// scrie variabilele initiale in fisierul tip sir
sprintf(s, "%d %f %s", x, d, c);
// scrie pe ecran fisierul tip sir
printf("%s\n", s);
int x1;
double d1;
116
char c1[10];
// citeste valorile variabilelor din fisierul tip sir
sscanf(s, "%d %lf %s", &x1, &d1, c1);
// scrie valorile variabilelor pe ecran
printf("x = %d d = %f c = %s\n", x1, d1, c1);
return 0;
}
Pentru citirea variabilei d1 de tip double am utilizat specificatorul de conversie %lf.
Menţionăm că parametrii funcţiei sscanf sunt de tip pointer (sunt parametri de ieşire).
In consecinţă, la apelarea funcţiei sscanf în program utilizăm adresele variabilelor x1
şi d1
sscanf(s, "%d %lf %s", &x1, &d1, c1);
Valorile scrise pe ecran cu cele trei instrucţiuni printf() sunt cele de mai jos.
5.3 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.
117
Exemplu. Vom calcula lungimea unui fişier text modificând indicatorul fişierului
astfel încât noua lui poziţie să fie sfârşitul fişierului. Lungimea fişierului va fi
valoarea indicatorului de poziţie.
#include <stdio.h>
// fisiere text C. Calculul lungimii fiserului
// prin modificarea indicatorului de pozitie.
int main()
{
char name[256];
FILE* file;
int nb = 0;
printf("introduceti numele fisierului\n");
scanf("%s", name);
// deschide fisierul in citire
file = fopen(name, "r");
if(file == NULL)
{
printf("nume de fisier eronat\n");
return EXIT_FAILURE;
}
// modifica indicatorul de pozitie la sfarsitul fisierului
fseek(file, 0, SEEK_END);
// citeste valoarea indicatorului de pozitie
nb = ftell(file);
fclose(file);
printf("fisierul %s contine %d octeti\n", name, nb);
return EXIT_SUCCESS;
}
Rezultatul rulării programului este cel de mai jos.
Fişierul citit este chiar fişierul sursă ce conţine programul, main.c.
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
plus ‘\0’, 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., cu o expresie de forma
‘0’ + i
unde i ia valorile 0, 1, 2, …
Programul este următorul.
118
#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 EXIT_FAILURE;
}
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 EXIT_FAILURE;
}
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);
return EXIT_SUCCESS;
}
Rezultatul rulării programului este cel de mai jos.
119
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 EXIT_FAILURE;
}
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");
120
return EXIT_FAILURE;
}
printf("fisierul citit\n");
// citeste sirurile pare
for(i = 0; i < 15; i += 2)
{
// pozitioneaza indicatorul fisierului in raport cu inceputul 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);
return EXIT_SUCCESS;
}
Menţionăm că puteam avansa indicatorul fişierului la sfârşitul ciclului for cu 10 octeţi
în raport cu poziţia curentă ca mai jos
// avanseaza indicatorul fisierului cu 10 in raport cu pozitia curenta
fseek(fil, (long)(10), SEEK_CUR);
în loc de a-l poziţiona în raport cu începutul fişierului.
Rezultatul rulării programului este cel de mai jos.
121
6 Structuri tip C şi uniuni
6.1 Structuri
O structură este un tip de date definit de utilizator. O structură este o colecţie de
variabile de tipuri diferite. Variabilele dintr-o structură vor fi numite componente sau
câmpuri. Definirea unei structuri se face cu instrucţiunea
struct nume {lista de declaraţii de variabile };
unde nume este noul tip de date.
De exemplu, următoarea 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 de forma
struct nume listă de variabile;
De exemplu, într-un program putem defini numerele complexe a şi b astfel
struct complex a, b;
Este posibil să combinăm cele două instrucţiuni în una singură
struct complex {
float real;
float imag;
} a, b;
Instrucţiunea de definire a unei structuri şi a variabilelor corespunzând acestui tip are
forma generală
struct nume {liste de declaraţii de tip } listă de variabile;
O structură poate fi iniţializată la declarare. Putem scrie
struct complex n = {1.1, -2.34};
Adresarea unui element al unei structuri se face cu operatorul de selecţie .
(punct), cu forma
nume.membru
Operatorul . are aceeaşi prioritate ca şi operatorii () şi [], vezi Anexa 2.
Putem da valori variabilei a definite anterior astfel :
a.real = 1.0;
a.imag = -1.2 + sin(0.3);
Putem utiliza valorile componentelor unei structuri în expresii :
float x;
x = a.real * a.imag;
Este posibil să atribuim o structură alteia, de exemplu :
struct complex a = {0.0, 1.0}, b;
b = a;
Exemplu. Vom defini o structură ce conţine date despre o persoană, numele,
prenumele şi vârsta. iniţializăm structura şi scriem datele pe ecran.
122
#include <stdio.h>
#include <string.h>
struct person {
char nume[64];
char prenume[64];
int varsta;
};
int main()
{
struct person p1, p2;
strcpy(p1.nume, "popescu");
strcpy(p1.prenume, "ioan");
p1.varsta = 50;
p2 = p1;
printf("nume : %s\n", p2.nume);
printf("prenume : %s\n", p2.prenume);
printf("varsta : %d\n", p2.varsta);
return 0;
}
Rezultatul rulării programului este prezentat mai jos.
Pentru a defini funcţii ce au ca argumente sau rezultat structuri în limbajul C,
definim un nou tip de date corespunzând structurii cu instrucţiunea typedef.
Reamintim că, forma instrucţiunii typedef este
typedef tip-existent tip-nou
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
{
double cmpa;
double cmpb;
} 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)
{
123
vector c;
c.cmpa = x.cmpa + y.cmpa;
c.cmpb = x.cmpb + y.cmpb;
return c;
}
int main()
{
vector cx, cy, cz;
cx.cmpa = 1;
cx.cmpb = 1;
cy.cmpa = 0;
cy.cmpb = 1;
cz = addcmp(cx, cy);
printf(“suma este (%f , %f) \n”, cz.cmpa ,cz.cmpb);
return 0;
}
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ă are
formele
struct nume * identificator;
unde nume este numele structurii sau
numetip * identificator;
dacă am definit un tip de structură numetip cu instrucţiunea typedef. In ambele cazuri
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 câmp al structurii este adresat astfel
(*p).membru
deoarece operatorul . are precedenţă mai mare decât operatorul *.
Prin definiţie, această scriere se prescurtează ca
p->membru
Operatorii . şi -> au aceeaşi prioritate ca operatorii () şi [], vezi Anexa 2.
Exemplu. Fie instrucţiunile
complex y;
struct complex * py;
py = &y;
Putem iniţializa structura y, utilizând 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
124
(*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ă. Rezultatul va fi scris sub forma (real, imag).
# 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
echivalente cu
++((*b).c);
++(b->c);
6.2 Uniuni
Uniunile conţin membri ale căror tipuri individuale pot diferi unul de altul dar toţi
membrii unei uniuni ocupă aceeaşi zonă de memorie. Reamintim că, în cazul
structurilor, fiecare membru ocupă o zonă proprie de memorie. Uniunile sunt utile în
125
aplicaţii cu multe variabile ale căror valori nu sunt necesare simultan. Instrucţiunea de
definire a unei uniuni este
union nume {lista de declaraţii de tip };
unde nume este noul tip de date. 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
union nume listă de variabile;
Exemplu.
union id {
int marime;
char culoare[21];
};
Variabilele tip id se declară ca
union id v1, v2;
Putem scrie direct ca şi în cazul structurilor
union id {
int marime;
char culoare[21];
} v1, v2;
O varibilă tip id poate conţine o mărime sau o culoare, dar nu ambele simultan.
Un membru individual al uniunii se adresează prin operatorii . şi -> ca şi în cazul
structurilor. Exemplu.
// atribuie o valoare marimii
v1.marime = 12;
printf(“marime %d \n”, v1.marime);
// atribuie o valoare culorii
strcpy(v1.culoare, “alb”);
printf(“culoare %s \n”, v1.culoare);
// afisaza dimensiunea uniunii
printf(“dimensiunea uniunii id : %d \n”, sizeof(id));
126
Partea II. Programarea orientată obiect
7 Clase
Pentru a rezolva o problemă trebuie să identificăm datele problemei şi operaţiile ce
trebuie efectuate asupra acestor date. Tipurile fundamentale de date, int, double, char,
etc., şi cele derivate, tablouri, fişiere, permit rezolvarea oricărei probleme. In cazul
proiectelor complexe este avantajos să putem defini propriile tipuri de date.
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 ce trebuie efectuate pentru a rezolva problema.
Să definim, de exemplu, un tip de date ce descrie funcţia liniară
f(x) = m x + n
Datele corespunzând acestui tip sunt m şi n, de tip real. Operaţiile corespunzătoare
sunt: calculul valorii funcţiei într-un punct, integrala definită, calculul funcţiei inverse,
etc. Alte operaţii ce se pot defini suma a două funcţii liniare, compunerea a două
funcţii liniare, etc.
Abstractizarea este structurarea problemei în entităţi prin definirea datelor şi
operaţiilor 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 utiliza 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
variabile (numite şi câmpuri sau atribute sau proprietăţi) şi funcţii (numite
şi metode sau operaţii) care implementează structura de date şi operaţiile
tipului abstract. Instanţele clasei se numesc obiecte. Clasa defineşte deci
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 unei clase (a unui tip abstract). El este unic
identificat prin nume şi defineşte o stare reprezentată de valorile atributelor
sale 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, cerând destinatarului să aplice o metodă asupra lui (să apeleze o functie).
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.
127
Reprezentarea tipurilor de date abstracte pe care le definim este cea de mai jos.
Nume clasă
Atribute
Operaţii
In programe creăm variabile de tipul unor clase care se numesc obiecte. Vom spune
că obiectele sunt instanţe sau realizări 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 :
• Incapsularea datelor este primul principiu al programării orientate obiect.
Acesta cere combinarea datelor şi metodelor într-o singură structură de date.
Acest principiu asigurară ascunderea structurii de date cu ajutorul 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 pentru înmulţirea numerelor
complexe î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 clasă particulară adaugă doar
elementele proprii. Clasa care este moştenită se numeşte clasă de bază sau
superclasă, iar clasele care moştenesc se numesc clase derivate sau subclase.
• 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.
7.1 Definirea unei clase
7.1.1 Definirea unei clase
Diagrama sintactică a definiţiei unei clase este
Clasele sunt definite utilizând cuvântul cheie class. identificator este numele clasei.
Specificatorii de acces sunt public, protected şi private, iar membru este o declaraţie
de funcţie membru sau dată a clasei. Implicit toţi membri clasei sunt privaţi.
128
In exemplele următoare vom adopta următoarea regulă : numele claselor vor începe
cu litere mari, iar numele câmpurilor şi metodelor cu litere mici.
Ca exemplu vom defini o clasă care reprezintă funcţia liniară
f(x) = mx + n
pentru care vom calcula valoarea funcţiei într-un punct, funcţia inversă şi integrala
definită. Clasa va avea o funcţie print() ce afişază parametrii m şi n ai obiectului.
Reprezentarea acestei clase este cea de mai jos.
Line
double m;
double n;
Line (double, double);
void assign();
void value(double);
double intgrl(double, double);
void print();
void invert();
Definiţia clasei este următoarea
/*
clasa Line descrie functia f(x) = m * x + n
*/
class Line
{
private:
double m, n;
public:
void assign(double, double);
double value(double);
double intgrl(double, double);
void print();
Line(double, double);
void invert();
};
Numele clasei este Line. Definiţia clasei este inclusă între acolade { şi }, urmate de ;.
Variabilele, (câmpurile, atributele) membre ale clasei sunt m şi n şi ele vor memora
valorile m şi n ale funcţiei descrisă de clasă. Ele sunt de tip double ş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 încapsularea datelor.
Funcţiile membre ale clasei, (metodele, operaţiile) sunt assign(), ce atribuie valori
variabilelor m şi n, value(), ce dă valoarea funcţiei într-un punct, invert(), ce
inversează funcţia şi print(), ce scrie variabilele m şi n şi intgrl() ce calculează
integrala definită a funcţiei.
Funcţia Line() este constructor. Ea crează obiecte şi iniţializează variabilele acestora.
Toate funcţiile sunt declarate publice şi vor putea fi utilizate de obiecte în afara clasei.
Funcţiile membre, (metodele), pot fi definite în interiorul clasei sau în afara ei.
129
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.
Vom defini metodele clasei Line în afara definiţiei clasei, ca mai jos :
// atribuirea de valori variabilelor m si n
void Line::assign(double a, double b)
{
m = a;
n = b;
}
// calculul valorii functiei in punctul x
double Line::value(double x)
{
return m * x + n;
}
// calculul integralei definite
double Line::intgrl(double a, double b)
{
return m * (b * b - a * a) / 2 + n * (b - a);
}
// afisarea parametrilor pe ecran
void Line::print()
{
cout << "functia f(x) = m * x + n, "
<< " m = " << m << " n = " << n << endl;
}
// constructor cu parametri
Line::Line(double a, double b)
{
m = a;
n = b;
}
// calculul functiei inverse
void Line::invert()
{
double temp;
temp = 1.0 / m;
m = temp;
n = - n * temp;
}
130
Apelul unei funcţii a clasei de către un obiect se face cu operatorul de selecţie .
(punct), conform următoarei diagrame sintactice
nume_obiect.nume_funcţie (lista de parametri)
Utilizarea unui câmp al un obiect se face cu operatorul de selecţie . (punct),
conform următoarei diagrame sintactice
nume_obiect.nume_camp
Declararea unui obiect se face în acelaşi fel cu declararea tipurilor standard.
Reamintim că, un obiect este o instanţă a unei clase. La declararea unui obiect se
apelează un constructor al clasei. Diagrama sintactică pentru definirea obiectelor
este cea de mai jos
nume_clasa nume_obiect(lista de parametri), …, ;
Orice obiect are propriile variabile.
Vom exemplifica utilizarea clasei definind funcţia liniară x + 1şi calculând valoarea ei
în punctul 0.5, integrala de la 0 la 1 şi inversa ei.
int main()
{
// creaza un obiect de tipul Line
Line a(1, 1);
a.print();
cout << "f(0.5) = " << a.value(0.5) << endl;
cout << "intgrala f(x) de la 0 la 1 = " << a.intgrl(0, 1) << endl;
cout << "functia inversa \n";
a.invert();
a.print();
return 0;
}
Pe ecran se vor afişa următoarele rezultate
In funcţia main(), x este declarat ca un obiect de tipul Line cu numele a (o instanţă a
clasei Line) cu instrucţiunea
Line a(1, 1);
El are cele două data membre, m şi n. Obiectul este creat prin apelarea constructorului
Line(double a, double b)
care iniţializează câmpurile m şi n la valorile 1 şi 1.
131
Obiectul poate apela funcţiile membre assign(), invert() şi print(), etc., 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.
Menţionăm că în funcţia main() nu putem scrie instrucţiunea
cout << a.m << “ “ << a.n << endl;
deoarece variabilele clasei, m şi n, 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 în funcţia main(), ca mai sus, ele ar trebui declarate publice.
Menţionăm că în limbajul C++ putem defini clase şi cu cuvântul cheie struct (spre
deosebire de limbajul C, unde cuvântul cheie struct defineşte structuri). In cazul unei
clase definite cu struct toţi membrii clasei sunt doar variabile şi sunt declaraţi implicit
ca publici.
Menţionăm că putem scrie definiţia unei clase într-un fişier antet (header) şi definiţia
funcţiei main() ce utilizează această clasă într-un fişier separat care să includă fişierele
antet cu definiţiile claselor. De exemplu, putem scrie definiţia clasei Line în fişierul
header Line.h ca mai jos.
/*
clasa Line descrie functia f(x) = m * x + n
*/
class Line
{
private:
double m, n;
public:
void print();
Line(double a, double b) { m = a; n = b; };
Line() { m = 0; n = 0; };
};
void Line::print()
{
cout << "functia f(x) = m * x + n, "
<< " m = " << m << " n = " << n << endl;
}
Scriem apoi definiţia funcţiei main() în alt fişier sursă în care includem fişierul Line.h.
In cazul fişierelor header definite de utilizator, în directiva include numele fişierului
header se scrie între ghilimele, ca î exemplul de mai jos.
#include <iostream>
using namespace std;
#include "Line.h"
int main()
{
// creaza un vector cu obiecte de tipul Line
Line v[2];
132
v[0] = Line(1, -1);
v[1] = Line(2, -2);
int i;
for(i = 0; i < 2; i++)
v[i].print();
return 0;
}
Rezultatul rulării programului este cel de mai jos.
7.1.2 Pointerul this
Fie o instrucţiune de definire a unor obiecte de tipul Line
Line x(1, 1), y(2.5, -1) ;
Considerăm instrucţiunile în care un obiect apelează o funcţie membru a clasei, de
exemplu
x.assign(3, 4);
y.assign(-1, 5);
Adresarea funcţiilor clasei de către obiecte se face în felul următor. Fiecare obiect are
o variabilă implicită numită this care este un pointer de tipul clasei ce conţine adresa
obiectului. 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 obiectelor
x şi y). 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 alte funcţii membre. Ca exemplu
vom rescrie funcţia invert() ca să utilizeze pointerul this la apelarea variabilelor m şi
n.
void Line::invert()
{
double temp;
temp = 1.0 / this->m;
m = temp;
n = - this->n * temp;
}
7.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
}
133
De exemplu, putem defini următorul spaţiu de nume
namespace general
{
int a, b;
}
Pentru a adresa variabilele şi funcţiile definite într-un spaţiu în funcţiile din
afara spaţiului se utilizează operatorul de rezoluţie :: Operatorul de rezoluţie
are cea mai mare prioritate, vezi Anexa 2.
De exemplu, pentru a utiliza variabila a din spaţiul anterior 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.
De exemplu, 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>
……………………………………
std::cout << std::endl;
• Putem utiliza directiva using pentru a declara doar anumite simboluri din
spaţiul de nume
#include <iostream>
using std::cout;
using std::endl;
……………………………………..
cout << endl;
7.2 Constructori şi destructori
7.2.1 Constructori
In general, obiectele trebuie să iniţializeze variabilele lor 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ă avea un constructor
Line (int, int);
Deoarece constructorii au, în general, rolul de a iniţializa variabilele obiectului,
limbajul are o sintaxă specială numită listă de iniţializatori. Constructorul anterior
se poate scrie
134
Line::Line(double a, double b)
: m(a), n(b)
{
}
Clasele au în general următoarele tipuri de constructori, identificaţi de definiţ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, compilatorul definieşte un constructor implicit.
Constructorul copiere este apelat atunci când vrem să creăm un obiect care să aibe
aceleaşi atribute ca un alt obiect. De exemplu instrucţiunea
X a(b);
declară un obiect a de tipul X şi îi atribuie ca valori ale atributelor, pe cele ale 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.
In consecinţă, orice clasă are cel puţin doi constructori.
Orice clasă include un operator implicit de atribuire = care are ca parametru un
obiect de tipul clasei.
Exemplu. Fie din nou clasa Line în care definim un constructor copiere, un
constructor fără parametri, şi un constructor cu parametri.
# include <iostream>
using namespace std;
/*
clasa Line descrie functia f(x) = m * x + n
*/
class Line
{
private:
double m, n;
public:
void assign(double, double);
double value(double);
double intgrl(double, double);
void print();
Line(double, double);
void invert();
Line();
Line(const Line&);
};
Vom prezenta doar definiţiile constructorilor, definiţiile celorlalte funcţii sunt cele
anterioare.
135
// constructor tip copiere
Line::Line(const Line& x)
{
m = x.m;
n = x.n;
}
// constructor implicit
Line::Line()
{
m = 0;
n = 0;
}
Vom rescrie definiţia clasei utilizând lista de iniţializatori în definiţiile constructorilor.
/*
clasa Line descrie functia f(x) = m * x + n
*/
class Line
{
private:
double m, n;
public:
void assign(double, double);
double value(double);
double intgrl(double, double);
void print();
Line(double a, double b) : m(a), n(b) {};
void invert();
Line() : m(0), n(0) {};
Line(const Line& x) : m(x.m), n(x.n) {};
};
Un program care testează definiţia clasei Line este cel de mai jos în care creăm un
obiect x, apelăm constructorul copiere pentru a crea un alt obiect y, şi iniţializăm un
obiect z cu operatorul =.
int main()
{
Line x(12, 25);
Line z;
Line y(x); // se apeleaza constructorul copiere
x.print();
y.print();
z = x;
z.print();
}
136
Constructorul copiere este apelat ori de câte ori :
• un obiect este copiat,
• un obiect este transmis prin valoare unei funcţii (obiectul este copiat în stivă),
• un obiect este returnat ca valoare de o funcţie.
Exemplu. Presupunem clasa Line definită anterior. Fie o funcţie globală f care are ca
parametru un obiect de tip Line şi ca rezultat un obiect de tip Line, care este inversul
obiectului ce este parametrul funcţiei. Definitia funcţiei f() este următoarea
Line f(Line r)
{
Line s = r;
s.invert();
return s; // se apeleaza constructorul copiere
}
int main()
{
Line x(11, 22);
Line y(x); // se apeleaza constructorul copiere
Line z;
// scrie numarul x
x.print();
z = f(y); // se apeleaza constructorul copiere
// scrie numărul z
z.print();
return 0;
}
Constructorul copiere este apelat
1) atunci când se declară obiectul
Line y(x);
2) când se apelează funcţia
z = f(y);
Parametrul funcţiei f (r în definiţia funcţiei) este transmis 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ă utilizând constructorul copiere.
Menţionăm că, în cazul în care o funcţie nu trebuie să modifice un parametru,
acest parametru trebuie declarat ca fiind constant. Acest lucru se face cu
cuvântul cheie const. Un exemplu este constructorul copiere al clasei Line. In cazul
funcţiei f, parametrul r nu trebuie modificat, deci definiţia funcţiei f poate fi
Line f(const Line r);
In acest caz, dacă funcţia modifică parametrul r, compilatorul detectează o eroare.
Un parametru obiect al unei funcţii poate fi de tip valoare sau de tip referinţă.
Când parametrul este de tip valoare, în stivă se poate pune valoarea obiectului,
când este de tip referinţă în stivă se pune adresa (referinţa) lui.
De exemplu, parametrul
Line r
este transmis prin valoare. Dacă parametrul este declarat ca
137
Line& r
el este transmis prin adresă. Parametrii tip pointer vor fi prezentaţi ulterior.
Putem defini tablouri de obiecte în mod obişnuit, de exemplu
Line x[20];
Pentru aceasta, clasa trebuie să definească un constructor fără parametri.
Tabloul definit poate fi prelucrat, de exemplu putem avea instrucţiunile
Line a(1, 2);
x[5] = a;
Exemplu. Vom crea un vector cu două componente de tipul Line şi vom afişa
parametri m şi n ai obiectelor.
# include <iostream>
using namespace std;
class Line
{
private:
double m, n;
public:
void print();
Line(double a, double b) : m(a), n(b) {};
Line() : m(0), n(0) {};
};
void Line::print()
{
cout << "functia f(x) = m * x + n, "
<< " m = " << m << " n = " << n << endl;
}
int main()
{
// creaza un vector cu obiecte de tipul Line
Line v[2];
v[0] = Line(1, -1);
v[1] = Line(2, -2);
int i;
for(i = 0; i < 2; i++)
v[i].print();
return 0;
}
Menţionăm că am definit un constructor fără parametri, Line(), care este apelat la
definirea vectorului de obiecte tip Line
Line v[2];
în funcţia main().
7.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
138
~ NumeClasa(){/* corpul destructorului*/}
Dacă el nu este definit explicit, compilatorul generează unul. Un obiect este creat într-
o funcţie sau într-un bloc dintr-o funcţie şi este distrus la ieşirea din funcţie sau din
bloc. Blocul este orice şir de instrucţiuni între acolade, { şi } din interiorul unei
funcţii.
Pentru a vedea cum sunt apelaţi constructorii şi destructorul, fie următoarea clasă în
care constructorul şi destructorul scriu un mesaj.
class Test
{
public:
Test() {cout << “obiectul este creat” << endl;}
~Test() {cout << “obiectul este distrus” << endl;}
};
Fie două blocuri de instrucţiuni în funcţia main. In fiecare bloc creăm câte un obiect.
int main()
{
{
cout << “intrare blocul 1” << endl;
Test x;
cout << “iesire blocul 1” << endl;
}
{
cout << “intrare blocul 2” << endl;
Test y;
cout << “iesire blocul 2” << endl;
}
}
Mesajele afişate vor fi:
Menţionăm că toate variabilele locale unei funcţii sau unui bloc, inclusive
obiectele, sunt create în stivă la intrarea în funcţie sau bloc şi sunt şterse din stivă
la ieşirea din funcţie sau bloc.
139
7.3 Funcţii prietene
In general avem nevoie să utilizăm şi să modificăm datele (câmpurile) unui obiect în
timpul execuţiei programului. Dacă datele sunt declarate private, nu putem face acest
lucru direct. Există două moduri de a rezolva această problemă.
In prima metodă se definesc funcţii member ale clasei care să modifice şi să
furnizeze valorile datelor unui obiect.
In cazul clasei Line putem defini funcţii membre ale clasei care furnizează parametrii
m şi n :
double getm();
double getn();
şi funcţii care modifică aceşti parametric :
void setm(double);
void setn(double);
Aceste funcţii se numesc funcţii de acces şi sunt de regulă publice.
Definiţiile funcţiilor getm() şi setm() pot fi:
double Line::getm()
{
return m;
}
void Line::setm(double x)
{
m = x;
}
Exemplu. Vom defini o funcţie globală care să adune două funcţii liniare. Prototipul
funcţiei va fi
Line sum(const Line x, const Line y);
Parametrii x şi y sunt cele două funcţii liniare ce sunt adunate. Deoarece obiectele nu
vor fi modificate, ele au fost declarate const.
Considerăm două funcţii liniare,
1 1 1
) ( n x m x f + · şi
2 2 2
) ( n x m x f + · . Suma lor va fi
funcţia ) ( ) ( ) (
2 1 2 1
n n x m m x f + + + · . In implementarea funcţiei nu putem folosi
direct variabilele m şi n, deoarece ele sunt declarate private în definiţia funcţiei. Vom
utiliza funcţiile getm() şi getn() pentru a obţine valoarea lor. Implementarea funcţiei
este următoarea
Line sum(Line x, Line y)
{
double a = x.getm();
double b = x.getn();
double c = y.getm();
double d = y.getn();
Line temp(a + c, b + d);
return temp;
}
140
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 Line este următoarea.
class Line
{
public:
Line (double, double);
Line (const Line & x);
Line ();
void assign(double, double);
double value(double);
double intgrl(double, double);
void print();
void invert();
friend Line sum(const Line x, const Line y);
private:
double m;
double n;
};
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
Line sum(const Line x, const Line y)
{
Line temp(x.m + y.m, x.n + y.n);
return temp;
}
Un program care testează funcţia sum este cel de mai jos.
int main()
{
Line a(1.5, 2.3);
a.print();
Line b(2.2, -1.5);
b.print();
Line c;
c = sum(a, b);
c.print();
return 0;
}
Rezultatul rulării programului este cel de mai jos.
141
7.4 Determinarea tipului unei expresii
Limbajul C++ are operatorul typeid ce permite determinarea tipului unei expresii.
Forma operatorului este
typeid(expresie)
Operatorul typeid are ca rezultat o referinţă la un obiect constant de tipul type_info.
Clasa type_info este o clasă predefinită a limbajului, definită în fişierul antet
<typeinfo>. Clasa defineşte metoda
const char * name();
care are ca rezultat un şir de caractere cu numele tipului expresiei şi operatorii == şi !
= cu care putem compara tipurile a două expresii.
Exemplu. Vom determina tipul expresiilor aritmetice şi booleene şi tipul unui pointer
şi al unei referinţe cu programul de mai jos.
#include <iostream>
#include <typeinfo>
using namespace std;
int main(int argc, char *argv[])
{
int a, b;
char c;
double x, y;
bool bl;
float f1, f2;
int * ptra;
int& ra = a;

cout << typeid(a + b).name() << endl;
cout << typeid(x + y).name() << endl;
cout << typeid(f1 + f2).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(bl).name() << endl;
cout << typeid(ptra).name() << endl;
cout << typeid(ra).name() << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
142
In program se include biblioteca <typeinfo> ce defineşte clasa type_info . Expresia
typeid(a + b)
are ca rezultat o referinţă la un obiect constant de tipul type_info. In consecinţă,
expresia
typeid(a + b).name()
apelează metoda name() a acestui obiect, care are ca rezultat un şir de caractere cu
numele tipului expresiei, i pentru int, d pentru double, etc.
Operatorul typeid poate avea ca parametru un obiect de un tip oarecare, definit de
programator, de exemplu o clasă.
Exemplu. Fie clasa Line definită mai sus. Programul următor determină tipul unui
obiect de tipul Line şi al unui poiner la un obiect de tip Line. Definiţia clasei Line nu
este arătată.
#include <iostream>
#include <typeinfo>
using namespace std;
int main(int argc, char *argv[])
{
Line a(1, 2);
Line * ptrln = new Line(-1, 2);
cout << typeid(a).name() << endl;
cout << typeid(ptrln).name() << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Programul afişază tipul unui obiect Line şi tipul unei variabile pointer de tip Line.
143
8 Siruri tip C++
8.1 Clasa string
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 ai şirurilor, <, <=, >, >= , = = şi != ce au ca
rezultat valorile true sau false.
Operatorii << şi >> scriu şi citesc şiruri tip string.
Exemple.
if(x < x1)
cout << x1;
else
cout << x;
Funcţii membre importante ale clasei sunt :
• int length(); – dă lungimea şirului tip string,
• int size(); – dă lungimea şirului tip string,
• 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 sau poate atribui
o valoare unui caracter din şir. Indicele primului caracter din şir este 0.
Exemplu. Să scriem un şir, caracter cu caracter.
string s = “abcd”;
for(j = 0; j < s.length(); j++)
cout << s[j];
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
144
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() cu prototipul
erase(int index, int size)
ş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”
• funcţia substr() cu prototipul
string substr(int index, int size);
crează un şir tip string din subşirul de lungime size ce începe cu caracterul index.
Exemplu. Fie instrucţiunile
string x = “abcxyz”;
string z = x.substr(3, 2);
A doua instrucţiune crează şirul tip string z
“xy”
• funcţia insert() cu prototipul
insert( int index, string str) ;
inserează şirul str începând cu indexul index.
• funcţia append() adaugă un şir la sfârşitul şirului curent. Ea are prototipurile:
append(const string& str);
append(const char* str);
append(const string& str, int index, int size);
145
Primele două funcţii adaugă şirul str la sfârşitul şirului conţinut în obiect. A treia
funcţie adaugă la sfârşitul şirului din obiect caracterele din şirul str de lungime size
începând cu indicele index. Trebuie să avem îndeplinită condiţia
index <= size
Exemplu. Fie un obiect tip string ce conţine şirul "abc". Se va adăuga la sfârşitul lui
şirul "xyz". Fie apoi un obiect tip string ce conţine şirul "xyz". Se va adăuga la
sfârşitul lui şirul "abc", începând odată de la indicele 1 şi altă dată de la indicele 2.
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char *argv[])
{
string a("abc");
a.append("xyz");
cout << a << endl;
string b = "xyz";
b.append("abc", 1, 3);
cout << b << endl;
b = "xyz";
b.append("abc", 2, 3);
cout << b << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Adăugând şirul “abc” începând cu indicele 1 se obţine şirul “xyzbc”, adăugând şirul
“abc” începând cu indicele 2 se obţine şirul “xyzc”.
Exemplu. Fir şirurile
string x = “abcd”, y = “xyz”;
Instrucţiunea
x.insert(1, y) ;
modifică şirul x la “axyzbcd” ;
Pentru citirea unui şir de caractere dintr-un fişier într-un obiect tip string, este definită
funcţia globală getline cu prototipurile :
getline(stream, string& str, char delim);
getline(stream, string& str);
Funcţia citeşte caractere din stream în obiectul str până la întâlnirea caracterului delim
(prima variantă) sau până la întâlnirea caracterului ‘\n’ (a doua variantă). In acest fel
este posibilă citirea unei linii dintr-un fişier tip text.
Exemplu. Instrucţiunile
string s;
getline(cin, str);
146
produc citirea unei linii de caractere din fişierul de intrare cin.
Există în plus, funcţii getline() ale obiectului cin cu prototipurile:
getline(char * s, int size, char delim);
getline(char * s, int size);
care citesc caractere în vectorul de caractere s, cel mult size – 1 caractere, sau până la
întâlnirea caracterului delimitator, delim (prima variantă), sau caracterului ‘\n’ (a doua
variantă).
Un program ce exemplifică utilizarea funcţiei insert este cel de mai jos care inserează
repetat un şir inaintea unui şir dat.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1 = "abcdg";
string str2 = "xy";
cout << str1 << endl;
str1.insert(0, str2);
cout << str1 << endl;
str1.insert(2, str2);
cout << str1 << endl;
str1.insert(2, "123");
cout << str1 << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
147
9 Supraîncărcarea operatorilor
Operatorii limbajului C++ sunt predefiniţ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
funcţie de forma
tip operator semn (parametri) {/* corpul funcţiei */}
unde semn este operatorul dorit +,-, *, / , [], (), <<, >>, =, etc., iar tip este un tip de
date definit de programator (o clasă). 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ă.
9.1 Supraîncărcarea operatorilor aritmetici
Să supraîncărcăm operatorul + pentru clasa Line, care să adune două funcţii liniare.
Fie funcţiile:
f
1
(x) = m
1
x + n
1
f
2
(x) = m
2
x + n
2
Suma lor este funcţia
f(x) = (m
1
+ m
2
) x + (n
1
+ n
2
)
Definiţia clasei Line este următoarea
class Line
{
private:
double m, n;
public:
void print();
Line();
Line(double, double);
double getm();
double getn();
void setm(double);
void setn(double);
};
Definiţiile funcţiilor menbre ale clasei sunt cele anterioare şi nu sunt repetate.
Operatorul +, definit ca o funcţie globală, este următorul.
Line operator+( Line x, Line y)
{
double a = x.getm();
double b = x.getn();
double c = y.getm();
double d = y.getn();
Line temp(a + c, b + d);
return temp;
}
148
A se compara această funcţie cu funcţia sum definită anterior. Funcţia are doi
parametri, obiectele ce vor fi adunate, deoarece este o funcţie globală. Putem utiliza
funcţia definită astfel
int main()
{
Line x(2, 3), y(3, -5), z;
z = x + y;
z.print();
return 0;
}
Rezultatul programului este cel de mai jos. Constructorul implicit este apelat la
definirea obiectului z.
Putem defini operatorul ca funcţie prietenă a clasei. In definiţia clasei vom scrie în
definiţia clasei instrucţiunea
friend Line operator+ (Line, Line);
In acest caz definiţia funcţiei este următoarea.
Line operator+( Line x, Line y)
{
Line temp(x.m + y.m, x.n + y.n);
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 Line. 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 Line cu operatorul + este
class Line
{
public:
Line (double, double);
Line ();
void print();
// functia operator+ are un singur parametru
Line operator+( Line);
private:
double m;
double n;
149
};
Implementarea funcţiei operator+ este următoarea
Line Line::operator+( Line x)
{
Line temp(m + x.m, n + x.n);
return temp;
}
Definiţiile celorlalte funcţii nu sunt repetate.
Putem utiliza funcţia definită astfel
int main()
{
Line x(2, 3), y(3, -5), z;
z = x + y;
z.print();
return 0;
}
Rezultatul este cel anterior.
Exemplu. Fie instrucţiunea
Line a(1, 3), b(4, -3), c;
Instrucţiunile
c = a + b;
şi
c = a.operator+(b);
sunt echivalente.
Exerciţiu. Să se scrie implementarea operatorilor de scădere a două funcţii liniare, -, şi
compunere a două funcţii liniare, *.
9.2 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 Line
{
private:
double m, n;
public:
Line ();
Line (const Line &);
void operator = (const Line &);
};
150
O implementare a definiţiei incorecte a operatorului de atribuire supraîncărcat este
următoarea
void Line::operator = (const Line & r)
{
m = r.m;
n = r.n;
}
Ea copiază obiectul r în obiectul ce apelează operatorul. Fie de exemplu instrucţiunea
Line 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 multiplă 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. Simbolizând operatorul = cu o funcţie f cu două argumente, putem scrie
instrucţiunea de atribuire multiplă de mai sus
f(x, f(y, f(z, 2)));
In consecinţă, operatorul de atribuire = trebuie să aibe ca rezultat o referinţă de acelaşi
tip cu valoarea pe care o atribuie operandului stâng (tipul operandului stâng). In acest
fel operatorul este asociativ la dreapta.
Definiţia corectă a operatorului de atribuire al clasei Line este
Line & operator = (const Line & );
Definiţia clasei va fi
class Line
{
private:
double m, n;
public:
Line ();
Line (const Line &);
Line & operator = (const Line &);
};
Prototipul operatorului de atribuire al unei clase T este
T& operator = (const T&);
151
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.
Line & Line::operator = (const Line & r)
{
// obiectul ce a apelat operatorul primeste valoarea obiectului r
m = r.m;
n = r.n;
// rezultatul funcţiei este referinta obiectului ce a apelat operatorul
return *this;
}
Putem scrie acum instrucţiunile
Line x, y, z(2, 3);
x = y = z;
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 funcţiile 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. Supraîncărcarea operatorului de inserţie << pentru clasa Line. Operatorul va
insera în streamul de ieşire şirul (m, n).
In definiţia clasei definim funcţia
friend ostream& operator << (ostream& stream, Line x);
Implementarea funcţiei este
ostream& operator << (ostream& stream, Line x)
{
stream << "(" << x.m << "," << x.n << ")";
return stream;
}
Exemplu. Operatorul de extracţie >> pentru clasa Line este supraîncărcat astfel
friend istream& operator >> (istream& stream, Line& x);
Implementarea lui este următoarea
istream& operator >> (istream& stream, Line& x)
{
stream >> x.m >> x.n;
152
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 sunt un
membri ai 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. Definiţia
unei funcţii prietene este, cum s-a arat mai înainte
friend tip nume_funcţie ( parametri);
Cu aceasta, definiţia clasei Line este
class Line
{
private:
double m, n;
public:
Line ();
Line (double, double);
Line operator+( Line);
friend ostream& operator << (ostream& stream, Line x);
friend istream& operator >> (istream& stream, Line& x);
};
Definiţia funcţiilor membre ale clasei este cea de mai sus.
Putem utiliza operatorii definiţi astfel
int main()
{
Line a, b, c;
cout << "suma a doua functii liniare" << endl;
cout << "parametrii primei functii " << endl;
cin >> a;
cout << "parametrii functiei a doua" << endl;
cin >> b;
c = a + b;
cout << "functia suma" << endl << c << endl;
return 0;
}
Rezultatul programului este cel de mai jos.
153
Reamintim că, trebuie ca în definiţia clasei să includem un constructor implicit, fără
parametri, de exemplu cu definiţia
Line() {m = 0; n = 0;}
Constructorul implicit nu mai este generat automat de compilator deoarece am definit
un constructor. Constructorul implicit este apelat la declararea obiectelor
Line a, b, c;
Reamintim că, orice clasă include un operator implicit de atribuire =, care are ca
parametru un obiect de tipul clasei.
154
10 Moştenirea
10.1 Pointeri la obiecte. Operatorii new şi delete
O clasă, odată definită, este un tip de date valid. Putem defini variabile pointer de
tipul clasei la fel cu pointeri la orice tip. O variabilă tip pointer la o clasă poate fi
iniţializată cu adresa unui obiect de tipul clasei, iar datele şi funcţiile acelui obiect pot
apelate utilizând pointerul.
Instrucţiunea de definire a unui pointer de tipul unei clase 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
Expresia *p se inchide în paranteze deoarece operatorul . are o prioritate mai mare ca
operatorul *. 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. Vom considera din nou definiţia clasei Line care reprezintă funcţia liniară
f(x) = mx + n
Clasa defineşte variabilele m şi n, un constructor ce iniţializează variabilele şi două
funcţii, value(), ce calculează valoarea funcţiei într-un punct şi print(), ce scrie pe
ecran valorile variabilelor.
Line
double m;
double n;
Line (double, double);
void value(double);
void print();
Definiţia clasei Line este următoarea
/*
clasa Line descrie functia f(x) = m * x + n
*/
class Line
{
private:
double m, n;
public:
double value(double x) {return (m * x + n) ;}
void print(){
cout << "functia f(x) = m * x + n, " <<
155
" m = " << m << " n = " << n << endl;
}
Line(double x, double y) {m = x; n = y;};
};
Vom apela funcţiile clasei Line direct şi prin pointer.
int main()
{
Line d(1, 0);
double a = 1.5;
// apeleaza functiile direct
d.print();
cout << "f(" << a << ")= " << d.value(a) << endl;
// defineste un pointer la obiect
Line * pl;
pl = &d;
// apeleaza functiile prin pointer
pl->print();
cout << "f(" << a << ")= " << pl->value(a) << endl;
return 0;
}
Datele afişate pe ecran sunt cele de mai jos.
Există trei moduri de a crea obiecte:
• obiecte globale declarate la nivelul programului, în afara oricărei funcţii. Ele
sunt create la începutul 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 apelând constructorul şi furnizează
adresa obiectului nou creat, adică un pointer. Forma operatorului new este
ptr = new tip (listă de parametri);
tip este numele unei clase, iar ptr este variabilă tip pointer de tipul clasei. Operatorul
delete are forma
delete ptr;
unde ptr este variabila tip pointer cu adresa obiectului.
Exemplu. Vom considera aceeaşi clasă Line, vom crea un obiect corespunzând
funcţiei
f(x) = x
156
cu operatorul new şi vom calcula f(1.5). Operatorul new are ca rezultat un pointer de
tipul variabilei create. Funcţia main() corespunzătoare este cea de mai jos.
int main()
{
// defineste un pointer la obiect
Line * pl;
// creaza un obiect cu operatorul new
pl = new Line (1, 0);
double a = 1.5;
// apeleaza functiile prin pointer
pl->print();
cout << "f(" << a << ")= " << pl->value(a) << endl;
// distruge obiectul
delete pl;
return 0;
}
In programul de mai sus puteam scrie
Line *pl = new Line(1, 0) ;
Putem crea vectori de obiecte. Pentru aceasta, clasa trebuie să aibă un constructor fără
parametri. Fie, din nou, definiţia clasei Line cu un constructor implicit.
# include <iostream>
using namespace std;
class Line
{
private:
double m, n;
public:
void print();
Line(double a, double b) : m(a), n(b) {};
Line() : m(0), n(0) {};
};
void Line::print()
{
cout << "functia f(x) = m * x + n, "
<< " m = " << m << " n = " << n << endl;
}
Exemplu. Vom crea vectori cu două componente, obiecte tip Line şi vom afişa pe
ecran parametrii obiectelor create.
In prima variantă, crearea vectorului cu două obiecte de tip Line se face astfel
Line vx[2] ;
La crearea acestui vector, este apelat de către sistemul de operare constructorul fără
parametri Line(). După definirea vectorului, putem iniţializa elementele sale cu
obiecte definite de constructorul cu parametri, de exemplu
Line vx[1] = Line(2.3, -1.2) ;
157
Programul este cel de mai jos.
int main()
{
Line vx[2];
// vx[0] = Line();
vx[1] = Line(2.3, -1.2);
// afisaza obiectele
vx[0].print();
vx[1].print() ;
return EXIT_SUCCESS;
}
Rezultatul rulării programului este cel de mai jos
La crearea vectorului, toate componentele sale sunt iniţializate cu constructorul fără
parametri. In consecinţă, instrucţiunea
vx[0] = Line() ;
este inutilă.
In a doua variantă, rescriem programul de mai sus astfel. Definim o variabilă pointer
de tip Line şi o iniţializăm cu adresa unei zone de memorie pentru două obiecte,
alocată cu operatorul new. După cum am spus într-un capitol anterior, instrucţiunile :
Line vx[2] ;
şi
Line * pvx = new Line[2] ;
sunt echivalente.
De exemplu, putem crea un vector cu două obiecte de tip Line astfel
Line * vect;
vect = new Line[2];
Programul anterior este acum următorul.
int main()
{
// vectori de obiecte
Line * pvx = new Line[2];
pvx[0] = Line(-1, 1);
pvx[1] = Line(-2, 2);
// afisaza obiectele
int i;
for(i = 0; i < 2; i++) {
pvx[i].print();
// (*(pvx + i)).print();
// (pvx + i)->print();
}
delete [] pvx;
158
return EXIT_SUCCESS;
}
Ca exerciţiu, obiectele au fost afişate cu instrucţiunea for. S-au arătat forme
echivalente ale apelării funcţiei print(). Conform celor spuse într-un capitol anterior,
expresiile
pvx[i]
şi
*(pvx + i)
sunt echivalente. Să se explice de ce expresia *(pvx + i) este scrisă în paranteze în
instrucţiunea
(*(pvx + i)).print();
Conform celor spuse mai sus, expresia
(*(pvx + i)).print();
este echivalentă cu expresia
(pvx + i) -> print();
memoria alocată cu operatorul new este ştearsă cu instrucţiunea
delete [] pvx;
Exemplu. Vom rezolva problema de mai sus cu vectori de pointeri. Pentru început
definim un vector de pointeri, pv[2] de tipul Line :
Line * pv[2] ;
Componentele vectorului pv sunt iniţializate cu adresele unor obiecte create cu
operatorul new
pv[0] = new Line(-1, 1) ;
pv[1] = new Line(-2, 2) ;
Apelarea funcţiei print() se face conform definiţiei de mai sus. De exemplu, adresa
primului obiect, indicat de componentul pv[0] al vectorului pv, este *pv[0], deci
funcţia print() se apelează
(*pv[0]).print();
sau, echivalent
pv[0]->print();
Programul este următorul
int main()
{
int i;
Line * pv[2];
pv[0] = new Line(-1, 1);
pv[1] = new Line(-2, 2);
// scrie obiectele
for(i = 0; i < 2; i++) {
(*pv[i]).print();
// pv[i]->print();
}
return 0;
}
Rezultatul rulării programului este cel de mai jos.
159
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. Clasa care este moştenită
se numeşte clasă de bază sau superclasă, iar clasele care moştenesc se numesc
clase derivate sau subclase. Clasa derivată moşteneşte membrii clasei de bază,
funcţii şi variabile. In acest fel, putem construi programe complexe din obiecte
simple. Clasa de bază conţine caracteristicile comune ale mai multor elemente.
Clasele care moştenesc sunt clase particulare ce adaugă doar elementele proprii.
Cu ajutorul moştenirii reutilizăm clase deja construite.
10.2.1 Definirea unei clase derivate
Forma definiţiei 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. In
primele exemple, vom utiliza acces public, care are semnificaţia că, membrii publici
ai clasei de bază, funcţii şi variabile, sunt membri publici şi în clasa derivată.
Exemplu. Vom defini o clasă numită Baza ce defineşte două variabile tip double a şi
b, un constructor cu parametri ce iniţializează variabilele a şi b şi o funcţie print() ce
afişază valorile variabilelor a şi b sub forma (a, b).
Baza
double a;
double b;
Baza (double, double);
void print();
Definiţia clasei Baza este următoarea
# include <iostream>
# include <cmath>
using namespace std;
class Baza
{
protected:
double a, b;
public:
Baza(double x, double y) {a = x; b = y;}
void print() {cout << "(" << a << "," << b << ")" << endl;}
};
160
Variabilele clasei Baza au specificatorul de acces protected. Semnificaţia lui este
aceea că variabilele a şi b pot fi utilizate în clasele derivate.
Vom defini două clase, ce descriu triunghiuri şi respectiv numere complexe, ce
moştenesc clasa Baza, conform diagramei de mai jos.
Baza
double a;
double b;
Baza (double,
double);
void print();
Triunghi
double aria() ;
void print() ;
Triunghi(double, double) ;
In clasa Triunghi, a va fi baza, iar b înălţimea. Clasa Triunghi defineşte o funcţie
aria() ce calculează aria triunghiului, o funcţie print() ce afişază valorile bazei şi
înălţimii şi un constructor cu parametri. Definiţia acestei clase este cea de mai jos.
class Triunghi : public Baza
{
public:
double aria() {return a * b / 2;}
void print(){cout << "(baza, inaltimea) = "; Baza::print(); }
Triunghi(double x, double y) : Baza(x, y) {}
};
Funcţia print() a clasei Triunghi apelează funcţia print() a clasei de bază pentru a scrie
valorile a şi b. Apelarea funcţiei print() din clasa de bază se face cu operatorul de
rezoluţie ::
Baza ::print() ;
deoarece simpla apelare cu instrucţiune print() ar fi însemnat apelare recursivă a
funcţiei print() din clasa Triunghi. Constructorul clasei Triunghi apelează
constructorul clasei Baza pentru a iniţializa variabilele a şi b.
In clasa Complex, a şi b vor fi partea reală şi partea imaginară a numărului complex.
Clasa Complex defineşte o funcţie abs() ce calculează modulul numărului complex, o
funcţie print() ce afişază numărul complex şi un constructor cu parametri.
class Complex : public Baza
{
Complex
double abs() ;
void print() ;
Complex(double, double) ;
161
public:
double abs() {return sqrt(a * a + b * b);}
void print() {cout << "(u, v) = "; Baza::print();}
Complex(double x, double y) : Baza(x, y) {}
};
Pentru a scrie numărul complex, funcţia print() a clasei Complex apelează funcţia
print() a clasei de bază cu operatorul de rezoluţie ca mai sus. Constructorul clasei
Complex apelează constructorul clasei Baza pentru a iniţializa variabilele a şi b.
Constructorul clasei derivate apelează totdeauna constructorul clasei de bază. Implicit,
se apelează constructorul fără parametri. Dacă vrem să apelăm alt constructor, el este
apelat în lista de iniţializatori, ca în exemplele de mai sus.
Pentru a utiliza clasele derivate de mai sus, creăm un triunghi şi îi calculăm aria, apoi
definim un număr complex şi îi calculăm modulul.
int main()
{
Triunghi trg(2.5, 1);
Complex c(1, 1);
trg.print();
cout << "aria = " << trg.aria() << endl;
c.print();
cout << "|(u, v)| = " <<c.abs() << endl;
return 0;
}
Rezultate afişate sunt cele de mai jos.
Reamintim că am definit variabilele clasei de bază h şi b de tipul protected.
Datele şi funcţiile declarate protected în clasa de bază pot fi utilizate în clasele
derivate, dar nu pot fi utilizate de obiecte.
De exemplu, nu putem scrie în funcţia main() instrucţiunea
cout << trg.a << endl;
Din acest motiv, pentru a afişa valorile variabilelor a şi b, am definit funcţia print().
Datele şi funcţiile declarate private în clasa de bază nu pot fi utilizate în clasele
derivate sau de obiecte de tipul claselor derivate.
10.2.2 Specificatorii de acces
Vom prezenta acum semnificaţia cuvântului cheie acces din definiţia clasei derivate.
In definiţia unei clase, specificatorii de acces sunt : public, private şi protected.
Semnificaţia lor este următoarea :
• membrii privaţi ai clasei pot fi utilizaţi doar în interiorul clasei,
• menbrii protejaţi ai clasei pot fi utilizaţi şi în clasele derivate,
162
• membrii publici ai clasei pot fi utilizaţi şi în afara clasei, în clasele derivate şi
în obiectele de tipul clasei sau al claselor derivate.
Clasa derivată moşteneşte membrii publici şi pretejaţi ai clasei de bază.
Membrii publici şi protejaţi ai clasei de bază apar ca şi cum ar fi declaraţi în
clasa derivată.
Fie definiţia unei clase numită Baza
class Baza
{
public:
int x;
protected:
int y;
private :
int v ;
} ;
Variabila x poate fi utilizată în clasa Baza, în clasele derivate şi în obiecte de tipul
clasei Baza sau clase derivate din Baza. Variabila y poate fi utilizată în clasa Baza şi
in clasele derivate, dar nu în obiecte de tipul clasei Baza sau clase derivate. Variabila
v poate fi utilizată doar în interiorul clasei Baza.
Considerăm acum specificatorul de acces din definiţia clasei derivate :
• public spune că toţi membrii publici şi protejaţi ai clasei de bază sunt moşteniţi
în clasa derivată ca membrii publici sau protejaţi,
• protected spune că toţi membrii publici şi protejaţi ai clasei de bază sunt
moşteniţi în clasa derivată ca membrii protejaţi,
• private spune că toţi membrii publici şi protejaţi ai clasei de bază sunt
moşteniţi în clasa derivată ca membrii privaţi.
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 specificatorul de acces al variabilelor unui obiect în funcţie de
specificatorii lor în clasa de bază şi specificatorul din definiţia clasei derivate.
specificatorul de acces în
definiţia clasei derivate
specificatorul de acces al variabilei în
clasa de bază
public protected private
public public protected acces interzis
protected protected protected acces interzis
private private private acces interzis
Putem sumariza acum accesul la variabilele unui obiect în funcţie de specificatorii lor.
specificatorul de acces al variabilei
din tabelul de mai sus
public protected private
membri ai aceleiaşi clase da da da
membri claselor derivate da da nu
163
nemembri da nu nu
Vom defini mai jos trei clase derivate din Baza cu specificatorii de acces public,
protected şi private şi vom analiza specificatorii de acces în aceste clase ai variabilelor
x şi y definite în clasa Baza..
Fie o clasă derivată din Baza cu specificatorul de acces public
class DerivPublic : public Baza
{
/* declaratii din clasa Deriv */
public:
int w;
};
In acest caz variabilele x şi y au în clasa DerivPublic specificatorul de acces definit în
clasa Baza, respectiv public pentru x şi protected pentru y. Ele pot fi utilizate de
funcţiile definite în clasa Deriv, dar numai variabila x poate fi utilizată de obiectele de
tipul Deriv, deoarece doar variabilele cu specificatorul de acces public pot fi utilizate
de obiectele de tipul clasei. Variabila y nu poate fi utilizată de obiecte de tipul
DerivPublic deoarece este declarată protected în clasa de bază.
De exemplu, în funcţia main() putem avea instrucţiunile
int main()
{
DerivPublic b;
cout << b.x;
}
dar nu putem avea instrucţiunea
cout << b.y;
In tabelul de mai jos se arată variabilele obiectului b, cu tipul lor.
Considerăm clasa derivată din Baza definită cu specificatorul de acces protected
class DerivProtected : protected Baza
{
/* declaratii din clasa Deriv */
public:
int w;
} ;
In acest caz variabilele x şi y au în clasa DerivProtected specificatorul de acces
protected. Ele pot fi utilizate de funcţiile definite în clasa DerivProtected, dar nu pot fi
utilizată de obiecte de tipul DerivProtected. Fie obiectul
DerivProtected c ;
Variabilele obiectului, cu tipurile lor, sunt cele din tabelul de mai jos.
Considerăm acum clasa derivată din Baza definită cu specificatorul de acces private
class DerivPrivate : private Baza
{
/* declaratii din clasa Deriv */
164
public:
int w;
} ;
In acest caz toate variabilele x si y din clasa Baza au în clasa DerivPrivate
specificatorul de acces private. Fie obiectul
DerivPrivate d ;
Variabilele obiectului, cu tipurile lor, sunt cele din tabelul de mai jos.
x, w :public
y : protected
w : public
x, y : protected
w : public
x, y : private
DerivPublic b; DerivProtected c; DerivProtected d;
O clasă derivată moşteneşte membri clasei de bază, ţinând cont de specificatorii de
acces, exceptând constructorii, destructorul şi operatorul =. Deşi constructorii şi
destructorii nu sunt moşteniţi, constructorul implicit şi destructorul clasei de bază sunt
întotdeauna 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 în lista de iniţializatori, care are forma
constructor_clasa_derivata (lista de parametri)
: constructor_clasa_de_baza (lista de parametri)
{
/* definitia constructorului clasei derivate */
}
Menţionăm că, la distrugerea unui obiect de tipul unei clase derivate, se apelează
destructorul clasei şi apoi destructorul clasei de bază.
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, o funcţie clear() ce pune la valoarea
zero cele două coordinate şi o funcţie print() ce scrie pe ecran coordonatele punctului.
Definiţia clasei Point este cea de mai jos
165
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;
}
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. Clasa Pixel va defini
funcţia clear() ce va pune la valoarea zero coordonatele şi culoarea obiectului tip Pixel
şi funcţia print() ce va scrie pe ecran coordonatele şi culoarea pixelului.
166
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, care va iniţializa coordonatele pixelului.
Pixel::Pixel(int a, int b, int c) : Point(a, b)
{
color = c;
}
In constructorul clasei derivate de mai sus am apelat constructorul clasei de bază în
secţiunea :
Pixel :: Pixel(int a, int b, int c) : Point(a, b)
void Pixel::clear()
{
Point::clear();
color = 0;
}
void Pixel::print()
{
Point::print();
cout << “culoarea : “ << color << endl;
}
167
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ă a funcţiei clear().
Un exemplu de utilizare a claselor Point şi Pixel este prezentat mai jos. Vom crea un
obiect pt tip Point şi vom afişa coordonatele lui, apelând funcţia print(), direct de către
obiect, şi printr-o variabilă tip pointer. Vom crea apoi un obiect p tip Pixel şi vom
afişa coordonatele şi culoarea apelând funcţia print(), direct de către obiect, şi printr-o
variabilă tip pointer.
int main()
{
// creaza un obiect de tipul Point si afisaza coordonatele lui
Point pt(124, 200);
cout << "coordonatele unui punct" << endl;
pt.print();
// afisaza coordonatele punctului utilizand un pointer
Point * ptrp = &pt;
ptrp->print();
// creaza un obiect de tipul Pixel si afisaza coordonatele si culoarea
Pixel p(1, 2, 15);
cout << "coordonatele si culoarea unui pixel" << endl;
p.print();
// afisaza coordonatele si culoarea utilizand un pointer
Pixel * ptrx = & p;
ptrx->print();
return 0;
}
Rezultatul programului este cel de mai jos.
10.3 Funcţii virtuale. Polimorfism
Considerăm următorul exemplu. Vrem să definim o listă care să conţină informaţii
despre angajaţii unei intreprinderi, numele şi departamentul în cazul tuturor
168
angajaţilor, iar în cazul managerilor şi poziţia. Aceste informaţii vor fi şiruri de
caractere.
O soluţie este aceea de a defini două clase diferite, una pentru angajaţii simpli, ce va
defini câmpuri cu numele angajatului şi departamentul şi altă clasă pentru manageri ce
va defini câmpuri cu numele angajatului, departamentul şi poziţia. Un astfel de
program este complicat deoarece trebuie să prelucreze separat cele două tipuri de
obiecte.
O altă soluţie, aplicată în continuare, este de a defini o clasă de bază ce conţine
câmpuri cu numele angajatului şi departamentul, şi o clasă derivată ce va defini în
plus un câmp pentru poziţia managerului.
Vom defini o clasă de bază numită Angajat, ce conţine :
• două variabile tip string, nume şi dept, cu numele persoanei şi al
departamentului,
• un constructor cu doi parametri pentru nume şi departament,
• o funcţie print() ce afişază aceste variabile.
Vom defini apoi o clasă numită Manager, ce moşteneşte clasa Angajat, ce conţine :
• variabilă tip string pozitie cu poziţia managerului,
• un constructor cu trei parametri pentru nume, departament şi poziţie,
• o funcţie print() ce afişază variabilele nume, dept şi poziţie.
Reprezentarea acestor clase este cea de mai jos.
Definiţia clasei Angajat este următoarea :
class Angajat
{
protected:
string nume;
string dept;
public:
Angajat(string s, string d) : nume(s), dept(d) {} ;
void print()
{cout << "Nume : " << nume << "\n";
cout << "Departament : " << dept << "\n";
}
};
169
Definiţia clasei Manager este următoarea.
class Manager : public Angajat
{
protected:
string pozitie;
public:
Manager(string s, string d, string p) : Angajat(s, d), pozitie(p) {};
void print()
{Angajat::print();
cout << " Pozitie : " << pozitie << "\n";
}
};
Presupunem că vrem să afişăm numele şi departamentul pentru toţi angajaţii şi, în
plus pentru manageri, poziţia. O soluţie este de a crea doi vectori cu pointeri, unul de
tipul Angajat şi altul de tip Manager cu adresele obiectelor respective şi de a le
prelucra separat. Această soluţie duce la programe complexe. Limbajul oferă
următoarea posibilitate. Un pointer de tipul clasei de bază conţine adresa unui
obiect de tipul clasei de bază sau de tipul unei clase derivate. Putem deci memora
adresele obiectelor de tip Angajat şi Manager într-un vector cu pointeri de tip Angajat
şi să apelăm funcţia print() a fiecărui obiect într-un ciclu. Fie următoarea funcţie
main() pentru rezolvarea problemei (necorectă). Definim două obiecte, unul de tipul
Angajat, altul de tipul Manager şi un vector de doi pointeri de tipul Angajat
Angajat x1("Alex", "proiectare");
Manager m1("George", "proiectare", "sef");
Angajat * vect[2];
Memorăm adresele celor două obiecte în componentele vectorului
vect[0] = &x1;
vect[1] = &m1;
şi într-o instrucţiune for apelăm funcţia print() a obiectelor
for(int i = 0; i < 2; i++)
vect[i]->print();
Programul este cel de mai jos
int main()
{
Angajat x1("Alex", "proiectare");
Manager m1("George", "proiectare", "sef");
Angajat * vect[2];
// memoreaza adresele obiectelor in vector
vect[0] = &x1;
vect[1] = &m1;
// apeleaza functia print() a obiectelor din vector
for(int i = 0; i < 2; i++)
vect[i]->print();
return 0;
}
Rezultatele sunt următoarele
170
Remarcăm că, în cazul celui de-al doilea angajat, nu se afişază poziţia. Problema
acestei soluţii este următoarea. Funcţia apelată este determinată de tipul variabilei
pointer, nu de tipul obiectului. In consecinţă, este apelată totdeauna funcţia print() a
clasei de bază, Angajat, deşi, în cazul obiectelor de tipul Manager, ar trebui să se
apeleze funcţia clasei Manager.
Putem face ca funcţia apelată să fie determinată de tipul obiectului şi nu de tipul
variabilei pointer definind funcţia respectivă virtuală. O funcţie virtuală are
acelaşi nume în clasa de bază şi în toate clasele ce o moştenesc. O funcţie este
definită ca virtuală scriind cuvântul cheie virtual în faţa definiţiei funcţiei.
Vom defini funcţia print() din exemplul anterior virtuală şi în acest caz funcţia apelată
este determinată de tipul obiectului. Programul complet este următorul.
# include <iostream>
# include <string>
using namespace std;
class Angajat
{
protected:
string nume;
string dept;
public:
Angajat(string s, string d)
{
nume = s;
dept = d;
}
virtual void print()
{
cout << "nume : " << nume << "\n";
cout << " departament : " << dept << "\n";
}
};
class Manager : public Angajat
{
protected:
string pozitie;
public:
Manager(string s, string d, string p) : Angajat(s, d)
{
171
pozitie = p;
}
virtual void print()
{
Angajat::print();
cout << " pozitie : " << pozitie << "\n";
}
};
int main()
{
Angajat x1("Alex", "proiectare");
Manager m1("Bob", "proiectare", "sef");
Angajat * vect[2];
// memoreaza adresele obiectelor in vector
vect[0] = &x1;
vect[1] = &m1;
// apeleaza functia print() a obiectelor din vector
for(int i = 0; i < 2; i++)
vect[i]->print();
return 0;
}
Reamintim că funcţia print() este virtuală, astfel încât apelul
vect[i]->print();
apelează funcţia print() din clasa corespunzătoare tipului obiectului, producând
rezultatul corect. Menţionăm de asemenea că instrucţiunea
Angajat x1(“Alex”, “proiectare”);
este corectă deoarece constructorul copiere al clasei string are ca argument un şir de
tip C sau de tip C++.
Rezultatul rulării programului este cel de mai jos.
Vom spune că apelul de funcţie
vect[i]->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ă.
Exerciţiu. Să se înlocuiască şirurile de tip string din clasele Angajat şi Manager cu
şiruri tip C.
Indicaţie. Definiţia clasei Angajat poate fi următoarea
class Angajat
{
protected:
172
char * nume;
char * dept;
public:
Angajat(char *, char *);
virtual void print();
};
Definiţia constructorului Angajat poate fi următoarea
Angajat::Angajat(char * s, char * d)
{
nume = new char[strlen(s) + 1]; strcpy(nume, s);
dept = new char[strlen(d) + 1]; strcpy(dept, d);
}
Exemplu. Fie clasele Point şi Pixel definite anterior. Vom redefini clasele Point şi
Pixel astfel ca, funcţia clear(), ce pune la valoarea zero variabilele, şi funcţia print(),
ce afişază valoarea variabilelor, vor fi declarate virtuale. Noua definiţie a clasei Point
este următoarea
class Point
{
private:
int x, y;
public:
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;
}
173
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. Noua definiţie a clasei Pixel este următoarea
class Pixel : public Point
{
private:
int color;
public:
virtual void print();
virtual void clear();
Pixel(int, int, int);
};
Definiţiile funcţiilor sunt următoarele.
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 << “ culoare = “ << color << endl;
}
In funcţia main() vom crea un obiect p de tip Point şi apoi un obiect px de tipul 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
cout << "coordonatele unui punct" << endl;
ptr->print();
// creaza un obiect de tipul Pixel
Pixel px(1, 2, 15);
// atribuie variabilei ptr adresa obiectului px de tip Pixel
174
ptr = &px;
// afisaza coordonatele si culoarea pixelului
cout << "coordonatele si culoarea unui pixel" << endl;
ptr->print();
return 0;
}
Rezultatul rulării programului este cel de mai jos.
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.
In consecinţă, apelul de funcţie
ptr->print();
este polimorfic, deoarece se modifică după natura obiectului indicat de pointer.
10.4 Destructori virtuali
La distrugerea unui obiect de tipul unei clase derivate este apelat destructorul clasei
derivate şi apoi destructorul clasei de bază.
Fie programul de mai jos în care definim o clasă de bază numită Baza şi o clasă
derivată numită Deriv. In ambele clase, constructorul şi desctructorul afişază un
mesaj. In funcţia main() definim o variabilă pointer de tipul Baza şi îi dăm ca valoare
adresa unui obiect de tipul Deriv creat cu operatorul new, după care ştergem obiectul
creat cu instrucţiunea delete
Baza * var = new Deriv();
delete var;
Programul este următorul
#include <iostream>
using namespace std;
class Baza
{
public:
Baza(){cout << "Constructor Baza" << endl;}
~Baza(){cout << "Destructor Baza" << endl;}
};
175
class Deriv : public Baza
{
public:
Deriv(){cout << "Costructor Deriv" << endl;}
~Deriv(){cout << "Destructor Deriv" << endl;}

};

int main(int argc, char *argv[])
{
Baza * var = new Deriv();
delete var;
return 0;
}
Rezultatul rulării programului este următorul
După cum se observă, este apelat destructorul clasei Baza şi nu al clasei Deriv,
deoarece, în cazul funcţiilor ce nu sunt virtuale, funcţia ce se apelează este dată de
tipul variabilei, care este Baza, şi nu de tipul obiectului memorat. Pentru a apela
destructorul clasei Deriv trebuie să declarăm destructorul clasei de bază de tip virtual,
ca mai jos
class Baza
{
public:
Baza(){cout << "Constructor Baza" << endl;}
virtual ~Baza(){cout << "Destructor Baza" << endl;}
};
Rezultatul rulării programului este acum următorul
Acum este apelat destructorul clasei derivate, ce apelează şi destructorul clasei de
bază.
176
10.5 Date şi funcţii statice
10.5.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 se iniţializează în afara clasei. Datele statice sunt la fel ca şi
variabilele globale, dar în spaţiul de nume al clasei.
Exemplu.
class A
{
public:
static int x;
};
int A::x = 0;
Datele statice există, chiar dacă nu există nici un obiect de tipul clasei. Pentru
exemplul clasei definite mai sus, putem utiliza variabila statică x ca A::x, sau A.x sau,
dacă există obiecte de tipul clasei, ca nume_obiect.x Datele statice sunt iniţializate
automat la zero.
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ă. Valoarea iniţială a variabilei statice este zero. Reamintim că
variabilele statice se iniţializează în afara clasei. Definiţia clasei este următoarea.
# 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;
177
cout << “exista “ << X::nb << “ obiecte” << endl;
}
cout << “exista “ << X::nb << “ obiecte” << endl;
return 0;
}
Rezultate afişate vor fi
După instrucţiunea
X a, b;
există două obiecte, a şi b. După instrucţiunea
X c, d;
există patru obiecte, a, b, c şi d. După ieşirea din blocul interior există două obiecte,
obiectele c şi d fiind distruse la ieşirea din bloc. Menţionăm că, putem adresa variabila
nb ca X.nb sau X::nb.
10.5.2 Funcţii statice
O clasă poate defini funcţii statice. Ele pot prelucra doar datele statice ale clasei.
Funcţiile statice pot fi apelate utilizând numele clasei sau al unui obiect de tipul clasei.
Definirea unei funcţii statice se face scriind cuvântul cheie static înaintea definiţiei
funcţiei. Funcţiile statice sunt la fel ca şi funcţiile globale, dar în spaţiul de nume al
clasei.
Un exemplu de utilizare a funcţiilor statice este tehnica de programare
ClassFactory. Ea constă în următoarele. Fie o clasă de bază numită Base ş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. Acest lucru se face definind o clasă cu o funcţie statică, ce crează un
obiect de tipul Deriv1 sau Deriv2, în funcţie de valoarea parametrului şi
furnizează adresa obiectului creat ca pointer de tipul clasei de bază Base.
Exemplu. Vrem să definim următoarele două clase :
• o clasă cu o metodă ce calculează suma a două numere,
• o clasă cu o metodă ce calculează produsul a două numere.
In ambele cazuri metoda se va numi oper(). Vrem ca în funcţie de valoarea unui
parametru să obţinem un obiect care să facă produsul sau suma a două numere.
Vom defini o clasă de bază ce defineşte două variabile a şi b, constructorii
corespunzători şi o funcţie virtuală oper() ce nu efectuează nici un calcul.
# include <iostream>
using namespace std;
class Base
{
protected:
float a, b;
178
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 virtuală oper() ce efectuează adunarea, In cazul
clasei Add, respectiv produsul a două numere, în cazul clasei Mul. Ele au următoarele
definiţii
class Add : public Base
{
public:
Add(float x, float y) : Base(x, y) {}
virtual float oper(){return a + b;}
};
class Mul : public Base
{
public:
Mul(float x, float y) : Base(x, y) {}
virtual float oper() {return a * b;}
};
Vom defini o clasă cu o metodă statică numită operfactory 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) cu adresa obiectului creat.
class ClassFactory
{
public:
static Base * operfactory(float, float, char);
};
Implementarea funcţiei operfactory este următoarea
Base * ClassFactory::operfactory(float x, float y, char c)
{
if(c == '+')
return new Add(x, y);
else
return new Mul(x, y);
};
179
Presupunem că vrem să calculăm suma a două numere. In funcţia main() se crează un
obiect de tipul Add apelând funcţia operfactory şi se memorează adresa obiectului
într-un pointer de tipul clasei de bază. Apoi utilizează metoda oper() a obiectului la
calculul sumei a două numere.
int main()
{
float z;
Base * base;
ClassFactory cf;
float x = 1.2, y = -2.35;
cout << "suma numerelor " << x << " si " << y << endl;
// creaza un obiect care sa calculeze suma a doua numere
base = cf.operfactory(x, y, '+');
// calculeaza suma numerelor
z = base->oper();
cout << z << endl;
cout << "produsul numerelor " << x << " si " << y << endl;
// creaza un obiect care sa calculeze produsul a doua numere
base = cf.operfactory(x, y, '*');
// calculeaza produsul numerelor
z = base->oper();
cout << z << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
180
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
obiectul cout o instanţă a clasei ostream. In afară de aceste două obiecte, mai sunt
predefinite: obiectul cerr, de tipul ostream, care este fişierul de ieşire standard pentru
erori şi obiectul clog, de tipul ostream, pentru înregistrarea diverselor mesaje. De
obicei, aceste două fişiere sunt tot ecranul.
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 clasele istream şi ostream. Definiţiile acestor clase se găsesc
în bibliotecile <ifstream>, <ofstream> şi <fstream>. O serie de constante, utilizate de
aceste clase, sunt definite în clasa ios. Pentru prelucrarea unui fişier se crează un
obiect, (un stream), instanţă a uneia din clasele de mai sus. Clasele istream şi ostream
sunt definite în biblioteca <iostream>. Diagrama moştenirii acestor clase este cea de
mai jos.
Funcţiile membre importante ale claselor sunt:
• constructor fără parametri,
• funcţia membră open() cu prototipul
open(char * filename, int mode);
asociază obiectul creat cu fişierul ce va fi prelucrat. 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.
• constructor cu parametrii funcţiei open(). Acest constructor crează un obiect şi
apoi deschide fişierul,
• funcţia
void close();
181
închide un fişier,
• funcţia
bool eof();
are valoarea adevărat dacă s-a detectat sfârşitul fişierului,
• funcţia
bool is_open();
are valoarea adevărat dacă fişierul este deschis.
In cazul citirii datelor de la tastatură, (din fişierul cin), sfârşitul de fişier este
indicat prin Ctrl+Z. După aceasta se apasă tasta return.
11.1 Fişiere text
Fişierele text sunt compuse din linii, care sunt şiruri formate din 0 sau mai multe
caractere, separate de caracterul ‘\n’. 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
) sin(
1
) ( cos
2
x
x
x x
e +
+
+
·
pentru x cuprins în intervalul [0, 2] cu pasul 0.2. Vom scrie valorile calculate într-un
fişier text cu numele rez.txt. In fiecare linie vom scrie o pereche de valori x şi e,
separate de caracterul ‘\t’. Apoi, vom citi fişierul creat şi vom afişa rezulatate pe
ecran. Programul este următorul.
# 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 EXIT_FAILURE;
}
// creaza fisierul
double x, e;
for(int i = 0; i < 11; i++)
{
// calculeaza expresia si scrie in fisier
182
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 EXIT_FAILURE;
}
// scrie antetul
cout << "x" << '\t' << "e" << endl;
fil2 >> x >> e;
while(!fil2.eof())
{
cout << x << ‘\t‘ << e << endl;
fil2 >> x >> e;
}
fil2.close();
return EXIT_SUCCESS;
}
Rezultatul rulării programului este cel de mai jos.
Exerciţiu. Să se rescrie programul de mai sus definind un singur obiect de tipul
fstream
fstream fil;
Se va deschide la început streamul în scriere pentru crearea fişierului cu instrucţiunea
fil.open(filename, ios::out);
şi apoi în citire pentru afişarea rezultatelor cu instrucţiunea
fil.open(filename, ios::in);
11.1.2 Funcţii intrare / ieşire tip caracter
Fişierele text sunt formate din linii ce conţin zero sau mai multe caractere, urmate de
caracterul ‘\n’. Pentru prelucrarea acestor fişiere sunt definite funcţii ce citesc sau
scriu caractere şi linii, adică şiruri de caractere terminate cu caracterul ‘\n’.
183
Fişierele text au indicator de poziţionare ce conţine numărul următorului caracter de
scris sau de citit. Acest indicator are valoarea zero la deschiderea fişierului şi apoi este
modificat de instrucţiunile de citire sau scriere a fişierului.
Clasa ifstream are următoarele funcţii membre pentru citirea caracterelor. Funcţiile :
int get();
ifstream& get(char&);
citesc un caracter dintr-un fişier.
Clasa ofstream are funcţia membră
ofstream& put(char);
ce scrie un caracter într-un fişier.
Reamintim că, clasele istream şi ostream, definesc aceleaşi funcţii.
Exemplu. Fie fis un obiect tip ifstream şi c o variabilă tip caracter. Instrucţiunile
c = fis.get() ;
şi
fis.get(c) ;
citesc un caracter din streamul fis în variabila c.
Reamintim că operatorul >> este un operator binar. Operandul stâng este un obiect tip
ifstream, operandul drept este o variabilă, rezultatul este o referinţă la un obiect tip
ifstream. Ţinând cont de prototipul funcţiei get()
ifstream& get(char&);
este posibil să citim un caracter şi un întreg
char c;
int x;
cu instrucţiunea
fis.get(c) >> x;
unde fis este un obiect tip fstream, deoarece operandul stâng este o referinţă tip
ifstream, rezultatul evaluării funcţiei get(char&). Acelaşi lucru este valabil pentru
operatorul << şi funcţia put();
Exemplu. Vom copia un fişier în altul, citind câte un caracter din primul fişier şi
copiindu-l în al doilea ş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 EXIT_FAILURE;
}
cout << endl << “Introduceti numele noului fisier” << endl;
184
cin >> filename;
ofstream filb(filename);
if(!filb.is_open())
{
cout << “Fisierul “ << filename << “ nu se poate crea” << endl;
return EXIT_FAILURE;
}
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 EXIT_SUCCESS;
}
Rezultatul rulării programului este cel de mai jos. In acest exemplu fişierul sursă,
main.cpp este copiat în fişierul main.txt.
Menţionăm că expresia
fila.get(car)
are valoarea fals la întâlnirea sfârşitului de fişier. In consecinţă, puteam copia fişierul
astfel
while(fila.get(car))
{
filb.put(car);
nl++;
}
Pentru citirea unui şir de caractere se utilizează funcţia getline() a clasei ifstream.
Funcţia
ifstream& getline(char * bloc, int size, char ch);
citeşte cel mult size – 1 caractere în vectorul bloc. Citirea se opreşte după citirea a
size-1 caractere sau la întâlnirea caracterului ch. Valoarea implicită a caracterului ch
este ‘\n’. In acest caz funcţia are forma
ifstream& getline(char * bloc, int size);
185
Citirea unui şir de caractere dintr-un fişier tip text într-un obiect tip string se poate
face şi cu funcţia globală
istream& getline(istream& file, string str, char ch) ;
unde file este un obiect tip istream. Funcţia citeşte caractere în obiectul str de tip
string până la întâlnirea caracterului ch. Valoarea implicită a caracterului ch este ‘\n’,
în care caz funcţia are forma
istream& getline(istream& file, string str);
Vom ilustra utilizarea funcţiei getline la afişarea unui fişier pe ecran. Programul
citeşte câte o linie şi o scrie pe ecran;
# include <iostream>
# include <fstream>
# include <string>
using namespace std;
int main()
{
string str;
ifstream fin("test.txt");
if(fin.is_open())
{
while(!fin.eof())
{
getline(fin, str);
cout << str << endl;
}
fin.close();
}
return 0;
}
In acest exemplu, se crează un obiect (stream) cu constructorul clasei ifstream cu
parametrii funcţiei open(). Constructorul crează obiectul şi deschide fişierul.
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 şi
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);
186
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;
// deschide fisierul in creare
fila.open(filename, ios::out | ios::binary);
if(!fila.is_open())
{
cout << “ Nu se poate crea fisierul “ << filename << endl;
return EXIT_FAILURE;
}
// creaza fisierul
for(int i = 0; i < 10; i++)
{
// creaza un bloc
for(int j = 0; j < 10; j++)
x[j] = ‘0’ + i;
x[10] = 0;
// scrie blocul in fisier
fila.write(x, 11);
}
fila.close();
ifstream filb;
// deschide fisierul
filb.open(filename, ios::binary | ios::in);
if(!filb.is_open())
{
cout << “Nu se poate citi fisierul “ << filename << endl;
return EXIT_FAILURE;
}
// citeste si afisaza fisierul
filb.read(x, 11);
while(!filb.eof())
{
187
cout << x << endl;
filb.read(x, 11);
}
filb.close();
return EXIT_SUCCESS;
}
Rezultatul rulării programului este cel de mai jos.
Exemplu. Vom crea un fişier binar format din 26 de blocuri, conţinând şiruri de 10
caractere fiecare. Primul bloc va conţine caractere ‘a’, al doilea bloc va conţine
caractere ‘b’, etc. Vom citi apoi fiecare al patrulea bloc, modificând indicatorul de
citire.
# 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 EXIT_FAILURE;
}
int i;
for(i = 0; i < 26; i++)
{
// creaza un bloc
for(int j = 0; j < 10; j++)
x[j] = ‘a’ + i;
x[10] = 0;
// scrie blocul in fisier
filx.write(x, 11);
}
188
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 EXIT_FAILURE;
}
// 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 EXIT_SUCCESS;
}
Rezultatul rulării programului este cel de mai jos.
11.3 Fişiere text tip string
Limbajul C++ defineşte, în afară de fişierele standard şi cele pe disc, şi fişiere tip
string. In cazul acestor fişiere scrierea / citirea datelor se face în / din şiruri tip string.
Limbajul defineşte clasele
istringstream
ostringstream
stringstream
pentru fişiere de intrare, ieşire şi respective intrare / ieşire tip string. Definiţiile acestor
clase se găsesc în biblioteca <sstream>. Clasele definesc constructori fară parametri şi
constructori cu un parametru tip string. Scrierea şi citirea se fac cu operatorii << şi >>.
Clasele definesc funcţia
string str();
care furnizează o copie a şirului din obiectul stream tip i/o/stringstream.
Exemplu. Vom scrie valoarea unor variabile de tip int, respectiv double, separate de
un spaţiu într-un fişier tip string, vom citi din acest fişier o variabilă tip string şi vom
scrie valoarea ei pe ecran. După fiecare operaţie vom afişa pe ecran şirul de caractere
din fişierul tip string.
189
# include <string>
# include <iostream>
# include <sstream>
using namespace std;
int main()
{
int x = 12;
double d = -3.42;
string t;
// creaza un obiect stringstream ce contine un sir vid
stringstream ss;
// scrie valoarea variabilelor x si d separate de un spatiu in obiectul ss
ss << x << “ ” << d;
// scrie valoarea sirului continut in obiectul ss
cout << “ss = ” << ss.str() << endl;
// citeste valoarea variabilei tip string t din obiectul ss
ss >> t;
// scrie valoarea variabilei t pe ecran
cout << “t = ” << t << endl;
// scrie valoarea sirului continut in obiectul ss
cout << “ss = ” << ss.str() << endl;
return 0;
}
Valorile afişate pe ecran vor fi
Variabilele tip int şi float sunt convertite în şir de caractere la scrierea în obiectul ss.
Variabila t va primi ca valoare prin citire şirul de caractere “12 “. Reamintim că, la
citirea unui şir de caractere, operatorul << citeşte caractere până primul caracter
spaţiu, ‘\t’ sau ‘\n’ întâlnit. Menţionăm că valoarea şirului conţinut într-un fişier de tip
string nu este modificată de operatorul de citire >>.
190
12 Tratarea excepţiilor
12.1 Excepţii
In timpul execuţiei programului pot apărea diverse erori, numite în continuare
excepţii. 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. Alt 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. Eroarea
este prinsă (tratată) de 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
}
Atunci când apare o excepţie, programul trebuie să execute instrucţiunea throw cu
forma
throw obiect;
Excepţia este obiectul 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 aparea mai multe tipuri de excepţii.
Fiecare tip de excepţie este tratată de o anumită instrucţiune catch. Instrucţiunea catch
executată este cea pentru care tipul argumentului coincide cu tipul obiectului generat
de instrucţiunea throw. In consecinţă, o instrucţiune try poate fi urmată de mai multe
instrucţiuni catch. Tipurile argumentelor din instrucţiunile catch trebuie să fie diferite.
191
Exemplu. Tratarea unei excepţii împărţire prin zero. Instrucţiunea throw va genera un
obiect de tip int ce conţine numărul liniei unde a apărut eroarea. Limbajul defineşte
două constante utile în cazul excepţiilor :
• constanta __LINE__ conţine numărul liniei curente compilate,
• constanta __FILE__ conţine numele fişierului curent compilat.
Vom utiliza constanta __LINE__ în instrucţiunea throw pentru a semnala linia în care
a apărut eroarea. Instrucţiunea throw va apărea într-un bloc try. Programul este
următorul.
#include<cstdlib>
# include <iostream>
using namespace std;
int main()
{
int x = 2, y = 0, z;
try
{
if(y == 0)
throw __LINE__;
z = x / y;
cout << "x = " << x << " y = " << y << " x / y = " << z << endl;
}
catch(int a)
{
cout << "impartire prin zero linia : " << a << endl;
}
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Blocul catch încearcă repararea erorii apărute. Dacă nu este posibil acest lucru, se pot
utiliza funcţiile :
void exit(int);
sau
void abort();
pentru a termina programul. Funcţia exit() termină programul dintr-o funcţie diferită
de main(). Prototipurile acestor funcţii se găsesc în biblioteca <cstdlib>.
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
192
Dacă dorim ca un singur bloc catch să preia toate excepţiile unui bloc try vom scrie
blocul catch astfel
catch(…)
{
// trateaza exceptiile
}
12.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 parametri) throw (lista de tipuri)
{
// corpul functiei
}
Lista de tipuri dă tipurile de excepţii ce vor fi lansate de instrucţiunea throw.
Exemplu. Vom defini o funcţie ce calculează câtul a două numere întregi şi lansează o
excepţie, un obiect 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, în cazul nostru, un şir tip
string ce va conţine mesajul
“impartire prin zero linia : ”

+ numărul liniei + “fisierul : “ + numele fisierului sursa.
Numărul liniei unde apare eroarea este constanta __LINE__ , vezi exemplul anterior.
Numele fişierului sursă este conţinut în constanta tip caracter __FILE__. Obiectul de
tip string ce reprezintă mesajul este creat cu secvenţa de instrucţiuni :
stringstream ss;
ss << "linia : " << __LINE__ << " fisierul : " << __FILE__ ;
Programul este următorul
# include <iostream>
# include <string>
#include <sstream>
using namespace std;
int fun(int x, int y) throw(string)
{
if(y == 0)
{
stringstream ss;
ss << "linia : " << __LINE__ << " fisierul : " << __FILE__;
throw string(“impartire prin zero ”

+ ss.str());
}
else
return x / y;
}
193
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;
}
Rezultatul rulării programului este cel de mai jos.
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.
Exerciţiu. Să se modifice funcţia fun() astfel ca şirul ss să fie de tip C.
Indicaţie. Se va defini un şir ss de tip C şi se vor înscrie în el constantele __LINE__ şi
__FILE__ cu funcţia sprintf() :
char ss[50];
sprintf(ss, "linia : %i fisierul : %s", __LINE__, __FILE__);
throw string(“impartire prin zero ”

+ string(ss));
Prototipul funcţiei sprintf() este definit în biblioteca <cstdio>.
12.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 şi un constructor copiere :
exception();
exception(exception&);
• operatorul = ,
• funcţia virtuală what(), cu prototipul
virtual char * what();
care are ca rezultat un mesaj despre excepţie.
Funcţiile din clasele standard ale limbajului C++ generează excepţii de tipul exception
sau de tipul unei clase standard derivate din clasa exception, după cum se va arăta în
continuare.
Vom prezenta un exemplu în care se prinde o eroare generată de o funcţie a clasei
string: funcţia append, apelată cu un parametru eronat: indicele primului caracter din
şirul adăugat depăşeşte indicele ultimului element din şirul adăugat.
194
#include <cstdlib>
#include <iostream>
#include <string>
using namespace std;
int main()
{
try
{
string str1("err");
string str2("test");
str1.append(str2, 4, 3);
cout<<str1<<endl;
}
catch (exception &e)
{
cout << "Exception: " << e.what() <<endl;
cout << "Type: " << typeid(e).name() <<endl;
}
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Excepţiile lansate de funcţiile din bibliotecile standard ale limbajului C++ sunt
grupate în patru categorii:
 excepţii lansate de operatorul new când nu se poate aloca memoria cerută,
excepţii lansate când o excepţie nu corespunde nici unei clauze catch, etc.;
aceste excepţii sunt descrise de clase ce moştenesc direct din clasa excepţion,
 excepţii logice, care pot fi determinate înainte de execuţia programului, de
exemplu încercarea de a apela o funcţie cu un argument invalid; aceste
excepţii sunt descrise de clase ce moştenesc din clasa logic_error,
 excepţii detectabile la execuţia programului, de exemplu depăşirea gamei
inferioare sau superioare de reprezentare a numerelor în timpul unei operaţii
aritmetice; aceste excepţii sunt descrise de clase ce moştenesc din clasa
runtime_error,
 excepţii intrare / ieşire ce apar la operaţii cu fişiere; aceste excepţii sunt
descrise de clasa ios::failure; ele vor fi tratate într-un paragraf special.
Clasele logic_error, runtime_error şi ios::failure extind direct clasa exception şi sunt
extinse de alte clase pentru a descrie diverse tipuri de erori. Clasele logic_error şi
runtime_error şi clasele ce le extind sunt definite în biblioteca standard <stdexcept>.
Exemplu. Să rescriem funcţia care ce calculează câtul a două numere întregi şi
lansează o excepţie, un obiect de tip runtime_error, dacă numitorul este zero. Tipul
excepţiei lansate de funcţie este precizat în linia de definiţie a funcţiei şi este, în cazul
195
nostru, este un obiect de tipul runtime_error ce conţine un mesaj de eroare. Programul
este următorul.
#include <iostream>
# include <stdexcept>
#include <sstream>
using namespace std;
int fun(int x, int y) throw(runtime_error)
{
if(y == 0)
{
stringstream ss;
ss << __LINE__;
throw runtime_error("impartire prin zero linia : " + ss.str());
}
else
return x / y;
}
int main(int argc, char *argv[])
{
int a = 10, b = 0, c;
try
{
// functie ce poate lansa o exceptie
c = fun(a, b);
cout << a << "/" << b << "=" << c << endl;
}
catch(runtime_error& exm)
{
cout << exm.what() << endl;
}
return 0;
}
Rezultatul rulării ptogramului este cel de mai jos.
12.4 Excepţii intrare / ieşire
Orice obiect de tip stream conţine următorii biţi, definiţi în clasa ios, ce dau starea
curentă a streamului :
• badbit poziţionat la unu la apariţia unei erori nereparabile (eroare intrare /
ieşire pe disc),
196
• failbit poziţionat la unu la apariţia unei erori reparabile (de exemplu citim o
variabilă numerică, dar streamul de intrare conţine litere, încercăm să
deschidem în citire un fişier inexistent, etc),
• eofbit poziţionat la unu când s-a detectat sfârşitul de fişier la citire,
• goodbit poziţionat la unu atunci când nu există eroare.
Aceşti biţi sunt definiţi în clasa ios şi sunt poziţionaţi după fiecare operaţie de scriere
sau citire. Ei pot fi testaţi cu următoarele funcţii definite în clasa ios :
• bool bad(); are valoarea true dacă bitul badbit are valoarea unu,
• bool fail(); are valoarea true dacă cel puţin unul din biţii badbit şi failbit are
valoarea unu,
• bool eof(); are valoarea true dacă bitul eofbit are valoarea unu,
• bool good(); are valoarea true dacă ceilalţi biţi au valoarea zero.
Menţionăm că funcţia eof() a fost deja folosită la citirea fişierelor. Implicit, erorile
streamurilor nu lansează excepţii. Putem permite lansarea excepţiilor cu funcţia
int exceptions(int except);
definită în clasa ios, unde except este o combinaţie a biţilor pentru care dorim să
urmărim excepţii:
ios::badbit
ios::failbit
Aceşti biţi se pot combina cu operatorul |. De exemplu
exceptions(ios::badbit | ios::failbit);
permite lansarea excepţiilor când biţii badbit şi failbit sunt poziţionaţi la unu, în urma
unei operaţii intrare / ieşire.
In cazul în care excepţiile sunt permise, instrucţiunile de prelucrare a fişierului vor fi
incluse într-un bloc try şi excepţiile sunt prinse în blocul catch corespunzător. Clasa
de bază pentru excepţiile lansate de streamuri este clasa failure, care moşteneşte din
clasa exception. Ea este o clasă internă a clasei ios şi defineşte constructorul
failure(string);
şi funcţia virtuală
const char * what();
vezi definiţia clasei exception.
Exemplu. Vom citi un fişier tip text şi vom afişa conţinutul lui pe ecran. Se vor lansa
excepţii în cazul apariţiei unei erori în care sunt poziţionaţi la unu biţii badbit şi
failbit.
#include <iostream>
#include <fstream>
#include <exception>
using namespace std;
int main()
{
ifstream file;
// permite lansarea unei exceptii
file.exceptions(ios::badbit | ios::failbit);
try
{
197
char car;
file.open(“rez.txt”);
car = file.get();
while(! file.eof())
{
cout << car;
car = file.get();
}
file.close();
}
catch(ios::failure e)
{
cout << “exceptie : “ << e.what() << endl;
}
return 0;
}
Blocul catch are ca parametru un obiect de tipul failure
catch(ios::failure e)
care este tipul excepţiilor lansate de streamuri.
198
13 Aplicaţii
13.1 Funcţii de timp
Limbajele C şi C++ au funcţii predefinite pentru a obţine timpul curent. Prototipurile
acestor funcţii se găsesc în bibliotecile <time.h>, pentru programele C, respectiv
<ctime>, pentru programele C++.
Timpul este măsurat în secunde, de la data de 1 Ianuarie 1970 GMT ora 0. 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ă o variabilă de acest tip este
time_t time(time_t * timeptr);
Funcţia time are ca rezultat timpul în parametrul timeptr şi ca valoare.
Exemplu. Fie instrucţiunile :
time_t t1, t2;
t1 = time(&t2);
Variabilele t1 şi t2 primesc ca valoare timpul curent.
Funcţia ctime converteşte timpul unei variabile de tip time_t într-un şir de caractere de
forma
luna ziua hh:mm:ss an
Prototipul funcţiei este
char* ctime(time_t * timeptr);
Exemplu. Instrucţiunile
cout << ctime(&t1) << endl;
sau
printf(“%s\n”, ctime(&t1));
afişază timpul din variabila t1 ca mai jos.
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 conţinut într-o variabilă time_t în structura tm.
Exemplu. Fie secvenţa de instrucţiuni
199
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;
Timpul afişat pe ecran este
Vom rescrie secvenţa de program astfel
time_t t1;
struct tm t2;
t1 = time(NULL);
t2 = localtime(&t1);
printf(“%d:%d:%d\n”, t2.tm_hour, t2.tm_min, t2.tm_sec);
Funcţia
time_t mktime(struct tm * t);
converteşte timpul dintr-o structură tip tm într-o variabilă 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.
Există două macroinstrucţiuni ce pot fi utilizate :
• __DATE__ , şir de caractere, ce dă data compilării fişierului sursă curent,
• __TIME__ , şir de caractere, ce dă timpul compilării fişierului sursă curent.
Instrucţiunile :
printf("DATE : %s \n", __DATE__ );
printf("TIME : %s \n", __TIME__ );
afişază cele două şiruri de caractere ca mai jos.

13.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. Sistemul de operare
alocă cuante de timp ale unităţii centrale pe rând fiecărui fir. Fiecare fir execută o
anumită funcţie cu prototipul
void f(void *);
Parametrul funcţiei este un pointer de un tip nespecificat.
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);
200
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 transmisă firului.
Parametrul
void (*f) (void *)
este interpretat astfel
void (*f) (void *) f este
void (*) (void *) pointer la
void (void *) funcţie cu un parametru de tip void*
void de tip void
• funcţia
_sleep(int ms);
suspendă execuţia firului de pentru un număr de milisecunde dat de parametrul ms.
Un fir se termină atunci când funcţia executată de fir se termină.
Exemplu. Vom face un program care să lanseze în execuţie două fire. Funcţiile
executate de fire vor consta dintr-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 primul fir
void fthr1(void * arg)
{
int i;
for(i = 0; i < 5; i++)
{
printf("thread1\n");
_sleep(1000);
}
return;
}
// functie executata de al doilea fir
void fthr2(void * arg)
{
int i;
for(i = 0; i < 5; i++)
{
printf("thread2\n");
_sleep(500);
}
return;
}
201
int main()
{
_beginthread(fthr1, 0, NULL);
_beginthread(&fthr2, 0, NULL);
return 0;
}
Execuţia primului fir se termină când se ajunge la instrucţiunea return a funcţiei fthr1.
Reamintim că primul parametru al funcţiei _beginthread() este adresa funcţiei ce va fi
executată de fir. Aceasta poate fi specificată ca
&fthr1
sau
fthr1
deoarece numele funcţiei este chiar adresa punctului de intrare în funcţie.
Rezultatul rulării programului este prezentat mai jos. După cum se vede fiecare fir
este executat de cinci ori.
13.3 Funcţia system
Funcţia system() execută o comandă a sistemului de operare. Prototipul funcţiei este
int system(const char * command) ;
Parametrul funcţiei este un şir de caractere ce conţine comanda.
Prototipul funcţiei system() este definit în biblioteca <stdlib.h>, pentru programele C
şi <cstdlib> pentru programele C++.
O comandă utilă este pause, ce opreşte execuţia programului până când se apasă tasta
Return. O altă comandă utilă este dir, ce afişază conţinutul unui director.
Vom utiliza comanda dir pentru a afişa conţinutul directorului curent al aplicaţiei.
Programul este următorul.
#include <cstdlib>
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
system("dir");
system("PAUSE");
return EXIT_SUCCESS;
}
202
Rezultatul rulării programului este cel de mai jos. Se pot observa în directorul curent :
fişierul sursă, main.cpp, fişierul obiect rezultat al compilării, main.o şi programul
executabil, main.exe.
203
14 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 (Biblioteca de şabloane standard, Standart Template Library,
STL) pentru crearea şi manipularea acestor structuri. Clasele bibliotecii de şabloane
standard sunt clase generice care au ca parametri tipul obiectelor manipulate.
Biblioteca de şabloane standard are trei componente :
• containere. Un container este o structură de date ce conţine alte obiecte,
numite elementele containerului. Exemple de containere sunt vectorii, listele,
şirurile tip string, etc.,
• 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.
Un exemplu de container sunt listele dublu înlănţuite reprezentate grafic ca mai jos.
Listele dublu înlănţuite pot fi parcurse în ambele sensuri, folosind iteratori. Putem
adăuga / şterge elemente în / din listă. Elementele listelor pot fi sortate în ordine
crescătoare sau descrescătoare, cu funcţiile existente în biblioteca de algoritme.
14.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 parametri, ce se pot utiliza la declararea
de variabile şi funcţii membre ale clasei generice. Un obiect de tipul unei clase
generice se declară cu constructorul clasei cu următoarele forme:
nume_clasa <tip1, tip2, …, tipn> nume_obiect;
nume_clasa <tip1, tip2, …, tipn> nume_obiect (parametrii);
Aici tip1, tip2, …, tipn sunt tipurile actuale cu care este apelat constructorul clasei
generice. Ele pot fi tipuri simple, int, double, etc., sau clase definite de programator.
Exemplu. Să definim o clasă generică X ce poate calcula pătratul unui număr întreg
sau real. O reprezentare a clasei este cea de mai jos. In această reprezentare tipul T
este parametrul clasei X. Clasa defineşte un câmp de tipul T ce va conţine numărul, un
constructor tip copiere, o funcţie de acces, geta(), şi o funcţie ce calculează pătratul
numărului, square().
204
Definiţia clasei este următoarea
# 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;
}
Rezultatul rulării programului este cel de mai jos.
Ca exerciţiu, vom repeta definiţia clasei generice X, cu funcţiile membre definite în
afara clasei. In acest caz, definiţia
template <class T>
205
trebuie repetată înaintea fiecărei definiţii de funcţie.
#include <iostream>
using namespace std;
template <class T>
class X
{
private:
T a;
public:
X(T b);
T square();
T geta();
};
template <class T>
X<T>::X(T b)
{
a = b;
}
template <class T>
T X<T>::square()
{
return a * a;
}
template <class T>
T X<T>::geta()
{
return a;
}
Compilatorul utilizează definiţia clasei generice pentru a genera clasele necesare. In
exemplul de mai sus, compilatorul generează două clase, una X_int şi una X_double.
14.2 Containere, iteratori şi algoritme generice
Un container este un obiect ce poate conţine alte obiecte, numite în continuare
elemente ale containerului. Cel mai cunoscut tip de container este vectorul. Obiectele
din container au acelaşi tip. Tipul obiectelor poate fi unul simplu, int, double, etc., sau
unul structurat, şiruri tip string, sau obiecte ale unei clase definite de utilizator.
Containerele permit inserarea şi ştergerea de obiecte. Ele au funcţii membre ce
efectuează aceste operaţii. De regulă, funcţiile ce inserează obiecte în container, alocă
şi memoria necesară.
Menţionăm că, clasa obiectelor memorate într-un container, trebuie să aibă un
constructor implicit (fără parametri), un constructor tip copiere şi operatorul =.
O operaţie obişnuită efectuată asupra unui container este parcurgerea obiectelor din
container (traversarea containerului). Parcurgerea elementelor containerului se face cu
un iterator. Iteratorul este similar unui pointer, el indică un anumit obiect din
206
container. Operaţiile efectate asupra iteratorilor, indiferent de tipul containerului, sunt
următoarele :
• iniţializarea iteratorului la un anumit obiect din container,
• furnizarea obiectului de la poziţia curentă a iteratorului,
• modificarea obiectului de la poziţia curentă a iteratorului,
• avansarea iteratorului la poziţia următoare din container.
Clasele ce definesc iteratori au următorii operatori, ce realizează operaţiile de mai sus
asupra iteratorilor :
• 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 sau modificarea
obiectului de la poziţia curentă a containerului indicată de iterator.
In cele ce urmează vom prezenta vectorii, listele şi numerele complexe.
Biblioteca de şabloane standard defineşte funcţii ce se pot aplica asupra
containerelor. Cele mai importante funcţii sunt sort(), ce sortează elementele
containerului, şi find(), care caută un element în container. Aceste funcţii vor fi
prezentate pentru fiecare tip de container.
14.3 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. Ea este definită
în biblioteca <vector>.
Clasa defineşte următorii constructori :
• constructorul implicit (fără parametri)
vector();
crează un vector vid,
• constructorul copiere
vector(const vector& p);
crează un vector în care copiază elementele vectorului p care este parametru,
• constructor ce crează un vector cu n elemente
vector(int n);
Clasa defineşte destructorul
~vector() ;
Fie T tipul componentelor vectorului (parametrul clasei generice). Clasa defineşte
următoarele funcţii :
• void push_back(const T&); adaugă un element la sfârşitul vectorului,
• void pop_back(); şterge 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,
• void clear(); şterge toate componentele vectorului,
• operatorul = este operatorul de atribuire,
• operatori de comparare a doi vectori , <, <=, = =, >, >=, != , element cu
element.
207
Menţionăm că un vector poate avea două sau mai multe elemente egale.
Elementele unui vector pot fi accesate în trei moduri : cu operatorul [] şi funcţia at(),
arătate mai sus, şi cu iteratori. Aceste moduri vor fi exemplificate în continuare.
Exemplu. Să creăm şi să listăm un vector cu componente întregi.
# include <iostream>
# include <vector>
using namespace std;
#include <string>
int main()
{
// definim un vector cu componente siruri
vector <string> v;
// adauga 4 elemente
v.push_back("abcd");
v.push_back("x+y");
v.push_back("1x2ycz");
v.push_back("29dx");
// afisaza componentele vectorului
cout << “ elementele vectorului “ << endl ;
for(int k = 0; k < v.size(); k++)
cout << v[k] << “ “;
cout << endl;
return EXIT_SUCCESS;
}
Rezultatul rulării programului este cel de mai jos.
Menţionăm că, expresia v[k], putea fi înlocuită cu expresia v.at(k).
La definirea obiectului v, se crează un vector vid. 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 alt element fictiv după ultimul element.
In exemplul următor comparăm doi vectori, vec1 şi vec2.
#include <cstdlib>
#include <iostream>
#include <vector>
using namespace std;
208
int main(int argc, char *argv[])
{
vector<int> vec1;
vec1.push_back(-2);
vec1.push_back(7);
vector<int> vec2;
vec2.push_back(-3);
vec2.push_back(5);
vec2.push_back(8);
if(vec1 < vec2)
cout << "vec1 < vec2" << endl;
else if(vec1 == vec2)
cout << "vec1 = vec2" << endl;
else
cout << "vec1 > vec2" << endl;
return EXIT_SUCCESS;
}
Primul element al vectorului vec1 este -2, primul element al vectorului vec2 este -3.
In consecinţă, vec1 > vec2. rezultatul programului este cel de mai jos.
14.3.1 Parcurgerea vectorilor cu 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 sau modificarea valorii
de la poziţia curentă a containerului indicată de iterator.
Un iterator poate fi iniţializat la orice poziţie din container. Vom menţiona că există
două tipuri de iteratori ce permit parcurgerea vectorilor, în sens direct şi respectiv în
sens invers. Dacă parcurgem un container fără să modificăm elementele componente,
putem defini iteratori constanţi.
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();
209
ce au ca rezultat un iterator, ce indică primul element al vectorului şi respectiv
elementul fictiv 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 in ordine directa utilizand iteratori
cout << "componentele in ordine directa" << endl;
vector <int>::iterator it;
for(it = v.begin(); it != v.end(); it++)
cout << *it << “ “;
210
cout << endl;
// afisaza conponentele vectorului in ordine inversa utilizand iteratori
cout << "componentele in ordine inversa" << endl;
vector<int>::reverse_iterator iter1;
for(iter1 = v.rbegin(); iter1 != v.rend(); iter1++)
cout << *iter1 << “ “;
cout << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Putem reprezenta parcurgerea vectorului în sens direct ca în figura de mai jos.
14.3.2 Stergerea şi inserarea de elemente
In afară de funcţia pop_back(), clasa vector defineşte şi funcţiile :
erase(iterator pos);
erase(iterator first, iterator last);
Prima funcţie şterge elementul din poziţia specificată de iteratorul pos, a doua funcţie
şterge elementele între poziţiile first şi last.
In afară de funcţia push_back(), clasa vector defineşte funcţia
insert(iterator pos, const T& val);
ce inserează elementul val înaintea poziţiei specificate de iteratorul pos.
După ştergerea sau inserarea unui element în vector, toţi iteratorii trebuie
reiniţializaţi.
Exemplu. Fie vectorul din exemplul precedent. Programul de mai jos şterge primul
element din vector şi apoi inserează valoarea 25 ca prim element al vectorului.
Programul este următorul. Creăm un vector v
vector <int> v;
care este apoi iniţializat ca în exemplul anterior. Fie it un iterator
vector <int>::iterator it;
Stergerea primului element al vectorului se face astfel
it = v.begin();
v.erase(it);
Inserarea valorii 25 ca prim element se face astfel
it = v.begin();
v.insert(it, 25);
Afişarea elementelor vectorului se face cu o instrucţiune for
211
for(i = 0; i < v.size(); i++)
cout << v.at(i) << " ";
cout << endl;
Programul este următorul
#include <iostream>
#include <vector>
using namespace std;
int main(int argc, char *argv[])
{
// definim un vector cu componente intregi
vector <int> v;
// se adauga 4 elemente
int i;
for(i = 1; i < 5; i++)
v.push_back(i * i);
// afisaza componentele vectorului
cout << "vectorul initial" << endl;
for(i = 0; i < v.size(); i++)
cout << v.at(i) << " ";
cout << endl;
vector <int>::iterator it;
// sterge primul element din vector
cout << “sterge primul element din vector” << endl;
it = v.begin();
v.erase(it);
cout << "vectorul modificat"
<< endl;
for(i = 0; i < v.size(); i++)
cout << v.at(i) << " ";
cout << endl;
// insereaza un element in prima pozitie
cout << “insereaza un element in prima pozitie” << endl;
it = v.begin();
v.insert(it, 25);
cout << “vectorul modificat" << endl;
for(i = 0; i < v.size(); i++)
cout << v.at(i) << " ";
cout << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
212
14.3.3 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() sortează elementele vectorului în ordine crescătoare şi are următorul
prototip
sort(iterator iter1, iterator iter2);
unde iteratorii iter1 şi iter2 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 initial
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 sortat
cout << "vectorul sortat" << endl;
for(i = 0; i < vst.size(); i++)
cout << vst.at(i) << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
213
A doua funcţie sort() are prototipul
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
comp() are rezultatul true dacă x < y şi false în caz contrar. Funcţia sort permută
elementele x şi y dacă funcţia comp are rezultatul false. Definind diferite funcţii comp
putem sorta vectorul în ordine crescătoare sau descrescătoare, vezi exemplul de mai
jos. 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;
}
214
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 initial
cout << “Vectorul initial parcurs in sens direct” << endl;
int i;
for(i = 0; i < v.size(); i++)
// cout << v[i] << endl;
cout << v.at(i) << “ “;
cout << endl;
// sorteaza vectorul in ordine crescatoare
cout << "Se sorteaza vectorul in ordine crescatoare" << endl;
sort(v.begin(), v.end(), comp);
// afisaza vectorul sortat
cout << "Vectorul sortat parcurs in sens direct \n";
for(i = 0; i < v.size(); i++)
cout << v[i] << “ “;
cout << endl;
// sorteaza vectorul in ordine descrescatoare
cout << "Se sorteaza vectorul in ordine descrescatoare" <<
endl;
sort(v.begin(), v.end(), comp2);
// afisaza vectorul sortat
cout << "Vectorul sortat parcurs in sens direct \n";
for(i = 0; i < v.size(); i++)
cout << v[i] << “ “;
cout << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
14.3.4 Căutarea unui element într-un vector
Biblioteca de şabloane standard defineşte funcţia globală find() cu prototipul
find(iterator first, iterator last, const T& val);
care caută elementul val printre elementele containerului, în intervalul dat de iteratorii
first şi last. Dacă elementul a fost găsit, funcţia are ca rezultat iteratorul ce indică
215
prima apariţie a elementului, în caz contrar funcţia are ca rezultat sfârşitul
intervalului.
Vom exemplifica utilizarea funcţiei find() cu programul de mai jos. Definim vectorul
vector <int> v;
care este apoi iniţializat. Definim un iterator ce va conţine rezultatul căutării
vector <int>::iterator it;
Funcţia find() va cauta valoarea 4 printre elementele vectorului
it = find(v.begin(), v.end(), 4);
Pentru a vedea dacă elementul a fost găsit, iteratorul it se compară cu sfârşitul
intervalului
if(it != v.end())
{
// componentul a fost găsit
}
Indicele elementului găsit se calculează ca
int i = it – v.begin();
Programul este următorul
#include <iostream>
#include <vector>
using namespace std;
int main(int argc, char *argv[])
{
// definim un vector cu componente intregi
vector <int> v;
// se adauga 4 elemente
int i;
for(i = 1; i < 5; i++)
v.push_back(i * i);
// afisaza componentele vectorului
cout << "componentele vectorului" << endl;
for(i = 0; i < v.size(); i++)
cout << v.at(i) << " ";
cout << endl;
// cauta un element
vector <int>::iterator it;
it = find(v.begin(), v.end(), 4);
if(it != v.end())
{
cout << "elementul " << *it << " a fost gasit" << endl;
i = it - v.begin();
cout << "indicele elemnetului este " << i << endl;
}
return 0;
216
}
Rezultatul rulării programului este cel de mai jos.
14.3.5 Copierea unui container. Iteratori pentru streamuri
Iteratorii pot itera pe streamuri, pentru a citi sau scrie date. Acest lucru este util pentru
a citi / scrie elementele unui container dintr-un / pe un fişier. In biblioteca de şabloane
standard există adaptori pentru interfaţa cu streamuri, definiţi în biblioteca <iterator>.
Adaptorul pentru interfaţa cu streamuri de intrare este
istream_iterator<T>
unde T este streamul de intrare.
Adaptorul pentru interfaţa cu streamuri de ieşire este
ostream_iterator<T, C>
unde T este streamul de ieşire, iar C este un şir de caractere ce este scris pe stream
după scrierea unui element.
Vom exemplifica utilizarea acestor iteratori la copierea de numere întregi din streamul
cin într-un vector de tip int, şi scrierea componentelor vectorului în streamul cout.
Biblioteca de şabloane standard defineşte funcţia copy ce copiază un container în
altul. Prototipul funcţie este
copy(iterator first, iterator last, iterator prim);
Funcţia are ca parametrii trei iteratori, first şi last dau primul şi ultimul element din
containerul sursă, iar prim dă primul element din containerul destinaţie.
Fie vectorul cu zece componente de tip int
vector<int> vec (10);
Vom copia elemente din streamul de intrare cin în vectorul vec astfel
copy(istream_iterator<int>(cin), istream_iterator<int>(), vec.begin());
Poziţia iteratorului ce corespunde sfârşitului de fişier al streamului de intrare este
istream_iterator<int>()
Vom copia elementele vectorului vec pe streamul de ieşire cout astfel
copy(vec.begin(), vec.end(), ostream_iterator<int>(cout, " "));
Programul este următorul
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;
int main(int argc, char *argv[])
{
vector<int> vec(10);

copy(istream_iterator<int>(cin), istream_iterator<int>(), vec.begin());
copy(vec.begin(), vec.end(), ostream_iterator<int>(cout, " "));
217
cout << endl;

return 0;
}
Rezultatul rulării programului este cel de mai jos.
Reamintim că, sfârşitul streamului de intrare cin, este indicat prin Ctrl+Z, după care se
apasă tasta Enter, vezi figura cu rezultatul programului.
Exemplu. Vom utiliza funcţia copy la copierea unui vector cu elemente de tip string în
altul. Vom iniţializa vectorul str cu elemente tip string şi vom copia elementele pe
ecran cu un iterator pentru streamul cout. Vom copia vectorul str în vectorul strcp cu
funcţia copy() şi vom copia elementele vectorului strcp pe ecran, tot cu un iterator
pentru streamul cout. Programul este următorul.
#include <iostream>
#include <vector>
#include <string>
#include <iterator>
using namespace std;
int main(int argc, char *argv[])
{
vector<string> str;
str.push_back("a+b");
str.push_back("3*2");
str.push_back("m*n+p");
cout << "vectorul initial" << endl;
copy(str.begin(), str.end(), ostream_iterator<string>(cout, " "));
cout << endl;
// se defineste un vector strcp de aceeasi lungime cu str
vector<string> strcp(str.size());
// se copiaza str in strcp
copy(str.begin(), str.end(), strcp.begin());
// se afisaza strcp
cout << "vectorul copiat" << endl;
copy(strcp.begin(), strcp.end(), ostream_iterator<string>(cout, " "));
cout << endl;
return 0;
}
218
Rezultatul rulaării programului este cel de mai jos.
14.4 Liste
Clasa generică list implementează o listă dublu înlănţuită, adică o listă ce poate fi
parcursă în ambele sensuri. Lista are elemente de acelaşi tip. Prototipul clasei este
definit în biblioteca <list>.
Clasa defineşte următorii constructori :
• constructorul implicit
list();
crează o listă vidă,
• constructorul copiere
list(const list& p);
crează o listă în care copiază elementele listei p.
Clasa defineşte destructorul
~list() ;
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ă,
• int size(); dă numărul de elemente din listă,
• bool empty() ; are valoarea true dacă lista este vidă,
• T& front(); are ca rezultat primul element din listă,
• T& back();are ca rezultat ultimul element din listă,
• void clear(); ştrerge toate elementele din listă,
• operatorul = este operatorul de atribuire,
• operatori de comparare = =, !=, <, <=, >, >=, compară două liste, element cu
element.
14.4.1 Parcurgerea listelor
Pentru parcurgerea componetelor unei liste se utilizează iteratori. Un iterator este
asemenea unui pointer la un element al listei. O listă poate fi parcursă în sens direct
sau în sens invers.
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 despre
vectori. Pentru parcurgerea directă a listelor clasa list defineşte funcţiile :
begin();
end();
219
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 despre
vectori.
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.
14.4.2 Sortarea listelor
Pentru sortarea în ordine directă a listelor, clasa list defineşte funcţia
void sort();
ce sortează elementele listei în ordine crescătoare.
Exemplu. Vom crea o listă cu elemente întregi, o vom sorta ascendent şi vom afişa
elementele listei sortate parcurgând-o în sens direct şi invers.
# 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 initiale in sens direct
cout << "-- lista initiala --\n";
list<int>::iterator iter;
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << " ";
cout << endl;
// sortarea elementelor listei in ordine crescatoare
ls.sort();
cout << "-- sorteaza lista in ordine creascatoare --\n";
// parcurgerea listei sortate in sens direct
cout << "-- lista sortata parcursa in sens direct --\n";
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << " ";
cout << endl;
// parcurgerea listei sortate in sens invers
220
cout << "-- lista sortata parcursa in sens invers --\n";
list<int>::reverse_iterator iter1;
for(iter1 = ls.rbegin(); iter1 != ls.rend(); iter1++)
cout << *iter1 << " ";
cout << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
14.4.3 Inversarea ordinii elementelor listelor
Pentru inversarea ordinii elementelor unei liste clasa list defineşte funcţia
void reverse();
Vom exemplifica utilizarea acestei funcţii, inversând elementele unei liste şi
parcurgând-o în sens direct şi apoi invers.
# include <iostream>
# include <list>
using namespace std;
int main()
{
list <int> ls;
ls.push_back(11);
ls.push_back(7);
ls.push_front(4);
ls.push_front(12);
// parcurgerea listei in sens direct
cout << "-- lista initiala --\n";
list<int>::iterator iter;
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << endl;
// inversarea ordinii elementelor listei
cout << "-- inversarea ordinii elementelor listei --\n";
ls.reverse();
// parcurgerea listei in ordine inversa
cout << "lista parcursa in sens direct\n";
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << endl;
// parcurgerea listei in ordine inversa
cout << "lista parcursa in sens invers\n";
221
list<int>::reverse_iterator iter1;
for(iter1 = ls.rbegin(); iter1 != ls.rend(); iter1++)
cout << *iter1 << endl;
return 0;
}
Rezultatul programului este cel de mai jos.
14.4.4 Inserarea şi ştergerea elementelor din liste
Clasa list defineşte şi funcţiile :
erase(iterator pos);
erase(iterator first, iterator last);
Prima funcţie şterge elementul din poziţia specificată de iteratorul pos, a doua funcţie
şterge elementele între poziţiile first şi last.
Clasa list defineşte funcţia
insert(iterator pos, const T& val);
ce inserează elementul val înaintea poziţiei specificate de iteratorul pos.
După ştergerea sau inserarea unui element în vector, toţi iteratorii trebuie
iniţializaţi.
Exemplu. Vom crea o listă cu şiruri de caractere, vom şterge primul element din listă,
şi vom insera un element în prima poziţie. Definim lista cu şiruri de caractere astfel
list<string> lst;
Iniţializăm apoi lista ca în exemplele anterioare. Definim un ierator ce va fi utilizat în
funcţiile erase() şi insert()
list<string>::iterator it;
Stergerea primului element din listă se face astfel
it = lst.begin();
lst.erase(it);
Inserarea unui element în prima poziţie din listă se face astfel
it = lst.begin();
lst.insert(it, "xns");
Reamintim că, după fiecare ştergere sau inserare de elemente, iteratorii trebuie
iniţializaţi.
Vom afişa lista iniţială şi după fiecare modificare. Pentru aceasta, definim o funcţie
afisarelista(), ce are ca parametri o listă cu şiruri de caractere şi doi iteratori, ce dau
prima poziţie şi ultima poziţie din listă. Prototipul funcţiei este
void afisarelista(list<string>, list<string>::iterator, list<string>::iterator);
222
Programul este următorul

#include <iostream>
#include <list>
#include <string>
using namespace std;
/*
functie ce afisaza lista
*/
void afisarelista(list<string> lst, list<string>::iterator beg,
list<string>::iterator den)
{
list<string>::iterator it;
for(it = beg; it != den; it++)
cout << *it << " ";
cout << endl;
}
int main(int argc, char *argv[])
{
list<string> lst;
// initializeaza lista
lst.push_back("abc");
lst.push_back("xyzp");
lst.push_front("a*b");
lst.push_front("zma");
lst.push_back("mzp");
// afisaza lista initiala
cout << "-- lista initiala --" << endl;
afisarelista(lst, lst.begin(), lst.end());

list<string>::iterator it;
// sterge primul element din lista
cout << "sterge primul element din lista" << endl;
it = lst.begin();
lst.erase(it);
cout << "-- lista modificata --" << endl;
afisarelista(lst, lst.begin(), lst.end());

// insereaza un element la inceputul listei
it = lst.begin();
lst.insert(it, "xns");
cout << "insereaza un element la inceputul listei" << endl;
cout << "-- lista modificata --" << endl;
afisarelista(lst, lst.begin(), lst.end());

223
return 0;
}
Rezultatul programului este cel de mai jos.
14.4.5 Copierea listelor
Copierea elementelor unei liste în altă listă, sau în alt container, se poate face cu
funcţia copy() cu prototipul
copy(iterator first, iterator last, iterator prim);
Funcţia are ca parametrii trei iteratori, first şi last dau primul şi ultimul element din
containerul sursă, iar prim dă primul element din containerul destinaţie.
Vom exemplifica utilizarea funcţiei copy în exemplul următor în care creăm o listă cu
numele list1, o copiem în lista list2 şi apoi copiăm lista list2 pe ecran. Cele două
operaţii de copiere se fac cu funcţia copy(). Pentru a copia list ape ecran utilizăm
adaptorul ostream_iterator ca mai sus. Programul este următorul.
#include <cstdlib>
#include <iostream>
#include <list>
#include <string>
#include <iterator>
using namespace std;
int main(int argc, char *argv[])
{
list<string> list1, list2(10);
list1.push_back("abv");
list1.push_back("a12c");
list1.push_back("n*m+p");
list1.push_back("x2y3");
copy(list1.begin(), list1.end(), list2.begin());
copy(list2.begin(), list2.end(), ostream_iterator<string>(cout, " "));
cout << endl;
return 0;
}
Rezultatul rulării programului este arătat mai jos.
224
Vom prezenta un exemplu în care copiem elementele unui vector într-o listă şi apoi
elementele listei pe ecran cu funcţia copy(). Programul este cel de mai jos.
#include <iostream>
#include <vector>
#include <list>
#include <iterator>
#include <string>
using namespace std;
int main(int argc, char *argv[])
{
vector<string> vec;
vec.push_back("vector");
vec.push_back("copiat");
vec.push_back("in");
vec.push_back("lista");
list<string> lis(4);
copy(vec.begin(), vec.end(), lis.begin());
copy(lis.begin(), lis.end(), ostream_iterator<string>(cout, " "));
cout << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
14.5 Parcurgerea şirurilor tip string cu iteratori
Sirurile tip string pot fi parcurse cu iteratori la fel ca orice container. Clasa string
defineşte o clasă internă iterator şi funcţiile :
begin();
end() ;
ce au ca rezultat un iterator pentru parcurgerea şirului în sens direct.
Clasa string defineşte clasa internă reverse_iterator şi funcţiile :
rbegin();
rend() ;
ce au ca rezultat un iterator pentru parcurgerea şirului în sens invers.
Exemplu. Vom defini un şir tip string şi îl vom parcurge în ambele sensuri folosind
iteratori.
# include <string>
# include <iostream>
using namespace std;
int main()
{
string str1(“abcdegh”);
225
// parcurge sirul in sens direct
string::iterator ii;
cout << "-- sirul parcurs in sens direct --" << endl;
for(ii = str1.begin(); ii != str1.end(); ii++)
cout << *ii << " ";
cout << endl;
// parcurge sirul in sens invers
string::reverse_iterator rii;
cout << "-- sirul parcurs in sens invers --" << endl;
for(rii = str1.rbegin(); rii != str1.rend(); rii++)
cout << *rii << " ";
cout << endl;
return 0;
}
Menţionăm că deoarece nu modificăm elementele şirului am putea utiliza iteratori
constanţi. Am putea deci defini iteratorul
string::const_iterator ii;
14.6 Numere complexe
Biblioteca de şabloane standard defineşte clasa complex pentru lucrul cu numere
complexe. Prototipul acestei clase este definit în biblioteca <complex>. Clasa are ca
parametru T, tipul double sau float al perechii de numere reale ce definesc numărul
complex. Clasa defineşte următorii constructori:
• Constructorul implicit fără parametri
complex();
ce crează numărul complex 0 + i 0,
• Constructorul cu parametri
Complex(T u, T v);
ce crează un număr complex u + iv,
• Constructorul copiere
complex(const complex<T>&);
Biblioteca defineşte următorii operatori:
• cei patru operatori aritmetici +, -, * , / ,
• operatorii relaţionali = = şi !=,
• operatorul de scriere <<. Numărul complex u + i v este scris de operatorul <<
ca
(u, v)
• operatorul de citire >>. Pentru citirea cu operatorul >> numărul complex u + i
v este introdus ca
(u, v)
Clasa defineşte următorii operatori de atribuire :
• =, +=, -=, *= , /=
Biblioteca defineşte următoarele funcţii matematice standard care au ca argumente
numere complexe :
asin sin exp pow
acos cos log sqrt
atan tan log10
Biblioteca defineşte următoarele funcţii:
226
• T real();
• T real(const complex<T>&);
dau partea reală a numărului complex, prima este funcţie membră a clasei,
• T imag();
• T imag(const complex<T>&);
dau partea imaginară a numărului complex, prima este funcţie membră a
clasei,
• complex <T> conj(complex<T>&);
are ca rezultat conjugatul numărului complex,
• T abs(complex<T>&);
dă valoarea absolută (norma) a numărului complex,
• T norm(complex<T>&);
dă pătratul valoarii absolute (normei) a numărului complex.
Exemplu. Fie un program ce defineşte numerele complexe a = 1 + i, b = 2.0 – 3.5i,
calculează suma lor c = a + b, apoi c = c + a şi valoarea absolută a lui c. Se citeşte
apoi numărul complex 2.7 – 3.5i de la tastatură care este scris pe ecran. Numărul este
introdus sub forma
(2.7, -3.5)
In final se scrie partea reală a numărului complex cu cele două funcţii real()
Programul este următorul.
#include <iostream>
#include <complex>
using namespace std;
int main()
{
complex <double> a(1, 1), b(2.0, -3.5), c;
c = a + b;
cout << ”c = ” << c << endl;
c += a;
cout << "c + a = " << c << endl;
cout << "abs(c) = " << abs(c) << endl;
// citeste si scrie un numar complex
cin >> a;
cout << "a = " << a << endl;
// scrie partea reala a numarului complex
cout << "real a = " << a.real() << endl;
cout << "real a = " << real(a) << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
227
228
Anexa 1. Reprezentarea informaţiilor
A.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
r n n
d d d N ) ... (
0 2 1 − −
·
Mărimea numărului natural corespunzător este:


·




· + + + ·
1
0
0
0
2
2
1
1
...
n
i
i
i
n
n
n
n
r d r d r d r d N
unde: r>1 este baza, n este numărul de cifre, iar
i
d
este cifra de pe poziţia i. Avem
totdeauna
r d
i
< ≤ 0
. 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
0
1
1
1
1
1
0
2 ... 2 2 b b b b N
n
n
n
i
i
i
+ + + · ·



·

In partea dreaptă avem un polinom de puteri ale lui 2. Coeficientul
i
b
este 0 sau 1. Din
expresia de mai sus a numǎrului se observǎ cǎ cifrele
o
b
,
1
b , etc se pot obţine ca
resturile impǎrţirilor repetate ale numǎrului N cu 2. Vom nota:
0
q N ·
şi vom scrie:
0 1 0
2 r q q + ·
de unde deducem:
0 0
b r ·
1
2
1 1
... 2 b b q
n
n
+ + ·


Vom scrie
1 2 1
2 r q q + ·
de unde deducem:
1 1
b r ·
2
3
1 2
... 2 b b q
n
n
+ + ·


Dupǎ n astfel de operaţii vom avea:
1 1
2
− −
+ ·
n n n
r q q
unde:
1 1 − −
·
n n
b r
0 ·
n
q
După cum se observǎ, resturile obţinute reprezintǎ chiar cifrele numǎrului binar.
Exemplu. Sǎ convertim numarul 14 din baza 10 în baza 2.
0 2 * 7 2 14
0 1
+ · + · r q
229
Avem deci:
0
0 0
· · r b
1 2 * 3 2 7
1 2
+ · + · r q
Avem: 1
1 1
· · r b
1 2 * 1 2 3
2 3
+ · + · r q
de unde obţinem:
1
2 2
· · r b
In final:
1 2 * 0 2 1
3 4
+ · + · r q
de unde obţinem:
1
3 3
· · r b
deci reprezentarea numǎrului 14 în binar este:
( ) ( )
2 10
1110 14 ·
Algoritmul de conversie a unui numǎr din baza 10 în baza 2 este urmǎtorul:
1.
N q ·
0
2. 0 · i
3. while
0 ≠
i
q
{
2
1
i
i
q
q ·
+
2 %
i i
q r ·
1 + · i i
}
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.
( ) ( ) ( )
16 2 10
1100 12 C · ·
( ) ( ) ( ) ( )
10 10 16 2
158 14 16 * 9 9 10011110 · + · · E
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.
( ) ( ) ( )
8 2 10
34 11100 28 · ·
Pentru verificare
( ) ( ) ( )
10 10
1
8
28 4 8 * 3 34 · + ·
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
230
numere în aceste baze se poate folosi aplicaţia Calculator a sistemului de operare
Windows.
In final prezentăm în tabela de mai jos numerele de la 0 la 15 reprezentate în bazele
10, 2, 8 şi 16.
Baza 10 Baza 2 Baza 8 Baza 16
0 0000 0 0
1 0001 1 1
2 0010 2 2
3 0011 3 3
4 0100 4 4
5 0101 5 5
6 0110 6 6
7 0111 7 7
8 1000 10 8
9 1001 11 9
10 1010 12 a
11 1011 13 b
12 1100 14 c
13 1101 15 d
14 1110 16 e
15 1111 17 f
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:


·

+ ·
2
0
1
2 2 * 0
n
i
i
i
n
a X
Numǎrul negativ X se reprezintǎ ca:


·

+ − ·
2
0
1
2 2 * 1
n
i
i
i
n
a X
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
231
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:


·


+ − ·
2
0
1
1
2 2 ) 1 (
n
i
i
i
n
n
a a X
unde coeficientul
1 − n
a
are valoarea 0 sau 1. Primul bit va fi interpretat ca şi
coeficientul lui
1
2


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


·

+ ·
2
0
1
2 2 * 0
n
i
i
i
n
a X
Numǎrul negativ X se reprezintǎ ca:


·

+ − ·
2
0
1
2 2 * 1
n
i
i
i
n
a X
unde:
i i
a a − ·1 . 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:


·

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


·

+ + − ·
2
0
1
1 2 2 * 1
n
i
i
i
n
a X
unde:
i
i a a − ·1 .
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:
232
1
2
0
2 1 2


·
· +

n
n
i
i
In consecinţǎ, putem scrie:
∑ ∑ ∑

·

·
− −

·

− · − + − · + − + −
2
0
2
0
1 1
2
0
1
2 2 2 2 * 1 1 2 ) 1 ( 2 * 1
n
i
i
i
n
i
i
i
n n
n
i
i
i
n
a a a
A.2 Deplasarea numerelor binare cu semn
Inmulţirea unui numǎr binar cu semn cu
1
2
sau
1
2

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


·

+ ·
2
0
1
2 2 * 0
n
i
i
i
n
a X
şi fie numărul înmulţit cu 2


·

+ · ·
2
0
1
2 2 * 0 * 2
n
i
i
i
n
b X Y
de unde se deduce:
i i
a b ·
+1
0
0
· b
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
1
2
sau
1
2

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
233
A.3 Reprezentarea numerelor reale
Numerele reale se reprezintǎ în calculator în virgulǎ mobilǎ. Numarul real R este pus
sub forma
e
b f R * ·
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.
2
10 * 3245 . 0 45 . 32 ·
1
10 * 35 . 0 035 . 0

− · −
Mantisa f are totdeauna un număr finit de cifre şi în plus:
1 < f
In continuare, pentru exemplificare vom considera cǎ mantisa are şapte cifre
semnificative. 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
... 3333333333 . 0 ) 3 .( 0
3
1
· ·
se reprezintǎ ca:
0
10 * 3333333 . 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:
2
10 * 1248 . 0
iar al doilea
0
10 * 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:
237.55 + 0.000425
Numerele sunt reprezentate astfel:
3
10 * 23755 . 0
0.3245 2
-0.35 -1
0.1248 2
0.35 0
0.23755 3
234
3
10 * 425 . 0

La adunare, mantisa celui de-al doilea numǎr este deplasatǎ la dreapta cu şase cifre.
Considerând lungimea mantisei de şapte cifre, se pierd doua cifre semnificative din al
doilea numǎr la aducerea la acelaşi exponent.
0.23755 3
0.0000004 3
Rezultatul adunǎrii este:
0.2375504 3
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 şapte cifre din
rezultat.
Exemplu. Fie de efectuat operaţia de scădere:
103.4567 – 23.45678
Numerele se reprezintǎ astfel:
0.1034567 3
0.2345678 2
Dacǎ mantisa are doar şapte cifre semnificative, când deplasǎm la dreapta cu o cifrǎ
mantisa celui de-al doilea număr, pierdem o cifră semnificativǎ.
0.0234567 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 opt cifre semnificative. In acest caz cele douǎ numere sunt
0.10345670 3
0.02345678 3
După scǎderea celor două numere obţinem:
0.07999992 3
iar dupǎ normalizare obţinem rezultatul corect:
0.7999992 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
0.425 -3
235
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 , 10 (
37 37

iar mantisa corespunde la opt cifre zecimale.
Exemple. Fie numărul
0
2 * 0 . 1 0 . 1 · · R . 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
1
2 * 0 . 1 5 . 0

· · R . 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 ∞ t 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 , 10 (
307 307

A.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
236
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.
237
Anexa 2. Priorităţile şi asociativitatea operatorilor
Operator Asociativitate
1 :: stânga
2 [ ] ( ) -> . stânga
3 ++ -- + - ! ~ sizeof (tip) new
delete * &
dreapta
4 (tip) dreapta
5 * / % stânga
6 + - stânga
7 << >> stânga
8 < <= > >= stânga
9 = = != stânga
10 & stânga
11 ^ stânga
12 | stânga
13 && stânga
14 | | stânga
15 ?: dreapta
16 = += -= *= /= %= <<= >>= &= |
= ^=
dreapta
17 , stânga
238
Anexa 3. Tipul complex în limbajul C
Limbajul C defineşte tipul complex pentru lucrul cu numere complexe. Un număr
complex este o pereche ordonată de două numere reale. In limbajul C vom avea
numere complexe constituite din două numere reale tip float şi separat, numere
complexe constituite din două numere reale tip double.
Un număr complex (o constantă complexă) are forma
a + b * I
unde a şi b sunt numere reale sau întregi ce definesc numărul complex.
Forma instrucţiunilor de declarare a variabilelor de tip complex este
complex double listă de variabile;
complex float listă de variabile;
Lista de variabile este formată din nume de variabile separate de virgule. Putem
defini, de exemplu, variabilele de tip complex următoare
complex float x, y, z ;
complex double m, n, p ;
Variabilele de tip complex pot fi iniţializate la definirea lor. La iniţializare se
utilizează constante complexe cu forma de mai sus sau cu forma
{a + b * I}
unde a şi b sunt cele două numere reale ce definesc constanta complexă.
Exemplu. Să definim variabilele complexe x = 2.35 – 7.4i şi y = -12.5 + 4.3i.
Instrucţiunea de definire a acestor variabile este
complex double x = 2.35 – 7.4 * I, y = -12.5 + 4.3 * I ;
sau
complex double x = {2.35 – 7.4 * I}, y = {-12.5 + 4.3 * I} ;
Operaţiile cu numere complexe sunt cele cunoscute, +, -, * şi /. Priorităţile şi
asociativitatea operatorilor sunt tot cele cunoscute. Expresiile se scriu după regulile
cunoscute. In scrierea expresiilor se pot utiliza ca termeni variabile şi numere întregi,
reale şi complexe.
Scrierea numerelor complexe se face cu instrucţiunea printf, în care vom defini doi
specificatori de conversie pentru fiecare număr complex, primul pentru partea reală, al
doilea pentru partea imaginară.
Pentru lucrul cu numere complexe trebuie să includem biblioteca antet <complex.h>.
Ca prim exemplu, definim două variabile complexe, d1 şi d2, şi calculăm expresiile
d1 * d2 şi d1 / d2 + 1. Variabila d1 este iniţializată la definirea di, în instrucţiunea
complex, la valoarea 2.35 – 3.45i, variabila d2 este iniţilizată în program la valoarea
4.3 - 2.5i -3.57 + 2.95i. Programul este următorul.
#include <stdio.h>
#include <stdlib.h>
#include <complex.h>
int main(int argc, char *argv[])
{
// definirea de variabile complexe
complex double d1 = 2.35 - 3.45 * I, d2, d3;
d2 = 4.3 - 2.5 * I -3.57 + 2.95 * I;
d3 = d1 * d2;
239
printf(" d1 = %6.2f %6.2f\n d2 = %6.2f %6.2f\n d1 + d2 = %6.2f %6.2f\n",
d1, d2, d3);
d3 = d1 / d2 + 1;
printf(" d1 = %6.2f %6.2f\n d2 = %6.2f %6.2f\n d1 / d2 + 1 = %6.2f %6.2f\n",
d1, d2, d3);
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Ca un alt exemplu, definim două variabile complexe, a1 şi a2, calculăm produsul lor,
a3=a1*a2 şi afişăm rezultatul pe ecran. Programul este următorul.
#include <stdio.h>
#include <stdlib.h>
#include <complex.h>
int main(int argc, char *argv[])
{

complex double a1 = {2.75 + 3.2 * I}, a2 = {-1 + 3.42 * I}, a3;
printf("a1 = (%6.2f %6.2f)\n", a1);
printf("a2 = (%6.2f %6.2f)\n", a2);
a3 = a1 * a2;
printf("a1 * a2 = (%6.2f %6.2f)\n", a3);
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Biblioteca <complex.h> include funcţiile uzuale pentru lucrul cu numere complexe.
Există funcţii separate pentru numere complexe ce constau din două numere de tip
double şi respective din numere de tip float.
Funcţiile pentru operaţiile cu numere complexe formate dintr-o pereche de două
numere tip double sunt următoarele:
240
creal csin cexp
cimag ccos clog
carg casin csqrt
cabs cacos cpow
conj catan
Toate funcţiile de mai sus au un argument număr complex, cu excepţia funcţiei cpow
ce are două argumente, numere complexe formate dintr-o pereche de două numere tip
double.
Funcţiile pentru operaţiile cu numere complexe cu sunt formate dintr-o pereche de
două numere tip float au numele de mai sus urmate de litera f.
Vom exemplifica utilizarea acestor funcţii calculând 1 − , apoi partea reală, partea
imaginară, argumentul şi modulul numărului complex 2.46 -6.25i. Programul este cel
de mai jos.
#include <stdio.h>
#include <stdlib.h>
#include <complex.h>
int main(int argc, char *argv[])
{
// complex double a = {-1 + 0 * I}, b, c = {2.46 - 6.25 * I};
complex double a = -1 + 0 * I, b, c = 2.46 - 6.25 * I;
double cr, ci;

// calcul sqrt(-1)
b = csqrt(a);
// b = csqrt(-1);
// b = csqrt(-1 + 0 * I);
printf("a = (%6.2f, %6.2f)\n", a);
printf("b = (%6.2f, %6.2f)\n", b);
// calcul partea reala si imaginara a unui numar complex
cr = creal(c);
ci = cimag(c);
printf("c = (%6.2f, %6.2f)\n", c);
printf("real(c) = %6.2f, imag(c) = %6.2f\n", cr, ci);
// calcul argumentul si modulul unui numar complex
cr = carg(c);
ci = cabs(c);
printf("arg(c) = %6.2f, abs(c) = %6.2f\n", cr, ci);
return 0;
}
In program am iniţializat variabilele folosind cele două forme arătate mai sus.
Calculul valorii 1 − se poate face cu una din formele
b = csqrt(a);
b = csqrt(-1);
b = csqrt(-1 + 0 * I);
241
unde variabila complexă a este iniţializată la valoarea -1.
Rezultatul rulării programului este cel de mai jos.
In exemplul următor vom citi un număr complex x de la tastatură şi vom calcula x
2
şi
x
i
. Vom utiliza numere complexe formate din două numere reale tip float. Vom citi
pertea reală şi cea imaginară cu instrucţiunea scanf. Pentru ridicarea la putere vom
utiliza funcţia cpow. Programul este următorul.
#include <stdio.h>
#include <stdlib.h>
#include <complex.h>
int main(int argc, char *argv[])
{
complex float x, y;
float xr, xi;

scanf("%f %f", &xr, &xi);
x = xr + xi * I;
printf("x = %f %f\n", crealf(x), cimagf(x));
y = cpow(x, 2);
printf("x ^ 2 = %f %f\n", crealf(y), cimagf(y));
y = cpow(x, 1 * I);
printf("x ^ I = %f %f\n", crealf(y), cimagf(y));
return 0;
}
Rezultatul rulării programului este cea de mai jos.
Vom exemplifica acum calculul valorii, modulului şi argumentului funcţiei de o
variabilă complexă
( )
( )
( )( )( ) 1 01 . 0 01 . 0 1 01 . 0 1
1 05 . 0 10
2 2
+ + + +
+
·
s s s s
s
s f
pentru s luând valori în intervalul [0, 14i] cu pasul 0.5i. Programul este cel de mai jos.
242
#include <stdio.h>
#include <stdlib.h>
#include <complex.h>
int main(int argc, char *argv[])
{
complex float ft;
complex float s, pas = {0.5 * I};
float realft, imagft, absft, argft, imags;
int i;
printf(" omega real imag abs arg\n");
for(i = 1; i <= 28; i++)
{
s = i * pas;
// s = i * (0.5 * I);
// s = i * 0.5 * I;
ft = 10 * (0.05 * s + 1) / (s + 1) / (0.01 * s + 1) /
(0.01 * 0.01 * s * s + 0.01 * s + 1);
imags = cimag(s);
realft = creal(ft); imagft = cimag(ft);
argft = carg(ft); absft = cabs(ft);
printf("%6.2f %6.2f %6.2f %6.2f %6.2f \n", imags, realft, imagft, absft, argft);
}
return 0;
}
Se va remarca modul de calcul al variabilei complexe s.
Rezultatul rulării programului este cel de mai jos.
243
244

1 Constante, variabile şi expresii....................................................................................5 1.1 Tipuri fundamentale.............................................................................................5 1.2 Variabile...............................................................................................................5 1.3 Modificatori de tip................................................................................................6 1.4 Operatorul typedef................................................................................................7 1.5 Constante..............................................................................................................7 1.6 Constante cu nume...............................................................................................9 1.7 Expresii aritmetice..............................................................................................11 1.8 Tablouri..............................................................................................................13 1.9 Instrucţiunea de atribuire....................................................................................15 1.10 Prototipuri de funcţii. Biblioteci de prototipuri...............................................16 1.11 Operaţii de intrare / ieşire................................................................................17 1.12 Funcţia main....................................................................................................21 1.13 Execuţia unui program....................................................................................22 1.14 Operatorul sizeof.............................................................................................22 1.15 Operatorii ++ şi - -...........................................................................................24 1.16 Operaţii cu numere întregi la nivel de bit........................................................27 1.16.1 Operatori de deplasare...............................................................................27 1.16.2 Operaţii logice la nivel de bit....................................................................28 2 Structuri de control fundamentale.............................................................................32 2.1 Algoritme...........................................................................................................32 2.2 Expresii relaţionale............................................................................................33 2.3 Expresii booleene..............................................................................................35 2.4 Operatorul if......................................................................................................36 2.5 Operatorul switch..............................................................................................39 2.6 Operatorul ?.......................................................................................................40 2.7 Operatorul while................................................................................................40 2.8 Operatorul do-while..........................................................................................43 2.9 Operatorul for....................................................................................................44 2.10 Operatorul ,......................................................................................................49 3 Funcţii........................................................................................................................51 3.1 Funcţii standard..................................................................................................51 3.1.1 Funcţii C standard de manipulare a caracterelor.........................................51 3.2 Definirea funcţiilor.............................................................................................54 3.3 Prototipuri de funcţii..........................................................................................56 3.4 Compilarea separată a funcţiilor.........................................................................57 3.5 Funcţii cu parametri tablouri .............................................................................58 3.6 Supraîncărcarea funcţiilor..................................................................................64 3.7 Transmiterea parametrilor către funcţii..............................................................66 3.8 Recursivitatea.....................................................................................................71 3.9 Funcţii generice..................................................................................................72 4 Pointeri şi referinţe....................................................................................................74 4.1 Pointeri...............................................................................................................74 4.1.1 Declararea variabilelor tip pointer...............................................................75 4.2 Referinţe.............................................................................................................79 4.3 Pointeri la funcţii................................................................................................82 4.4 Interpretarea instrucţiunilor ce conţin pointeri...................................................83 4.5 Pointeri şi tablouri unidimensionale...................................................................85 4.6 Poineri şi şiruri tip C..........................................................................................89

2

4.7 Pointeri şi tablouri multidimensionale ..............................................................92 4.8 Parametrii funcţiei main. Parametrii liniei de comandă.....................................97 4.9 Alocarea dinamică a memoriei..........................................................................97 5 Fişiere tip C.............................................................................................................106 5.1 Fişiere tip text...................................................................................................107 5.1.1 Funcţii intrare / ieşire cu format................................................................108 5.1.2 Funcţii intrare / ieşire tip caracter..............................................................112 5.2 Fişiere text tip şir de caractere..........................................................................115 5.3 Fişiere binare....................................................................................................117 6 Structuri tip C şi uniuni...........................................................................................122 6.1 Structuri............................................................................................................122 6.2 Uniuni...............................................................................................................125 7 Clase .......................................................................................................................127 7.1 Definirea unei clase..........................................................................................128 7.1.1 Definirea unei clase...................................................................................128 7.1.2 Pointerul this..............................................................................................133 7.1.3 Spaţii de nume...........................................................................................133 7.2 Constructori şi destructori................................................................................134 7.2.1 Constructori...............................................................................................134 7.2.2 Destructori.................................................................................................138 7.3 Funcţii prietene.................................................................................................140 7.4 Determinarea tipului unei expresii...................................................................142 8 Siruri tip C++...........................................................................................................144 8.1 Clasa string.......................................................................................................144 9 Supraîncărcarea operatorilor...................................................................................148 9.1 Supraîncărcarea operatorilor aritmetici............................................................148 9.2 Supraîncărcarea operatorului de atribuire........................................................150 9.3 Supraîncărcarea operatorilor << şi >>..............................................................152 10 Moştenirea.............................................................................................................155 10.1 Pointeri la obiecte. Operatorii new şi delete..................................................155 10.2 Moştenirea....................................................................................................160 10.2.1 Definirea unei clase derivate...................................................................160 10.2.2 Specificatorii de acces.............................................................................162 10.3 Funcţii virtuale. Polimorfism........................................................................168 10.4 Destructori virtuali........................................................................................175 10.5 Date şi funcţii statice.....................................................................................177 10.5.1 Date statice..............................................................................................177 10.5.2 Funcţii statice..........................................................................................178 11 Fişiere tip C++.......................................................................................................181 11.1 Fişiere text.....................................................................................................182 11.1.1 Funcţii intrare / ieşire cu format..............................................................182 11.1.2 Funcţii intrare / ieşire tip caracter............................................................183 11.2 Fişiere binare.................................................................................................186 11.3 Fişiere text tip string......................................................................................189 12 Tratarea excepţiilor................................................................................................191 12.1 Excepţii..........................................................................................................191 12.2 Excepţii lansate de funcţii.............................................................................193 12.3 Excepţii standard...........................................................................................194 12.4 Excepţii intrare / ieşire..................................................................................196 13 Aplicaţii ................................................................................................................199

3

13.1 Funcţii de timp...............................................................................................199 13.2 Fire de execuţie.............................................................................................200 13.3 Funcţia system...............................................................................................202 14 Biblioteca de şabloane standard............................................................................204 14.1 Clase generice................................................................................................204 14.2 Containere, iteratori şi algoritme generice ...................................................206 14.3 Vectori...........................................................................................................207 14.3.1 Parcurgerea vectorilor cu iteratori...........................................................209 14.3.2 Stergerea şi inserarea de elemente ..........................................................211 14.3.3 Sortarea componentelor unui vector........................................................213 14.3.4 Căutarea unui element într-un vector......................................................215 14.3.5 Copierea unui container. Iteratori pentru streamuri................................217 14.4 Liste...............................................................................................................219 14.4.1 Parcurgerea listelor..................................................................................219 14.4.2 Sortarea listelor........................................................................................220 14.4.3 Inversarea ordinii elementelor listelor.....................................................221 14.4.4 Inserarea şi ştergerea elementelor din liste..............................................222 14.4.5 Copierea listelor......................................................................................224 14.5 Parcurgerea şirurilor tip string cu iteratori....................................................225 14.6 Numere complexe..........................................................................................226 Anexa 1. Reprezentarea informaţiilor.......................................................................229 A.1 Reprezentarea numerelor întregi în sistemul binar.........................................229 A.2 Deplasarea numerelor binare cu semn ...........................................................233 A.3 Reprezentarea numerelor reale.......................................................................234 A.4 Reprezentarea caracterelor.............................................................................236 Anexa 2. Priorităţile şi asociativitatea operatorilor...................................................238 Anexa 3. Tipul complex în limbajul C......................................................................239

4

De exemplu abc şi Abc sunt două nume de variabile diferite. simbolizat %. Instrucţiunea de declarare a tipului are forma tip listă de variabile. Tipul unei variabile este mulţimea valorilor pe care le poate lua acea variabilă şi operaţiile ce se pot efectua cu acele valori. Lista de variabile este formată din nume de variabile separate de virgule. Tipurile predefinite în limbajul C++ sunt cele din tabelul 1. Tabelul 1. not Pentru fiecare tip există constante predefinite ce dau limitele minimă şi maximă ale domeniului de valori.* /. De exemplu.true 16 7 7 31 31 Operaţii +-*/% +-*/ +-*/ +-*/% +-*/% and. Tipul unei variabile este făcut explicit cu o declaraţie de tip. Menţionăm că.1 Tipuri fundamentale In matematică variabilele se clasifică după tipul lor. ceilalţi biţi sunt biţi ai numărului. primul bit este utilizat pentru semn. Tipuri de bază ale limbajului C++ Tip int float double char wchar_t bool Semnificaţie Numere întregi Numere reale în virgulă mobilă scurtă Numere reale în virgulă mobilă lungă Caractere în cod ASCII şi numere întregi pe un octet Numere pozitive şi caractere UNICODE pe doi octeţi Valoare booleană Dimensiune în octeţi 4 4 8 1 2 1 Domeniu de valori [-2 … 2 ) (−10 38 10 38 ) (−10 307 10 307 ) [-2 … 2 ) [0 … 2 ) false. pentru tipul întreg se adaugă restul împărţirii a două numere întregi. Literele mari sunt diferite de cele mici. In cazul tipului bool valoarea false este reprezentată prin zero iar true prin unu. şi începe totdeauna cu o literă sau caracterul _. variabile şi expresii 1. 5 .2 Variabile Numele unei variabile este format din cifre.Partea I Programarea procedurală 1 Constante. 1. pentru tipurile char şi int. litere şi caracterul _ (underscore). operaţiile tipului real sunt + . Diagrama sintactică este cea de mai jos. or.

4). iar tipul unsigned char are domeniul de valori [0 … 28) . f3. 1. instrucţiunea double d (1. Toţi biţii sunt biţi ai numărului. instrucţiunea double d = 1 + sin(2. tipul unsigned int are domeniul de valori [0 … 231). Primul bit este bit de semn. 6 .2 – cos(1. Menţionăm că tipul bool nu este definit în limbajul C. instrucţiunea float f1. Conform instrucţiunii de declarare a tipului de mai sus. O variabilă poate fi iniţializată la declararea tipului ei.2 – cos(1. Aceşti modificatori sunt: • unsigned. O alt mod de iniţializare a unei variabile la declararea tipului ei este dat de diagrama de mai jos Valoarea cu care se iniţializează variabila este expresia din paranteze.7)). declară o variabilă de tip double pe care o iniţializează la valoarea 1+sin(2. valorile variabilelor sunt pozitive.4). scriind numele lor separate de virgule. declară o variabilă a de tipul int şi o variabilă b de tipul char. Valorile variabilelor sunt numere cu semn.3 Modificatori de tip Modificatorii de tip schimbă domeniul de valori pe care le poate lua o variabilă de tip int sau char. conform următoarei diagrame sintactice De exemplu. De exemplu.7). La utilizarea acestui modificator. declară o variabilă de tip double şi o iniţializează la valoarea 1. De exemplu. char b. f2. mai multe variabile de acelaşi tip pot fi declarate cu o singură instrucţiune.De exemplu. • signed. instrucţiunile int a. De exemplu. declară trei variabile de tipul float.

sau 0x02F reprezintă valoarea zecimală -47.4 Operatorul typedef Operatorul typedef permite să definim tipuri de date bazate pe tipuri existente.31E1 Numărul real -41. Ele nu pot începe cu cifra 0.5 -4. In limbajul C nu există tipul predefinit bool.• • short.5 Constante Tipurile de constante din limbaj corespund tipurilor de date.0 Constantele reale pot avea exponent. Tipul wchar_t este tipul unsigned short int predefinit. Forma instrucţiunii este typedef tip-existent tip-nou Exemple.31e1 sau 2. Exemple de constante zecimale sunt : 275. • constantele zecimale sunt numere întregi reprezentate în baza 10. Putem să definim tipul bool folosind tipul char cu instrucţiunea typedef char bool 1. Constante întregi Constantele întregi sunt numere întregi scrise în bazele 8. • constante octale sunt numere întregi reprezentate în baza 8. Exemple : 1.31e+1 sau +2. De exemplu numărul 2. 9 şi A … F sau a … f.23 27. Ele încep cu 0x sau 0X.12 7 .31*101 se scrie ca 2. De exemplu. Exemple de constante hexazecimale : 0x1E sau 0x1e sau 0X1e sau 0X1E care reprezintă valoarea zecimală 30 -0x2f sau -0x2F. • constante reale în virgulă mobilă scurtă. Cifrele hexazecimale sunt 0. Acest modificator se aplică tipului int. Exemple de constante octale : 016 reprezintă numărul zecimal 14 (să se arate că (14)10 = (16)8 ) -014 reprezintă numărul zecimal -12 (să se arate că (-12)10 = (-14)8 ). long 1. -325. un număr întreg ce reprezintă puterea lui zece cu care se înmulţeşte constanta. Instructiunea typedef float REAL32 defineşte tipul REAL32 ce poate fi utilizat în locul tipului float.2* 10-1 se scrie ca şi constantă reală -41. 10 sau 16. Constante reale Constantele reale sunt numere reale în simplă sau dublă precizie. Partea subunitară este separată de cea întreagă prin punct zecimal.2e-1 sau -4. +12 • constante hexazecimale sunt numere întregi reprezentate în baza 16. tipul short int are domeniul de valori [-215 … 215). iar tipul unsigned short int are domeniul de valori [0 …216). …. Ele încep obligatoriu cu cifra 0.

• constante reale în virgulă mobilă lungă. Numărul hexazecimal hh reprezintă echivalentul hexazecimal al caracterului în codul ASCII. Caracterul 1 în codul ASCII este ‘1’ sau echivalent. constanta “a” se reprezintă pe doi octeţi. urmate de un octet ce conţine valoarea 0. numărul real 1. Se scriu după aceleaşi reguli ca şi constantele reale în virgulă mobilă scurtă şi sunt urmate de sufixul L sau l. Caracterul ‘1’ se reprezintă ca ‘\x31’. Constanta tip caracter ‘a’ se reprezintă pe un singur octet. caracterele ‘a’ şi ‘A’ se reprezintă ca ‘\x61’ şi respective ‘\x41’. Sirul anterior se reprezintă ca a b c d \0 Caracterele speciale se reprezintă în interiorul unui şir folosind secvenţa de evitare. caracterul ASCII a se scrie ‘a’ sau echivalent. De exemplu. ca şi constante întregi.5 se scrie ca şi constantă în virgulă mobilă lungă 1. 0x61 sau 97 Caracterul ASCII A se scrie ‘A’ sau echivalent. Caracterele se scriu între apostrofuri. Ele reprezintă un şir de caractere ASCII scris între ghilimele. De exemplu. 8 . 0x41 sau 65. De exemplu. Caracterele speciale ale codului ASCII se reprezintă folosind secvenţa de evitare ‘\n’ CR ‘\r’ LF ‘\t’ tab orizontal ‘\”’ ghilimele ‘\’’ apostrof ‘\\’ backslash ‘\?’ semnul intrebării ‘\0’ null (valoarea 0 pe un octet) constante tip şir de caractere. 0x31 sau 49. De exemplu “abcd” Un şir de caractere este reprezentat de codurile ASCII ale caracterelor pe câte un octet. Un alt mod de a defini constante tip caracter este de a le scrie sub forma ‘\xhh’ unde h este o cifră hexazecimală. 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”.5L sau 15e-1L Constante de tip caracter • • constante de tip caracter reprezintă un caracter al codului ASCII pe un octet.

14 • enumerări.• constante tip caracter UNICODE corespund tipului wchar_t. Directiva define ce defineşte o constantă are forma # define nume valoare unde valoare este interpretată de preprocessor ca un şir de caractere. defineşte tipul CLRX şi trei constante aaa. directiva # define PI 3. O enumerare defineşte constante întregi sau un tip întreg şi valorile asociate acelui tip. bbb. bbb. prima are valoarea zero şi fiecare constantă următoare are o valoare mărită cu unu : aaa = 0 bbb = 1 ccc = 2 9 . Constante booleene constante booleene sunt true şi false şi se reprezintă prin valorile unu şi respective zero.14 are ca efect înlocuirea numelui PI în program cu şirul de caractere 3. Preprocesorul citeşte diversele directive şi modifică programul sursă corespunzător acestora.6 Constante cu nume In program putem defini constante cu nume în următoarele feluri • utilizarea directivei define . Instrucţiunea enum de definire a unor constante întregi are forma Instrucţiunea enum de declarare a unui tip întreg şi a valorilor asociate lui are forma De exemplu. Ele se definesc sub forma L’\xhhhh’ unde h reprezintă o cifră hexazecimală. De exemplu. ccc aferente acestui tip. Compilatoarele limbajelor C şi C++ au un preprocessor care modifică programul sursă înainte de compilare. 1. enum CLRX {aaa. ccc}.

definesc constantele a. De exemplu enum cstval {ppp = 2. Valoarea fiecărei constante poate fi specificată printr-o expresie întreagă constantă. Fie de exemplu tipul enumx definit mai jos enum enumx {ena. Instrucţiunea următoare defineşte variabila z de tipul enumx enumx z. instrucţiunile : const int a = 7.5e-2 * 4. defineşte tipul cstval şi constantele de acest tip ppp = 2 dd = 3 cc = -1 nn = 14 Instructiunea enum BAZA {BIN = 2. ce au valorile specificate în instrucţiune.2) + cos(1. cc = -1. const char b = ‘a’.14. y(2 + ln(1. Aceste variabile pot primi ca valori doar constantele definite în instrucţiunea enum. const float z = log(12. Instrucţiunea enum are şi o formă mai simplă enum {listă de nume}. const float c = 2. enb. care defineşte doar constante şi nu un tip. DEC = 10}. const double x = sin(0.7)). utilizarea cuvântului cheie const. n. z şi y. Orice constantă definită cu instrucţiunea enum poate fi utilizată ca orice constantă întreagă. n(-2).5) / 2. enc}. dd. De exemplu.5). x.3. defineşte tipul BAZA şi constantele aferente BIN = 2 HEX = 16 OCT = 8 DEC = 10 Putem defini variabile corespunzând tipului definit prin enum. OCT = 8. nn = 2 + 3 * 4}. b. O asemenea instrucţiune are forma diagramelor sintactice de mai jos unde tip este un tip al limbajului iar valoare este o expresie constantă. 10 . HEX = 16. Putem atribui o valoare variabilei z astfel z = ena.• In program constantele sunt înlocuite cu valorile lor. c.

şi nu valoarea lor absolută. Exemple de expresii aritmetice şi scrierea lor sunt prezentate mai jos.7 Expresii aritmetice Expresiile aritmetice sunt formate din constante.1. se defineşte astfel a % b = a – (a / b) * b De exemplu 19 % 4 = 3 19 / 4 = 4 19 % (-4) = 3 19 / (-4) = -4 -19 % 4 = -3 -19 / 4 = -4 -19%(-4) = -3 -19 / (-4) = 4 La schimbarea semnului unui operand. 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) Expresia a b c + c b se scrie ca a / (b / c + c / b) Funcţiile matematice standard uzuale ale limbajelor C şi C++ sunt cele de mai jos acos asin atan cos sin tan exp ceil log floor log10 fabs pow sqrt Funcţia floor(x) calculează valoarea  x  (cel mai mare număr întreg cuprins în x). Toate funcţiile de mai sus au argumente de tip double şi rezultatul de tip double. Restul împărţirii a două numere întregi. Pentru gruparea termenilor se folosesc paranteze rotunde. Vom presupune că variabilele din aceste expresii au fost declarate în prealabil de tip double şi au primit valori. Apelarea unei funcţii se face scriind numele funcţiei ca termen într-o expresie urmat în paranteze de parametrii actuali.* şi / pentru operanzi reali. ( şi ). 11 . Funcţia pow are prototipul double pow(double a. şi % (restul împărţirii a două numere întregi). De exemplu. double b) şi calculează expresia ab. iar funcţia ceil(x) calculează valoarea  x  (cel mai mic număr întreg mai mare ca x). iar în cazul operanzilor de tip întreg. Operatorii sunt + . se poate schimba doar semnul câtului şi restului. variabile şi funcţii. a şi b.

în expresii putem folosi orice fel de constante întregi. a+3. expresia 8 / 4 / 2 este interpretată ca (8 / 4) / 2 şi are rezultatul 1. Asociativitatea la stânga a operatorilor înseamnă următorul mod de execuţie. Reamintim că. octale sau caracter sunt convertite în numărul întreg corespunzător.5) Menţionăm că. / şi % au o prioritate mai mare decât + şi . Expresia x/y*z este interpretată ca (x / y ) * z adică operatorii / şi * ce au aceeaşi prioritate se execută de la stânga la dreapta. pentru a modifica ordinea de execuţie o operaţiilor. Expresia x + 100 se poate scrie x + 100 sau x + 0x64 sau x + ‘d’ Codul ASCII al caracterului d este 0x64. expresia a + 27 se poate scrie a + 27 sau a + 0x1b sau a + 033 Constantele hexazecimală 0x1b şi octală 033 reprezintă valoarea zecimală 27.a * cos 2 ( x) + b * sin( x) 2. reale sau caracter. se fac conversii.5 (a*cos(x)*cos(x)+b*sin(x))/(2. Expresia a/b/c este interpretată ca (a / b) / c De exemplu. La evaluarea unei expresii constantele hexazecimale. De exemplu.75+fabs(x)) (exp(x)+exp(-2*x))/5 log10(fabs(x)+2)+log(fabs(1+cos(x))) ( x + y) pow(x+y.unar */% +Asociativitate la dreapta la stânga la stânga Conform tabelei de mai sus operatorii *. se utilizează paranteze rotunde..75 + x e x + e −2 x 5 log( x + 2) + ln 1 + cos( x ) a + 3. • tipurile char şi short int se convertesc la int. Prioritatea operatorilor + unar. Regulile după care compilatorul face aceste conversii sunt următoarele: • tipul float se converteşte la double. Deoarece în expresii intervin operanzi de diverse tipuri. . Evaluarea expresiilor aritmetice se face ţinând cont de priorităţile operatorilor şi de asociativitatea lor. 12 .

71 convertită la int este 3. nume[număr întreg – 1] De exemplu instrucţiunea int a[10]. a[1]. Elementele tabloului sunt nume[0]. expresia (i + f) % 2 nu este corectă deoarece expresia in parantezele rotunde are tipul double. {}. long int şi double. iar valoarea -3. Un tablou poate fi iniţializat la declararea sa scriind valorile elementelor între acolade.• • tipurile unsigned char şi unsigned short int se convertesc la unsigned int. a[9]. 1. Elementele vectorului sunt a[0].32. Conversia de la un tip real la un tip întreg se face prin trunchiere. …. şi separate de virgule.4 convertită la int este -3. unde număr întreg reprezintă numărul de elemente ale tabloului. …. short int şi variantele lor unsigned char.15. rezultatul ei este 5.45}. In limbajul C++ este posibilă încă o formă de convertire a expresiilor. double = 3. -2. dacă în expresie există un operand de tipul long int. 4. unsigned short int şi float sunt doar pentru memorare.47 .8 Tablouri Un tablou este o mulţime de elemente de acelaşi tip. dacă i este o variabilă întreagă cu valoarea 7 şi f este o variabilă de tip double cu valoarea 3. 13 . 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ă. cu forma tip(expresie) De exemplu. expresia anterioară se poate scrie int(i + f) % 2 Menţionăm că se pot face conversii între toate tipurile standard existente în limbaje. Instrucţiunea de declarare a unui tablou cu o dimensiune este următoarea tip nume [ număr întreg] .47 int i = 7 . valoarea 3. tipurile char. Valoarea unei expresii poate fi convertită într-un tip diferit dacă este nevoie. Tablourile sunt tipuri structurate simple. declară un vector cu zece elemente de tip int. nume[1]. Expresia (int)(i + f) % 2 are tipul int şi este corectă. Prima formă a expresiei de conversie de tip este (tip) expresie De exemplu. Exemple de instrucţiuni ce declară tablouri şi le atribuie valori double x[3] = {1. Dimensiunea unui tablou este fixată la declararea sa şi nu se poate schimba. Calculele se efectuează doar cu variabile de tip int. tipul int şi unsigned int se convertesc la long int. Există două forme ale expresiilor de conversie a tipurilor.

2. char s[4] = {‘a’. char s[4] = {0x61. ‘c’. 0x62. Presupunem următoarele declaraţii de tablouri 14 . ‘x’}. 0x63. ‘b’. Menţionăm că un vector de caractere poate fi iniţializat cu o constantă tip şir de caractere. la declararea lui putem omite numărul de elemente al tabloului. 99. ‘c’ . ‘m’}. Diagrama sintactică a declaraţiei unui tablou este următoarea De exemplu. ‘>’. 0}. In cazul iniţializării unui tablou. Ultimul tablou este reprezentat în memorie astfel a b c x In cazul în care lista de valori este mai scurtă decât numărul de elemente declarate. Un tablou cu mai multe dimensiuni poate fi de asemenea iniţializat la declararea sa. La utilizarea într-o expresie. -3}}. deci vectorul declarat are 4 componente. 7. Fie de definit matricea m cu elemente întregi. ‘b’. char s[4] = {97.{3. Reamintim că o constantă şir de caractere este terminată printr-un octet 0. instrucţiunea float b[7][3] declară o matrice cu şapte linii şi trei coloane. 98. ‘a’. putem scrie char s[] = “abc”. Elementele matricei sunt : b[0][0] … b[6][0] b[0][1] … b[6][1] b[0][2] … b[6][2] Elementele tablourilor sunt memorate pe linii. De exemplu. Un tablou poate avea oricâte dimensiuni. ‘m’}. ‘>’. ultimele elemente sunt iniţializate cu zero. 5}. Instrucţiunea precedentă este echivalentă cu instrucţiunea char x[3] = {‘#’. ‘c’ şi ‘\0’ si este reprezentat în memorie ca ‘a’ ‘b ’ ‘c’ ‘\0’ Instrucţiunea precedentă este echivalentă cu oricare dintre instrucţiunile următoare: char s[4] = “abc”. 0} . pozitive 1 2 5  m=  3 7 − 3 Instrucţiunea corespunzătoare este int m[2][3] = {{1. Compilatorul calculează dimensiunea tabloului din numărul de valori utilizate pentru iniţializare. putem scrie char x[] = {‘#’. ‘b’. ‘\0’}.char c[4] = {‘a’. indicii elementelor tablourilor pot fi orice expresii întregi. De exemplu.

z = (x + y) / (y + cos(x) * cos(x)). Forma instrucţiunii de atribuire este variabilă = expresie. a[0] selectează primul element al tabloului a.double a[10]. la atribuire. b. ce are doi operanzi: un nume de tablou şi un indice. y. returnează valoarea calculată de funcţie. Aceşti operatori se definesc astfel: 15 . operatorul de apelare a unei funcţii (). Limbajele C şi C++ au operatori speciali pentru scrierea prescurtată a instrucţiunilor de atribuire.34. /= şi %=.7. Priorităţile şi asociativitatea operatorilor Operator [] () + unar. Operatorul de atribuire = atribuie o valoare unei variabile. y şi z .9 Instrucţiunea de atribuire O operaţie definită pentru toate tipurile fundamentale este atribuirea. z. -=. a = x. de exemplu b[0][0]. (tip) */% +Asociativitate la stânga la dreapta la stânga la stânga 1. double x = 2. [] este un operator de selecţie. y = sin(x) + cos(x * x). +=. De exemplu cos(1. El se aplică asupra unui nume de tablou şi selectează un element al acelui tablou.unar. selectează primul element din prima linie a lui b. Menţionăm că. De exemplu. aplicat asupra unui nume de funcţie. int i. j. iar b[0] selectează prima linie a tabloului b. De exemplu.2) reprezintă aplicarea operatorului () asupra numelui funcţiei cos. Aplicând încă o dată operatorul de selecţie asupra lui b[0]. Operatorul [] este asociativ la stânga. *=. In acelaşi mod. float x. y[3][4]. Variabila întreagă a primeste valoarea 2. Următoarele expresii ce conţin elemente de tablouri : ai + b * y ij cos(b) − sin(b) e cos(b ) + y i . Fie de exemplu instrucţiunile int a. j +1 a jk + b 2 2 n −3 (a[i]+b*y[i][j])/(cos(b)-sin(b)) exp(cos(b)) + y[i][j + 1] a[j][k] + b[2 * n – 3] * b[2 * n – 3] x[1][2] * y[2][3] x12 * y 23 La utilizarea elementelor unui tablou. . următoarele instrucţiuni atribuie valori variabilelor x . x = -1. expresia din partea dreaptă este convertită în tipul variabilei din stânga.

Menţionăm în final că. y. ). un operator de atribuire are doi operanzi şi ca rezultat valoarea operandului din stânga. biblioteca cu prototipurile funcţiilor matematice este math. Operanzii de atribuire sunt asociativi la dreapta (se execută de la dreapta la stânga). Această definiţie se numeşte şablon sau prototip şi are forma tip numefuncţie(tip. …. Instrucţiunea x op= e. tip. Tabelul următor prezintă aceşti operatori Forma prescurtată a operatorilor de atribuire Instrucţiune x=x+e x=x–e x=x*e x=x/e x=x%e Forma prescurtată x += e x -= e x *= e x /= e x %= e Alţi operatori de acest tip vor fi prezentaţi ulterior. este echivalentă cu x = x op e. biblioteca cu prototipurile funcţiilor de prelucrat 16 .Considerăm o variabilă x şi o expresie e. De exemplu. In cazul limbajului C fişierele header au extensia h. utilizatorul poate defini biblioteci cu prototipuri.h. Limbajele C şi C++ au biblioteci standard cu prototipurile funcţiilor limbajului. atribuie variabilei y valoarea 2 şi variabilei z valoarea 3. De ce? 1. atribuie variabilelor x şi y valoarea 1.iar rezultatul este de tip double. z = 1. se scrie prescurtat x += cos(y) .h. instrucţiunea x = x + cos(y) . Biblioteci de prototipuri Atunci când compilatorul întâlneşte un apel la o funcţie. Directiva include este diferită în cazul limbajului C de cea din limbajul C++. De exemplu.10 Prototipuri de funcţii. biblioteca cu prototipurile funcţiilor intrare/ieşire tip C este stdio. Toate aceste biblioteci sunt semnalate compilatorului cu directiva include cu forma # include nume_bibliotecă Aceste biblioteci sunt fişiere numite fişiere header sau antet. prototipul funcţiei sin este double sin(double). funcţia sin are un singur parametru de tip double. De asemenea. 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. De exemplu. Instrucţiunea x += y += z. Fie de exemplu instrucţiunea int x. Pentru aceasta este nevoie de o definiţie a funcţiei în care apar tipurile parametrilor şi tipul rezultatului. Instrucţiunea x = y = z.

El inserează date în streamul de ieşire. Streamul de intrare asociat tastaturii are denumirea cin. Fişierul standard de intrare este tastatura iar cel de ieşire este ecranul. Există două tipuri de fişiere: text şi binare. Ele vor fi prezentate detaliat într-un capitol ulterior. double x = 1 + sin(0.2). etc. Mai există alte două streamuri cerr şi clog pentru scrierea mesajelor de eroare. La utilizarea în limbajul C++. streamul de ieşire asociat ecranului se numeşte cout. In program. <cstring>. sau cout << “j =” << j << “\n” << “x = “ << x << “\n”. 123. In plus.11 Operaţii de intrare / ieşire Orice aplicaţie ce rulează pe un calculator are un director curent asociat şi fişiere standard de intrare şi ieşire. Pentru a afişa valorile variabilelor j si x pe două linii. In consecinţă.şiruri tip C este string. 1. orice fişier este asociat unui obiect numit stream. In consecinţă. De exemplu. expresia << “i= “ inserează în stream şirul de caractere i= . secvenţa de instrucţiuni int i = 123. biblioteca cu prototipurile funcţiilor intrare/ieşire tip C++ este iostream. toate funcţiile standard ale limbajelor C şi C++ sunt grupate într-un spaţiu de nume denumit std. etc. în cazul unui program în limbajul C vom semnala compilatorului aceste biblioteci cu directivele # include <stdio. care este predefinit ca endl. vom scrie cout << “j = “ << j << endl << “x = “ << x << endl. afişază pe ecran i = 123 Operatorul << inserează în stream valoarea expresiei din dreapta sa. Operatorul de scriere a datelor Operatorul de scriere a datelor este <<. Pentru a afişa valori pe mai multe linii. putem semnala compilatorului bibliotecile standard iostream şi math astfel # include <iostream> # include <cmath> using namespace std. cout << “i = ” << i. Liniile constau din zero sau mai multe caractere urmate de un caracter ‘\n’. expresia << i inserează în stream valoarea variabilei i. trebuie ca după fiecare linie să scriem caracterul ‘\n’. Putem defini propriile biblioteci cu prototipuri pe care să le semnalăm compilatorului cu directiva include. sau cout << “j = “ << j << ‘\n’ << “x = “ << x << ‘\n’. Fişierele standard asociate unei aplicaţii sunt de tipul text. Numele propriilor biblioteci cu prototipuri sunt scrise între ghilimele. De exemplu. 17 .h> In limbajul C++ fişierele header nu au extensie.h. fie instrucţiunile int j = -7. <cmath>. De exemplu. etc.h> # include <math. bibliotecile specifice limbajului C sunt redefinite ca <cstdio>. De exemplu. Fişierul text este este compus dintr-un şir de caractere grupate în linii.

Se procedează la fel pentru al doilea număr : • se citeşte şi se ignoră spaţiul. citeşte două valori de la tastatură şi le atribuie variabilelor a şi b. etc. O instrucţiune cin poate citi oricâte date din stream. echivalent. • se citesc următoarele caractere corespunzând tipului variabilei sau până la întâlnirea unui caracter spaţiu. cout << “x = “ << x << endl. cin >> b. Operatorul << poate scrie orice tip predefinit de date: int. Valorile introduse de la tastatură sunt analizate şi atribuite variabilelor de către instrucţiunea cin. ‘\t’ sau ‘\n’ şi valoarea citită se atribuie variabilei. Aceleaşi valori pot fi introduce ca Fie de iniţializat prin citire o variabilă x tip caracter la valoarea ‘A’. secvenţa de instrucţiuni int a. Operatorul de citire a datelor Operatorul de citire dintr-un stream este >>. float. şi rezultă numărul 25. Operatorul >> este urmat de numele variabilei ce va memora valoarea citită. • se citesc cifrele până la primul caracter diferit de cifră şi rezultă numărul 17. deoarece trebuie citit un număr întreg. cu doua instrucţiuni cout cout << “j =” << j << endl. Fie de exemplu valorile 25 şi 17 de citit pentru variabilele de tip întreg a şi b cu una din instrucţiunile precedente. Pentru fiecare variabilă se procedează astfel : • se citesc şi se ignoră toate caracterele spaţiu. ‘\t’ sau ‘\n’). El extrage date din streamul de intrare şi le atribuie unor variabile. Fie instrucţiunea int a. şiruri de caractere. Ele pot fi introduse de la tastatură separate de un spaţiu ca Şirul introdus este analizat astfel : • se citesc toate caracterele spaţiu. Instrucţiunea de citire este cin >> x. • se citesc apoi toate caracterele până la primul caracter diferit de cifră. cin >> a. Ea poate fi scrisă ca cin >> a. b . Variabila este definită cu instrucţiunea char x. Instrucţiunea cin >> a >> b. De exemplu.sau. ‘\t’ sau ‘\n’ (acesta din urmă este generat la apăsarea tastei Return). ‘\t’ sau ‘\n’ până la prima cifra întâlnită şi se ignoră (în acest caz primul caracter este diferit de spaţiu. Putem introduce caracterul A astfel: 18 . va citi o valoare introdusă de la tastatură şi o va atribui variabilei a (expresia >>a citeşte o valoare de la tastatură şi o atribuie variabilei a).

Fie instrucţiunea int k = 20. cout << hex << k. Pentru afişarea bazei se utilizează manipulatorii: • showbase pentru scrierea bazei • noshowbase pentru a nu scrie baza Exemplu. int i . In cazul numerelor reale avem manipulatorii: 20 14 0x14 24 024 19 . cout << showbase << oct << k.sau Citirea caracterului se face după regula de mai sus : se citesc şi se ignoră toate caracterele spaţiu. Valoarea afişată este 27. etc are numele iostream. cin >> hex >> a . Numărul poate fi introdus ca în tabelul de mai jos. Valorile introduse de la tastatură sunt analizate şi atribuite variabilelor de către instrucţiunea cin doar după introducerea unui caracter ‘\n’. ‘\t’ sau ‘\n’ şi se citeşte un caracter care este atribuit variabilei x. Presupunem introdus de la tastatură şirul de caractere de mai jos Valoarea atribuită prin citire variabilei a este 27. Pentru a modifica formatul de scriere sau citire a datelor putem utiliza manipulatori definiţi în biblioteca iomanip. cout << a << endl. De ce ? In următorul exemplu vom citi numărul negativ -44 în baza 16. Reamintim că biblioteca de prototipuri pentru streamurile cin. Pentru a afişa valoarea variabilei a în baza 16 trebuie să scriem cout << hex << a << endl . cout << showbase << hex << k. cout. Fie următorul exemplu int a . Tabloul următor prezintă exemple de utilizare a manipulatorilor pentru a afişa pe ecran valoarea variabilei k. cout << oct << k. cout << k. cin >> hex >> i . 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 baza 10 este implicită.

64 1. Fie instrucţiunea float x = 122.cadrare la stânga 20 .• fixed numărul este scris fără exponent • scientific numărul este scris cu exponent Exemplu. cout << setprecision(6) << pi << endl. 12. Tabelul următor prezintă exemple de utilizare a funcţiilor anterioare la afişarea variabilei z cu instrucţiunea cout.264000e+001 23 23 17 *23 3. cout << fixed << x. 10 sau 16.63. Tabloul următor prezintă exemple de utilizare a manipulatorilor pentru a afişa pe ecran valoarea variabilei x. 8.1459). sau este prea mică. Valoarea implicită a acestui manipulator este 0. Tabloul următor prezintă exemple de utilizare a funcţiilor operatorului <<. double pi(3. cout << setprecision(3) << z. cout << x. cout << scientific << x.63 122.1459 3. Valoarea implicită a acestui caracter este spaţiul.1459 Manipulatorii următori specifică modul de cadrare al valorilor scrise : • left . cout << setprecision(4) << pi << endl. Funcţia setfill(char) indică un caracter cu care se umplu spaţiile libere ale unui câmp. Dacă dimensiunea câmpului nu este specificată. cout << pi << endl.226300e+002 Funcţiile următoare sunt apelate de operatorul de scriere << : • setbase(int) • setw(int) • setfill(char) • setprecision(int) Funcţia setbase(int) indică baza în care va fi afişat un număr întreg. cout << setw(15) << scientific << z. Exemplu. cout << setbase(16) << x: cout << dec << setw(3) << setfill(‘*’) << x.15 3. cout << x.146 3. Funcţia setw(int) dă dimensiunea câmpului în caractere pe care este scris un număr. cout << setw(5) << x. cout << setprecision(3) << pi << endl.6 12. Exemplu.64. cout << setw(8) << setfill(‘*’) << z. Fie instrucţiunile int x = 23.64 ***12. Funcţia setprecision(int) dă numărul de cifre cu care este scris un număr real. cout << setw(8) << z. 122. numărul este scris pe câte caractere este necesar. Fie instrucţiunea float z = 12.63 1.

cout << “valoarea introdusa este “ << i << endl. Fie instrucţiunile int x . Valoarea variabilei x este afişată în baza 16 cu ambele instrucţiuni. /* se afisaza valoarea calculata */ cout << “patratul valorii este “ << j << endl. Funcţia main are ca rezultat o valoare întreagă şi nu are parametri. Prototipul acestei funcţii este int main(). /* se citeste o valoare intreaga */ cin >> i. j. cin >> x . Putem scrie acum primul program care citeşte o valoare întreagă de la tastatură şi afişează pătratul ei. { şi }. int main() { int i. // program care se citeste o valoare intreaga si calculeaza patratul ei # include <iostream> using namespace std. /* se calculeaza patratul valorii citite */ j = i * i. cout << hex << x << endl . Semnificaţia acestui prototip este următoarea. deoarece manipulatorul de scriere a bazei are valoarea hex. 1. Ceilalţi manipulatori rămân la valoarea prescrisă până sunt modificaţi. Corpul funcţiei conţine şi o instrucţiune return ce termină execuţia funcţiei şi transmite în programul appelant valoarea calculată de funcţie. adică o secvenţă de instrucţiuni scrise între acolade. Exemplu. return 0. cout << “introduceti o valoare intreaga” << endl. cout << x << endl .12 Funcţia main Orice program scris în limbajele C sau C++ se compune din funcţii care se apelează unele pe altele. Corpul funcţiei este o instrucţiune compusă. Definiţia unei funcţii este tip numefunctie (lista de parametri) { instrucţiuni } Una din funcţii are numele main iar execuţia programului începe cu această funcţie. 21 . Programul poate conţine comentarii. Comentariile plasate între delimitatorii /* şi */ se pot întinde pe mai multe linii. Pentru a afişa din nou variabilele întregi în baza 10 trebuie să scriem o instrucţiune cout << dec << x << endl . Comentariile ce încep cu caracterele // se întind pe o singură linie.• right – cadrare la dreapta Menţionăm că dimensiunea câmpului prescrisă de funcţia setw(int) se aplică doar următorului număr de scris.

cos. In această etapă sunt ataşate programului funcţiile din biblioteci. El este un fişier cu extensia exe. Editorul de legături generează un program executabil din toate fişierele obiect. Atunci când compilatorul întâlneşte un apel de funcţie (de exemplu sin. el generează doar o secvenţă de apel la funcţie. limbaajul defineşte şi constanta EXIT_FAILURE ce are valoarea unu. • Etapa a doua este editarea legăturilor. Fiecare fişier sursă este compilat separat. In această etapă programul este verificat pentru erori sintactice. expresia sizeof (char) are valoarea 1. • In etapa a treia programul excutabil este încărcat în memorie şi executat. Limbajul C++ defineşte constanta EXIT_SUCCESS ce are valoarea zero. putem scrie return EXIT_SUCCESS. Expresia 22 . sau o operaţie intrare/ieşire. Există două forme ale acestui operator sizeof (tip) sizeof (expresie) De exemplu. • Prima etapă este compilarea programului. Fie instrucţiunea double a[5].).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. In consecinţă. expresia sizeof(int) are ca rezultat valoarea 4. etc. 1. In cazul unui tablou rezultatul este numărul total de octeţi ocupat de tablou. 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.} Menţionăm că funcţia main() returnează valoarea 0 cu instrucţiunea return 0. 1. Rezultatul rulării programului este cel de mai jos. etc. Funcţiile respective sunt precompilate în biblioteci speciale şi programul editor de legături ataşează aceste funcţii programului obiect. Pentru claritatea programelor. Din fiecare fişier sursă rezultă un fişier obiect.13 Execuţia unui program Reamintim etapele de execuţie a unui program.

Expresia sizeof(x + m) are valoarea 8. // scrie dimensiunea tipurilor standard cout << ”\tint: “ << sizeof(int) << endl. cout << “\tchar: “ << sizeof(char) << endl. Programul este următorul : // dimensiunea tipurilor standard # include <iostream> using namespace std.. 23 . double m. cout << “\tfloat: “ << sizeof(float) << endl. Fie de exemplu instrucţiunile int x. operatorul sizeof dă numărul de octeţi ocupat de rezultatul expresiei.. b..sizeof(a) are valoarea 40 iar expresia sizeof(a) / sizeof(a[0]) dă numărul elementelor tabloului a. char : . // scrie dimensiunea unui vector cout << “\tvectorul float a[10]: “ << sizeof(a) << endl. // scrie dimensiunea rezultatului unei expresii cout << “\texpresia a[0]+b: ” << sizeof(a[0]+ b) << ‘\n’. expresia a[0]+b : . Vom scrie un program care să afişeze numărul de octeţi utilizaţi pentru memorarea tipurilor fundamentale. Exemplu... Vrem ca rezultatele să fie afişate pe ecran astfel: Numarul de octeti utilizati int : . } Rezultatul rulării programului este prezentat mai jos. float : .. cout << “Numarul de octeti utilizati:” << endl... ‘\t’... Pentru a afişa datele deplasate la stânga cu un număr de spaţii vom scrie un caracter tab. double : …. vectorul float a[10] : …. int main() { float a[10]. cout << “\tdouble: “ << sizeof(double) << endl. deoarece tipul expresiei este double... In cazul cand operandul este o expresie.

Şirului de caractere “\tint: “ duce la scrierea caracterului ‘\t’ şi a şirului ”int: “.. reprezintă scrierea prescurtată a expresiilor: x = x – 1. --x O expresie de forma f(++x). După execuţia instrucţiunii x = ++i variabilele au valorile i = 2 şi x = 2. sau endl.reprezintă instrucţiunea x=x–1 Cazul operatorilor prefix.15 Operatorii ++ şi . In acelaşi fel. Modul de execuţie a expresiei este următorul: 1. se incrementează sau decrementează valoarea variabilei x cu unu. f(x). operatorul . x. Fie declaraţia de variabile int i = 1. ++x. x. reprezintă scrierea prescurtată a secvenţei de instrucţiuni j = j – 1. este scrierea prescurtată a expresiilor: x = x + 1. Exemplu.decrementează o variabilă întreagă cu unu. Exemplu. 2. Exemplu. k.Menţionăm că este echivalent dacă în instrucţiunea cout utilizăm caracterul ‘\n’. reprezintă scrierea prescurtată a secvenţei de instrucţiuni i = i + 1. f(x). --x • postfix x++. Instrucţiunea cout << ”\tint: “ << sizeof(int) << endl.Operatorul ++ incrementează o variabilă întreagă cu valoarea unu. valoarea incrementată sau decrementată se utilizează mai departe în calcule. Fie declaraţia de variabile int j = 3. Instrucţiunea x = ++i. Aceşti operatori pot fi : • prefix ++x.-x şi x.Expresiile ++x şi x++ reprezintă instrucţiunea x=x+1 iar expresiile . 1. x = i. Instrucţiunea k = --j. sau şirul de caractere “\n”. După execuţia instrucţiunii variabilele au valorile j = 2 şi k = 2. Fie secventa de instrucţiuni 24 . k = j. putea fi scrisă ca cout << ‘\t’ << ”int: “ << sizeof(int) << endl. expresia f(--x).

se utilizează în calcule valoarea variabilei x (neincrementate sau nedecrementate). cout << i << endl.2. b. 8. r = a[i]. reprezintă scrierea prescurtată a expresiilor f(x). Expresia ++i este evaluată la 2. x = x – 1. Fie cele două secvenţe de instrucţiuni de mai jos : int a. 3}. k = 1. x-O expresia de forma f(x++). expresia f(x--). x++. cout << “i = “ << ++i << endl.int i = 1. Fie următoarele declaraţii. se incrementează / decrementează apoi variabila x. Modul de execuţie a expresiei este următorul : 1. 2. Exemplu. cout << “i = “ << i << endl. b = 3.2 Cazul operatorilor postfix. Valorile afişate vor fi i=1 i=2 i=2 deoarece a doua instructiune cout este echivalentă cu instrucţiunile cout << “i = “. se poate scrie r = a[--k]. Expresia a = b++. cout << “i = “ << i << endl. Exemplu. Expresia --k este evaluată la 0 iar r primeşte valoarea a[0] = 1. -5. double a[4] = {1. double r. i = i + 1. reprezintă scrierea prescurtată a expresiilor: f(x). se poate scrie r = a[++i]. In acelaşi mod. corespunde secvenţei de instrucţiuni : 25 . iar r primeşte valoarea a[2] = 8. int i = 1. a = b++. x = x + 1. r = a[k]. Secvenţa de instrucţiuni i = i +1. Secvenţa de instrucţiuni k = k – 1.

d = x[++j]. expresia b++ are valoarea 3 (valoarea neincrementată a variabilei) şi apoi se incrementează b. cout << endl. cout << “j = “ << j << endl. -3. i = 1.a = b.4. double d.4 şi i=2 Fie acum secvenţa de instrucţiuni următoare : int j. In consecinţă expresia ++j are valoarea 2 şi avem d = -3. Expresia i++ are valoarea 1 şi apoi se incrementează i.1 0}. Valorile afişate vor fi : j=1 j=1 j=2 deoarece a doua instrucţiune cout este echivalentă cu instrucţiunile : cout << “j = “ << j. 14. -3. i = i + 1. Instrucţiunea d = x[++j]. Instrucţiunea d = x[i++]. d = x[i++]. j = j + 1. b = b + 1. 14. Fie instructiunile : int j = 1. Exemplu. j = 1. Fie secvenţa de instrucţiuni de mai jos : int i. Avem rezultatul a=3 b=4 Exemplu. cout << “j = “ << j++ << endl. corespunde secvenţei de instrucţiuni : j = j + 1.4e1. corespunde secvenţei de instrucţiuni : d = x[i].4. double x[4] = {2. Avem deci d = 14. In consecinţă.4e1. double x[4] = {2.1 0}. d = x[j].1 şi j=2 26 . cout << “j = “ << j << endl. double d.

Dacă expresia de deplasat este de tipul unsigned biţii adăugaţi sunt zerouri. Operatorii de deplasare. Expresia de deplasare a unui număr întreg are forma Rezultatul expresiei din stânga este numărul întreg ce va fi deplasat. Expresia din dreapta dă numărul de biţi cu care se face deplasarea. Variabila j va primi valoarea 3. se scrie prescurtat x <<= n . la deplasarea la stânga se adaugă zerouri). (La deplasarea la dreapta se propagă bitul de semn. 8. b = a << 4. enum {X = 1. Deplasarea la dreapta a unui întreg cu un bit reprezintă câtul împărţirii acelui număr cu doi. Y = X << 1. int y = 0xff. folosind instrucţiunea enum. să definim constantele X. i si j şi secvenţa de instrucţiuni : i = 7. reprezentat pe patru octeţi. se scrie prescurtat 27 . c = y >> 4.16 Operaţii cu numere întregi la nivel de bit 1. Exemple.1 Operatori de deplasare Deplasarea la stânga a unui întreg cu un bit reprezintă înmulţirea acelui număr cu doi. De exemplu. Numărul a. Ca un alt exemplu. j = i >> 1. R = X << 3}. are valoarea hexazecimală a = 000000ff Numărul a deplasat la stânga cu 4 biţi este 00000ff0 Numărul y deplasat la dreapta cu 4 biţi este 0000000f Numărul 0xff convertit în zecimal este 255. Z şi R care să aibe valorile 1. Deplasarea se face după regulile deplasării numerelor binare cu semn. 2. Care este valoarea numerelor 0xf şi 0xff0 în zecimal? Fie două variabile întregi. vezi anexa. int b. prin deplasarea numărului 7 la dreapta cu un bit se obţine rezultatul 3. Reamintim că 7 / 2 = 3. Y.1. Variabila x primeşte valoarea 3 (câtul înpărţirii lui 7 la 2 este 3). Operatorii de deplasare a unui număr întreg cu un număr de biţi sunt << pentru deplasare la stânga şi >> pentru deplasare la dreapta. Fie instrucţiunile de mai jos int a = 0xff. 4. Z = Y << 1. int c. Fie instrucţiunea int x = 7 >> 1. Instrucţiunea x = x << n . iar instrucţiunea x = x >> n . Limbajele C şi C++ au operatorii de atribuire <<= şi >>= pentru scrierea prescurtată a instrucţ iunilor ce conţin operatorii << şi >>. << şi >> sunt operatori aritmetici.16.

1. // deplasează variabila b la dreapta cu 3 pozitii b = b >> 3. cout << “x deplasat la stanga cu 1 bit = “ << dec << x << “ “ << hex << showbase << x << endl. Reamintim că. se utilizează manipulatorii : • hex pentru baza 16 • dec pentru baza 10 • oct pentru baza 8 La începerea execuţiei unui program baza 10 este implicită. // deplasează variabila a la stanga cu un bit x = x << 1. cout << “b deplasat la dreapta cu 3 biti = “ << dec << b << “ “ << hex << showbase << b << endl. # include <iostream> using namespace std. } Rezultatele rulării programului sunt prezentate în figura de mai jos. cout << “b = “ << dec << b << “ “ << hex << showbase << b << endl. cout << “x = “ << dec << x << “ “ << hex << showbase << x << endl. int b = 50. return 0.x >>= n . Vom exemplifica utilizarea operatorilor de deplasare deplasând două variabile întregi la stânga şi la dreapta cu un bit şi apoi cu trei biţi şi vom afişa rezultatele în bazele 10 şi 16. pentru scrierea sau citirea unui număr întreg în instrucţiunile cout sau cin în diferite baze. /* uitlizarea operatorilor de deplasare */ int main() { int x = 10.16. Vom încheia acest paragraf cu un program care deplasează numere întregi şi afişază valoarea lor în zecimal şi hexazecimal.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 de adevăr următoare 28 .

b. b = 0xabcd. d. Operatorii &. Rezultatele sunt c = 0xa000 d = 0xfbcd e = 0x5bcd In primul caz. d = a | b. a = 0xf000. 1 1 1 1 & 1 0 1 0 1 0 1 0 Pentru calculul valorii e = a ^ b vom calcula expresia 0xf ^ 0xa = 0x5 Rezultatul se obţine conform calculului de mai jos 1 1 1 1 ^ 1 0 1 0 0 1 0 1 Pentru calculul valorii d = a | b vom calcula expresia 0xf | 0xa = 0xf 29 . c = a & b. | ^ sunt operatori binari. | şi ~ sunt operatori aritmetici. vom exemplifica doar calculul expresiei 0xf & 0xa = 0xa Rezultatul se obţine conform calculului de mai jos. Fie următoarea secvenţă de program : int a.a 0 0 1 1 b 0 1 0 1 a&b 0 0 0 1 a 0 1 ~a 1 0 a|b 0 1 1 1 a^b 0 1 1 0 Operatorii &. Exemple. c. la calculul valorii c = a & b. Expresiile cu aceşti operatori logici au forma următoare Operaţiile sunt aplicate asupra fiecărei perechi de biţi din cei doi operanzi. e = a ^ b. e. Expresiile cu acest operator au forma următoare Operatorul ~ complementează fiecare bit din expresia întreagă. Operatorul ~ este operator unar.

• Pentru a pune biţii din număr la valoarea 1 se execută operaţia | între număr şi mască. cout << “primii 4 biti din a = “ << hex << showbase << b << endl. // selecteaza ultimul octet din a b = a & 0xff. | şi ^. etc. Fie variabila întreagă a = 0x6dbc. Pentru a selecta cifrele octale dintr-un număr întreg vom utiliza ca mască valorile 07. Exemplu. 0xf00. • Pentru a pune biţii din număr la valoarea 0 se execuţă operaţia & între număr şi ~mască. apoi ultimii 10 biţi şi în final primii 4 biţi ai variabilei a. 0x2. pentru a selecta biţii unei cifre hexazecimale vom utiliza ca mască valorile 0x1. 0xf0. De exemplu expresia x=x&a. cout << “ultimii 8 biti inversati din a = “ << hex << showbase << b << endl. |= şi ^= pentru scrierea prescurtată a instrucţiunilor de atribuire ce conţin operatori &. Vrem ca în variabila întreagă b să selectăm ultimul octet. return 0. 070. cout << “a = “ << hex << showbase << a << endl. b. // operatii logice la nivel de bit # include <iostream> using namespace std.Rezultatul se obţine conform calculului de mai jos 1 1 1 1 | 1 0 1 0 -----------------1 1 1 1 Expresia ~a are valoarea 0x0fff. • Pentru inversarea biţilor se efectuează operaţia ^ între număr şi mască. etc. // selecteaza ultimii 10 biti din a cout << “ultimii 10 biti din a = “ << hex << showbase << b << endl. De ce? Limbajele C şi C++ au operatorii &=. Inversarea unor biţi din variabila a este prezentată în finalul exemplului. Pentru a selecta sau modifica anumiţi biţi dintr-un număr întreg se defineşte un şir de biţi numit mască ce conţine 1 pe poziţiile biţilor ce trebuie selectaţi sau modificaţi. cout << “ultimul octet din a = “ << hex << showbase << b << endl. } 30 . 0700. // pastram primii opt biti si inversam ultimii opt biti din a b = a ^ 0x00ff. Pentru a selecta cifrele hexazecimale dintr-un număr întreg vom utiliza ca mască valorile 0xf. De exemplu. b = a & 0x3ff. int main() { int a = 0x6dbc. // selectează primii patru biti din a b = a & 0xf000. Se scrie prescurtat x &= a . 0x4 şi 0x8. • Pentru selectarea biţilor se efectuează operaţia & între numărul iniţial şi mască.

Rezultatul rulării programului este cel de mai jos.

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

31

2 Structuri de control fundamentale
2.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 încât 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, indiferent 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ă atâta timp cât expresia booleană condiţie are valoarea adevărat. • Operatorul do are forma do s; while (condiţie) In acest caz acţiunea s se execută atâta timp cât condiţie are valoarea adevărat.

32

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. In toţi operatorii de mai sus, acţiunea este o instrucţiune simplă sau o instrucţiune compusă (un bloc). In program, o instrucţiune compusă (un bloc), este un grup de instrucţiuni inclus între acolade, { şi }. Intr-un bloc putem defini variabile locale, al căror domeniu de existenţă este limitat la acel bloc. Variabilele locale sunt create la intrarea în bloc şi sunt şterse la ieşirea din bloc. 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. Exemplu. Algoritmul de calcul pentru n! care este definit ca n!= ∏ i
i =1 n

s = 1; for i = 1; i <= n; i = i + 1 s = s * i;

2.2 Expresii relaţionale
O operaţie definită pentru tipurile de date fundamentale este compararea. Operatorii relaţionali ai limbajelor C şi C++ sunt, în ordinea priorităţilor <, <=, >, >= = =, != O expresie relaţională are următoarea formă

33

Rezultatul evaluării unei expresii relaţionale este fals sau adevărat (false sau true). 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ă a*b >= c b+2 > a *c a+b = = 3 a != b b/a = = 1 Valoare true false false true true

Reamintim că în limbajul C++ valoarea true este reprezentată prin 1 iar valoarea false prin 0. La scrierea valorii unei variabile booleene valoarea afişată este 0 sau 1, după cum variabila are valoarea false, respectiv true. Pentru afişarea valorii booleene ca true, respectiv false se utilizează manipulatorul boolalpha. 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 valorile true sau false. Un exemplu de program este cel de mai jos. #include <iostream> using namespace std; int main() { int a = 3, b = -2, c = 5; bool r; cout << " a = " << a << " b = " << b << " c = " << c << endl; r = a + b == c; cout << "a + b == c " << boolalpha << r << endl; r = a - b >= 2; cout << "a - b >= 2 " << boolalpha << r << endl; r = a * a != - b; cout << "a * a != - b " << boolalpha << r << endl; return EXIT_SUCCESS; }

34

în ordinea priorităţilor ! && | | care reprezintă operatorii nu (not). In cursurile de logică operatorii booleeni se notează astfel: nu (not) şi (and) sau (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 35 .3 Expresii booleene Operatorii booleeni ai limbajelor C şi C++ sunt. In final reamintim că operatorul = este operator de atribuire. x false false true true y false true false true x && y false false false false x||y false true true true x !x false true true false Rezultatul evaluării unei expresii booleene este true sau false. iar operatorul = = este operator de comparare. 2. respectiv sau (or). Operatorul nu este operator unar. iar expresia a==b este o expresia relaţională care are ca rezultat valoarea true sau valoarea false.Rezultatul rulării programului este prezentat în continuare. Aceşti operatori se definesc folosind tabelele de adevăr. şi (and). Instrucţiunea a=b atribuie variabilei a valoarea variabilei b.

Modul de execuţie al operatorului if este următorul: 1. else S2. 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 []() ++ -. Forma operatorului if este if(condiţie) S1. în care se execută instrucţiunea S când condiţia are valoare diferită de zero. Operatorul if poate avea şi o formă simplificată if(expresie) S. dacă valoarea condiţiei este diferită de zero se execută instrucţiunea S1 altfel instrucţiunea S2. 2. 36 .¬(a ∨ b) = ¬a ∧ ¬b ¬(a ∧ b) = ¬a ∨ ¬b care se demonstrează cu ajutorul tabelelor de adevăr. se evaluează condiţia.4 Operatorul if Acest operator execută o anumită instrucţiune în funcţie dacă o anumită condiţie este îndeplinită sau nu.+ .! ~ sizeof (tip) */% +<< >> < <= > >= = = != & ^ | && || = += -= *= /= %= <<= >>= &= |= ^= Asociativitate stânga dreapta stânga stânga stânga stânga stânga stânga stânga stânga stânga stânga stânga 2.

d. # include <iostream> using namespace std.Ca prim exemplu vom scrie un program care să testeze dacă un întreg este divizibil cu altul. cin >> n >> m. 37 . // calculul maximului a doua numere intregi # include <iostream> using namespace std. else cout << m << endl. } Un exemplu de rulare a programului este prezentat mai jos. Pentru a testa dacă un întreg n este divizibil cu un alt întreg d. } Un exemplu de execuţie a programului este dat mai jos. if(n > m) cout << n << endl. cout << “introduceti doi intregi : “ << endl. Cei doi întregi se citesc de la tastatură. m. cout << “ maximul dintre “ << m << “ si “ << n << “ este ”. int main() { int n. vom calcula expresia n % d. cin >> n >> d. return 0. return 0. else cout << n << “ nu este divizibil cu “ << d << endl. restul împărţirii lui n la d. Un alt exemplu este calculul maximului a două numere întregi citite de la tastatură. int main() { int n. if(n % d == 0) cout << n << “ este divizibil cu “ << d << endl. cout << “introduceti doi intregi : “.

este un grup de instrucţiuni cuprinse între acolade. 38 . cin >> x >> y. Exemplu. // ordonarea descrescatoare a două numere intregi citite de la tastatura # include <iostream> using namespace std. } Un exemplu de execuţie a programului este cel de mai jos. y. cout << “introduceti doi intregi : “. (este diferită se zero). int main() { int x. In acest exemplu. if(x < y) { int temp = x. (un bloc). e2. O instrucţiune compusă. instrucţiunile ce permută variabilele x şi y constituie un bloc. } cout << «numerele ordonate descrescator : « << x << “.Instrucţiunile din expresia operatorului if pot fi instrucţiuni simple sau instrucţiuni compuse. Blocul defineşte variabila locală temp. y = temp. { şi }. Dacă expresia booleană e1 are valoarea adevărat. e3 sunt expresii booleene. în caz contrar se execută instrucţiunea if(e3). return 0.” << y << endl. se execută instrucţiunea if(e2). x = y. executat când x < y. else s2. putem avea instrucţiunea compusă if (e1) if (e2) s1. else s4. De exemplu. Menţionăm că o instrucţiune if poate conţine alte instrucţiuni if. else if(e3) s3. Ordonarea descrescătoare a două numere întregi citite de la tastatură. unde e1.

-. case ‘%’ : cout << x << oper << y << "=" << x % y. case expint: instructiune } Una dintre etichete poate fi default. cin >> oper. Forma operatorului switch este următoarea switch(expresie) { case expint: instructiune case expint: instructiune ……. Pentru ieşirea din instrucţiunea switch se utilizează instrucţiunea break. default : 39 . int main() { int x. In acest caz. cout << “introduceti un operator : “. *. y. se trece la execuţia instrucţiunilor cu eticheta default. cout << “intoduceti doi intregi : ”. Vom citi două numere întregi în două variabile x şi y şi un operator +. break. / sau % într-o variabilă tip char şi vom calcula rezultatul operaţiei corespunzătoare. break. char oper. Exemplu. break. break. // calculator cu numere intregi #include <iostream> using namespace std. cin >> x >> y. Fiecare instrucţiune este etichetată cu o constantă întreagă. dacă expresia din instrucţiunea switch nu are nici una din valorile etichetelor din instrucţiunile case. break. Instrucţiunea conţine o expresie întreagă ce determină ce etichetă trebuie aleasă. case ‘/’ : cout << x << oper << y << "=" << x / y.5 Operatorul switch Acest operator execută o instrucţiune din mai multe posibile. case ‘-‘ : cout << x << oper << y << "=" << x – y. case ‘*’ : cout << x << oper << y << "=" << x * y. switch(oper) { case ‘+’ : cout << x << oper << y << "=" << x + y.2. Programul este următorul.

Forma operatorului while este while (condiţie) S. return 0.6 Operatorul ? Acest operator este o formă simplificată a operatorului if. Vom rezolva din nou problema divizibilităţii a două numere. # include <iostream> using namespace std. Forma lui este expresie ? S1 : S2. 40 . m. } Rezultatul rulării programului este cel de mai jos. Dacă expresie are o valoare diferită de zero. break. se execută instrucţiunea S1. cout << “intoduceti doi intregi : ”. } Menţionăm că instrucţiunile corespunzătoare etichetelor sunt urmate de instrucţiunea break ce asigură ieşirea din operatorul switch. Rezultatul rulării programului este cel de mai jos. 2. Instrucţiunea ((k % m)? " nu ": "") are ca rezultat şirul de caractere “ nu “ când k nu divide m sau şirul “” în caz contrar. return 0. cin >> k >> m. } cout << endl. int main() { int k. 2.cout << “eroare”. altfel instrucţiunea S2.7 Operatorul while Operatorul while execută repetat un grup de instrucţiuni. cout << k << ((k % m)? " nu ": "") << " este divizibil cu " << m << endl.

Putem rescrie partea de calcul din program astfel s = 0. { şi }. } Instrucţiunile s = s + a[i]. int i. while(i < 4) { s = s + a[i].32. return 0. float s. constituie un bloc ce este executat repetat. } cout << “suma componentelor este “ << s << endl. 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. Elementele vectorului a au indicii 0. i = 0. i = i + 1. -7. Vom exemplifica utilizarea acestui operator calculând suma elementelor unui vector a cu patru componente. Instrucţiunea S poate fi o instrucţiune simplă sau o instrucţiune compusă. 2 şi 3. while(i < 4) s = s + a[i]. while(i < 4) { s += a[i]. 73}. i = 0. Programul pseudocod este următorul. 2. /* calculul sumei componentelor unui vector */ int main() { float a[4] = {2. (un bloc). formată dintr-un grup de instrucţiuni. 41 .34. 1.instrucţiunea S se execută atâta timp cât condiţia este diferită de zero (are valoarea adevărat). s = 0. s = 0.5e-1. cât timp i < 4. i = i + 1. i = 0. cuprinse între acolade. s = ∑ ai i =0 3 Vom utiliza o variabilă f de tip float pentru a calcula suma. Programul este următorul #include <iostream> using namespace std. i = i + 1.

} media = suma / n. double suma = 0. Fie n variabila întreagă în care vom citi lungimea şirului de numere. i = 0. cout << "cate numere ? ". int main() { int n. Programul pseudocod este următorul. Variabila suma va conţine suma numerelor deja citite. i = i + 1. x. suma = 0. 42 . media.i++. suma = suma + x. } In următorul exemplu vom calcula media unui şir de numere reale citite de la tastatură. i o variabilă în care numărăm valorile citite şi x o variabilă în care citim câte un număr. read n. i = i + 1. write media. i = 1. } sau s = 0. suma = suma + x. cin >> n. while(i < 4) { s += a[i++]. // calculul sumelor partiale while(i <= n) { cin >> x. while (i <= n) { read x. Programul corespunzător este următorul // calculul mediei unui sir de numere reale citite de la tastatura # include <iostream> using namespace std. } // calculul mediei media = suma / n. i = 1.

2. i = 1. formată dintr-un şir de instrucţiuni între acolade. Acest operator are forma do S while (condiţie). return 0. do 43 . Instrucţiunea S se execută atâta timp cât condiţia este diferită de zero (are valoarea adevărat). n. (un bloc). n = 1. } Rezultatul rulării programului este cel de mai jos. i=1 do n = n * i. Exemplu.8 Operatorul do-while Operatorul do-while este tot un operator repetitiv. /* calculul valorii 5! */ int main() { int i. while (i < 6) Programul corespunzător este următorul # include <iostream> using namespace std. i = i + 1. Instrucţiunea S poate fi o instrucţiune simplă sau o instrucţiune compusă. n = 1. Programul pseudocod este următorul. Vom calcula valoarea 5! care se defineşte ca 5!= ∏ i i =1 5 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.cout << "media este : " << media << endl. { şi }.

return 0. Reamintim că instrucţiunea n *= i++. Putem rescrie partea de calcul din program astfel int i = 1. do n *=i++. este echivalentă cu instrucţiunile n *= i. i++.9 Operatorul for Operatorul for permite execuţia repetată a unui grup de instrucţiuni cu modificarea unei variabile de control după fiecare iteraţie. între acolade. } while(i < 6). i = i + 1. expresie3) 44 . while(i < 6). sau int i = 1. } while(i < 6). i++. i = i + 1. Operatorul for are forma for(expresie1. după care variabila i este incrementată. n = 1. 2. condiţie. deoarece dorim să executăm repetat mai multe instrucţiuni cu instrucţiunea do-while n = n * i.{ n = n * i. do { n *= i. n = 1. } Rezultatul rulării programului este cel de mai jos. conform modului de execuţie a operatorului postfix ++ : valoarea expresiei este chiar valoarea variabilei i neincrementate. scriem aceste instrucţiuni ca o instrucţiune compusă. cout << “5! are valoarea “ << n << endl. { şi }. Menţionăm că.

y = exp(x) + 2 * x + sqrt(x). i < 6. …. double x. ca mai jos. O primă variantă de program pseudocod este următorul for (i = 0. i = i + 1) { x = 1 + 0. x y …. } return 0.2. y. poate fi o instrucţiune simplă sau o instrucţiune compusă. executată repetat. Rezultatul rulării programului este cel de mai jos. int main() { int i.2 * i y = e x + 2x + x } Vrem ca rezultatul să fie afişat în două coloane cu antetul x şi y.2 * i. Operatorul for execută repetat instrucţiunea S. (un bloc).S expresie1 are rolul de a iniţializa variabila de control. separate de caracterul ‘\t’ (tab). } Pentru a scrie antetul tabelului cu valori am utilizat instrucţiunea cout << setw(4) << "x" << '\t' << setw(5) << "y" << endl. i = i + 1) { x = 1 + 0. …. In program rezultatele sunt scrise de asemenea separate de tab. …. Exemplu. 45 . expresie3 are rolul de a modifica valoarea variabilei de control după fiecare execuţie. { şi }. Instrucţiunea S. Pentru a prescrie numărul de coloane al unui câmp se utilizează funcţia setw() din biblioteca <iomanip>. cout << setw(4) << "x" << '\t' << setw(5) << "y" << endl. for(i = 0. cout << setw(4) << x << '\t' << setw(5) << y << endl. Vom calcula valoarea expresiei e x + 2 x + x pentru x cuprins între 1 şi 2 cu pasul 0. Programul este următorul # include <iostream> # include <cmath> # include <iomanip> using namespace std. atât timp cât condiţie este diferită de zero. i < 6. ce constă dintr-un grup de instrucţiuni între acolade. respectiv 5 coloane. care scrie şirurile de caractere x şi y pe câte 4.

Menţionăm că instrucţiunea for se putea scrie astfel for(i = 0. Ele pot fi definite în interiorul acestei instrucţiuni compuse (bloc). i++) putem scrie for(int i = 0. i < 6. y. i < 6. int main() { cout << setw(4) << "x" << '\t' << setw(5) << "y" << endl. i < 6. for (i = 0. for(int i = 0. De exemplu. In acest fel programul este mai clar. x = 1 + 0.2 * i. variabilele definite într-un bloc există doar în acel bloc. ++i) Este posibil să definim variabila de control chiar în instrucţiunea for. i++) Variabilele x şi y sunt utilizate doar în instrucţiunea compusă executată de for. în loc de instrucţiunile int i.2) care modifică pe x de la 1 la 2 cu pasul 0. y = exp(x) + 2 * x + sqrt(x). Programul este următorul # include <iostream> # include <cmath> # include <iomanip> using namespace std.Instrucţiunile dintre acolade formează instrucţiunea compusă executată repetat de instrucţiunea for. cout << setw(4) << x << '\t' << setw(5) << y << endl. i < 6. 46 . i = i + 1) { double x. } Reamintim că. în expresie1 din definiţia instrucţiunii for. } return 0. x <= 2. x = x + 0. Un alt mod de a calcula valorile expresiei de mai sus este de a utiliza instrucţiunea for(x = 1. Ele sunt create la intrarea în bloc şi şterse la ieşirea din bloc.2. i < 6. int main() { double x. i++) sau for(i = 0.

/* calculul sumei componentelor pare si impare ale unui vector */ int main() { float s1 = 0. else s2 = s2 + x[i]. 47 . if(i % 2 == 0) s1 = s1 + x[i]. } cout << “suma elementelor pare este “ << s1 << endl << “suma elementelor impare este “ << s2 << endl. } return 0. i = i + 1) { cin >> x[i]. x <= 2.2) In acest caz. float x[6] . for (x = 1.2) putem scrie for (double x = 1. i < 6. Exemplul următor calculează suma componentelor pare şi impare ale unui vector x cu şase elemente. x = x + 0. } In loc de instrucţiunile double x. x = x + 0. i=i+1) if (i % 2 = = 0) s1 = s1 + xi else s2 = s2 + xi Elementul xi este adunat la suma componentelor pare s1 sau impare s2 după cum expresia i % 2 are valoarea zero sau nu. i < 6. s2 = 0.cout << setw(4) << "x" << '\t' << setw(5) << "y" << endl. Ea poate fi utilizată doar în instrucţiunile executate repetat de for. x <= 2. x <= 2. y = exp(x) + 2 * x + sqrt(x). for(int i = 0. variabila de control definită în instrucţiunea for există doar în interiorul instrucţiunii for. for(x = 1. for( i = 0. Programul este următorul. s2 = 0. x = x + 0. cout << "Dati componentele vectorului"<< endl.2) { double y. cout << setw(4) << x << '\t' << setw(5) << y << endl. Programul pseudocod pentru calculul sumelor este următorul s1 = 0. #include <iostream> using namespace std.

1e+1. i++) z[i] = x[i] + y[i]. cout <<"media numerelor pozitive" << endl.12. } Rezultatul rulării programului este cel de mai jos. 3. 13. In acest program. între { şi }. Vom citi n numere reale de la tastatură şi vom calcula media celor pozitive. Vom încheia acest paragraf cu un program care să calculeze suma a doi vectori cu câte trei componente reale fiecare. return 0. #include <iostream> using namespace std. i < 3. while sau do-while. m = 0.h> int main() { float x[3] = {1. cout << ‘\n’. în caz contrar vom prelucra numărul citit.return 0. i++) cout << z[i] << ‘\t’. -2. Instrucţiunea break produce ieşirea imediată din instrucţiunile for. while. float z[3]. Dacă da. variabila i poate fi utilizată doar în instrucţiunile executate repetat de for. do-while sau switch. int main() { int n. menţionăm instrucţiunile break şi continue. i < 3. 13. iniţializaţi la definirea lor în program. vom trece la următoarea iteraţie cu instrucţiunea continue. vom citi câte un număr şi vom testa dacă este negativ. float y[3] = {-2.41}. 48 .5. media.57. for(int i = 0. /* calculul sumei a doi vectori */ # include <iostream. cin >> n. cnt.2}. for(int i = 0. float x. La fiecare iteraţie a instrucţiunii for. Exemplu. Instrucţiunea continue trece la următoarea iteraţie a instrucţiunii for. cout << "cate numere ? ". } In final. sum = 0.

Programul este următorul: #include <iostream> #include <cstring> using namespace std. una ce creşte de la zero la (n . } media = sum / m. n. In program vom citi un şir de caractere într-un vector de caractere str. condiţie. sum = sum + x. exp2.for(cnt = 1. int cntup. cout << "media = " << media << endl. are următoarea formă expresie1. Pentru a calcula lungimea unui şir de caractere vom utilize funcţia strlen() cu prototipul int strlen(char[]). se poate utiliza oriunde în mod normal se utilizează o singură expresie. return 0. dar sunt necesare două expresii. Vom testa dacă un cuvânt este palindrom (este acelaşi cuvânt citit în ambele sensuri). Fie n numărul de caractere citite. 49 . if(x < 0) continue. Vom iniţializa cele două variabile în instrucţiunea for cu operatorul . cntdn. Operatorul . nst. m = m + 1. rezultatul evaluării expresie1 se neglijază. exp1 şi exp2. } Rezultatul rulării programului este prezentat mai jos. când este necesar să iniţializăm două expresii. exp3) Exemplu. cin >> x. din biblioteca <cstring> . int main() { char str[80]. ++cnt) { cout << "dati un numar : ". Vom compara literele din prima jumătate a cuvântului cu cele din a doua jumătate..10 Operatorul . cnt <= n. Operatorul . for(exp1.1) / 2 . utilizând două variabile. Un exemplu este instrucţiunea for. expresie2 Rezultatul operatorului este expresie2. 2. iar alta ce scade de la (n – 1). bool pn = true.

for(cntup = 0. 50 . cin >> str. else cout << str << " nu este palindrom" << endl. n = strlen(str). if(pn) cout << str << " este palindrom" << endl.1) / 2. nst = (n . } Rezultatul rulării programului este prezentat mai jos.1. cntup++. cntdn = n . cntdn--) if(str[cntup] != str[cntdn]) pn = false. cntup <= nst. // initializarea a doua variabile cu operatorul . return 0.cout << "introduceti un cuvant " << endl.

3 Funcţii Limbajele de programare au funcţii predefinite ce corespund funcţiile matematice uzuale : sin. In cazul programelor în limbajul C++. Funcţie int isalnum (int c).h>. ‘\t’) Funcţiile au un rezultat diferit de zero dacă argumentul este conform descrierii funcţiei. log. în cazul limbajului C prototipurile acestor funcţii sunt definite în bibliotecile <stdlib. etc. Descriere Test dacă un caracter este alfanumeric Test dacă un caracter este alfabetic Test dacă un caracter este o cifră zecimală Test dacă un caracter este o cifră hexazecimală Test dacă un caracter este literă mică Test dacă un caracter este literă mare Test dacă un caracter este spaţiu (‘ ‘. int islower (int c).1 Funcţii C standard de manipulare a caracterelor Funcţiile ce manipulează caractere tip ASCII sunt un exemplu de funcţii standard ale limbajelor C şi C++.1 Funcţii standard Limbajele C şi C++ au multe funcţii standard. exp.h> şi <string. limbajele de programare permit definirea de funcţii care să realizeze anumite calcule. In plus. Un program constă din una sau mai multe funcţii. Următoarele funcţii convertesc literele mari în litere mici şi invers. int isspace (int c). cos. matematice. tolower(‘A’) tolower (‘a’) tolower (‘*’) ‘a’ ‘a’ ‘*’ 51 .1. aceste instrucţiuni se plasează într-o funcţie care este apelată unde este necesară. 3. etc. int isdigit (int c). log10. int isalpha (int c).h>. Atunci când un grup de instrucţiuni se utilizează în mai multe locuri din program.h>. In acest fel programele mari pot fi formate din module. int isxdigit(int c). int toupper(int c). Una dintre funcţii are numele main şi execuţia programului începe cu această funcţie. <ctype. <cctype> şi <cstring>. Descriere converteşte în litere mici converteşte în litere mari Exemple de utilizare a acestor funcţii sunt prezentate în tabelul de mai jos. Funcţiile pot fi grupate în biblioteci şi utilizate în diverse programe. Funcţiile următoare testează tipul unui caracter. int isupper (int c). Vom prezenta funcţiile de manipulare a caracterelor. ‘\n’. Funcţiile pot fi testate separat şi programele sunt mai clare. de prelucrare a caracterelor. Funcţie int tolower(int c). Mai înainte am prezentat cele mai utile funcţii matematice. Prototipurile lor se află în bibliotecile <cctype> şi <ctype. prototipurile acestor funcţii sunt definite în bibliotecile <cstdlib>. Descompunerea unui program în module are avantaje. 3.

cin >> x. for(i = 0. return 0. “abc”) <0 =0 >0 "abc" < "bcd" deoarece ‘a’ < ‘b’ şirurile sunt egale şirul “abcd” este mai lung Prototipul acestei funcţii se găseşte în bibliotecile <cstring> şi <string. “bcd”) strcmp(“xyz”. Exemple de utilizare a funcţiei strcmp sunt prezentate în tabelul de mai jos strcmp(“abc”. i++) if(islower(x[i])) cnt = cnt + 1. i < n. caracter cu caracter. // calculul numarului de litere mici. sau > 0.h>. i++) if(isupper(x[i])) cnt++. i < n. int i. literele mari şi cifrele dintr-un şir citit de la tastatură. Prototipul acestei funcţii este int strlen(char[]). // numara literele mici din sir cnt = 0. // calculeaza lungimea sirului n = strlen(x). şi se găseşte în bibliotecile <cstring> şi <string. până la primul caracter diferit. “xyz”) strcmp(“abcd”. Vom scrie o funcţie care să numere literele mici. 52 . // numara cifrele din sir cnt = 0. = 0. for(i = 0. Rezultatul este un număr < 0. cnt. const char s2[]). # include <iostream> # include <cstring> # include <cctype> using namespace std. // numara literele mari din sir cnt = 0. i < n. cout << “introduceti un sir”. cout << “sirul contine : “ << cnt << “ litere mari” << endl. fără caracterul ‘\0’ terminal. litere mari si cifre dintr-un sir int main() { char x[50]. după cum sunt caracterele diferite comparate. i++) if(isdigit(x[i])) cnt = cnt + 1.h>. cout << “sirul contine : “ << cnt << “ cifre” << endl. compară cele două şiruri. for(i = 0.O funcţie utilă la prelucrarea şirurilor este strlen() care dă lungimea unui şir de caractere. Funcţia int strcmp(const char s1[]. n. Programul este următorul. cout << “sirul contine : “ << cnt << “ litere mici” << endl.

h>.22e-1”. int x. Funcţia strlen() are un parametru şi calculează o valoare. Să se scrie un program care să convertească literele mici ale unui şir citit de la tastatură în litere mari. Apelarea ei se face scriind numele funcţiei urmat în paranteze de argumentul funcţiei într-o expresie. Fie două şiruri ce conţin numere. int main() { char s1[] = “-123”. care convertesc un şir de caractere într-un număr întreg şi respectiv real. z. Exerciţiu. char s2[] = “1. double atof(char s[]). // scrie sirurile cout << “sirul s1 : ” << s1 << endl << “sirul s2 : ” << s2 << endl. Vom converti aceste şiruri în numere şi vom efectua produsul lor. z = x * y. Un exemplu de utilizare a acestor funcţii poate fi următorul. # include <cstdlib> # include <iostream> using namespace std. Funcţia strlen() este apelată în membrul drept al unei instrucţiuni de atribuire. 53 .} Rezultatul rulării programului este următorul. Apelarea funcţiei şi transmiterea valorii calculate de funcţie în punctual din program unde este apelată este prezentată schematic mai jos. // converteste sirurile in numere x = atoi(s1). y = atof(s2). double y. Prototipurile acestor funcţii se află în bibliotecile <cstdlib> şi <stdlib. Alte funcţii ce prelucrează şiruri de caractere sunt int atoi(char s[]).

} 54 . z. ce descriu calculele efectuate de funcţie. z=x+y. tip2. arg2. Parametrii din definiţia funcţiei se numesc parametri formali. tipn reprezintă tipurile parametrilor arg1. In corpul funcţiei vom defini o variabilă de tip double. cout << “x * y = “ << z << endl.2 Definirea funcţiilor O funcţie calculează o valoare pe baza valorilor argumentelor sale. …. • numefuncţie este numele funcţiei ales de programator. Valoarea calculată de funcţie este transmisă în punctul de apelare a funcţiei cu instrucţiunea return ce are forma return expresie. Orice funcţie are un nume şi parametri sau argumente. 3. în care vom calcula rezultatul. { şi }. return 0 . Definiţia unei funcţii este tip numefuncţie (lista de parametri) { corpul funcţiei } Prima linie conţine : • tip este tipul valorii calculate de funcţie (se mai numeşte tipul funcţiei). tip2 arg2. tipn argn unde tip1. Exemplu. double y) { double z . x şi y. return z . valoarea calculată de funcţie va fi de tip double. argn. ….// scrie numerele si produsul lor cout << “x = “ << x << “ y = “ << y << endl. Instrucţiunea return termină totodată şi execuţia funcţiei. } Rezultatul rulării programului este cel de mai jos. • lista de parametri (argumente ale funcţiei) are forma : tip1 arg1. parametrii ei vor fi două variabile de tip double. …. Numele funcţiei va fi suma. Să definim o funcţie care să calculeze suma a două numere reale de tip double. Tipul acestei expresii trebuie să fie acelaşi cu tipul funcţiei. furnizate de un program care apelează funcţia. Corpul funcţiei este format dintr-o secvenţă de instrucţiuni între acolade. /* functie ce calculeaza suma a doua numere reale */ double suma (double x.

putem apela funcţia suma cu parametrii orice constante.73 * 2. return 0 . In consecinţă.Parametri x şi y din definiţia funcţiei suma sunt parametri de intrare. Valoarea calculată de funcţie se transmite în punctul de apelare de către instrucţiunea return. b . Vom utiliza funcţia definită mai sus.5 + a. b). Pasarea parametrilor către funcţii se face prin intermediul unei stive. Vom defini două variabile de tip double. -2. cout << " b = " . Funcţia suma este utilizată în membrul drept al unei instrucţiuni de atribuire. int main() { double a. ele se numesc variabile locale. adică o funcţie se poate apela pe ea însăşi. Variabila z din funcţia de mai sus este locală. Reamintim că parametrii x şi y din definiţia funcţiei sunt parametrii de intrare. Parametrii din listă sunt separaţi de virgule. Parametrii de intrare actuali ai unei funcţii pot fi orice constante.73. ce vor conţine valorile citite de la tastatură şi o variabilă c de tip double. Apelarea unei funcţii se face scriind numele funcţiei urmat de lista de parametrii inclusă în paranteze. sau c = suma(1.22). Parametrii de apelare a funcţiei se numesc parametri actuali sau argumente. Din această cauză. deoarece calculează o valoare asociată numelui. variabile sau expresii. b) . cin >> b . Menţionăm că numele parametrilor actuali (din instrucţiunea de apelare a funcţiei) pot fi diferite de cele ale parametrilor formali din definiţia funcţiei. Variabilele definite într-o funcţie există doar în timpul execuţiei funcţiei. } Rezultatul rulării programului este sel de mai jos. c = suma(a. a – b + 2). cin >> a . b şi c sunt variabile locale ale funcţiei main. ca termen într-o expresie. citite de la tastatură. double c . în cadrul funcţiei main pentru calculul sumei a două numere reale. ei conţin valori ce sunt utilizate de funcţie în calcule. a şi b. Funcţia poate utiliza valorile acestor parametrii în calcule. In spaţiul alocat pentru parametrii funcţiei se pun valorile acestor parametri. 55 . de exemplu c = suma(1. cout << " a = " . Variabilele nu pot fi utilizate în afara funcţiei. Funcţiile sunt recursive. La apelarea funcţiei. a. variabile sau expresii. Ea este apelată în instrucţiunea c = suma(a. se alocă spaţiu într-o stivă pentru parametrii funcţiei şi pentru variabilele locale ale funcţiei. cout << " a + b = " << c << endl . ce va conţine rezultatul.

Prototipurile funcţiilor matematice standard sunt definite în bibliotecile standard. iar instrucţiunea return are forma return. compilatorul cunoaşte informaţiile necesare din linia de definiţie a funcţiei. biblioteca <cmath> conţine prototipurile funcţiilor matematice. // // // prototipul functiei max(). asociate unor parametri. In acest caz o parte din parametrii funcţiei sunt parametri de intrare şi ei conţin valorile ce vor fi prelucrate de funcţie. dar este definite ulterior double max (double.ce sunt transmise în programul apelant şi vor fi numiţi parametri de ieşire. Definiţia funcţie max va fi scrisă după definiţia funcţiei main(). In acest caz trebuie să definim prototipul funcţiei max() înainte de definiţia funcţiei main(). Tipul rezultatului este utilizat pentru a converti rezultatul funcţiei dacă funcţia este un termen al unei expresii. etc. 3..3 Prototipuri de funcţii La întâlnirea unei instrucţiuni în care se apelează o funcţie. Modul de definire a parametrilor de ieşire va fi arătat ulterior.. In cazul funcţiei suma definită anterior prototipul este double suma(double. etc. biblioteca <iostream> conţine prototipurile funcţiilor intrare / ieşire pentru fişierele standard ale sistemului (tastatura şi monitorul). De remarcat că prototipul unei funcţii este chiar linia de definiţiei a funcţiei urmată de caracterul .). Prototipul unei funcţii are următoarea definiţie tip nume(lista de parametri). sau double suma(double x. In acest caz tipul ei este void. #include <iostream> using namespace std. Este posibil ca într-un program să definim funcţii şi după ce ele au fost utilizate. Vom defini o funcţia max() care să calculeze maximul a două numere de tip double. double) . In cazul în care funcţia este definită în program înainte a fi apelată.Menţionăm că este posibil ca o funcţie să nu calculeze nici o valoare. ceilalţi parametri vor conţine valorile calculate de funcţie . In prototip putem să identificăm doar tipurile parametrilor. De exemplu. o funcţie poate calcula mai multe valori care să fie transmise în programul apelant. Acesta este cazul programului de mai sus. Menţionăm că. double y) . astfel încât compilatorul poate verifica dacă apelarea funcţiei este corectă (tipul şi numărul parametrilor. Functia max() este apelata in functia main(). Aceste biblioteci sunt semnalate compilatorului cu directiva # include. double). Exemplu. Compilatorul utilizează numărul şi tipurile parametrilor pentru a verifica dacă funcţia este apelată corect. definitia functiei main() 56 . compilatorul trebuie să cunoască prototipul funcţiei adică numărul şi tipurile parametrilor şi tipul rezultatului (tipul valorii calculate de funcţie). In acest caz compilatorul nu cunoaşte prototipul funcţiei în momentul apelării ei şi este obligatoriu să definim prototipul funcţiei înainte de a o utiliza. în general.

b. o funcţie. aceste fişiere au extensia h. la fel ca şi funcţiile standard. cout << " a = " . char *argv[]) { double a. b. else return y . în fişierele în care sunt utilizate cu directive include.h" int main(int argc. 57 . c = max(a. poate avea mai multe instrucţiuni return. cout << " max(a. Programul anterior este rescris cu funcţia max() definită în fişierul max. Funcţiile definite în fişiere separate sunt semnalate compilatorului.4 Compilarea separată a funcţiilor Funcţiile definite de programator pot fi scrise în fişiere separate. înainte de a fi utilizate. Cele două fişiere sunt prezentate mai jos. Avantajul compilării separate a funcţiilor este acela că ele pot fi testate separat. De regulă. double y) { if( x > y) return x . cout << " b = " . #include "max. b) .cpp #include <iostream> using namespace std.h. De remarcat că. } // definitia functiei max() double max (double x. 3. } Rezultatul rulării programului este cel de mai jos. Forma directivei include este #include “nume fisier” Numele fişierului este scris aici între ghilimele. b) = " << c << endl . cin >> b . c . Fişierul main. cin >> a . c . return 0 . Aceste fişiere vor fi compilate separat.int main() { double a.

2) In memorie. La utilizarea unui element al unui tablou. fără paranteze sau indici. In linia de definiţie a unei funcţii. 2) (1. cin >> a . etc. Prin definiţie. } 3. Fie de exemplu matricea A[2] [3] de mai sus. în stivă se pune adresa tabloului. b) = " << c << endl . Poziţia elementului aij este dată de formula i*3+j 58 . int X[7]) La apelarea unei funcţii cu parametri tablouri. Linia de definiţie a funcţiei are forma double fun (double A[2] [3]. …. 1) (1. dimensiunile tabloului. dimn sunt numere. poziţia elementului a01 este 1. 1) (1. parametrul formal tablou este un parametru de ieşire. atunci când un parametru este un tablou. matrice. o matrice A cu două linii şi trei coloane cu elemente tip double şi un vector X cu şapte componente întregi iar rezultatul funcţiei de tip double. elementele matricei sunt memorate pe linii. 0) (0. cout << " b = " . iar dim1. Am spus mai înainte că pasarea parametrilor către funcţii se face prin intermediul unei stive. c = max(a. Elementele matricei au indicii din tabelul de mai jos. b) . parametrii formali tablouri au forma tip numetablou [dim1] [dim2] … [dimn] unde tip este tipul elementelor. vectori. (0.5 Funcţii cu parametri tablouri Funcţiile pot avea ca parametri tablouri. (0.h // definitia functiei max() double max (double x. 0) (1.cout << " a = " . return 0. double y) { if( x > y) return x . se calculează adresa acestui element relativă la începutul tabloului. ca mai jos. Dacă elementele tabloului sunt modificate în funcţie. dim2. 1) (0. } Fişierul max. parametrul actual tablou este numele tabloului. else return y . etc. 2) Poziţia primului element a00 este 0. Fie de exemplu o funcţie cu numele fun cu doi parametri formali. 1) (0. 0) (0. 0) (1. modificarea apare şi în programul ce a apelat funcţia. 2) (1. cin >> b . cout << " max(a. In consecinţă.

i < n. De exemplu. Parametrii funcţiei vor fi vectorul a şi dimensiunea sa. nu intervine prima dimensiune a tabloului. a10 ocupă poziţia 3. După cum am spus mai sus. Exemplu. Un program simplu ce testează funcţia de mai sus este următorul. (de intrare. odată scrisă. parametrii formali tablouri au şi forma tip numetablou [] [dim2] … [dimn] în care prima dimensiune nu se specifică. Un parametru formal vector are forma tip numevector[] sau tip numevector[dim] unde dim este numărul de componente ale vectorului. int n) { int i. la o matrice cu n linii şi m coloane. în această formulă. int X[]) Vom considera pentru început parametrii formali vectori. în linia de definiţie a unei funcţii. Definiţia funcţiei este următoarea /* funcţie ce calculeaza suma componentelor unui vector float suma(float a[]. trebuie testată. Vom presupune că. 59 . float s = 0. a01 ocupă poziţia 1. i = i + 1) s = s + a[i].De exemplu. de ieşire). In general. } O funcţie. condiţii asupra parametrilor de intrare. parametrul formal vector are forma float a[] Definiţia funcţiei va fi precedată de un comentariu ce descrie parametrii funcţiei. Vom scrie o funcţie care să calculeze suma componentelor unui vector a cu elemente numere reale de tip float. In consecinţă. la apelarea funcţiei elementele vectorului sunt iniţializate. valoarea calculată de funcţie. return s. poziţia elementului aij este i*m+j Se vede că. a12 ocupă poziţia 5. linia de definiţie a funcţie din exemplul de mai sus este double fun (double A[] [3]. // calculeaza suma componentelor for (i = 0. etc. Parametri de intrare a – vector n – dimensiunea vectorului a Iesire suma – suma componentelor vectorului a Preconditie : elementele vectorului a sunt initializate */ float suma(float a[]. int n). Valoarea calculată va fi ataşată numelui funcţiei.

5). parametrii. i < 5. Reamintim că.3. Vrem să testăm dacă valoarea z se află printre componentele vectorului. 5). etc. După cum am spus mai sus. Metoda căutării binare constă în testarea elementului de la mijloc al vectorului cu indicele i=(ls+ld)/2 şi restrângerea domeniului de căutare dacă valoarea z nu a fost găsită. cum s-a arătat mai sus. for(i = 0. -2. condiţiile asupra parametrilor. // scrie componentele vectorului cout << “suma componentelor vectorului “ << endl. Domeniul de căutare este restrâns astfel: dacă x[i] < z atunci ls = i + 1 dacă x[i] > z atunci ld = i – 1 La fel ca la funcţia precedentă. return 0. la apelarea funcţiei parametrul actual este numele vectorului. Funcţia este apelată cu instrucţiunea z = suma(x. este posibil de a scrie definiţia unei funcţii după funcţia main(). // scrie rezultatul cout << “este “ << z << endl./* testarea functiei suma */ int main() { float x[5] = {11. când un parametru formal al unei funcţii este vector. Iniţial ls = 0 şi ld =n-1. i++) cout << x[i] << “\t”. float z. Fie un vector x cu n elemente reale ordonate crescător. cout << endl. Componentele vectorului sunt afişate pe ecran separate de caracterul tab pe un rând iar rezultatul pe rândul următor.5.67. // calculeaza suma componentelor z = suma(x. 14}. fără indici sau paranteze drepte. înainte de definiţia funcţiei main() trebuie să scriem prototipul funcţiei. -2. } Rezultatul rulării programului este cel de mai jos.34. int i. Fie indicii ls şi ld domeniului de căutare în vector. In exemplul anterior definiţia funcţiei este scrisă înainte de utilizarea ei în funcţia main. Exemplu. Funcţia ce implementează acest algoritm este următoarea /* Cautarea binara 60 . In acest caz. 0. definiţia funcţiei este precedată de un comentariu ce descie funcţia. Căutarea binară într-o listă ordonată crescător.

Preconditie : x[0] <= x[1] <= .valoarea cautata find = indicele elementului z in lista daca exista. 4. else cout << "elementul " << z << " nu este in lista" << endl. 6.int find (double x[].. parametrul vector este scris ca numele tabloului. 3. while(ls <= ld) { i = (ls + ld) / 2. z). ld = n . return 0. double z) Parametri de intrare : x .2. int main() { double a[6] = {-2. fără indici sau paranteze drepte. if(x[i] == z) return i. la apelarea funcţiei. = -1 daca z nu exista in lista.1] */ int find (double x[].14. iteraţia ls ld i x[i] 61 . } Un program de testare a funcţiei este următorul. int n. else ld = i . int k. 5. int n.vector cu elemente sortate crescator n . 1. ls = 0. 7. } Tabela de mai jos prezintă iteraţiile algoritmului pentru z = 4.2. z = 4. double z.. } return -1. if(x[i] < z) ls = i +1.77.35. k = find(a.2. if(k < 6) cout << "elementul " << z << " are rangul " << k << endl. Reamintim că.dimensiunea lui x z . <= x[n .1. ld. double z) { int ls.1.12.09}. i.

/* funcţie ce citeste elementele unei matrice void rdmat(double a[NLIN][NCOL]) Parametrii : NLIN – numarul de linii NCOL – numarul de coloane Paramertii de iesire: a – matrice cu NLIN linii si NCOL coloane Elementele matricei se introduc pe linii. Vom încheia acest paragraf cu un program ce defineşte funcţii pentru lucrul cu matrice: • funcţie ce citeşte o matrice de la tastatură. ce vor fi prescrise în program cu două directive define. Matricele pot avea oricâte linii şi de coloane. for(i = 0. O funcţie poate avea ca parametri şi matrice. etc.12 4. */ void rdmat(double a[NLIN][NCOL]) { int i. j++) cin >> a[i][j]. Fiecare funcţie este precedată de un comentariu ce descrie parametrii şi operaţia realizată de funcţie. } 62 . i++) for(j = 0. j < NCOL. Reamintim că primul element din vector are indicele zero. ca în exemplul de mai jos. • o funcţie ce calculează suma a două matrice.14 5. In acest caz. j. #include <iostream> #include <iomanip> # define NLIN 2 # define NCOL 2 using namespace std. i < NLIN. în definiţia funcţiei parametrul trebuie să conţină şi numărul de linii şi de coloane al matricei.2 Rezultatul rulării programului este cel de mai jos. return. • o funcţie ce scrie elementele unei matrice pe ecran.1 2 3 0 3 3 5 5 3 2 4 3 3.

double b[NLIN][NCOL]. for(i = 0. j++) cout << setw(6) << a[i][j] << " ". } /* program pentru testarea functiilor */ int main(int argc. j. i < NLIN. z[NLIN][NCOL]. i++){ for(j = 0. } return. double c[NLIN][NCOL]) { int i./* functie ce afisaza elementele unei matrice pe linii Parametrii de intrare: NLIN – numarul de linii NCOL – numarul de coloane a – matrice cu NLIN linii si NCOL coloane */ void wrmat(double a[NLIN][NCOL]) { int i. 63 . rdmat(x). char *argv[]) { double x[NLIN][NCOL]. y[NLIN][NCOL]. j < NCOL. rdmat(y). j. j++) c[i][j] = a[i][j] + b[i][j]. return. cout << "introduceti a doua matrice pe linii" << endl. } /* functie ce aduna doua matrice cu elemente tip double Parametrii de intrare: a – matrice de intrare cu NLIN linii şi NCOL coloane b – matrice de intrare cu NLIN linii şi NCOL coloane Parametrii de iesire c – matricea suma cu NLIN linii şi NCOL coloane Preconditie Matricele a si b sunt initializate. */ void addmat(double a[NLIN][NCOL]. for(i = 0. cout << endl. i < NLIN. i++) for(j = 0. j < NCOL. // citeste elementele matricelor x si y cout << "introduceti prima matrice pe linii" << endl.

z). ce calculează minimul a două. c = -32. 3. else return c. respective trei numere întregi. cout << "min " << a << ". " << c << " este " 64 .6 Supraîncărcarea funcţiilor Intr-un program putem defini mai multe funcţii cu acelaşi nume dar cu parametri de tipuri diferite sau cu un număr de parametri diferit (prototipuri diferite). } // calculul minimului a trei numere intregi int min(int a. cout << "matricea suma" << endl. b = 47. Acest lucru se numeşte supraîncărcarea funcţiilor. int y) { return (x < y? x: y)." << b << ". int c) { int x = a < b? a: b. Variabilele ce conţin aceşti întregi vor fi parametri de intrare pentru funcţii. if(x < c) return x.addmat(x. # include <iostream> using namespace std. y." << b << " este " << min(a. int b. Vom exemplifica supraîncărcarea funcţiilor definind două funcţii cu acelaşi nume. b) << endl. return 0. } int main() { int a = 29. // calculul minimului a doua numere intregi int min(int x. wrmat(z). } Rezultatul rulării programului este prezentat mai jos. cout << "min " << a << ". min.

<< min(a. return (x < c? x : c). double b). cout << "min " << a << ". b. } // calculul minimului a trei numere reale double min(double a. respectiv trei numere reale după funcţia main. " << c << " este " << min(a." << b << ". return 0. double). // functia main() int main() { double a = 68. b). double). b) << endl. double b.3. # include <iostream> using namespace std. c = 32. cout << "min " << a << ". c) << endl. double c) { double x = min(a. } Vom defini acum cele două funcţii. double min(double. // calculul minimului a doua numere reale double min(double x. double.5. b = -77. c) << endl. 65 . double y) { return (x < y? x: y). Vom declara mai întâi prototipurile celor două funcţii şi vom defini funcţia main. } Rezultatul programului este cel de mai jos. // defineste prototipurile celor doua functii min() double min(double." << b << " este " << min(a. b. Vom exemplifica utilizarea prototipurilor definind cele două funcţii cu numele min ce calculează minimul a două. } Menţionăm că prototipurile celor două funcţii puteau fi scrise respectiv double min(double a.29. return 0.

Vom rezuma proprietăţile celor două moduri de transmitere a parametrilor către funcţii. Una dintre valori este transmisă la punctul de apelare de instrucţiunea return. Fie un parametru de un tip T al unei funcţii. • utilizare parametrilor tip pointer. In consecinţă. parametrii actuali şi variabilele locale ale funcţiei sunt memorate într-o stivă. valoarea lor se modifică în programul apelant (deoarece în stivă este adresa acestor parametri). Există două moduri de a transmite parametrii către funcţii: prin valoare şi prin referinţă (adresă). double c). Dacă în corpul funcţiei modificăm aceşti parametri. parametrii transmişi prin valoare sunt parametri de intrare (nu pot fi parametri de ieşire ai funcţiei). Referinţele şi variabilele tip pointer vor fi prezentate pe larg în capitolul următor. el trebuie transmis prin adresă (referinţă). Vom prezenta acum doar parametrii tip referinţă. este transmis funcţiei prin valoare. parametrul formal int x. De exemplu. Dacă în corpul funcţiei modificăm aceşti parametri. Celelalte valori vor fi asociate unor parametri de ieşire. double b.double min(double a. In consecinţă. în cazul transmiterii prin adresă în stivă se pune adresa parametrului. 66 . Referinţa la un astfel de parametru are tipul T&. în tabelul de mai jos. 3. In cazul transmiterii unui parametru prin valoare. pentru ca un parametru al unei funcţii să fie parametru de ieşire. variabilă Argumentul este o variabilă sau expresie In general o funcţie poate calcula mai multe valori. parametrul formal int& x este transmis prin adresă.7 Transmiterea parametrilor către funcţii La apelarea unei funcţii. în stivă se pune chiar valoarea parametrului. • Cazul parametrilor transmişi prin valoare. valoarea lor se modifică doar în stivă şi nu în programul apelant. Definirea parametrilor tip referinţă (adresă) Există două moduri de a defini parametri transmişi prin adresă: • utilizarea parametrilor tip referinţă. care vor fi obigatoriu transmişi prin adresă. • Cazul parametrilor transmişi prin adresă (referinţă). In definiţia funcţiei Parametri transmişi prin valoare Sunt parametri de intrare pentru funcţie Parametrul este o copie a argumentului Funcţia nu poate modifica parametrul Parametri transmişi prin adresă Sunt parametri de ieşire pentru funcţie Parametrul este adresa argumentului Funcţia poate modifica parametrul La apelarea funcţiei Parametri transmişi prin valoare Parametri transmişi prin adresă Argumentul este o constantă.

b). // functie ce modifica valorile parametrilor // primul parametru este transmis prin valoare. şi atunci când acest parametru este modificat în corpul funcţiei el este modificat direct în programul apelant (este parametru de ieşire). doarece nu este transmis prin referinţă. Pentru a vedea care este diferenţa între cele două moduri de transmitere a parametrilor considerăm o funcţiei care modifică valoarile parametrilor săi. Valorile variabilelor a şi b înainte de prima apelare a funcţiei sunt La prima apelare a funcţiei stiva este După prima execuţie a funcţiei valorile variabilelor a şi b în program sunt 67 . al doilea prin adresa # include <iostream> using namespace std. } Rezultate afişate sunt cele de mai jos. b = “ << b << endl. b). void f(int x. int& y) { x = 52. return 0. return. y = 65. } int main() { int a = 20. Menţionăm că primul parametru se modifică doar în stivă şi nu în programul apelant. cout << “a = “ << a << “ . b = “ << b << endl. cout << “a = “ << a << “ . f(a. f(2*a + 3. cout << “a = “ << a << “ . b = “ << b << endl. b = 30.Dacă un parametru al funcţiei este tip referinţă. Primul parametru va fi transmis prin valoare iar celălalt prin adresă. la apelarea funcţiei se va pune în stivă adresa acestui parametru.

perm(x. } Testarea acestei funcţii produce rezultatele de mai jos. cout << “valorile initiale x = “ << x << “ y = “ << y << endl. int& b) { int c. Funcţia va avea doi parametri care la apelare au valorile variabilelor ce trebuie permutate. Menţionăm că în cazul în care un parametru este un tablou. /* functie ce permute valoarea a doua variabile */ void perm(int& a. deoarece primul parametru al funcţiei este transmis prin valoare. parametri formali tablouri 68 . a = b. int main() { int x = 7. c = a. Să construim o funcţiei care să permute valoarea a două variabile de tip întreg. Valoarea lui actuală este rezultatul evaluării unei constante. y = 12. în stivă se pune adresa primului element al tabloului. In consecinţă. • un parametru tip referinţă este un parametru de ieşire. Exemplu. In stivă se pun adresele variabilor x şi y deoarece parametrii funcţiei sunt de tip referinţă. variabile sau expresii în programul apelant. Argumentul transmis este o variabilă din programul apelant. cout << “valorile permutate x = “ << x << “ y = “ << y << endl. în cazul nostru perm(x. b = c. iar la ieşirea din funcţie trebuie să aibe valorile permutate. y). Menţionăm că apelarea funcţiei se face utilizând pentru parametrii referinţă nişte variabile din programul apelant (nu constante sau expresii).Menţionăm că valoarea variabilei a nu se modifică. return. • un parametru transmis prin valoare este un parametru de intrare. y) Vom recapitula acum diferenţele între parametrii pasaţi prin valoare şi cei pasaţi prin adresă (referinţă). } O funcţie main() ce testează funcţia scrisă este următoarea.

b. Parametrii de intrare : x – vector y – vector n – dimensiunea vectorilor x. i < n. for(int i = 0. } Rezultatul rulării programului este cel de mai jos. i < 3.15. 3). 6. x2 . i++) cout << c[i] << " ". for(i = 0. c. y.92}. double y[]. z Parametrii de iesire : z – vector. int n) . } Un program ce testează funcţia de mai sus este următorul int main() { double a[3] = {1. z = x + y Preconditii : Parametrii de intrare sunt initializati */ void sumvect(double x[]. sumvect(a. xn . i++) z[i] = x[i] + y[i]. Tipul funcţiei este void deoarece toate valorile calculate de funcţie sunt asociate unor parametrii de ieşire. 5.21}.25. -3. double z[]. Vectorul sumă va fi z. /* Calculul sumei a doi vectori void sumvect(double x[]. Vom încheia acest paragraf definind o funcţie care să calculeze media şi dispersia unui şir de numere x1. return. Vom defini o funcţie ce calculează suma a doi vectori x şi y de numere reale de dimensiune n. cout << "vectorul suma" << endl. modificarea unui element al tabloului în funcţie produce modificarea lui în programul ce a apelat funcţia. cout << endl. double y[].pot fi parametri de ieşire. double c[3]. int n) { int i. Exemplu.29. Valoarea medie a elementelor şirului este 69 . -3. double z[].73. b[3] = {0. return 0.

Deoarece valorile calculate. int main() { float a[4] = {1. 1. i < n. #include <iostream> using namespace std. int i. float& m. 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. int n. funcţia are tipul void. float& d).33}. } Un program ce testează funcţia de mai sus este următorul.5. float& d) { float s = 0. /* Calculul mediei si dispersiei componentelor unui vector void md(float x[]. float& m. // calculeaza media for(i = 0. return. m = s / n. -2. int n. unde x este vectorul de numere de dimensiune n pentru care calculăm media m şi dispersia d. for(i = 0. 3.2e-1.m= 1 n ∑ xi n i =1 1 n ∑ ( xi − m)2 n − 1 i =1 iar dispersia d= Tipul funcţiei va fi void. 70 . i < n.34. d = d / (n – 1). m şi d sunt asociate unor parametri. i++) s = s + x[i]. i++) d = d + (x[i] – m) * (x[i] – m). Prototipul funcţiei va fi void md(float x[]. float& d). Media şi dispersia vor fi asociate unor parametri de ieşire ai funcţiei. // calculeaza dispersia d = 0. float& m.

şi stiva devine n=3 s=? n=2 s=? n=1 s=1 71 . return s. return s.float media. i <= n. După apelul fact(3) stiva este n=3 s=? Deoarece n este diferit de unu. else s = n * fact(n – 1). dispersia. Reamintim că parametrul n şi variabila locală s sunt memoraţi în stivă. return 0. } Varianta recursivă 1 n =1  n!=  n * (n − 1)! n > 1 // calculul factorialului int fact(int n) { int s. 3. Varianta nerecursivă n!= ∏ i i =1 n // calculul factorialului int fact(int n) { int s = 1. md(a. ++n) s = s * i. 4. } Vom prezenta stiva la apelarea variantei recursive pentru n = 3. fact(1). se execută instrucţiunea s = n * fact(n – 1) adică se apelează încă o dată funcţia. cout << “ media = “ << media << “ dispersia = “ << dispersia << endl. dispersia). media. } Rezultatul rulării programului este cel de mai jos. Stiva de n=3 s=? n=2 s=? Se apelează încă o dată funcţia.8 Recursivitatea Funcţiile limbajelor C şi C++ sunt recursive. fact(2). for(int i = 1. if(n = = 1) s = 1. adică 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ă.

putem calcula maximul a două numere întregi sau reale astfel. variabilele corespunzând acestei apelări se strerg din stivă n=3 s=6 După care se obţine rezultatul final. c = maxval<int>(a. 3. T b) { return (a > b ? a : b). fb = -9. valoarea 6. } Rezultatul rulării programului este cel de mai jos. tip. cout << “ maximul dintre “ << a << “ si “ << x << “ este “ << c << endl. cout << “maximul dintre “ << fa << “ si “ << fb << “ este “ << maxval<float>(fa. 72 . } Apelarea unei funcţii generice se face astfel nume_funcţie <tip. x). int main() { // maximul a doua numere intregi int a = 15. Definirea unei funcţii generice se face cu instrucţiunea template <typename identificator. x = 20.3. …>(parametri). c.După această ultimă apelare se ajunge la instrucţiunea return. De exemplu. return.43. // maximul a doua numere reale float fa = 3. fb) << endl. Stiva devine n=3 s=? n=2 s=2 Din nou se ajunge la instrucţiunea return. 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.9 Funcţii generice In limbajul C++ putem să definim funcţii generice în care putem defini tipuri generale pentru parametri şi valoarea returnată de funcţie. după care variabilele corespunzând ultimei apelări a funcţiei se sterg din stivă.

Exerciţiu. 73 . Să se definească o clasă generică ce permută două variabile de acelaşi tip.

Fie de exemplu instrucţiunea int a. In cazul unui tablou se alocă memorie fiecărui component al tabloului. b. aceste variabile se utilizează la transmiterea parametrilor funcţiilor prin adresă. Acestea au tipul pointer şi referinţă.1 Pointeri Un pointer este o variabilă ce conţine adresa unei alte variabile. de exemplu a 1000 b 1004 x 1008 c 1016 Menţionăm că unitatea centrală a calculatoarelor are un registru acumulator utilizat în calcule. iar pn este o variabilă de tipul pointer la int. După cum vom vedea. De exemplu. şi se memorează conţinutul registrului acumulator la adresa variabilei a (în cazul nostru adresa 1000). x[2].4 Pointeri şi referinţe Compilatorul alocă oricărei variabile definite în program o zonă de memorie egală cu numărul de octeţi corespunzând tipului variabilei. ce are valoarea 3. In programul generat de compilator nu există nume de variabile. Pointerii sunt utilizaţi şi la prelucrarea tablourilor. parametrii transmişi prin adresă pot fi parametri de ieşire ai funcţiilor. este tradusă de compilator astfel: se încarcă în registrul acumulator valoarea de la adresa variabilei b (în cazul nostru adresa 1004) se adună la registru valoarea de la adresa variabilei c (în cazul nostru adresa 1016). ci doar adrese. O instrucţiune de atribuire de forma a = b + c. Limbajele C şi C++ permit definirea unor variabile ce conţin adrese ale altor variabile. putem reprezenta cele două variabile astfel 74 . c. O instrucţiune de atribuire de forma a=b. este tradusă de compilator astfel : se încarcă în registrul acumulator valoarea de la adresa variabilei b (în cazul nostru adresa 1004) şi se memorează conţinutul acumulatorului la adresa variabilei a (în cazul nostru adresa 1000). Compilatorul alocă zone de memorie variabilelor ca mai jos Compilatorul crează o tabelă cu numele variabilelor şi adresele lor în memorie. dacă n este o variabilă de tip int. ce conţine adresa lui n. 4.

(Variabila pn a fost iniţializată în prealabil cu adresa lui n). int * pn. Instrucţiunea int u. int * pn. Fie de exemplu instrucţiunile int k.1. De exemplu int * pn. expresia &x este adresa lui x. *pv. // initializeaza pn cu adresa lui n Variabilele de tip pointer pot fi iniţializate la declararea lor. *pn = &n. k primeşte valoarea 14. defineşte o variabilă u de tip int şi o variabilă pv de tip pointer la int. Variabilele tip pointer pot fi iniţializate doar cu adrese.1 Declararea variabilelor tip pointer O variabilă de tip pointer se defineşte cu instrucţiunea tip * nume. *pk.4. şi pn = &n. k = *pn. Imaginea memoriei după execuţia ultimelor două instrucţiuni este 14 k 14 n adresa lui n (2004) pn pk 75 2000 2004 2008 2012 . Imaginea memoriei este următoarea Tabela cu adrese creată de compilator poate fi k n pn pk Instrucţiunile k = n. defineşte o variabilă tip pointer la int ce poate conţine adresa unei variabile de tip int. Dacă x este o variabilă oarecare. sunt echivalente. Pentru a obţine valoarea variabilei indicate de un pointer se utilizează operatorul * (numit şi operator de adresare indirectă). // pn este de tipul pointer la int pn = &n. putem scrie int n. Pentru a calcula adresa unei variabile se utilizează operatorul &. De exemplu. Putem scrie de exemplu int n. n= 14.

deoarece variabila pn nu a fost iniţializată. u = 3. int * pu. Instrucţiunile k = n. u = 3. şi pk = &k. următoarea secvenţă de instrucţiuni nu este corectă : int *pn. v = 2 * (*pu + 5). şi pk = &k. pu = &u. *pn = 5. poate fi scrisă cu variabile tip pointer ca int u. Valoarea de la adresa 2004 se memorează la adresa 2000 (adresa lui k). sunt echivalente. instrucţiunile k = n. w = 2 * (3 + *pu + 5) Limbajul C nu are referinţe. w = 2 * ( 3 + u + 5). v = 2 * (u + 5). Instrucţiunea k = *pn . 76 . *pk = *pn.Reamintim modul de execuţie al instrucţiunii k = n. secvenţa de instrucţiuni int u. este ca aceştia să fie de tip pointer. Exemplu. De exemplu. Vom scrie o funcţie care să permute valorile a două variabile ce vor fi parametri de ieşire ai funcţiei. în cazul nostru 2004. pn = &n. int x. In final. se execută astfel : valoarea de la adresa 2008 (adresa lui pn) este adresa operandului. In acest caz variabila pn a fost iniţializată cu adresa unei variabile de tip întreg. De exemplu. pn = &x. v. vezi tabela de mai sus. Variabilele tip pointer pot fi utilizate în expresii aritmetice în locul variabilelor aritmetice a căror adresă o conţin. O secvenţă corectă este int *pn. w. valoarea de la adresa 2004 (adresa lui n) se memorează la adresa 2000 (adresa lui k). ea nu conţine adresa unei variabile de tip int. singurul mod de a declara parametri de ieşire ai unei funcţii în limbajul C. w. Orice variabilă tip pointer trebuie iniţializată înainte de a fi utilizată. sunt echivalente. *pn = 5. *pk = n. In consecinţă. v. Aceşti parametri vor fi variabile tip pointer.

c = *a. Instrucţiunile b = a. *b = c. *b = c. şi b = *&a. // scrie variabilele inainte de permutare cout << “ valori initiale : “ << “x = “ << x << “ y = “ << y << endl.! ~ sizeof (tip). // scrie variabilele dupa permutare cout << “ valori permutate : “ << “x = “ << x << “ y = “ << y << endl. b. instrucţiunile din funcţie *a = *b. y = -4. modifică valorile parametrilor (variabilele x şi y) în funcţia main. Operatorii * şi & au aceeaşi prioritate cu operatorii ++ -. int* b) { int c.+ . stiva este următoarea: In consecinţă. a = 7. } Rularea programului produce rezultatele de mai jos. Fie instrucţiunile int a. 77 . sunt echivalente. return 0.// functie ce permuta valorile a doua variabile void perm(int* a. vezi Anexa 2. La apelarea funcţiei perm(). } Utilizarea acestei funcţii se face astfel : int main() { int x = 3. Operatorii * şi & sunt unari şi asociativi la dreapta. Operatorii * şi & sunt inverşi. Exemplu. &y). perm(&x. *a = *b.

Exemplu. ele trebuie să coincidă. z = *pf. *pb. b. * pf. Iniţializăm variabila pb cu adresa variabilei b şi apoi dăm o valoare variabilei b. int main() { int a. Fie instrucţiunile 78 . In programul de mai jos definim două variabile de tip int. // se scrie valoarea variabilei b si valoarea variabilei cu adresa in pb b = 15. Instrucţiunile z = x[3]. Un pointer la tabloul x are valoarea &x[0] sau x . } Rezultatul programului este cel de mai jos. Fie pc o variabilă de tip pointer la char. A doua instrucţiune cout afişază de două ori aceeaşi valoare. pa şi pb. a şi b şi două variabile de tip pointer la tipul int. // definim variabile tip pointer int *pa. pf = x. pf va conţine adresa celui de al patrulea element al tabloului x. Dacă scriem pf = &x[3]. Iniţializăm variabila pa cu adresa variabilei a şi afişăm adresa variabilei a şi valoarea variabilei pa. Fie de exemplu instrucţiunile float x[7]. // se scrie adresa variabilei a si valoarea variabilei pa cout << “ adresa variabilei a : “ << &a << “ . prin definiţie. // initializam variabilele tip pointer pa = &a. şi pf = &x[3] . z. Cele două valori trebuie să coincidă. Prima instrucţiune cout afişază de două ori aceeaşi adresă. return 0 . “ << *pb << endl. Afişăm variabila b şi valoarea variabilei a cărei adresă este memorată în pb. un pointer constant la primul element al tabloului. cout << “ valoarea variabilei b : “ << b << “ . Menţionăm că numele unui tablou este. #include <iostream> using namespace std. Ea poate primi ca valoare adresa unui vector de tip char (şir de caractere). Putem deci scrie pf = &x[0]. pb = &b. 15. sau. “ << pa << endl. echivalent. sunt echivalente.

i < 5. Secvenţa anterioară se poate scrie int x.2 Referinţe O referinţă (sau adresă) este un alt nume pentru o variabilă.char *pc. } Rezultatul programului este cel de mai jos. Putem scrie pc = y . # include <iostream> using namespace std. 79 . De ce ? 4. int& rx = x. int main() { int x[5]. Legătura între tablouri şi variabile tip pointer va fi prezentată pe larg în paragrafele următoare. declară pe rx ca fiind o referinţă a lui x (este obligatoriu ca variabila x de tip întreg a fost declarată anterior). y[7] . Fie T tipul unei variabile şi fie instrucţiunea ce defineşte o variabilă T nume_variabilă. sau putem iniţializa pe pc cu adresa unui şir constant pc = “ abcd “ . Pentru a verifica acest lucru vom considera următorul exemplu în care scriem valoarea unei variabile şi a referinţei corespunzătoare. return 0. Numele x şi rx sunt două nume diferite pentru aceeaşi variabilă. Ele au totdeauna aceeaşi valoare. Adresele se modifică cu 4 octeţi. Instrucţiunea de definire a unei referinţe este T& nume_referinţă = nume_variabilă unde nume_referinţă este numele variabilei referinţă (adresă). &rx = x. Exemplu. De exemplu instrucţiunile int x. Putem să definim de exemplu un pointer şi să-l iniţializăm în aceeaşi instrucţiune astfel char * ax = “aceg“. i++) cout << “x[“ << i << “] adresa : “ << &x[i] << endl. Să afişăm adresele elementelor unui vector de numere întregi. for(int i = 0. Variabila nume_variabilă trebuie să fie declarată înainte şi să aibe tipul T.

float& fr = f. fr = fr + 25. double& dbref = dbl.23. Pentru a verifica acest lucru. După cum am văzut în paragraful anterior. După fiecare instrucţiune de scriere.44. // scrie adresele variabilelor dbl si dblref cout << "adresa lui dbl = " << &dbl << endl << "adresa lui dbref = " << &dbref << endl. f = f – 3. Variabila şi referinţa au aceeaşi adresă. în următorul program vom afişa adresa unei variabile şi a referinţei la această variabilă. // scrie adresele variabilelor dbl si dbref2 cout << "adresa lui dbl = " << &dbl << endl << "adresa lui dbref2 = " << &dbref2 << endl. cout << “f = “ << f << “ fr = “ << fr << endl. cout << “f = “ << f << “ fr = “ << fr << endl. dacă un parametru formal al unei funcţii este de tip referinţă.8. double& dbref2 = dbl. return 0.3. două variabile tip referinţă dbref şi dbref2 la dbl şi o altă variabilă tip referinţă dbref3 ce va fi iniţializată cu valoarea lui dbref. return 0. cout << “f = “ << f << “ fr = “ << fr << endl. // scrie adresele variabilelor dbref si dbref3 cout << "adresa lui dbref = " << &dbref << endl << "adresa lui dbref3 = " << &dbref3 << endl. 80 . int main() { double dbl = 23. } Rezultatele rulării programului sunt cele de mai jos. el poate fi un parametru de ieşire. double& dbref3 = dbref.int main() { float f = 12. Vom defini o variabilă de tip double dbl. } In toate cazurile se tipăreşte aceeaşi valoare după cum se vede mai jos. valorile f şi fr vor fi aceleaşi. O referinţă nu este o variabilă separată.

Parametri de intrare : x .. 1. dimensiunea n a acestui vector şi valoarea z care este căutată printre elementele lui x.12. Algoritmul a fost prezentat anterior. int n. ld. 7.dimensiunea lui x z . int& k). double z. Funcţia va avea tipul void. while(ls <= ld) { k = (ls + ld) / 2. 4. 81 . cu elementele sortate crescător.14.vector cu elemente sortate crescator n . ls = 0..77.09}. } k = n.1.1] */ void find(double x[]. else ld = k .35. int n. Funcţia va avea ca parametri de intrare vectorul x. int& k) { int ls.2. 5. if(x[k] < z) ls = k +1. int k. double z.1. if(x[k] == z) return.indicele elementului z in lista. /* Cautarea binara void find(double x[]. Indicele elementului în listă este parametrul de ieşire k. 3. double z. Preconditie : x[0] <= x[1] <= .Vom încheia acest paragraf definind o funcţie care implementează căutarea binară.valoarea cautata Parametri de iesire : k . ld = n . return. } Un program ce testează funcţia este următorul int main() { double a[6] = {-2. <= x[n .

} Sablonul funcţiei este int f(int. O variabilă de tip pointer la această funcţie are definiţia Tip (*ident) (lista de parametri). In programul care apelează funcţia trebuie să definim o variabilă pointer de tipul funcţiei respective. z). } Recapitulăm în tabelul de mai jos prototipuri posibile pentru funcţia find şi modul de apelare a funcţiei.3 Pointeri la funcţii O funcţie are un punct de intrare care este linia ei de definiţie. In şablon trebuie să specificăm tipul parametrilor. n. iar ident este numele variabilei pointer. find(a. k). 6. z. Numele parametrilor din şablon pot fi diferite de cele din definiţia funcţiei. Apelarea unei funcţii folosind o variabilă de tip pointer se face astfel: • se atribuie variabilei pointer ca valoare numele funcţiei. ca în exemplul de mai jos. return 0. double z. ca mai sus. • se aplică operatorul () asupra adresei conţinută în variabila tip pointer. z. else cout << "elementul " << z << " nu este in lista" << endl. int). int y) { return x + y. int& k) . Considerăm şablonul definiţiei unei funcţii Tip nume(lista de parametri). Prototip Apelare int find(double x[].14. if(k < 6) cout << "elementul " << z << " are rangul " << k << endl. double z). unde ident este numele variabilei tip pointer.z = 3. int n. şablonul funcţiei anterioare putea fi declarat ca int f(int a. n. unde Tip şi lista de parametri din variabila pointer corespund cu tip şi lista de parametri din şablonul definiţia funcţiei. void find(double x[]. Exemplu. y. Definiţia funcţiei este următoarea int f(int x. find(a. 4. In şablon putem specifica şi numele parametrilor. int). 82 . k). k = find(a. 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. De exemplu. O variabilă de tip pointer la această funcţie este int (*ident)(int . int b). Fie o funcţie f care calculează suma a două numere întregi x. int n.

sm. care este numele variabilei al cărei tip este definit de şablon. tip – tip. 2. se atribuie acestei variabile ca valoare adresa punctului de intrare în funcţie (adresa punctului de intrare este chiar numele funcţiei) . // apelarea directa a funcţiei sm = f(a. int main() { int a = 5. int). scrişi aici în ordinea priorităţilor : 1. etc. [] reprezintă un tablou. cout << “suma numerelor “ << a << “ si “ << b << “ este “ sm << endl. * . Instrucţiunea poate conţine şi un identificator.Vom apela acum funcţia f definită mai sus direct şi folosind o variabilă tip pointer. () 2. Interpretarea şabloanelor se face considerând operatorii în ordinea priorităţii lor. [].4 Interpretarea instrucţiunilor ce conţin pointeri O instrucţiune de declarare a unui tip pointer la funcţie poate conţine următorii operatori. const 3. *. pointer la functia f int (*ptr)(int. Fie şablonul float *f(). Exemplu.pointer la. [] – tablou de. b = 7. 4. // se apeleaza functia f prin pointer sm = (*ptr)(a. return 0. 4. Fiecare operator se înlocuieşte cu o expresie : 1. • se defineşte o variabilă de tip pointer la funcţie . apelarea unei funcţii se poate face în două feluri: • se scrie numele funcţiei. cout << “suma numerelor “ << a << “ si “ << b << “ este “ sm << endl. const – constant. urmat în paranteze de parametri actuali. * reprezintă un pointer. // se defineste variabila ptr. ca termen într-o expresie (se aplicând operatorul () asupra numelui funcţiei). () reprezintă o funcţie. // se initializeaza variabila ptr prt = f. Se începe cu identificatorul din şablon. () – funcţie care returnează. 5. } In consecinţă. ca termen într-o expresie (aplicând operatorul () asupra adresei conţinută în variabila tip pointer). 3. Interpretarea lui se face astfel: float *f() float * () f este funcţie care returnează 83 . urmată în paranteze de parametrii funcţiei. tipuri de date Operatorii [] şi () sunt asociativi la stânga. apelarea se face scriind variabila de tip pointer. b). b). Operatorii * şi const sunt asociativi la dreapta.

Exemplu. Operatorii () consecutivi din linia a doua s-au citit de la stânga la dreapta. 84 . Fie următoarea instrucţiune const char * pc = “abcd”. nu putem scrie pc[3] = ‘c’. Fie şablonul float (*pf) (). Sablonul int (*r) (int. int şi care returnează o valoare de tip int. Interpretarea tipului variabilei pc se face astfel: const char * pc const char * const char char pc este pointer la constant 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). putem modifica vectorul. Prin această instrucţiune compilatorul generează un vector cu 5 componente tip caracter şi atribuie adresa acestui vector variabilei tip pointer pc. de exemplu putem scrie cp[3] = ‘c’. 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. ‘a’. Interpretarea ei se face astfel: char * const cp char * const char * char cp este constant pointer la char Instrucţiunea declară un pointer constant la un vector de caractere. se interpretează ca mai sus : r este un pointer la o funcţie cu doi parametri de tip int. adică pointerul poate fi iniţializat cu o altă valoare. In consecinţă. deoarece şirul de caractere este constant. dar putem scrie pc = “ghij”. Exemplu. int). Fie acum instrucţiunea char * const cp = “abcd”. Exemplu. ‘d’ şi ‘\0’ (şirurile sunt terminate prin zero). In consecinţă. ‘c’.float * float pointer la o valoare tip float f este o funcţie ce returnează un pointer de tip float. Reamintim că şirul de caractere “abcd” este un vector cu 5 componente tip caracter. Exemplu. ‘b’.

La execuţia unui program. scrierile &x[i] şi x+i sunt echivalente. în cazul unui tablou x. variabila px conţine adresa variabilei buffer[0] şi. *px este valoarea variabilei buffer[0]. Considerăm şablonul char (*(*f())[])(). La fel. Ele reprezintă valoarea variabilei x[i]. această valoare poate fi atribuită unei variabile tip pointer la tipul elementelor tabloului. Prin definiţie. Exemplu. Vom rescrie funcţia ce permută două variabile întregi ca mai jos. In consecinţă. Variabila buffer conţine adresa primului element al tabloului (adresa elementului buffer[0]). în consecinţă. o variabilă tip tablou conţine adresa primului element al tabloului (elementul cu indicele zero al tabloului). Interpretarea lui se face astfel: char (*(*f())[])() char (*(*())[])() char (*(*)[])() char (*[])() char (*)() char () char f este funcţie ce returnează pointer la vector de pointeri la funcţie ce returneaza o valoare 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. char * px. // functie ce permute valorile a doua variabile intregi void perm(int a[]. deoarece pointerul este o constantă. scrierile : x[i] şi *(x + i) sunt echivalente. Variabila tip tablou este un pointer constant la primul element al tabloului. px = buffer. Funcţia va avea ca parametri doi vectori cu câte o componentă.5 Pointeri şi tablouri unidimensionale Prin definiţie. Fie instrucţiunile : char buffer[20] . Ele reprezintă adresa variabilei x[i]. Fie de exemplu instrucţiunea char buffer[20]. int b[]) { 85 . 4. numele unui tablou este convertit la un pointer la primul element al tabloului.dar nu putem scrie cp = “ghij”.

int i. void perm(int* a. c = *a . *b = c . b[0] = c. return. return. int* b) { int c . 4}. 6. Exemplu. Vom face acest lucru utilizând elementele vectorului x[i] şi pointeri *(z + i). } Tabelul de mai jos arată cele două variante ale funcţiei ce permută două variabile. b[0] = c. } Deoarece instrucţiunile : *a şi a[0] sunt echivalente. a[0] = b[0]. 2. #include <iostream> using namespace std. -8. return. cea de la începutul capitolului. int main () { float x[5] = {1.01}. } . 9. cu vectori. Se cere să afişăm componentele vectorilor. int z[3] = {-11. cu pointeri şi cea de mai sus. perm(x. Fie x un vector cu 5 componente tip float şi un vector z cu 3 componente întregi.23. Un program posibil este următorul. 0. } Funcţia main() ce apelează funcţia perm() este următoarea. y[1] = {-4}. cele două funcţii sunt identice. return 0. int b[]) { int c. c = a[0].5e2. c = a[0]. // scrie variabilele dupa permutare cout <<" valori permutate : " << "x = "<< x[0]<<" y = "<<y[0] << endl. a[0] = b[0].int c.89. *a = *b . y). int main() { int x[1] = {3}. 86 void perm(int a[]. // scrie variabilele inainte de permutare cout << " valori initiale : " << "x = " << x[0] << " y = " << y[0] << endl.

int main() { float x[5] = {1. -8. etc. 9. 0. şi o variabilă pz de tip pointer la tipul int pentru vectorul z.01}.23. Operaţiile cu variabilele tip pointer sunt adunarea şi scăderea unor valori întregi. cout << endl. return 0. cout << endl. patru pentru tipul int sau float. i < 3. 4}. i++) cout << ‘\t’ << *(px + i) . } Programul de mai sus are rolul de a arăta echivalenţa între pointeri şi tablouri. pz = z. i++) cout << ‘\t’ << pz[i]. La adunarea sau scăderea unei valori întregi dintr-o variabilă tip pointer. i++) cout << ‘\t’ << x[i]. Fie din nou instrucţiunile : 87 .) astfel încât variabila tip pointer conţine adresa unui alt element de tablou. int * pz. Variabilele vor fi initializate cu adresele vectorilor.5e2.// scrie componentele vectorului x for(i = 0. float * px. i < 5. // scrie componentele vectorului z for(i = 0. 6. i < 5. px = x. int z[3] = {-11. Definim o variabilă px de tip pointer la tipul float ce va conţine pe rând adresele componentelor vectorului x. } Rezultatul rulării programului este cel de mai jos Programul poate fi rescris astfel. // scrie componentele vectorului z for(i = 0. // scrie componentele vectorului x for(i = 0. # include <iostream> using namespace std. int i. 2. cout << endl. i < 3. la variabilă se adună sau se scade valoarea respectivă înmulţită cu dimensiunea în octeţi a tipului variabilei (unu pentru tipul char. cout << endl.89. return 0 . i++) cout << ‘\t’ << *(z + i).

} Echivalent. } Pentru a vedea că ultima formă este corectă. In consecinţă. int main() { 88 . Putem să rescriem programul precedent astfel : # include <iostream> using namespace std. px++. return 0. instrucţiunea for în care scriem poate fi for(i = 0. int main() { float x[5] = {1. i < 5. -8. In expresie se utilizează valoarea neincrementată a variabilei. float * px. px = x.89. Expresia px este adresa elementului buffer[0]. i < 5.23. se utilizează expresia *px după care variabila px este incrementată. reamintim modul de execuţie a operatorului postfix ++. Vom incheia acest paragraf cu un program care să scrie adresele componentelor unui vector utilizând o variabilă tip pointer # include <iostream> using namespace std. iar *px este chiar valoarea elementului buffer[0]. 6. i++) { cout << ‘\t’ << *px++. In general. Pentru a aduna sau scădea o unitate dintr-o variabilă de tip pointer se pot folosi operatorii ++ şi --. i++) { cout << ‘\t’ << *px. Expresia px + 1 este adresa variabilei buffer[1].5e2. 0. char * px. int i. px = px + 1.char buffer[20]. după care variabila este incrementată. // scrie componentele vectorului x for(i = 0. iar *(px + 1) este chiar valoarea componentei buffer[1]. 9. px + i este adresa variabilei buffer[i] iar *(px + i) este chiar valoarea componentei buffer[i]. px = buffer.01}. } cout << endl. Instrucţiunea *px++ este echivalentă cu instrucţiunile *px.

au fost prezentate mai înainte şi sunt repetate aici. 6. for(i = 0. Funcţia returnează adresa şirului s1.89. De ce ?. Câteva dintre aceste funcţii. Parametrul s2 este declarat de tip const.23. i < 5. până la primul caracter diferit.5e2.6 Poineri şi şiruri tip C Şirurile tip C sunt vectori de caractere ASCII la care ultimul caracter este ‘\0’.h>. px = x. 4. compară cele două şiruri. Funcţia int strcmp(const char * s1. calculează lungimea şirului s (numărul de caractere ce preced ‘\0’). 89 .float x[5] = {1. adresele cresc cu 4 octeţi. Funcţia returnează un pointer la caracterul găsit sau NULL în caz contrar. caracter cu caracter. caută prima apariţie a caracterul c (convertit la char) în şirul s. după cum sunt caracterele diferite comparate. 0. const char * s2). } return 0. float * px. inclusiv caracterul ‘\0’. ale căror prototipuri se află în bibliotecile <cstring> sau <string. Tipul size_t este tipul unsigned int. Funcţia char * strchr(const char * s. Rezultatul este un număr < 0.01}. Funcţia size_t strlen(const char * s). Funcţia char * strcpy(char * s1. sau > 0. Bibliotecile standard ale limbajelor C şi C++ au numeroase funcţii pentru lucrul cu şiruri tip C şi caractere ASCII. i++) { cout << “x[” << i << “]\t” << px << endl. = 0. int c). } Rezultatul rulării programului este cel de mai jos. 9. adică funcţia nu modifică şirul s2. // scrie adresele componentele vectorului x cout << “adresele componentelor vectorului” << endl. Valoarea NULL este predefinită în limbajele C şi C++. px = px + 1. copiază şirul s2 în s1. int i. const char * s2). -8. După cum se observă.

Funcţii de manipulare a caracterelor ASCII Funcţie int isalnum (int c). Descriere Test dacă un caracter este alfanumeric Test dacă un caracter este alfabetic Test dacă un caracter este o cifră zecimală Test dacă un caracter este o cifră hexazecimală Test dacă un caracter este literă mică Test dacă un caracter este spaţiu (‘ ‘.h> sunt repetate aici. c < UCHAR_MAX. /* genereaza toate caracterele si scrie pe cele pentru care functia are valoarea adevarat */ for(c = 0. ‘\t’) Test dacă un caracter este literă mare Vom utiliza constanta predefinită UCHAR_MAX din biblioteca <climits> sau <limits. int isspace (int c).h> care dă valoarea maximă a unui obiect de tipul unsigned char (această valoare este 255). Caracterele vor fi generate cu o instrucţiune for a cărei variabilă de control are tipul unsigned char. ++c) 90 . int isupper (int c). ‘\n’. Definiţia funcţiei este următoarea. literele mici şi mari ale codului ASCII. int isxdigit(int c). int isdigit (int c). Prototipul acestor funcţii este int nume(int). #include <cstdlib> #include <climits> #include <iostream> using namespace std. alfanumerice. Vom construi o funcţie care generează toate caracterele codului ASCII (toate numerele de la 0 la 255) şi vom tipări în fiecare caz caracterele pentru care funcţiile de mai sus returnează o valoare pozitivă (valoarea adevărat).Vom exemplifica acum utilizarea funcţiilor de manipulare a caracterelor ASCII tipărind caracterele alfabetice. int islower (int c). // scrie numele functiei cout << nume << “ : “. sir de caractere fn – pointer la functie */ void prtcars(const char * nume. cifrele. int (*fn)(int)) { unsigned char c. /* functie care afisaza caractere ASCII Parametri de intrare nume – numele functiei. Funcţia pe care o vom construi va avea ca parametru un pointer de tipul funcţiilor de mai sus. Aceste funcţii au fost prezentate anterior şi prototipurile lor din bibliotecile <cctype> sau <ctype. int isalpha (int c). iar un pointer la o asemenea funcţie are tipul int (* fn)(int).

prtcars(“islower”. int len = strlen(s2). char * s2) { int i. O primă variantă este cea în care copiem un vector de caractere în alt vector. Vom exemplifica utilizarea pointerilor la tablouri unidimensionale construind o funcţie care copiază un şir în altul (ca şi funcţia standard strcpy). i < len + 1. char * s2) { int i. prtcars(“isalpha”. i++) s1[i] = s2[i]. prtcars(“isxdigit”. int main() { prtcars(“isdigit”. char * s2). &isdigit). o variabilă tip tablou este un pointer constant la primul element al tabloului. 91 . prtcars(“isalnum”. O altă variantă este void copy(char * s1. cout << endl. } Următorul program testează această funcţie. return. unde şirul s2 este sursa iar şirul s1 este destinaţia. return. &isalpha). for(i = 0.if((*fn)(c)) cout << c. prtcars(“isupper”. } Reamintim că. void copy(char * s1. } Rezultatul rulării programului este cel de mai jos. &islower). return 0. Prototipul ei va fi void copy(char * s1. &isupper). &isxdigit). &isalnum).

(s1[i] = s2[i]) != ‘\0’. Reamintim că operatorul de atribuire are ca rezultat valoarea atribuită.7 Pointeri şi tablouri multidimensionale Tablourile se definesc conform urmăroarei diagrame sintactice 92 . Instrucţiunea while se execută până când această valoare este 0 (până când caracterul ‘\0’ al şirului s2 este copiat în s1). Se execută instrucţiunea de atribuire s1[i] = s2[i] după care rezultatul s1[i] este comparat cu valoarea ‘\0’. } care este o scriere condensată a variantei anterioare. char * s2) { while(*s1++ = *s2++) . char * s2) { while(*s1 = *s2) { s1++. care este caracterul de terminare al şirului s2.for(i = 0. return. void copy(char * s1. void copy(char * s1. 4. în cazul nostru s1[i]. O altă variantă utilizează pointeri în locul elementelor de tablouri şi faptul că şirurile tip C sunt terminate prin zero. i++) . Ea este executată astfel. return. } Condiţia care se testează pentru execuţia instrucţiunii for este (s1[i]=s2[i]) != ‘\0’. In final putem scrie varianta următoare. s2++. } return. } Expresia testată de către instrucţiunea while este *s1 = *s2 Rezultatul evaluării expresiei este valoarea *s1 (un caracter copiat în şirul s1).

echivalent. expresiile a[i] şi *(a + i) sunt echivalente. După cum am spus mai sus. x[0] selectează primul element din x care este vectorul x[0][0]. de exemplu x[0][1]. Reamintim că operatorul de selecţie este asociativ la stânga. conform celor de mai sus. echivalent *(x + i) selectează o linie a tabloului x. In cazul matricei de mai sus x conţine adresa elementului x[0][0]. Numele unui tablou este o variabilă de tip pointer ce conţine adresa primului element al matricei. fiecare element fiind un vector cu trei elemente de tip int. x[0][1]. Fie o matrice x cu două linii şi trei coloane. El este un operator binar. *(x + 0) şi *(x + 1). După cum am spus anterior. sau. Elementele tabloului pot fi selectate folosind operatorul *. Prima variantă a programului este cea de mai jos. De exemplu. prin definiţie. x[1][2] El este considerat ca un vector cu 2 elemente. deci. x[1][0]. cu elemente de tip întreg. De exemplu. Operatorul [] selectează un element al unui tablou (este operator de selecţie). iar adresele lor sunt x[0] şi x[1]. x[0][2]. Aplicăm această regulă pentru tabloul de mai sus int x[2][3]. este echivalentă cu *((x[i]) + j) care este echivalentă cu expresia *(*(x + i) + j) Reamintim că x[i] selectează un element al tabloului x. operandul drept fiind un indice. cele două linii ale matricei sunt vectori cu trei componente. este compus din elementele : x[0][0]. cu trei programe echivalente. Vom afişa pentru început adresele celor două linii ale matricei. Vom prezenta un exemplu de utilizare a pointerilor la tablouri cu două dimensiuni. x[0][1]. x[0] şi x[1]. x[0][2] Aplicând operatorul de selecţie asupra lui x[0]. x[1][1]. Indicii unui element de tablou pot fi orice expresii întregi pozitive. aici o linie. Fie de exemplu tabloul double a[3]. Expresia x[i][j] este echivalentă cu (x[i])[j] care. operandul stâng fiind un tablou. selectăm un element al acestuia. tabloul int x[2][3]. #include <iostream> using namespace std. int main() 93 .Elementele tablourilor sunt memorate pe linii.

return 0. 94 . } A treia variantă a programului este următoarea. i < 2. In program. int main() { int x[2][3]. De ce? A doua variantă afişază adresele liniilor matricei x utilizând expresia echivalentă a lui x[i]. } Rezultatul rulării programului este cel de mai jos. int i. // afisarea adreselor liniilor matricei x for(i = 0. Definim o variabilă y de tip pointer la întreg int * y. return 0. #include <iostream> using namespace std. adresa lui x[1] este mai mare cu 12 octeţi (0xc) decât adresa lui x[0]. int main() { int x[2][3]. #include <iostream> using namespace std. i++) cout << "adresa lui x[" << i << "] = " << *(x + i) << endl. După cum se observă. care este *(x + i). i++) cout << "adresa lui x[" << i << "] = " << x[i] << endl. // afisarea adreselor liniilor matricei x for(i = 0. int i. Reamintim că linia matricei este un vector cu elemente întregi. int i. ea va primi ca valoare adresa liniei matricei x (adresa unui vector cu elemente întregi). i < 2.{ int x[2][3]. int * y.

int main(int argc. } Rezultatul rulării programului este cel de mai jos. char *argv[]) { int x[2][3]. cout << "adresa lui x[" << i << "] = " << y << endl. adresele cresc cu patru octeţi. In general. Vom afişa acum adresele elementelor matricei x cu două programe echivalente. i < 2. De ce ? O altă variantă a programului este cea de mai jos. 95 . j < 3. i++) { y = x[i]. j. } } return 0. x[1] + 2. x[1] + 1. j++) { cout << "adresa lui x[" << i << "][" << j << "] = " << x[i] + j << endl. In această variantă. } Menţionăm că. i < 2. adresa elementului x[i][j] al unei matrice x este x[i] + j Prima variantă a programului este următoarea #include <iostream> using namespace std. i++) { for(j = 0. în locul instrucţiunii y = x[i] . Adresele elementelor primei linii sunt x[0] + 0. Adresele elementelor limiei a doua sunt x[1] + 0. int i. } return 0. // afisarea adreselor elementelor matricei x for(i = 0. se putea scrie y = *(x + i) . După cum se observă. expresia x[i] + j este înlocuită cu expresia *(x + i) + j.for(i = 0. x[0] + 2. x[0] + 1.

*(*(x + i) + j) Elementele matricei sunt afişate pe câte trei coloane. j. int main(int argc. j < 3. int i. } Rezultatul programului este afişat mai jos. 96 . j < 3. int main(int argc. -4. Elementul x[i][j] al matricei x este. char *argv[]) { int x[2][3] = {{1. j++) { cout << "adresa lui x[" << i << "][" << j << "] = " << *(x + i) + j << endl. conform regulilor de mai sus. char *argv[]) { int x[2][3]. {-2. } return 0. i < 2. i++) { for(j = 0. #include <iostream> #include <iomanip> using namespace std. } In final prezentăm un program ce afişază componentele matricei x. fiecare element este urmat de două spaţii. -6}}. } } return 0. // afisarea elementelor matricei x for(i = 0. int i.#include <iostream> using namespace std. 2. i < 2. j. 3}. j++) cout << setw(4) << *(*(x + i) + j) << " ". cout << endl. // afisarea adreselor elementelor matricei x for(i = 0. Pentru a prescrie dimensiunea câmpului utilizăm în instrucţiunea cout funcţia setw(4). i++) { for(j = 0.

Funcţia main poate avea şi următorul prototip int main(int argc. char * argv[]) { int i. In programele în care alocăm memorie cu funcţia malloc() sau cu operatorul new trebuie să definim o variabilă pointer de un tip corespunzător ce va conţine adresa memoriei alocate. ++i) cout << argv[i] << endl. 4. Pointerii sunt adresele unor şiruri de caractere ce corespund argumentelor liniei de comandă. for(i = 0. char * argv[]). } Un mod de a furniza parametri liniei de comandă este de a rula programul într-o fereastră DOS. i < argc. Primul şir de caractere este chiar numele programului. Parametrii liniei de comandă Prototipul funcţiei main() utilizat până acum este int main(). de la linia de comandă  aplicatie. return 0.exe parametru1 parametru2 … parametrun Alt mod este de a introduce aceşti parametri într-o casetă de dialog a mediul de programare.4.8 Parametrii funcţiei main. Considerăm al doilea parametru din această definiţie char * argv[] El este interpretat astfel char * argv [] char * [] char * char argv este vector de pointeri la char argv reprezină un vector de pointeri la caracter. Alocarea dinamică a memoriei cu funcţia malloc() 97 . la declararea unei variabile de un tip oarecare • cu funcţia malloc() • cu operatorul new Alocarea memoriei unei variabile cu funcţia malloc() sau operatorul new se face la execuţia programului. Ca exemplu fie un program ce tipăreşte argumentele liniei de comandă. int main(int argc.9 Alocarea dinamică a memoriei Alocarea memoriei unei variabile scalare sau tablou se face astfel : • de către compilator. iar argc este dimensiunea acesui vector.

free(v). unde p este o variabilă tip pointer ce conţine adresa zonei de memorie ce va fi eliberate. Prototipul acestor funcţii se află în biblioteca <cstdlib> pentru limbajul C+ +. memoria alocată trebuie eliberată cu funcţia free(). unde size este numărul de octeţi de alocat. int main() { int j. j < 10. int * v = (int *) malloc(10 * sizeof(int)).h>. j < 10. alocat cu funcţia malloc. for(j = 0. Rezultatul rulării programului este cel de mai jos. 98 . // cout << v[j] << “ “. în timp ce vectorul v[10]. alocă un vector cu 10 componente de tipul int. şi el trebuie convertit în pointer la tipul alocat. # include <iostream> # include <cstdlib> using namespace std. Instrucţiunea converteşte pointerul furnizat de funcţia malloc() la tipul (int *). Exemplu. } for(j = 0. iar în cazul programelor C. Vom aloca un vector cu 10 componente întregi. In program definim o variabilă v de tip pointer la întreg ce va conţine adresa memoriei alocate de funcţia malloc().Alocarea de memorie se poate face cu funcţia malloc() cu prototipul void * malloc(int size). După alocare. v[j] = 2 * j. j++) { // *(v + j) = 2 * j. La sfârşitul programului. } Instrucţiunea int * v = (int *) malloc(10 * sizeof(int)). Tipul funcţiei este void*. } cout << endl. Ea are acelaşi efect cu instrucţiunea int v[10]. poate fi şters. dar vectorul v[10] este alocat de compilator şi nu poate fi şters. Eliberarea memoriei alocate se face cu funcţia void free(p). vectorul poate fi utilizat pentru calcule. îl vom iniţializa şi îl vom scrie pe ecran. numele bibliotecii este <stdlib. j++) { cout << *(v + j) << “ “. adică un pointer de tip nespecificat. // elibereaza memoria return 0.

z[i] şi *(z + i) // afisaza adresele liniilor matricei x cout << "adresele liniilor matricei" << endl. {-4. Vectorul x[0] de exemplu. i++) { cout << "adresa lui x[" << i << "] : " << z[i] << endl. } // afisaza adresele liniilor matricei x cout << "adresele liniilor matricei" << endl. for(i = 0. aşa cum am spus mai sus int * Matricea x constă deci dintr-un vector cu două componente al căror tip este int *. 3}. x[0] şi x[1] . Vom afişa adresele liniilor matricei. Definiţia acestei matrice este int x[2][3] . Iniţializarea variabilei z cu adresa vectorului y se face cu instrucţiunea z = y. i < 2. -6}}. ce corespund celor două linii ale matricei. adresele elementelor matricei şi elementele matricei utilizând o variabilă tip pinter. i < 2. x[0] şi x[1]. y[1] = x[1]. Tipul variabilei z va fi int ** int ** z . i++) { cout << "adresa lui x[" << i << "] : " << *(z + i) << endl. y[0] = x[0]. z[i] + j şi *(z + i) + j 99 . x[0][2]. 2. Vom afişa adresele liniilor matricei x cu cele două expresii echivalente. } Afişăm apoi adresele elementelor matricei x cu cele două expresii echivalente. -5. x[0][1]. Pentru a determina tipul variabilei pointer ce va conţine memoria alocată cu funcţia malloc().In continuare vom arăta cum se alocă memorie pentru o matrice. In continuare prezentăm secvenţe din program în care utilizăm variabila z pentru a afişa adresele liniilor matricei. Definim în final o variabilă z de tip pointer ce va fi iniţializat cu adresa vectorului y. int * y[2]. deci tipul variabilei x va fi int ** Prezentăm mai jos un program în care am definit matricea int x[2][3] = {{1. for(i = 0. vom analiza cazul unei matrice x cu elemente întregi cu două linii şi trei coloane. Tipul vectorilor x[0] şi x[1] este. Matricea x este formată din doi vectori. conţine elementele x[0][0]. Definim un vector y cu două elemente de tipul int * ce va fi iniţializat cu adresele celor două linii ale matricei x. adresele elementelor matricei şi valorile elementelor matricei x.

i < 2. } Programul complet este cel de mai jos. for(i = 0. int main(int argc. 2. 3}. 100 . j++) cout << "adresa lui x[" << i << "][" << j << "] : " << *(z + i) + j << endl. -5. j++) cout << setw(3) << *(*(z + i) + j) << " ". i < 2. {-4. i++) { for(j = 0. i++) { for(j = 0. #include <iostream> #include <iomanip> using namespace std. -6}}. j < 3. for(i = 0.// afisaza adresele elementelor matricei cout << "adresele elementelor matricei" << endl. i < 2. } // afisaza elementele matricei x pe linii cout << "elementele matricei" << endl. j < 3. char *argv[]) { int x[2][3] = {{1. } // afisaza adresele elementelor matricei cout << "adresele elementelor matricei" << endl. int ** z. afişăm elementele matricei x pe linii. j < 3. j < 3. int * y[2]. cout << endl. for(i = 0. i++) { for(j = 0. j++) cout << setw(3) << z[i][j] << " ". } In final. i < 2. cu cele două expresii echivalente. cout << endl. for(i = 0. z[i][j] şi *(*(z + i) + j // afisaza elementele matricei x pe linii cout << "elementele matricei" << endl. i++) { for(j = 0. j++) cout << "adresa lui x[" << i << "][" << j << "] : " << z[i] + j << endl.

for(i = 0. cout << endl. j. i < 2. for(i = 0. j++) cout << "adresa lui x[" << i << "][" << j << "] : " << z[i] + j << endl. for(i = 0. j++) 101 . i++) { for(j = 0.z = y. } // afisaza elementele matricei x pe linii cout << "elementele matricei" << endl. } // afisaza adresele liniilor matricei x cout << "adresele liniilor matricei" << endl. } // afisaza adresele elementelor matricei x cout << "adresele elementelor matricei" << endl. i++) { for(j = 0. j < 3. i < 2. i++) { cout << "adresa lui x[" << i << "] : " << *(z + i) << endl. i < 2. j < 3. y[1] = x[1]. i++) { cout << "adresa lui x[" << i << "] : " << z[i] << endl. j < 3. for(i = 0. i++) { for(j = 0. } // afisaza adresele elementelor matricei x cout << "adresele elementelor matricei" << endl. i++) { for(j = 0. for(i = 0. // afisaza adresele liniilor matricei x cout << "adresele liniilor matricei" << endl. i < 2. for(i = 0. int i. j++) cout << setw(3) << z[i][j] << " ". i < 2. } // afisaza elementele matricei x pe linii cout << "elementele matricei" << endl. i < 2. y[0] = x[0]. j < 3. j++) cout << "adresa lui x[" << i << "][" << j << "] : " << *(z + i) + j << endl.

Vom aloca memorie pentru o matrice cu elemente întregi cu nlin linii şi ncol coloane. cin >> nlin. deci tipul vectorului liniei este int*. Fiecare linie a matricei este un vector cu componente de tip int. } return 0. cout << "introduceti # de linii ". După cum am spus mai sus. ncol. primeşte ca valoare adresa unui vector de ncol întregi. Alocarea se face astfel. unde nlin şi ncol se vor citi de la tastatură.cout << setw(3) << *(*(z + i) + j) << " ". cin >> ncol. j. Variabila z ce conţine adresa acestui vector are tipul int **. int main() { int i. cout << "introduceti # de coloane ". o matrice este compusă din vectori corespunzători liniilor. cout << endl. int nlin. Exemplu. Programul este următorul # include <iostream> # include <cstdlib> using namespace std. // aloca un vector de dimensiune nlin de pointeri de tip int * 102 . Apoi. } Rezultatul rulării programului este cel de mai jos. In final se atribuie valori componentelor matricei z[i][j] = i * j . fiecare element al acestui vector. Se alocă mai întâi un vector de dimensiune nlin ce va conţine elemente de tip int*.

} Prima funcţie malloc() alocă memorie pentru un vector cu nlin elemente de tipul int*. // aloca pentru fiecare linie un vector de ncol intregi for(i = 0. Operatorii new şi delete sunt utili la operaţii cu vectori. Primele două instrucţiuni puteau fi scrise double *ptr = new double. Operatorul new alocă memoria corespunzătoare tipului de variabilă şi are ca rezultat adresa zonei de memorie alocată. i < nlin. } return 0. următoarele instrucţiuni alocă memorie pentru o variabilă scalară double * ptr. Exemplu. i++) { for(j = 0.int ** z = (int **) malloc(nlin * sizeof(int *)). i < nlin. j++) cout << z[i][j] << '\t'. A doua funcţie malloc() alocă memorie pentru un vector cu ncol elemente de tipul int. *ptr = 1. • pentru eliberarea memoriei alocate unui vector delete [] ptr. // atribuie valori componentelor matricei for(i = 0. i < nlin. i++) z[i] = (int*) malloc(ncol * sizeof(int)). Instrucţiunea 103 . Variabila ptr conţine adresa memoriei alocată mai înainte cu new. i++) for(j = 0. j < ncol. ptr = new double. Memoria alocată cu operatorul new poate fi eliberată cu operatorul delete cu formele • pentru eliberarea memoriei allocate unei variabile scalare delete prt. j < ncol.7. Rezultatul rulării programului este arătat mai jos. for(i = 0. cout << endl. j++) z[i][j] = i * j. Alocarea dinamică a memoriei cu operatorul new Limbajul C++ permite alocarea memoriei necesară unei variabile cu operatorul new cu formele: • pentru alocarea de memorie pentru un scalar new tip • pentru alocarea de memorie pentru un vector cu un număr de componente dat de expresie întreagă new tip[expresie întreagă] De exemplu.

j++) { cout << v[j] << “ “.float *pf = new float[10]. for(j = 0. delete [] v. Pentru tipul variabilelor utilizate în program vezi exemplu anterior. } Rezultatul rulării programului este cel de mai jos. int main() { int * v = new int[10]. // cout << *(v + j) << “ “. } cout << endl. îl vom iniţializa şi vom afişa pe ecran componentele lui. Exemplu. cu elemente întregi. Un vector alocat cu new poate fi şters cu operatorul delete astfel delete [] pf. # include <iostream> # include <cstdlib> using namespace std. unde nlin şi ncol se vor citi de la tastatură. j++) { *(v + j) = j. are acelaşi efect cu instrucţiunea int x[10]. // v[j] = j. } for(j = 0. Vom aloca un vector cu 10 componente întregi. return 0. j < 10. alocă un vector de 10 componente de tip float. Exemplu. j < 10. Instrucţiunea int * v = new int[10]. vectorul v[10] alocat de compilator nu poate fi şters. int j. Vom aloca o matrice cu nlin linii şi ncol coloane. dar vectorul v[10] alocat cu new poate fi şters. int main() { 104 . iat variabila pf conţine adresa vectorului. Elementele matricei vor primi aceleaşi valori ca în exemplul anterior.

for(i = 0. i++) for(j = 0. i < nlin. int ** v. j++) cout << v[i][j] << '\t'. ncol. for(i = 0. i < nlin. j < ncol. alocă memorie pentru un vector cu nlin elemente de tipul int*. for(i = 0. Instrucţiunea v = new int * [nlin]. cin >> ncol. 105 . int nlin. cout << "introduceti # de linii ". } Rezultatul rulării programului este cel de mai jos. cout << endl. j++) v[i][j] = i * j. i < nlin. cout << "introduceti # de coloane ". j. i++) v[i] = new int[ncol]. cin >> nlin. } return 0. i++) { for(j = 0. v = new int * [nlin].int i. La fel ca mai înainte variabila tip pointer v are tipul int**. j < ncol.

In program. const char * mode). (end of file). Parametrii acestei funcţii sunt 106 . poziţionat dacă a apărut o eroare intrare/ieşire. Există două tipuri de fişiere. La deschiderea unui fişier în citire. Prototipurile funcţiilor ce efectuează operaţii intrare/ieşire în limbajul C se află în biblioteca <stdio. Limbajele C şi C++ au tipul de structură FILE ce poate înregistra toate informaţiile necesare pentru controlul unul stream. Când se crează un fişier el se deschide în scriere. Această funcţie deschide un fişier. Funcţiile importante pentru prelucrarea fişierelor sunt următoarele : • FILE* fopen(const char * filename. Menţionăm că. inclusiv extensia h. Pentru prelucrare. Prototipurile funcţiilor intrare / ieşire pentru fişierele tip C sunt memorate în fişiere header şi trebuie semnalate compilatorului cu directive include. inclusiv: • numele fişierului. Ea crează o structură tip FILE cu informaţii despre fişier şi are ca rezultat adresa ei. un fişier se deschide în citire. Reamintim cele două forme ale directivei include: • în cazul programelor C. urmate de un caracter ‘\n’. fiecare fişier este asociat unui obiect numit stream. 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 în obiectul stream asociat. La închiderea unui fişier în creare se completează eticheta de pe disc a fişierului cu adresa ultimului octet al fişierului. Forma directivei include este #include <stdio.h>. Acest indicator este iniţial pus la zero şi este actualizat de operaţiile de citire sau scriere. poziţionat la valoare true dacă în timpul operaţiei de citire s-a ajuns la sfârşitul fişierului. directiva include conţine doar numele fişierului antet (header). text şi binare. • indicatorul de sfârşit de fişier. adresa de început şi adresa de sfârşit a fişierului. • un fişier binar este un şir ordonat de octeţi. Un stream este disociat de un fişier prin operaţia de închidere a fişierului. Forma directivei include este #include <cstdio> In exemplele următoare vom utiliza stilul C.5 Fişiere tip C In limbajele C şi C++ fişierele sunt considerate ca un şir ordonat de octeţi. eof. Pentru limbajul C++ numele fişierului header este redefinit ca <cstdio>.h> • în cazul programelor C++. • indicatorul de poziţie al fişierului. sistemul de operare caută pe disc fişierul cu numele specificat şi memorează adresele de început şi de sfârşit ale fişierului în obiectul stream asociat. 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. fiecare fişier pe disc are o etichetă ce conţine numele fişierului. • un fişier tip text este un şir ordonat de caractere grupate în linii. • indicator de eroare. Fiecare linie constă din zero sau mai multe caractere. directiva include conţine numele fişierului antet (header). Fişierele au un indicator de poziţie ce dă adresa următorului octet de citit sau scris. Un stream este asociat unui fişier prin operaţia de deschidere a fişierului.

trebuie ca parametrul filename să conţină şi calea. Funcţia închide un fişier. Fiecare aplicaţie are un director curent asociat. • stdout . sfârşitul de fişier este indicat prin Ctrl+Z. prelucreaza fisierul fclose(fp). • int feof(FILE * stream). Citirea secvenţială a unui fişier se face cu instrucţiunea while ce va testa indicatorul de sfârşit de fişier cu funcţia feof(). absolută sau relativă spre fişier. “r”). 5. (din fişierul stdin). fişierul se află în directorul curent. Atunci când parametrul filename din instrucţiunea fopen() conţine doar numele fişierului. După aceasta se apasă tasta return.fişier text deschis în scriere “rb” – fişier binar deschis în citire “wb” .fişierul standard de ieşire asociat ecranului. filename . mode .un pointer la şirul de caractere ce dă numele fişierului. Schema prelucrării unui fişier text în citire este următoarea FILE * fp. la care operaţiile de citire / scriere se fac sub controlul unui format. La lansarea în execuţie a unui program există trei streamuri standard tip text deschise: • stdin – fişierul standard de intrare asociat tastaturii. Exemplu. Parametrul mode poate avea următoarele valori: “r” – fişier text deschis în citire “w” .fişierul standard de ieşire pentru scrierea mesajelor de eroare. 2. Funcţia returnează o valoare diferită de zero dacă operaţia avut succes.un pointer la un şir de caractere ce dă modul de deschidere. • int remove(char * filename). după fiecare operaţie de citire trebuie să testăm dacă s-a ajuns la sfârşitul fişierului. sau dacă funcţia de citire a returnat valoarea EOF. 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. In cazul citirii datelor de la tastatură. Pentru a prelucra fişiere în alte directoare.h>). • stderr . predefinită în biblioteca <stdio. // Menţionăm că la citirea secvenţială a unui fişier. şterge fişierul cu numele filename. La întâlnirea sfârşitului de fişier indicatorul de sfârşit de fişier este poziţionat la o valoare diferită de zero iar funcţiile de citire returnează o valoare specifică (constanta EOF.fişier binar deschis în scriere • int fclose(FILE * stream). fp = fopen(“numefisier”. 107 .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. • funcţii care citesc / scriu caractere.1. Ea returnează valoarea zero dacă operaţia a avut succes.

iar specificatorul %7. Funcţiile au ca rezultat numărul de octeţi scrişi. const char * format. In cazul unei erori intrare / ieşire funcţiile au ca rezultat un număr negativ. Pentru scriere. argumentele sunt parametri de ieşire.număr real tip double cu exponent %p – pointer După % poate urma un număr întreg care dă lungimea minimă a câmpului în cazul instrucţiunii printf şi lungimea maximă a câmpului în cazul instrucţiunii scanf. argumente). fişierele standard sau fişiere tip şir de caractere (fişiere memorate în vectori tip char). int scanf( const char * format. argumente). • funcţiile de scriere / citire pentru fişierele tip şir de caractere sunt sprintf şi sscanf. • funcţiile de scriere / citire pentru fişiere pe disc sunt fprintf şi fscanf. argumente). De exemplu specificatorul %7f descrie un număr real ce ocupă 7 poziţii. aceste funcţii sunt: int fprintf(FILE * stream. int sscanf(char * s. La scrierea numerelor cu specificatorul %f putem specifica şi numărul de cifre ale părţii subunitare. In cazul unei erori intrare / ieşire funcţiile au ca rezultat un număr negativ. argumente). In cazul fişierelor şir de 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 tip float %lf – număr real tip double %e – număr real cu exponent %le. 108 . int sprintf(char * s. Menţionăm că. argumente).3f descrie un număr real ce ocupă 7 poziţii din care 3 sunt pentru partea subunitară. în cazul instrucţiunilor scanf. const char * format. const char * format. Pentru citire. • funcţiile de scriere / citire pentru fişierele standard stdout / stdin sunt printf şi scanf.1. deci de tip pointer.1 Funcţii intrare / ieşire cu format Aceste funcţii scriu / citesc valori în / din fişiere pe disc. Parametrul format este un şir de caractere compus din specificatori de conversie definiţi de % şi alte caractere.5. argumente). parametrul s este un şir tip C în care se scriu sau din care se citesc valorile variabilelor. Funcţiile au ca rezultat numărul de valori citite. const char * format. funcţiile sunt int fscanf(FILE * stream. int printf( const char * format.

i).h> #include <stdlib. Exemplu. scanf(“%x”. c = a + b. printf("Dati doua numere reale\n"). } Rezultatul programului este cel de mai jos. printf(“\n numarul zecimal : %d\n numarul hexazecimal : %x\n”. In consecinţă. &i). b = %f.h> int main(int argc. b. &i). Reamintim că. scanf("%f %f". la apelarea funcţiei scanf în program utilizăm adresa variabilei i scanf(“%x”. b. printf(“introduceti un numar hexazecimal\n”). a.h> int main() { int i. a + b = %f\n". 109 . i. parametrii funcţiei scanf sunt de tip pointer (sunt parametri de ieşire). printf("a = %f.Exemplu. c. &b). &a. Să citim un număr hexazecimal de la tastatură şi să-l scriem în format zecimal şi hexazecimal pe ecran. #include<stdio. c). return 0. Să citim două numere reale de tip float de la tastatură şi să afişăm suma lor. Programul este următorul #include <stdio. } Rezultatul programului este cel de mai jos. char *argv[]) { float a.

n. Exemplu. rez). Să definim o funcţie ce calculează suma unui şir de numere reale tip double citite de la tastatură. De exemplu. x. instrucţiunea printf(“\n numarul zecimal : %d\n numarul hexazecimal : %x\n”. i). (trecerea la un nouă linie). rez = sumnum(). scanf("%d". char *argv[]) { double rez.h> #include <stdlib. &n). return 0. etc. printf("cate numere ?\n"). i. #include <stdio. } int main(int argc. &x). a şirului “numarul zecimal” şi a valorii variabilei i. are ca rezultat scrierea caracterului ‘\n’. printf(" suma numerelor = %lf\n". Vom scrie un program care calculează valoarea unei expresii 1 + cos(2 x) + 2 sin( x) x + ln x 110 . for(i = 1. Numărul termenilor din şir va fi citit tot de la tastatură.In cazul instrucţiunilor printf specificatorii de conversie dau formatul de scriere a variabilelor. s = s + x. Programul este dat în continuare.h> double sumnum() { int i. } return s. s = 0. double s. Exemplu. i++) { scanf("%lf". } Rezultatul rulării programului este cel de mai jos. în timp ce restul caracterelor dn format sunt scrise în fişier. Numerele reale tip double se citesc şi se afişază cu specificatorul de conversie tip %lf. i <=n.

txt". printf(" %4. şi instrucţiunile de scriere devin printf(format. #include <stdio. Valoarea lui x va ocupa 4 caractere. } Rezultatul rulării programului este cel de mai jos. z). x = 1.0. x. cu un caracter pentru partea subunitară. fprintf(fp.h> #include <math. x.pentru valori ale lui x cuprinse între 1 şi 2 cu pasul 0. Exemplu.2f \n”. float x. z). " x \t f(x) \n"). fprintf(fp. // scrie antetul pe ecran si in fisier printf(" x \t f(x) \n"). z). format. z. Valorile vor fi separate de un caracter tab. " %4.2f \n". fprintf(fp. Valorile expresiei vor fi scrise pe ecran şi într-un fişier cu numele rez.h> int main () { FILE * fp.1f pentru variabila x şi %6. Specificatorii de format vor fi %4. // calculeaza valorile expresiei si scrie aceste valori in fisiere for(i = 0. cea a lui z ocupă 6 caractere. sau char format[] = “ %4.2.2f \n”. "w"). i++) { z = (1 + cos(2 * x) + 2 * sin(x)) / (x + log(fabs(x))). int i.1f \t %6. x. return 0.1f \t %6.2f pentru expresie.1f \t %6. Formatul se va încheia cu un caracter ‘\n’. i < 6. x. In programul anterior puteam defini un şir de caractere cu specificatorii de format char * format = “ %4. două pentru partea subunitară. Vom afişa componentele vectorului sub forma 111 .2. z).2f \n". fp = fopen("rez. x = x + 0. ‘\t’.txt.1f \t %6. } fclose(fp). Consideram un vector cu 5 componente numere reale.

# include <stdio. 5. • funcţia int getchar().h>. printf(“Element\tValoare”). • funcţia int ungetc(int c. Caracterul ‘\n’ este introdus în şirul citit. FILE * stream).h> int main() { double x[5] = {1. Ea este echivalentă cu funcţia fgetc(stdin).1}. 112 . Dacă se întâlneşte sfârşitul de fişier. ….98. i++) printf(“x[%d] \t\t%d\t”. Funcţia returnează caracterul scris sau constanta EOF în caz de eroare. scrie caracterul c in streamul stdout.2 Funcţii intrare / ieşire tip caracter Fişierele text sunt formate din linii ce conţin zero sau mai multe caractere.. • funcţia int fputc(int c. funcţia returnează valoarea EOF care este o constantă predefinită în biblioteca <stdio.12. • funcţia int putchar(int c). pune caracterul c în streamul de intrare. scrie caracterul c în fişierul specificat de stream.23. urmate de caracterul ‘\n’. Acest indicator are valoarea zero la deschiderea fişierului şi apoi este modificat de instrucţiunile de citire sau scriere a fişierului. • funcţia char * fgets(char * s. x[i]). citeşte cel mult n – 1 caractere în vectorul s. După ultimul caracter citit se adaugă caracterul ‘\0’ în vectorul s.Element x[0] x[1] Valoare …. Funcţia returnează adresa vectorului s. adică şiruri de caractere terminate cu caracterul ‘\n’. 4. // scrie componentele vectorului for(i = 0. i < 5. citeşte un caracter din streamul stdin.1. Ea se opreşte dacă întâlneşte caracterul ‘\n’ sau sfârşitul de fişier. La întâlnirea sfârşitului de fişier sau la o eroare funcţia returnează valoarea NULL. citeşte un caracter din fişierul de intrare şi avansează indicatorul de poziţionare al fişierului cu valoarea unu. -23. FILE * stream). return 0. 0. int n. Pentru prelucrarea acestor fişiere este util să putem citi sau scrie caractere şi linii. int i. i.. FILE * stream). • funcţia int fgetc(FILE * stream). } 5. Fişierele text au indicator de poziţionare ce conţine numărul următorului caracter de scris sau de citit. Următoarele funcţii citesc sau scriu linii din fişiere tip text.

Vom exemplifica utilizarea acestor funcţii cu un program care să calculeze dimensiunea în octeţi a unui fişier. Funcţia fopen are ca rezultat valoarea NULL dacă operaţia nu a avut succes. stdout). read(file) while(! feof(file)) { // prelucreaza blocul citit read(file) } Exemplu. stdout). Presupunem că utilizăm o funcţie read(file) ce citeşte câte un bloc de date din fişier. /* calculul dimensiunii unui fisier */ #include <stdio. FILE* file. printf("introduceti numele fisierului\n"). Apoi se va citi câte un caracter pană la întâlnirea sfârşitului de fişier şi se vor număra octeţii citiţi. // deschide fisierul in citire file = fopen(name. name). "r").h> int main() { char name[64]. pentru a afişa câte un şir de caractere pe rând. Operaţia de citire poziţionează indicatorul de sfârşit de fişier la o valoare diferită de zero dacă în cursul operaţiei de citire s-a detectat sfârştul fişierului şi acest lucru este detectat cu funcţia feof(). char car. In consecinţă. Reamintim că funcţia fputs() nu scrie caracterul ‘\n’ în fişier. scanf("%s". // test daca fisierul exista if(file == NULL) { printf("nume de fisier eronat\n"). scrie şirul de caractere s (terminat cu ‘\0’) în fluxul stdout. FILE * stream). Schema principială de citire a unui fişier secvenţial este următoarea. int nb = 0. • funcţia int puts(char * s).int fputs(const char * s. scrie şirul de caractere s (terminat cu ‘\0’) în fluxul stream. Citirea secvenţială a fişierului se va face cu instrucţiunea while. la scrierea în fişierul stdout cu funcţia fputs vom scrie : fputs(x. Caracterul ‘\0’ nu este scris. şi va deschide fişierul în citire. Caracterul ‘\0’ nu este scris. în timp ce funcţia puts() scrie caracterul ‘\n’ în fişierul stdout după şir. fputs(“\n”. Programul va citi numele unui fişier de la tastatură. 113 • funcţia . Funcţia scrie şi caracterul ‘\n’ după ce a scris şirul s.

Citirea secvenţială a primului fişier se va face cu instrucţiunea while. if(fin == NULL) { printf("fisierul %s nu exista\n". char nume1[64]. utilizând faptul că funcţia fopen are ca rezultat valoarea NULL dacă operaţia nu a avut succes. nb). 114 . // citeste numele primului fisier printf(“introduceti numele fisierului ce va fi copiat\n”). Exemplu. Fie un fişier în directorul curent. while(car != EOF) { // numara caracterul citit nb = nb + 1. nume1). Vom deschide cele două fişiere şi vom testa dacă operaţia a avut success. } // citeste numele noului fisier printf(“introduceti numele noului fisier\n”). nume2[64]. } Rezultatul rulării programului este cel de mai jos.return EXIT_FAILURE. name. Numele celor două fişiere se vor citi de la tastatură. return EXIT_FAILURE. Un alt mod de a rezolva această problemă va fi arătat ulterior.h> int main () { FILE * fin. * fout. printf("fisierul %s contine %d octeti\n". nume1). // citeste un caracter car = fgetc(file). până la întâlnirea sfârşitului primului fişier. return EXIT_SUCCESS. scanf(“%s”. Vom copia acest fişier într-un alt fişier tot în directorul curent. # include <stdio. fin = fopen(nume1. } fclose(file). Apoi se citeşte repetat câte un caracter din primul fişier şi se scrie în al doilea. "r"). } // citeste cate un caracter până la sfarsitul fisierului // citeste un caracter car = fgetc(file). Vom face un program care să copieze un fişier existent în altul.

fout). int sscanf(char * s. } // copiaza fisierul int c. 5. Instrucţiunile ce citesc şi scriu din fişiere pot fi rescrise ca int c. while(c != EOF) { fputc(c.2 Fişiere text tip şir de caractere Limbajul oferă posibilitatea de a lucra cu fişiere tip text memorate într-un şir tip C. c = fgetc(fin). Parametrul s este şirul de caractere tip C în care se scriu sau din care se citesc datele. nume2). Vom converti aceste şiruri în două variabile tip int şi double. fout = fopen(nume2. } Rezultatul rulării programului este cel de mai jos.23e-1”. Fie doi vectori tip char ce conţin şirurile “104” şi “1. #include <stdio. Asemenea fişiere sunt utile la conversii ale valorilor numerice în şiruri de caractere şi invers. } fclose(fin). const char * format. Exemplu. fclose(fout). vom face suma lor şi o vom scrie pe ecran. nume2). while((c = fgetc(fin)) != EOF) fputc(c. c = fgetc(fin). return EXIT_FAILURE. const char * format. return EXIT_SUCCESS. fout). if(fout == NULL) { printf("fisierul %s nu se poate crea".h> int main() { 115 . "w"). Parametrul format este un şir de caractere compus din specificatori de conversie definiţi de % şi alte caractere. argumente).scanf(“%s”. argumente). Instrucţiunile de scriere / citire sunt: int sprintf(char * s.

&i). char c[] = "abc". double d1. sscanf(y. // scrie variabilele initiale in fisierul tip sir sprintf(s. char y[] = "1. d. double d1. r. Fie variabilele int x = 10. double d = -2.34.char x[] = "104". 116 . x. double z. double d = -2. Exemplu. şi vom scrie valorile lor pe ecran. printf("i = %d \n". c). într-un fişier tip şir de caractere char s[100] . z). d. # include <stdio. char c[] = "abc". x. printf("i + y = %lf\n". separate de spaţii. int x1. r = z + i.h> int main() { char s[100].23e-1". Vom apoi citi valorile din vectorul s şi vom iniţializa alte variabile int x1. Reamintim că la citirea unei variabile tip double se utilizează specificatorul de conversie %lf. printf("y = %lf \n". sscanf(x. r). // scrie pe ecran fisierul tip sir printf("%s\n". "%d %f %s". Vom scrie valorile acestor variabile. char c1[10]. return 0. int x = 10. "%d".34. // scrie pe ecran variabilele initiale printf("x = %d d = %f c = %s\n". s). "%lf". &z). } Rezultatul rulării programului este cel de mai jos. int i. c). i).

size_t size. size_t size. • funcţia long ftell(FILE* stream). In consecinţă. // scrie valorile variabilelor pe ecran printf("x = %d d = %f c = %s\n". "%d %lf %s". size_t nmb. 5. • funcţia size_t fwrite(const void * ptr. &x1.3 Fişiere binare Pentru scrierea şi citirea de date din fişiere binare există funcţiile fread şi fwrite. &x1. c1). FILE * stream). funcţia returnează valoarea 0. &d1. x1. Aceste funcţii pot citi sau scrie unul sau mai multe blocuri de date. Funcţia returnează numărul efectiv de blocuri scrise. // citeste valorile variabilelor din fisierul tip sir sscanf(s. "%d %lf %s". c1). • funcţia size_t fread(void * ptr. la apelarea funcţiei sscanf în program utilizăm adresele variabilelor x1 şi d1 sscanf(s.char c1[10]. c1). 117 . are ca rezultat valoarea indicatorului de poziţie al fişierului. FILE * stream). • 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. 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. citeşte în vectorul ptr cel mult nmb blocuri de dimensiune size din fişierul stream. d1. următoarea instrucţiune poate fi o operaţie de citire sau scriere. long int offset. } Pentru citirea variabilei d1 de tip double am utilizat specificatorul de conversie %lf. &d1. Funcţia returnează numărul efectiv de blocuri citite. int whence). Indicatorul de poziţie al fişierului este avansat cu numărul de octeţi scrişi. Indicatorul de poziţie al fişierului este avansat cu numărul de octeţi citiţi. return 0. scrie în vectorul ptr cel mult nmb blocuri de dimensiune size în fişierul stream. In cazul întâlnirii sfarşitului de fişier. size_t nmb. Menţionăm că parametrii funcţiei sscanf sunt de tip pointer (sunt parametri de ieşire). Valorile scrise pe ecran cu cele trei instrucţiuni printf() sunt cele de mai jos.

main. Un rezultat zero al acestei funcţii semnifică întâlnirea sfârşitului de fişier. int nb = 0. al doilea bloc caractere ‘1’. int main() { char name[256]. printf("fisierul %s contine %d octeti\n".Exemplu. Vom calcula lungimea unui fişier text modificând indicatorul fişierului astfel încât noua lui poziţie să fie sfârşitul fişierului. Citirea secvenţială se va face cu instrucţiunea while. } // modifica indicatorul de pozitie la sfarsitul fisierului fseek(file.. Exemplu. Vom crea un fişier binar pe care îl citim apoi secvenţial. if(file == NULL) { printf("nume de fisier eronat\n"). Fişierul citit este chiar fişierul sursă ce conţine programul. "r"). } Rezultatul rulării programului este cel de mai jos. SEEK_END). name). Menţionăm că putem genera caracterele ‘0’. fclose(file). … Programul este următorul.c. return EXIT_FAILURE. Vom scrie în fişier 10 blocuri de câte 15 octeţi fiecare. Calculul lungimii fiserului // prin modificarea indicatorului de pozitie. După fiecare citire testăm rezultatul funcţiei fread. primul bloc caractere ‘0’. #include <stdio. ‘1’. nb). name. etc.h> // fisiere text C. // citeste valoarea indicatorului de pozitie nb = ftell(file). 0. Lungimea fişierului va fi valoarea indicatorului de poziţie. scanf("%s". etc. 118 . return EXIT_SUCCESS. // deschide fisierul in citire file = fopen(name. FILE* file. cu o expresie de forma ‘0’ + i unde i ia valorile 0. 2. Blocurile vor consta din şiruri de câte 14 caractere plus ‘\0’. printf("introduceti numele fisierului\n"). 1.

return EXIT_FAILURE. i < 10. } // inchide fisierul fclose(fil). ++j) x[j] = ‘0’ + i . // deschide fisierul in citire fil = fopen(“fil. } int xct. // citeste un sir xct = fread(x. // scrie blocul in fisier fwrite(x. 15. j. while(xct != 0) { // scrie sirul pe ecran (in streamul stdout) printf(“%s\n”. i++) { // creaza un bloc for(j = 0. j < 14. 1. “rb”). // deschide fisierul in creare fil = fopen(“fil. } // inchide fisierul fclose(fil). x). 1.#include <stdio. 1. 15. fil).txt”. fil). x[14] = 0. 119 .h> int main() { FILE * fil. if(fil == NULL) { printf(“fisierul nu se poate deschide\n”). if(fil == NULL) { printf(“fisierul nu se poate crea\n”). int i. // citeste un sir xct = fread(x. } Rezultatul rulării programului este cel de mai jos. return EXIT_FAILURE. fil). char x[15].txt”. return EXIT_SUCCESS. 15. “wb”). } for(i = 0.

// deschide fisierul in creare fil = fopen(“fil”. j < 9. 1. char x[10].h> int main() { FILE * fil. ++j) x[j] = ‘a’ + i + j. if(fil == NULL) { printf("fisierul nu se poate citi\n"). “wb”). 2. // scrie blocul in fisier fwrite(x. 120 . } // inchide fisierul fclose(fil). i < 15. } for(i = 0. fil). “rb”). 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. // deschide fisierul in citire fil = fopen(“fil”. // scrie sirul pe ecran (in fluxul stdout) puts(x). x[9] = 0. if(fil == NULL) { printf("fisierul nu se poate crea\n"). 1. Vom crea un fişier binar în care vom scrie 15 blocuri de câte 10 caractere şi apoi vom citi blocurile pare. j. … #include <stdio. return EXIT_FAILURE. int i. i++) { // creaza un bloc for(j = 0. 10.Exemplu.

Rezultatul rulării programului este cel de mai jos. // citeste sirurile pare for(i = 0. return EXIT_SUCCESS. 1. în loc de a-l poziţiona în raport cu începutul fişierului. } Menţionăm că puteam avansa indicatorul fişierului la sfârşitul ciclului for cu 10 octeţi în raport cu poziţia curentă ca mai jos // avanseaza indicatorul fisierului cu 10 in raport cu pozitia curenta fseek(fil. stdout). i += 2) { // pozitioneaza indicatorul fisierului in raport cu inceputul fisierului fseek(fil. (long)(i * 10). (long)(10). stdout). 10. 121 . fputs(“\n”. SEEK_CUR). } // inchide fisierul fclose(fil).return EXIT_FAILURE. // scrie sirul pe ecran (in fluxul stdout) fputs(x. fil). } printf("fisierul citit\n"). // citeste un sir fread(x. SEEK_SET). i < 15.

2 + sin(0. într-un program putem defini numerele complexe a şi b astfel struct complex a. De exemplu. Definirea unei structuri se face cu instrucţiunea struct nume {lista de declaraţii de variabile }. (punct). Putem da valori variabilei a definite anterior astfel : a.0. b = a. O structură poate fi iniţializată la declarare. O structură este o colecţie de variabile de tipuri diferite.1. Menţionăm că o structură poate conţine componente de orice tip. vezi Anexa 2. float imag.3). are aceeaşi prioritate ca şi operatorii () şi []. } a.6 Structuri tip C şi uniuni 6. 122 . Variabilele dintr-o structură vor fi numite componente sau câmpuri. Putem defini apoi variabile corespunzând acestui nou tip cu o instrucţiune de forma struct nume listă de variabile.34}. -2. Vom defini o structură ce conţine date despre o persoană. Adresarea unui element al unei structuri se face cu operatorul de selecţie . 1.real * a. b.imag. Este posibil să atribuim o structură alteia. Este posibil să combinăm cele două instrucţiuni în una singură struct complex { float real. }. cu forma nume.membru Operatorul . x = a.0}.1 Structuri O structură este un tip de date definit de utilizator. următoarea structură poate defini tipul numere complexe struct complex { float real.imag = -1. de exemplu : struct complex a = {0. b. prenumele şi vârsta. Putem scrie struct complex n = {1.0. b. Instrucţiunea de definire a unei structuri şi a variabilelor corespunzând acestui tip are forma generală struct nume {liste de declaraţii de tip } listă de variabile. float imag.real = 1. numele. Putem utiliza valorile componentelor unei structuri în expresii : float x. Exemplu. De exemplu. iniţializăm structura şi scriem datele pe ecran. chiar şi alte structuri. unde nume este noul tip de date. a.

Vom declara variabile de acest tip în funcţia main şi în funcţia ce adună vectorii.nume. int main() { struct person p1.#include <stdio.prenume). printf("varsta : %d\n". Funcţia va avea ca parametri două structuri şi ca rezultat o structură. char prenume[64]. printf("nume : %s\n".varsta = 50. strcpy(p1. printf("prenume : %s\n". }.prenume. return 0.h> struct person { char nume[64].h> #include <string. vector y) { 123 .varsta). "popescu"). p2. Vom construi o funcţie care să adune doi vectori. strcpy(p1. p1. "ioan"). p2. } vector.h> typedef struct { double cmpa. forma instrucţiunii typedef este typedef tip-existent tip-nou Pentru exemplificare vom defini (cu instrucţiunea typedef) un tip numit vector ce va fi o structură ce conţine două numere reale. p2. p2. } Rezultatul rulării programului este prezentat mai jos. double cmpb. vector addcmp(vector x. p2 = p1. definim un nou tip de date corespunzând structurii cu instrucţiunea typedef. Reamintim că. int varsta.nume). Pentru a defini funcţii ce au ca argumente sau rezultat structuri în limbajul C. # include <stdio.

In ambele cazuri identificator este numele variabilei tip pointer. Putem utiliza valorile variabileleor definite de structura y.23. cy. ce vor conţine adresa unei structuri.imag = 1.cmpa = x. De exemplu. cy). } int main() { vector cx.4. return 0. py = &y.real = 0. cz.cmpb. cz = addcmp(cx.cmpb = 1.vector c. c. struct complex * py.cmpb = 1. vezi Anexa 2. Sunt corecte şi scrierile 124 . dacă am definit un tip de structură numetip cu instrucţiunea typedef. %f) \n”. Exemplu. cx. float fm = pc->imag. are precedenţă mai mare decât operatorul *. şi -> au aceeaşi prioritate ca operatorii () şi []. printf(“suma este (%f . utilizând pointerul py py->real = -23. cz.cz. unde nume este numele structurii sau numetip * identificator. un câmp al structurii este adresat astfel (*p).cmpb = x. Prin definiţie. Variabila tip pointer poate fi iniţializată ca orice variabilă tip pointer.cmpa = 0. In cazul unui pointer p la o structură.cmpa .24.cmpb + y. py->imag = 1. Putem iniţializa direct componentele structurii y.cmpa.cmpb). } La fel ca şi în cazul variabilelor putem defini variabile tip pointer la o structură. return c. cx. Putem iniţializa structura y.cmpa + y. Fie instrucţiunile complex y. c. defineşte un pointer de tip struct complex. cy. y.cmpa = 1. cy. Instrucţiunea de definire a unui pointer la o structură are formele struct nume * identificator. struct complex * pc.membru deoarece operatorul . această scriere se prescurtează ca p->membru Operatorii . folosind operatorul de calcul al adresei & pc = &a.2.

6. Fie structura struct strx { int c. int main () { numcmp cr = {1.imag + (*pc).1. Vom iniţializa o variabilă de acest tip şi vom scrie valorile ei pe ecran folosind un pointer la structură. Operatorul ++ are o prioritate mai mică decât -> şi . b->c += 1. float d. %f)\n". float imag. pv->imag). } Vom prezenta un exemplu de utilizare a operatorilor de incrementare ++ în cazul pointerilor la structuri. -1. Vom iniţializa pe b cu adresa lui a. Uniunile sunt utile în 125 . Rezultatul va fi scris sub forma (real. ++b->c. struct strx a.h> typedef struct { float real.real. (*b).c). în cazul structurilor.c. fiecare membru ocupă o zonă proprie de memorie.real = cos(1. numcmp * pv.2}. ++(*b). b->c = 0. Putem incrementa pe c astfel: b->c = b-> + 1. pv->real. # include <stdio. b = &a.(*pc). Vom defini un tip de structură numcmp ce descrie numerele complexe cu instrucţiunea typedef. Exemplu. double d = (*pc).2 Uniuni Uniunile conţin membri ale căror tipuri individuale pot diferi unul de altul dar toţi membrii unei uniuni ocupă aceeaşi zonă de memorie. Reamintim că. imag). Fie o variabilă tip strx şi o variabilă tip pointer la structura strx. ++(b->c). struct strx * b. } numcmp. şi elementul c al structurii la valoarea zero. }. pv = &cr.23). printf("(%f . astfel încât ultimele expresii sunt echivalente cu ++((*b).c += 1.

unde nume este noul tip de date. // atribuie o valoare culorii strcpy(v1.marime = 12. v1. Putem defini apoi variabile corespunzând acestui nou tip cu o instrucţiune union nume listă de variabile. v2. “alb”). Instrucţiunea de definire a unei uniuni este union nume {lista de declaraţii de tip }.culoare). union id { int marime. dar nu ambele simultan. Variabilele tip id se declară ca union id v1. printf(“marime %d \n”.marime). Menţionăm că o structură poate conţine componente de orice tip. O varibilă tip id poate conţine o mărime sau o culoare. v2. // atribuie o valoare marimii v1. }. char culoare[21]. v1. printf(“culoare %s \n”.aplicaţii cu multe variabile ale căror valori nu sunt necesare simultan. sizeof(id)). // afisaza dimensiunea uniunii printf(“dimensiunea uniunii id : %d \n”. 126 . } v1. chiar şi alte structuri.culoare. şi -> ca şi în cazul structurilor. Un membru individual al uniunii se adresează prin operatorii . Putem scrie direct ca şi în cazul structurilor union id { int marime. Exemplu. Exemplu. char culoare[21].

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. cerând destinatarului să aplice o metodă asupra lui (să apeleze o functie). Ea asigură detaliile de implementare pentru structura de date şi pentru operaţii. Alte operaţii ce se pot defini suma a două funcţii liniare. Tipurile fundamentale de date.argumentele metodei. Un obiect este o instanţă a unei clase (a unui tip abstract). Mesajul conţine : . integrala definită. Pentru a rezolva o problemă trebuie să creăm un model al problemei. In cazul proiectelor complexe este avantajos să putem defini propriile tipuri de date. un tip de date ce descrie funcţia liniară f(x) = m x + n Datele corespunzând acestui tip sunt m şi n. Setul de operaţii definite se numeşte interfaţă. Comportarea unui obiect este definită de metodele ce se pot aplica asupra lui. Programarea orientată obiect 7 Clase Pentru a rezolva o problemă trebuie să identificăm datele problemei şi operaţiile ce trebuie efectuate asupra acestor date. Un program este o multime de obiecte create. etc. Partea de definire se numeşte interfaţă. compunerea a două funcţii liniare.numele metodei. El este unic identificat prin nume şi defineşte o stare reprezentată de valorile atributelor sale la un moment dat. Clasa defineşte variabile (numite şi câmpuri sau atribute sau proprietăţi) şi funcţii (numite şi metode sau operaţii) care implementează structura de date şi operaţiile tipului abstract. tablouri. etc.. de tip real. double. Clasa defineşte deci 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. Interacţiunea este bazată pe mesaje trimise de la un obiect la altul. de exemplu. fişiere. • Obiect. Procesul de modelare se numeste abstractizare. . Instanţele clasei se numesc obiecte. Să definim. Abstractizarea este structurarea problemei în entităţi prin definirea datelor şi operaţiilor asociate acestor entităţi. O metoda este asociată unei clase. şi cele derivate. • Metoda. char. Operaţiile corespunzătoare sunt: calculul valorii funcţiei într-un punct. int. calculul funcţiei inverse. In acest proces trebuie să definim : • datele afectate. etc.Partea II. Clasa defineşte variabile şi funcţii ce prelucrează aceste variabile. Un obiect invoca o metodă ca reacţie la primirea unui mesaj. • operaţiile ce trebuie efectuate pentru a rezolva problema. distruse şi care interacţionează. Operaţiile interfeţei sunt singurul mecanism de acces la structura de date. Clasa este un tip de date definit de utilizator. permit rezolvarea oricărei probleme. Un mesaj este o cerere către un obiect ca el să invoce una din metodele lui. • Mesaje. 127 . Definiţiile pe care le vom utiliza sunt următoarele : • Clasa este o reprezentare a unui tip de date abstracte.

Acest principiu cere definirea unei clase generale ce conţine caracteristicile comune ale mai multor elemente. iar clasele care moştenesc se numesc clase derivate sau subclase. • Polimorfismul. Funcţiile din clasele derivate redefinesc operaţiile necesare claselor derivate. protected şi private.1 Definirea unei clase Diagrama sintactică a definiţiei unei clase este Clasele sunt definite utilizând cuvântul cheie class. fiecare clasă particulară adaugă doar elementele proprii. Membri protejaţi vor fi prezentaţi ulterior. Exemplu. Membri publici ai clasei pot fi utilizaţi de orice funcţie din program. Când apelăm funcţia respectivă. Principiile programării orientate obiect sunt următoarele : • Incapsularea datelor este primul principiu al programării orientate obiect. Apoi această clasă este moştenită de alte clase particulare. Membri privaţi ai clasei pot fi utilizaţi doar de funcţiile membre ale clasei. 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. Clasa care este moştenită se numeşte clasă de bază sau superclasă. Acest principiu asigurară ascunderea structurii de date cu ajutorul unei interfeţe pentru adresarea datelor. 7. se apelează versiunea corespunzătoare tipului obiectului. Definind o operaţie pentru înmulţirea numerelor complexe încapsulăm detaliile şi înmulţim două numere complexe fără să ne intereseze cum se face operaţia în detaliu. Obiectele unei clase sunt create şi iniţializate de funcţii membre ale clasei special definite pentru acest scop numite constructori. Nume clasă Atribute Operaţii In programe creăm variabile de tipul unor clase care se numesc obiecte. Implicit. Vom spune că obiectele sunt instanţe sau realizări ale unor clase. Datele şi funcţiile membre ale clasei pot fi publice. identificator este numele clasei. • Moştenirea. Implicit toţi membri clasei sunt privaţi. toate datele şi funcţiile unei clase sunt private. Avem funcţii cu acelaşi nume în clasa de bază şi în clasele derivate. iar membru este o declaraţie de funcţie membru sau dată a clasei.Reprezentarea tipurilor de date abstracte pe care le definim este cea de mai jos. Numere complexe. Specificatorii de acces sunt public. Acesta cere combinarea datelor şi metodelor într-o singură structură de date. 128 .1. 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.1 Definirea unei clase 7. private sau protejate. Un număr complex este o pereche ordonată de numere reale.

Funcţiile membre. Variabilele. atributele) membre ale clasei sunt m şi n şi ele vor memora valorile m şi n ale funcţiei descrisă de clasă.In exemplele următoare vom adopta următoarea regulă : numele claselor vor începe cu litere mari. pot fi definite în interiorul clasei sau în afara ei. Ca exemplu vom defini o clasă care reprezintă funcţia liniară f(x) = mx + n pentru care vom calcula valoarea funcţiei într-un punct. double). Reprezentarea acestei clase este cea de mai jos. Ele sunt de tip double şi sunt declarate private. Clasa va avea o funcţie print() ce afişază parametrii m şi n ai obiectului. iar numele câmpurilor şi metodelor cu litere mici. n. void invert(). 129 . Numele clasei este Line. ce inversează funcţia şi print(). double). void invert(). Funcţia Line() este constructor. Line double m. }. void assign(). ce atribuie valori variabilelor m şi n. Definiţia clasei este inclusă între acolade { şi }. Line(double. Funcţiile membre ale clasei. void print(). Interzicerea accesului din afara clasei este un principiu al programării orientate obiect numit încapsularea datelor. double). value(). double). double intgrl(double. double). ce dă valoarea funcţiei într-un punct. In acest fel ele vor fi accesibile doar funcţiilor membre ale clasei. urmate de . double n. Ea crează obiecte şi iniţializează variabilele acestora. invert(). (metodele. funcţia inversă şi integrala definită. Line (double. void print(). void value(double). Definiţia clasei este următoarea /* clasa Line descrie functia f(x) = m * x + n */ class Line { private: double m. Toate funcţiile sunt declarate publice şi vor putea fi utilizate de obiecte în afara clasei. (câmpurile. public: void assign(double. ce scrie variabilele m şi n şi intgrl() ce calculează integrala definită a funcţiei.. (metodele). double intgrl(double. operaţiile) sunt assign(). double value(double).

n * temp. double b) { m = a. } // afisarea parametrilor pe ecran void Line::print() { cout << "functia f(x) = m * x + n. In limbajul C++ avem posibilitatea de a grupa anumite nume în spaţii de nume. n = b. şi nu un apel la funcţie. } // calculul integralei definite double Line::intgrl(double a. n = b. double b) { m = a. Operatorul de rezoluţie :: se utilizează pentru a defini o funcţie membru a clasei în afara clasei. datele şi funcţiile unei clase constituie un spaţiu de nume ce are numele clasei. double b) { return m * (b * b . } // calculul functiei inverse void Line::invert() { double temp. m = temp. n = . } // constructor cu parametri Line::Line(double a. } 130 . " << " m = " << m << " n = " << n << endl. compilatorul generează direct textul lor în program. ca mai jos : // atribuirea de valori variabilelor m si n void Line::assign(double a.Funcţiile definite în interiorul clasei sunt funcţii inline. Vom defini metodele clasei Line în afara definiţiei clasei.a).0 / m.a * a) / 2 + n * (b . Prin definiţie. } // calculul valorii functiei in punctul x double Line::value(double x) { return m * x + n. Operatorul de rezoluţie :: arată că un nume de dată sau de funcţie aparţine unui spaţiu de nume. temp = 1.

double b) care iniţializează câmpurile m şi n la valorile 1 şi 1. a. 1). int main() { // creaza un obiect de tipul Line Line a(1. un obiect este o instanţă a unei clase.5) = " << a. …. La declararea unui obiect se apelează un constructor al clasei. Diagrama sintactică pentru definirea obiectelor este cea de mai jos nume_clasa nume_obiect(lista de parametri). Reamintim că. a.nume_camp Declararea unui obiect se face în acelaşi fel cu declararea tipurilor standard. conform următoarei diagrame sintactice nume_obiect. x este declarat ca un obiect de tipul Line cu numele a (o instanţă a clasei Line) cu instrucţiunea Line a(1.Apelul unei funcţii a clasei de către un obiect se face cu operatorul de selecţie . 131 . m şi n.print().nume_funcţie (lista de parametri) Utilizarea unui câmp al un obiect se face cu operatorul de selecţie .value(0. (punct). a. 1).invert(). cout << "functia inversa \n". cout << "f(0.intgrl(0. Orice obiect are propriile variabile. cout << "intgrala f(x) de la 0 la 1 = " << a. . } Pe ecran se vor afişa următoarele rezultate In funcţia main(). 1) << endl. integrala de la 0 la 1 şi inversa ei. Vom exemplifica utilizarea clasei definind funcţia liniară x + 1şi calculând valoarea ei în punctul 0. (punct). Obiectul este creat prin apelarea constructorului Line(double a. return 0.5. conform următoarei diagrame sintactice nume_obiect. El are cele două data membre.5) << endl.print().

Menţionăm că în funcţia main() nu putem scrie instrucţiunea cout << a. Menţionăm că putem scrie definiţia unei clase într-un fişier antet (header) şi definiţia funcţiei main() ce utilizează această clasă într-un fişier separat care să includă fişierele antet cu definiţiile claselor. Acesta este un principiu al programării orientate obiect. }. De exemplu. m şi n. etc. în directiva include numele fişierului header se scrie între ghilimele. sunt declarate private.m << “ “ << a. Pentru a putea utiliza datele clasei de către obiecte în funcţia main(). /* clasa Line descrie functia f(x) = m * x + n */ class Line { private: double m. }. Menţionăm că în limbajul C++ putem defini clase şi cu cuvântul cheie struct (spre deosebire de limbajul C.h" int main() { // creaza un vector cu obiecte de tipul Line Line v[2]. 132 . In cazul fişierelor header definite de utilizator. " << " m = " << m << " n = " << n << endl. }. putem scrie definiţia clasei Line în fişierul header Line.. n = 0. n. unde cuvântul cheie struct defineşte structuri). Line() { m = 0. ca mai sus. Ele pot fi utilizate doar de funcţiile clasei şi nu de obiecte de tipul clasei. public: void print(). care au fost declarate publice în definiţia clasei. In cazul unei clase definite cu struct toţi membrii clasei sunt doar variabile şi sunt declaraţi implicit ca publici. Line(double a. ele ar trebui declarate publice. ca î exemplul de mai jos. invert() şi print().h. #include "Line.Obiectul poate apela funcţiile membre assign(). n = b. deoarece variabilele clasei. #include <iostream> using namespace std. Nu utilizăm variabile şi funcţii globale. void Line::print() { cout << "functia f(x) = m * x + n.h ca mai jos. ci fiecare obiect are propriile lui date. } Scriem apoi definiţia funcţiei main() în alt fişier sursă în care includem fişierul Line.n << endl. double b) { m = a.

de exemplu x.assign(3. void Line::invert() { double temp.assign(-1. return 0.v[0] = Line(1. funcţii.1. Adresarea funcţiilor clasei de către obiecte se face în felul următor. } 7. temp = 1. un pointer de tipul clasei. Atunci când un obiect apelează o funcţie a clasei.2 Pointerul this Fie o instrucţiune de definire a unor obiecte de tipul Line Line x(1. Fiecare obiect are o variabilă implicită numită this care este un pointer de tipul clasei ce conţine adresa obiectului. Pointerul this poate fi utilizat în orice funcţie membră a clasei pentru a apela variabile sau alte funcţii membre. -1). v[1] = Line(2.3 Spaţii de nume In limbajul C++ putem defini spaţii de nume. int i.0 / this->m. y. -1) . Definiţia unei clase crează automat un spaţiu cu numele clasei ce cuprinde toate variabilele şi funcţiile membre ale clasei. for(i = 0. Considerăm instrucţiunile în care un obiect apelează o funcţie membru a clasei. 4). Un spaţiu de nume ne permite să grupăm o mulţime de clase. obiecte şi funcţii globale sub un nume. In acest fel aceste funcţii pot adresa variabilele obiectului (în cazul nostru ale obiectelor x şi y). 1). 5). y(2. Funcţiile clasei au un parametru implicit.print(). } Rezultatul rulării programului este cel de mai jos. obiecte } 133 . 7. n = .this->n * temp. Ca exemplu vom rescrie funcţia invert() ca să utilizeze pointerul this la apelarea variabilelor m şi n. Declararea unui spaţiu de nume se face astfel namespace identificator { // declaraţii de clase. m = temp. parametrul implicit al funcţiei primeşte ca valoare pointerul this. i++) v[i].1. i < 2.5. -2).

cout << endl. limbajul are o sintaxă specială numită listă de iniţializatori. obiectele şi funcţiile din bibliotecile C++ standard sunt definite în spaţiul std.2. vezi Anexa 2. • Utilizând numele spaţiului # include <iostream> …………………………………… std::cout << std::endl. pentru a utiliza variabila a din spaţiul anterior scriem general::a Directiva using namespace asociază un spaţiu de nume cu un anumit nivel al programului. Pentru aceasta se definesc funcţii constructor ale clasei. } Pentru a adresa variabilele şi funcţiile definite într-un spaţiu în funcţiile din afara spaţiului se utilizează operatorul de rezoluţie :: Operatorul de rezoluţie are cea mai mare prioritate. astfel încât obiectele şi funcţiile din acel spaţiu sunt accesibile direct ca şi când ar fi fost definite ca globale. using std::endl. 7.2 Constructori şi destructori 7. Un constructor este o funcţie membru a clasei apelată automat atunci când obiectul este declarat. De exemplu. int). Constructorul are acelaşi nume cu al clasei şi nu are nici un tip. în general. …………………………………… cout << endl. De exemplu. Constructorul anterior se poate scrie 134 . Clasa precedentă avea un constructor Line (int. Această directivă are forma using namespace identificator. • Putem utiliza directiva using pentru a declara doar anumite simboluri din spaţiul de nume #include <iostream> using std::cout. obiectul cout corespunzător streamului de ieşire standard poate fi utilizat astfel: • Folosind directiva using namespace # include <iostream> using namespace std. Deoarece constructorii au.1 Constructori In general. b.. ……………………………………. putem defini următorul spaţiu de nume namespace general { int a. Toate clasele.De exemplu. rolul de a iniţializa variabilele obiectului. obiectele trebuie să iniţializeze variabilele lor la declararea obiectului.

135 . Constructorul implicit nu are parametri. double). compilatorul definieşte un constructor implicit. definiţiile celorlalte funcţii sunt cele anterioare. pe cele ale obiectul b. El este apelat ori de câte ori declarăm un obiect cu instrucţiunea X obiect. El interzice modificarea argumentului în timpul copierii. }. Fie din nou clasa Line în care definim un constructor copiere. Orice clasă include un operator implicit de atribuire = care are ca parametru un obiect de tipul clasei. public: void assign(double. In consecinţă. // constructorul implicit X(const X&). double value(double). De exemplu instrucţiunea X a(b). Line(double. void invert(). // constructorul copiere unde X numele clasei. double intgrl(double. el este definit de compilator. /* clasa Line descrie functia f(x) = m * x + n */ class Line { private: double m.Line::Line(double a. Vom prezenta doar definiţiile constructorilor. void print(). Dacă nu definim niciun constructor. n. Menţionăm cuvântul cheie const din definiţia constructorului copiere. şi un constructor cu parametri. double). Constructorul copiere este apelat atunci când vrem să creăm un obiect care să aibe aceleaşi atribute ca un alt obiect. # include <iostream> using namespace std. orice clasă are cel puţin doi constructori. identificaţi de definiţia lor : X(). declară un obiect a de tipul X şi îi atribuie ca valori ale atributelor. Line(). Exemplu. un constructor fără parametri. n(b) { } Clasele au în general următoarele tipuri de constructori. double b) : m(a). Line(const Line&). Dacă nu definim un constructor copiere. double).

n = x. Line y(x).print(). }. Line(const Line& x) : m(x.m). 25). Line() : m(0). double b) : m(a). public: void assign(double.n. Line(double a. // se apeleaza constructorul copiere x. } Vom rescrie definiţia clasei utilizând lista de iniţializatori în definiţiile constructorilor. double value(double). n. void print(). void invert(). apelăm constructorul copiere pentru a crea un alt obiect y. double intgrl(double. double). n(0) {}. n(x.print(). z = x.n) {}. /* clasa Line descrie functia f(x) = m * x + n */ class Line { private: double m. n(b) {}. n = 0.// constructor tip copiere Line::Line(const Line& x) { m = x. z. y.print().m. Line z. Un program care testează definiţia clasei Line este cel de mai jos în care creăm un obiect x. double). şi iniţializăm un obiect z cu operatorul =. int main() { Line x(12. } // constructor implicit Line::Line() { m = 0. } 136 .

care este inversul obiectului ce este parametrul funcţiei. 2) când se apelează funcţia z = f(y). In cazul funcţiei f. return s. în stivă se poate pune valoarea obiectului. Când parametrul este de tip valoare. s. 22). obiectul s se copiază în stivă utilizând constructorul copiere. Dacă parametrul este declarat ca 137 . // se apeleaza constructorul copiere Line z. Menţionăm că. z = f(y).print(). return 0. Parametrul funcţiei f (r în definiţia funcţiei) este transmis prin valoare. Acest lucru se face cu cuvântul cheie const. // scrie numarul x x. In acest caz. Un exemplu este constructorul copiere al clasei Line. • un obiect este transmis prin valoare unei funcţii (obiectul este copiat în stivă). acest parametru trebuie declarat ca fiind constant. } Constructorul copiere este apelat 1) atunci când se declară obiectul Line y(x). parametrul Line r este transmis prin valoare. când este de tip referinţă în stivă se pune adresa (referinţa) lui. // se apeleaza constructorul copiere } int main() { Line x(11. deci definiţia funcţiei f poate fi Line f(const Line r).invert().print(). Fie o funcţie globală f care are ca parametru un obiect de tip Line şi ca rezultat un obiect de tip Line. deci în stivă se pune o copie a obiectului creată cu constructorul copiere. Definitia funcţiei f() este următoarea Line f(Line r) { Line s = r. dacă funcţia modifică parametrul r. compilatorul detectează o eroare. // se apeleaza constructorul copiere // scrie numărul z z. Un parametru obiect al unei funcţii poate fi de tip valoare sau de tip referinţă. Presupunem clasa Line definită anterior. • un obiect este returnat ca valoare de o funcţie. Exemplu. în cazul în care o funcţie nu trebuie să modifice un parametru. 3) când se execută instrucţiunea return s. Line y(x). De exemplu.Constructorul copiere este apelat ori de câte ori : • un obiect este copiat. parametrul r nu trebuie modificat.

2). " << " m = " << m << " n = " << n << endl. double b) : m(a).2 Destructori Destructorul unei clase este apelat automat când un obiect este distrus. -1). n. -2). v[0] = Line(1. Putem defini tablouri de obiecte în mod obişnuit. de exemplu Line x[20]. i++) v[i]. Pentru aceasta. Vom crea un vector cu două componente de tipul Line şi vom afişa parametri m şi n ai obiectelor. class Line { private: double m. care este apelat la definirea vectorului de obiecte tip Line Line v[2].Line& r el este transmis prin adresă. Parametrii tip pointer vor fi prezentaţi ulterior. # include <iostream> using namespace std. Diagrama sintactică a destructorului este următoarea 138 . v[1] = Line(2. Line(double a. }. int i. clasa trebuie să definească un constructor fără parametri. Line(). public: void print(). void Line::print() { cout << "functia f(x) = m * x + n. Line() : m(0). } int main() { // creaza un vector cu obiecte de tipul Line Line v[2]. n(b) {}.print(). Exemplu. de exemplu putem avea instrucţiunile Line a(1. n(0) {}. } Menţionăm că am definit un constructor fără parametri. Fiecare clasă are un singur destructor. for(i = 0.2. 7. x[5] = a. Destructorul nu are tip şi nici parametri. Tabloul definit poate fi prelucrat. i < 2. return 0. în funcţia main().

} ~Test() {cout << “obiectul este distrus” << endl. In fiecare bloc creăm câte un obiect. } } Mesajele afişate vor fi: Menţionăm că toate variabilele locale unei funcţii sau unui bloc. Un obiect este creat întro funcţie sau într-un bloc dintr-o funcţie şi este distrus la ieşirea din funcţie sau din bloc. fie următoarea clasă în care constructorul şi destructorul scriu un mesaj. class Test { public: Test() {cout << “obiectul este creat” << endl. } { cout << “intrare blocul 2” << endl. Test y. cout << “iesire blocul 1” << endl. compilatorul generează unul.} }. sunt create în stivă la intrarea în funcţie sau bloc şi sunt şterse din stivă la ieşirea din funcţie sau bloc. Test x. Blocul este orice şir de instrucţiuni între acolade. Pentru a vedea cum sunt apelaţi constructorii şi destructorul. int main() { { cout << “intrare blocul 1” << endl. Fie două blocuri de instrucţiuni în funcţia main. { şi } din interiorul unei funcţii. cout << “iesire blocul 2” << endl. inclusive obiectele. 139 .~ NumeClasa(){/* corpul destructorului*/} Dacă el nu este definit explicit.

deoarece ele sunt declarate private în definiţia funcţiei. nu putem face acest lucru direct. } void Line::setm(double x) { m = x. ele au fost declarate const. Suma lor va fi funcţia f ( x ) = ( m1 + m2 ) x + (n1 + n2 ) . double d = y. } 140 .getn(). Considerăm două funcţii liniare. Line temp(a + c. void setn(double). Parametrii x şi y sunt cele două funcţii liniare ce sunt adunate. In implementarea funcţiei nu putem folosi direct variabilele m şi n. Vom utiliza funcţiile getm() şi getn() pentru a obţine valoarea lor. Există două moduri de a rezolva această problemă.getm().7. double getn(). b + d). Implementarea funcţiei este următoarea Line sum(Line x.getn(). Dacă datele sunt declarate private. In cazul clasei Line putem defini funcţii membre ale clasei care furnizează parametrii m şi n : double getm(). const Line y). f 1 ( x) = m1 x + n1 şi f 2 ( x ) = m2 x + n2 .3 Funcţii prietene In general avem nevoie să utilizăm şi să modificăm datele (câmpurile) unui obiect în timpul execuţiei programului. double c = y. In prima metodă se definesc funcţii member ale clasei care să modifice şi să furnizeze valorile datelor unui obiect. şi funcţii care modifică aceşti parametric : void setm(double). Vom defini o funcţie globală care să adune două funcţii liniare. Line y) { double a = x. } Exemplu.getm(). Prototipul funcţiei va fi Line sum(const Line x. return temp. Aceste funcţii se numesc funcţii de acces şi sunt de regulă publice. double b = x. Definiţiile funcţiilor getm() şi setm() pot fi: double Line::getm() { return m. Deoarece obiectele nu vor fi modificate.

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 Line este următoarea. class Line { public: Line (double, double); Line (const Line & x); Line (); void assign(double, double); double value(double); double intgrl(double, double); void print(); void invert(); friend Line sum(const Line x, const Line y); private: double m; double n; }; 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 Line sum(const Line x, const Line y) { Line temp(x.m + y.m, x.n + y.n); return temp; } Un program care testează funcţia sum este cel de mai jos. int main() { Line a(1.5, 2.3); a.print(); Line b(2.2, -1.5); b.print(); Line c; c = sum(a, b); c.print(); return 0; } Rezultatul rulării programului este cel de mai jos.

141

7.4 Determinarea tipului unei expresii
Limbajul C++ are operatorul typeid ce permite determinarea tipului unei expresii. Forma operatorului este typeid(expresie) Operatorul typeid are ca rezultat o referinţă la un obiect constant de tipul type_info. Clasa type_info este o clasă predefinită a limbajului, definită în fişierul antet <typeinfo>. Clasa defineşte metoda const char * name(); care are ca rezultat un şir de caractere cu numele tipului expresiei şi operatorii == şi ! = cu care putem compara tipurile a două expresii. Exemplu. Vom determina tipul expresiilor aritmetice şi booleene şi tipul unui pointer şi al unei referinţe cu programul de mai jos. #include <iostream> #include <typeinfo> using namespace std; int main(int argc, char *argv[]) { int a, b; char c; double x, y; bool bl; float f1, f2; int * ptra; int& ra = a; cout << typeid(a + b).name() << endl; cout << typeid(x + y).name() << endl; cout << typeid(f1 + f2).name() << endl; cout << typeid(c).name() << endl; cout << typeid(bl).name() << endl; cout << typeid(ptra).name() << endl; cout << typeid(ra).name() << endl; return 0; } Rezultatul rulării programului este cel de mai jos.

142

In program se include biblioteca <typeinfo> ce defineşte clasa type_info . Expresia typeid(a + b) are ca rezultat o referinţă la un obiect constant de tipul type_info. In consecinţă, expresia typeid(a + b).name() apelează metoda name() a acestui obiect, care are ca rezultat un şir de caractere cu numele tipului expresiei, i pentru int, d pentru double, etc. Operatorul typeid poate avea ca parametru un obiect de un tip oarecare, definit de programator, de exemplu o clasă. Exemplu. Fie clasa Line definită mai sus. Programul următor determină tipul unui obiect de tipul Line şi al unui poiner la un obiect de tip Line. Definiţia clasei Line nu este arătată. #include <iostream> #include <typeinfo> using namespace std; int main(int argc, char *argv[]) { Line a(1, 2); Line * ptrln = new Line(-1, 2); cout << typeid(a).name() << endl; cout << typeid(ptrln).name() << endl; return 0; } Rezultatul rulării programului este cel de mai jos.

Programul afişază tipul unui obiect Line şi tipul unei variabile pointer de tip Line.

143

8 Siruri tip C++
8.1 Clasa string
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 ai şirurilor, <, <=, >, >= , = = şi != ce au ca rezultat valorile true sau false. Operatorii << şi >> scriu şi citesc şiruri tip string. Exemple. if(x < x1) cout << x1; else cout << x; Funcţii membre importante ale clasei sunt : • int length(); – dă lungimea şirului tip string, • int size(); – dă lungimea şirului tip string, • 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 sau poate atribui o valoare unui caracter din şir. Indicele primului caracter din şir este 0. Exemplu. Să scriem un şir, caracter cu caracter. string s = “abcd”; for(j = 0; j < s.length(); j++) cout << s[j]; 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 144

int find(char * substring). int size). 3. char * sir). Ea are prototipurile : replace(int index. int size) şterge size caractere începând cu caracterul cu indicele index. int index. Ea are prototipurile: append(const string& str). replace(int index. Instrucţiunea str. şterge două caractere începând cu caracterul ‘*’ ce are indicele 4. • funcţia append() adaugă un şir la sfârşitul şirului curent. append(const char* str). Noul şir este “ABCDGHIJK” • funcţia replace() înlocuieşte un subşir cu altul.replace(5.find(“cd”) << endl. dă indicele primei apariţii a şirului substring în şirul respectiv.erase(4. inserează şirul str începând cu indexul index. append(const string& str. Fie şirul string str = “ABCDGHIJK”. int size). int size. “xyz”). crează un şir tip string din subşirul de lungime size ce începe cu caracterul index. 2). şi înlocuieşte subşirul de lungime size ce începe cu caracterul index cu subşirul sir. Fie şirul string str = “ABCD*FGHIJK”. A doua instrucţiune crează şirul tip string z “xy” • funcţia insert() cu prototipul insert( int index. string str) . 145 . string z = x. Exemple.find(“xyz”) << endl. int size. 2). s2). Instrucţiunea str. Fie instrucţiunile string x = “abcxyz”. Exemplu. Exemplu. afişază valoarea 2. Fie şirul string str = “abcdefg”. Noul şir este “ABCDGxyzJK” Fie şirul string s2 = “abc”. (indicele caracterului ‘c’ este 2).substr(3. afişază valoarea 7 (lungimea şirului). Dacă şirul substring nu există în şir funcţia are ca rezultat lungimea şirului. • funcţia erase() cu prototipul erase(int index. Exemple. Instrucţiunea cout << str. înlocuieşte subşirul “xyz” cu şirul “abc”.replace(5. înlocuieşte subşirul “HI” cu subşirul “xyz”. 2. Instrucţiunea str. string sir). Noul şir este “ABCDGabcJK” • funcţia substr() cu prototipul string substr(int index. Instrucţiunea cout << str.

Exemplu.append("xyz"). 3). string b = "xyz". cout << a << endl. modifică şirul x la “axyzbcd” . str). y = “xyz”. In acest fel este posibilă citirea unei linii dintr-un fişier tip text. 2. Exemplu. Funcţia citeşte caractere din stream în obiectul str până la întâlnirea caracterului delim (prima variantă) sau până la întâlnirea caracterului ‘\n’ (a doua variantă). cout << b << endl. b. string& str. string& str).append("abc". 3).append("abc". Fie apoi un obiect tip string ce conţine şirul "xyz". } Rezultatul rulării programului este cel de mai jos. Se va adăuga la sfârşitul lui şirul "abc". A treia funcţie adaugă la sfârşitul şirului din obiect caracterele din şirul str de lungime size începând cu indicele index. adăugând şirul “abc” începând cu indicele 2 se obţine şirul “xyzc”. Trebuie să avem îndeplinită condiţia index <= size Exemplu. este definită funcţia globală getline cu prototipurile : getline(stream.Primele două funcţii adaugă şirul str la sfârşitul şirului conţinut în obiect. int main(int argc. y) . cout << b << endl. #include <iostream> #include <string> using namespace std. getline(cin. getline(stream. Pentru citirea unui şir de caractere dintr-un fişier într-un obiect tip string. Fir şirurile string x = “abcd”. return 0. char *argv[]) { string a("abc"). începând odată de la indicele 1 şi altă dată de la indicele 2. b = "xyz". Instrucţiunile string s. Instrucţiunea x. a. b. Adăugând şirul “abc” începând cu indicele 1 se obţine şirul “xyzbc”. char delim). 1. Se va adăuga la sfârşitul lui şirul "xyz". 146 .insert(1. Fie un obiect tip string ce conţine şirul "abc".

str1. Un program ce exemplifică utilizarea funcţiei insert este cel de mai jos care inserează repetat un şir inaintea unui şir dat. str1. cout << str1 << endl. str2).insert(0. sau până la întâlnirea caracterului delimitator. getline(char * s. #include <iostream> #include <string> using namespace std. Există în plus.produc citirea unei linii de caractere din fişierul de intrare cin. } Rezultatul rulării programului este cel de mai jos. funcţii getline() ale obiectului cin cu prototipurile: getline(char * s. cel mult size – 1 caractere. int size. sau caracterului ‘\n’ (a doua variantă). cout << str1 << endl. care citesc caractere în vectorul de caractere s. str2). 147 .insert(2. str1. cout << str1 << endl.insert(2. char delim). int main() { string str1 = "abcdg". delim (prima variantă). return 0. string str2 = "xy". "123"). int size). cout << str1 << endl.

getm().. double getn(). =. []. Această operaţie se numeşte supraîncărcarea operatorilor (operator overloading).getm(). etc. orice operator supraîncărcat poate fi definit ca funcţie membră a clasei sau ca funcţie globală. }. public: void print(). Pentru a supraîncarca un operator definim o funcţie de forma tip operator semn (parametri) {/* corpul funcţiei */} unde semn este operatorul dorit +. Operatorul +. Line(). Menţionăm că. Line y) { double a = x. <<. / . double). Când definim o clasă nouă. definit ca o funcţie globală. double c = y. b + d). 9. void setn(double). Fie funcţiile: f1(x) = m1x + n1 f2(x) = m2x + n2 Suma lor este funcţia f(x) = (m1 + m2) x + (n1 + n2) Definiţia clasei Line este următoarea class Line { private: double m. ().getn(). etc. char. este următorul. Operatorii limbajului pot fi definiţi şi pentru tipurile nou create. double.-. iar tip este un tip de date definit de programator (o clasă). Line operator+( Line x. Definiţiile funcţiilor menbre ale clasei sunt cele anterioare şi nu sunt repetate. double getm(). double b = x.1 Supraîncărcarea operatorilor aritmetici Să supraîncărcăm operatorul + pentru clasa Line. creăm un nou tip. n. return temp. Line temp(a + c. care să adune două funcţii liniare.9 Supraîncărcarea operatorilor Operatorii limbajului C++ sunt predefiniţi pentru tipurile fundamentale: int. Line(double. >>.getn(). Operatorii supraîncărcaţi au aceeaşi prioritate ca cei originali şi acelaşi număr de parametri. *. void setm(double). double d = y. } 148 .

De accea. b. funcţia corespunzătoare are un singur parametru. operandul drept. Putem scrie c = a. şi este apelată de operandul stâng. atunci când operatorul este o funcţie membră a clasei.m + y. echivalent c = a + b. Line operator+( Line x. z.operator+(b). Constructorul implicit este apelat la definirea obiectului z. x. c obiecte de tip Line. deoarece este o funcţie globală.n + y.A se compara această funcţie cu funcţia sum definită anterior. In acest caz definiţia funcţiei este următoarea. 149 . Definiţia clasei Line cu operatorul + este class Line { public: Line (double.print(). double n. 3). In definiţia clasei vom scrie în definiţia clasei instrucţiunea friend Line operator+ (Line. private: double m. -5). // functia operator+ are un singur parametru Line operator+( Line). z. Line y) { Line temp(x. Funcţia are doi parametri. y(3. sau. } Al doilea mod de a defini operatorul + este ca funcţie membră a clasei. Line (). } Rezultatul programului este cel de mai jos. z = x + y. void print(). Putem defini operatorul ca funcţie prietenă a clasei.m. return 0. return temp. Line). obiectele ce vor fi adunate.n). Putem utiliza funcţia definită astfel int main() { Line x(2. O funcţie membră a clasei este apelată de un obiect. Fie a. double).

public: Line (). z = x + y.print(). 9. -. Supraîncărcarea lui aici este doar un exerciţiu. class Line { private: double m. z.m. 3).n). şi compunere a două funcţii liniare. constructorul copiere şi operatorul de atribuire. y(3. Instrucţiunile c = a + b. }. sunt echivalente. b(4. 150 . -3). return 0. void operator = (const Line &). *. O definiţie incorectă a operatorului de atribuire este următoarea # include <iostream> using namespace std. } Definiţiile celorlalte funcţii nu sunt repetate. c. Menţionăm că operatorul de atribuire este automat definit pentru orice clasă. Exemplu. return temp. n. } Rezultatul este cel anterior. Să se scrie implementarea operatorilor de scădere a două funcţii liniare. Line (const Line &). z. 3).}. Putem utiliza funcţia definită astfel int main() { Line x(2. n + x. -5). Implementarea funcţiei operator+ este următoarea Line Line::operator+( Line x) { Line temp(m + x. Fie instrucţiunea Line a(1. Exerciţiu.operator+(b).2 Supraîncărcarea operatorului de atribuire Considerăm clasa definită anterior în care definim constructorul implicit. şi c = a.

operatorul de atribuire = trebuie să aibe ca rezultat o referinţă de acelaşi tip cu valoarea pe care o atribuie operandului stâng (tipul operandului stâng). 2))). apoi y = z. Operatorul = este asociativ la dreapta şi are ca rezultat valoarea atribuită operandului stâng. }. n. Line (const Line &). y. f(y.m. z = 2. y. public: Line ().n. z.O implementare a definiţiei incorecte a operatorului de atribuire supraîncărcat este următoarea void Line::operator = (const Line & r) { m = r. Definiţia corectă a operatorului de atribuire al clasei Line este Line & operator = (const Line & ). Line & operator = (const Line &). Prototipul operatorului de atribuire al unei clase T este T& operator = (const T&). Pentru a vedea care este definiţia corectă a operatorului de atribuire considerăm instrucţiunea int x. putem scrie instrucţiunea de atribuire multiplă de mai sus f(x. etc. deoarece rezultatul funcţiei este void. f(z. dar nu putem scrie x = y = z. Prima dată se execută instrucţiunea z = 2. } Ea copiază obiectul r în obiectul ce apelează operatorul. In acest fel operatorul este asociativ la dreapta. Fie de exemplu instrucţiunea Line x. Simbolizând operatorul = cu o funcţie f cu două argumente. y = z. In consecinţă. Limbajul permite o instrucţiune de atribuire multiplă de forma x = y = z = 2. n = r. Definiţia clasei va fi class Line { private: double m. Cu această definiţie putem scrie x = z. 151 .

3 Supraîncărcarea operatorilor << şi >> Operatorul << este numit operator de inserţie. 3)." << x. Pentru aceasta se utilizează pointerul this. z(2.m. Toate funcţiile de inserţie au forma ostream& operator << (ostream& stream. Implementarea corectă a operatorului de atribuire supraîncărcat este următoarea. // rezultatul funcţiei este referinta obiectului ce a apelat operatorul return *this.Operatorul are ca rezultat o referinţă la obiectul ce apelează operatorul. Operatorul >> este numit operator de extracţie.m >> x. y. Implementarea lui este următoarea istream& operator >> (istream& stream. Supraîncărcarea operatorului de inserţie << pentru clasa Line. } Putem scrie acum instrucţiunile Line x. In definiţia clasei definim funcţia friend ostream& operator << (ostream& stream. } Primul parametru este o referinţă la streamul de ieşire. Operatorul va insera în streamul de ieşire şirul (m. tip obiect) { // corpul functiei return stream. n).n << ")". Operatorul de extracţie >> pentru clasa Line este supraîncărcat astfel friend istream& operator >> (istream& stream.n. Line x). x = y = z. 9. Line& x) { stream >> x. el extrage caractere dintr-un stream. Line x) { stream << "(" << x. Line & Line::operator = (const Line & r) { // obiectul ce a apelat operatorul primeste valoarea obiectului r m = r. Al doilea este obiectul ce trebuie inserat. } Exemplu. return stream. Ultima instrucţiune este return stream. 152 .n. Exemplu. n = r. el insereaza caractere într-un stream. Implementarea funcţiei este ostream& operator << (ostream& stream.m << ". Line& x).

operandul stâng (transmis prin this) este cel care apeleaza operatorul.return stream. public: Line (). cin >> b. cout << "parametrii primei functii " << endl. Definiţia unei funcţii prietene este. return 0. Reamintim că. b. respectiv istream. cout << "parametrii functiei a doua" << endl. Definiţia funcţiilor membre ale clasei este cea de mai sus. In consecinţă. 153 . ea trebuie declarată în definiţia clasei ca funcţie prietenă friend. deoarece operandul stâng ostream. c. n. Line operator+( Line). } Când un operator este membru al unei clase. aceşti operatori vor fi funcţii externe. Operatorii << şi >> pe care i-am definit. Line& x). }. Line x). definiţia clasei Line este class Line { private: double m. Putem utiliza operatorii definiţi astfel int main() { Line a. nu pot fi membri ai clasei. cum s-a arat mai înainte friend tip nume_funcţie ( parametri). nu sunt un membri ai clasei. cin >> a. Cu aceasta. pentru ca o funcţie externă să aibe acces la membrii privaţi ai clasei. } Rezultatul programului este cel de mai jos. cout << "suma a doua functii liniare" << endl. double). friend istream& operator >> (istream& stream. friend ostream& operator << (ostream& stream. c = a + b. cout << "functia suma" << endl << c << endl. Line (double.

care are ca parametru un obiect de tipul clasei. c. 154 . fără parametri. Constructorul implicit este apelat la declararea obiectelor Line a. b. Reamintim că. n = 0. de exemplu cu definiţia Line() {m = 0. trebuie ca în definiţia clasei să includem un constructor implicit.Reamintim că.} Constructorul implicit nu mai este generat automat de compilator deoarece am definit un constructor. orice clasă include un operator implicit de atribuire =.

are o prioritate mai mare ca operatorul *. un constructor ce iniţializează variabilele şi două funcţii. Operatorii .membru Expresia *p se inchide în paranteze deoarece operatorul . această scriere se prescurtează p->membru O funcţie membră a clasei se apelează astfel (*p).} void print(){ cout << "functia f(x) = m * x + n. Exemplu. este un tip de date valid. ce scrie pe ecran valorile variabilelor. ce calculează valoarea funcţiei într-un punct şi print(). void value(double). Un câmp al obiectului este adresat astfel (*p). Fie p un pointer la un obiect. odată definită. " << 155 .1 Pointeri la obiecte. n. Putem defini variabile pointer de tipul clasei la fel cu pointeri la orice tip. iar identificator este numele variabilei tip pointer. O variabilă tip pointer la o clasă poate fi iniţializată cu adresa unui obiect de tipul clasei. unde tip este numele clasei. Line double m. şi -> au aceeaşi prioritate ca operatorii () şi []. iar datele şi funcţiile acelui obiect pot apelate utilizând pointerul. value(). Vom considera din nou definiţia clasei Line care reprezintă funcţia liniară f(x) = mx + n Clasa defineşte variabilele m şi n. Variabila tip pointer poate fi iniţializată.funcţie(parametri). Operatorii new şi delete O clasă. Definiţia clasei Line este următoarea /* clasa Line descrie functia f(x) = m * x + n */ class Line { private: double m. folosind operatorul de calcul al adresei &. Prin definiţie. double n. Line (double. Instrucţiunea de definire a unui pointer de tipul unei clase este tip * identificator. double). void print(). public: double value(double x) {return (m * x + n) . Prin definiţie. această scriere se prescurtează astfel p->funcţie(parametri). ca orice variabilă tip pointer.10 Moştenirea 10.

return 0. Forma operatorului new este ptr = new tip (listă de parametri). adică un pointer. Ele trebuie distruse cu operatorul delete când nu mai sunt necesare. } Datele afişate pe ecran sunt cele de mai jos. 0). Operatorul delete are forma delete ptr. double a = 1. • obiecte locale.print(). } Line(double x. // apeleaza functiile prin pointer pl->print(). Exemplu. vom crea un obiect corespunzând funcţiei f(x) = x 156 . iar ptr este variabilă tip pointer de tipul clasei. double y) {m = x. • obiecte create în memorie cu operatorul new. Ele sunt create într-o zonă specială de memorie denumită heap. Operatorul new crează un obiect în memorie apelând constructorul şi furnizează adresa obiectului nou creat. cout << "f(" << a << ")= " << d. // apeleaza functiile direct d. pl = &d.}. unde ptr este variabila tip pointer cu adresa obiectului. // defineste un pointer la obiect Line * pl. int main() { Line d(1. în afara oricărei funcţii. cout << "f(" << a << ")= " << pl->value(a) << endl. declarate în funcţii sau în blocuri din funcţii." m = " << m << " n = " << n << endl. între { şi } Ele sunt create la intrarea în bloc şi distruse la ieşirea din bloc. Vom considera aceeaşi clasă Line. Ele sunt create la începutul execuţiei programului şi distruse la sfârşitul execuţiei.5.value(a) << endl. n = y. Vom apela funcţiile clasei Line direct şi prin pointer. }. Există trei moduri de a crea obiecte: • obiecte globale declarate la nivelul programului. tip este numele unei clase.

" << " m = " << m << " n = " << n << endl. 0) .3. n(b) {}. -1. return 0. După definirea vectorului. double b) : m(a). crearea vectorului cu două obiecte de tip Line se face astfel Line vx[2] . } Exemplu. n(0) {}. Vom crea vectori cu două componente.2) .cu operatorul new şi vom calcula f(1. este apelat de către sistemul de operare constructorul fără parametri Line(). }. obiecte tip Line şi vom afişa pe ecran parametrii obiectelor create. Operatorul new are ca rezultat un pointer de tipul variabilei create. 0). Line(double a. int main() { // defineste un pointer la obiect Line * pl. Funcţia main() corespunzătoare este cea de mai jos. de exemplu Line vx[1] = Line(2. void Line::print() { cout << "functia f(x) = m * x + n. double a = 1.5). clasa trebuie să aibă un constructor fără parametri. // apeleaza functiile prin pointer pl->print(). Pentru aceasta.5. public: void print(). } In programul de mai sus puteam scrie Line *pl = new Line(1. // distruge obiectul delete pl. class Line { private: double m. Fie. // creaza un obiect cu operatorul new pl = new Line (1. # include <iostream> using namespace std. 157 . n. Putem crea vectori de obiecte. In prima variantă. cout << "f(" << a << ")= " << pl->value(a) << endl. Line() : m(0). putem iniţializa elementele sale cu obiecte definite de constructorul cu parametri. La crearea acestui vector. din nou. definiţia clasei Line cu un constructor implicit.

Programul este cel de mai jos. alocată cu operatorul new.print(). toate componentele sale sunt iniţializate cu constructorul fără parametri.print(). vx[1]. şi Line * pvx = new Line[2] . In consecinţă. vect = new Line[2]. După cum am spus într-un capitol anterior. // afisaza obiectele int i. i++) { pvx[i]. for(i = 0. } Rezultatul rulării programului este cel de mai jos La crearea vectorului. // (*(pvx + i)). este inutilă. int main() { Line vx[2]. 2). // vx[0] = Line(). Programul anterior este acum următorul. // afisaza obiectele vx[0]. pvx[1] = Line(-2. rescriem programul de mai sus astfel. sunt echivalente. putem crea un vector cu două obiecte de tip Line astfel Line * vect. instrucţiunile : Line vx[2] . } delete [] pvx.2). return EXIT_SUCCESS.print(). int main() { // vectori de obiecte Line * pvx = new Line[2]. De exemplu. -1. Definim o variabilă pointer de tip Line şi o iniţializăm cu adresa unei zone de memorie pentru două obiecte. In a doua variantă.print() .3. vx[1] = Line(2. pvx[0] = Line(-1. // (pvx + i)->print(). i < 2. instrucţiunea vx[0] = Line() . 158 . 1).

este echivalentă cu expresia (pvx + i) -> print(). echivalent pv[0]->print(). obiectele au fost afişate cu instrucţiunea for. S-au arătat forme echivalente ale apelării funcţiei print(). Vom rezolva problema de mai sus cu vectori de pointeri.print(). i < 2. Pentru început definim un vector de pointeri. 1) .print(). } Ca exerciţiu. Apelarea funcţiei print() se face conform definiţiei de mai sus. Line * pv[2]. memoria alocată cu operatorul new este ştearsă cu instrucţiunea delete [] pvx. adresa primului obiect. 159 . // pv[i]->print(). // scrie obiectele for(i = 0. 2). sau. expresia (*(pvx + i)).print(). Componentele vectorului pv sunt iniţializate cu adresele unor obiecte create cu operatorul new pv[0] = new Line(-1. 1). este *pv[0]. Să se explice de ce expresia *(pvx + i) este scrisă în paranteze în instrucţiunea (*(pvx + i)). pv[1] = new Line(-2.return EXIT_SUCCESS. } return 0. pv[0] = new Line(-1.print(). indicat de componentul pv[0] al vectorului pv. De exemplu. Exemplu. } Rezultatul rulării programului este cel de mai jos. i++) { (*pv[i]). pv[1] = new Line(-2. Conform celor spuse mai sus. Conform celor spuse într-un capitol anterior. 2) . expresiile pvx[i] şi *(pvx + i) sunt echivalente. Programul este următorul int main() { int i. deci funcţia print() se apelează (*pv[0]). pv[2] de tipul Line : Line * pv[2] .

Definiţia clasei Baza este următoarea # include <iostream> # include <cmath> using namespace std.} }. Exemplu. In primele exemple. funcţii şi variabile. 160 ." << b << ")" << endl. Clasa care este moştenită se numeşte clasă de bază sau superclasă. public: Baza(double x. putem construi programe complexe din obiecte simple.2.1 Definirea unei clase derivate class nume_clasa_derivata : acces nume_clasa_baza { // definitia clasei derivate }. double y) {a = x. b. double b. un constructor cu parametri ce iniţializează variabilele a şi b şi o funcţie print() ce afişază valorile variabilelor a şi b sub forma (a. Clasa de bază conţine caracteristicile comune ale mai multor elemente. b = y. class Baza { protected: double a. Clasele care moştenesc sunt clase particulare ce adaugă doar elementele proprii. Vom defini o clasă numită Baza ce defineşte două variabile tip double a şi b. Cu ajutorul moştenirii reutilizăm clase deja construite.} void print() {cout << "(" << a << ". vom utiliza acces public. Baza (double. 10.10. Baza double a. protected sau private. double). Clasa derivată moşteneşte membrii clasei de bază. sunt membri publici şi în clasa derivată. Moştenirea permite să creăm clase care sunt derivate din alte clase existente. Forma definiţiei unei clase derivate este In această definiţie. In acest fel. void print(). funcţii şi variabile. membrii publici ai clasei de bază. iar clasele care moştenesc se numesc clase derivate sau subclase.2 Moştenirea Moştenirea este un concept al programării cu obiecte. care are semnificaţia că. b). cuvântul cheie acces poate fi public.

Clasa Triunghi defineşte o funcţie aria() ce calculează aria triunghiului. ce descriu triunghiuri şi respectiv numere complexe. Clasa Complex defineşte o funcţie abs() ce calculează modulul numărului complex. void print() .} void print(){cout << "(baza. } Triunghi(double x. Triunghi double aria() . Complex double abs() . class Triunghi : public Baza { public: double aria() {return a * b / 2. void print() . Baza double a. void print(). Apelarea funcţiei print() din clasa de bază se face cu operatorul de rezoluţie :: Baza ::print() . a va fi baza. Baza (double. y) {} }. Semnificaţia lui este aceea că variabilele a şi b pot fi utilizate în clasele derivate. Baza::print(). double) . o funcţie print() ce afişază valorile bazei şi înălţimii şi un constructor cu parametri. a şi b vor fi partea reală şi partea imaginară a numărului complex. Funcţia print() a clasei Triunghi apelează funcţia print() a clasei de bază pentru a scrie valorile a şi b. Triunghi(double. double b. conform diagramei de mai jos. double y) : Baza(x. Vom defini două clase. o funcţie print() ce afişază numărul complex şi un constructor cu parametri. double) . inaltimea) = ". ce moştenesc clasa Baza.Variabilele clasei Baza au specificatorul de acces protected. Constructorul clasei Triunghi apelează constructorul clasei Baza pentru a iniţializa variabilele a şi b. double). In clasa Complex. Definiţia acestei clase este cea de mai jos. class Complex : public Baza { 161 . In clasa Triunghi. Complex(double. iar b înălţimea. deoarece simpla apelare cu instrucţiune print() ar fi însemnat apelare recursivă a funcţiei print() din clasa Triunghi.

Implicit. Pentru a utiliza clasele derivate de mai sus.print().public: double abs() {return sqrt(a * a + b * b). y) {} }. Reamintim că am definit variabilele clasei de bază h şi b de tipul protected. int main() { Triunghi trg(2. dar nu pot fi utilizate de obiecte. Constructorul clasei derivate apelează totdeauna constructorul clasei de bază.aria() << endl. cout << "|(u. el este apelat în lista de iniţializatori.print(). 162 . Pentru a scrie numărul complex.} Complex(double x. return 0. Datele şi funcţiile declarate protected în clasa de bază pot fi utilizate în clasele derivate. } Rezultate afişate sunt cele de mai jos. Din acest motiv.a << endl. pentru a afişa valorile variabilelor a şi b. Baza::print(). double y) : Baza(x. nu putem scrie în funcţia main() instrucţiunea cout << trg. Constructorul clasei Complex apelează constructorul clasei Baza pentru a iniţializa variabilele a şi b. trg. 1).abs() << endl. • menbrii protejaţi ai clasei pot fi utilizaţi şi în clasele derivate. se apelează constructorul fără parametri. v) = ". Dacă vrem să apelăm alt constructor.2 Specificatorii de acces Vom prezenta acum semnificaţia cuvântului cheie acces din definiţia clasei derivate. creăm un triunghi şi îi calculăm aria. c.} void print() {cout << "(u. 1). specificatorii de acces sunt : public. v)| = " <<c.2. funcţia print() a clasei Complex apelează funcţia print() a clasei de bază cu operatorul de rezoluţie ca mai sus. private şi protected. De exemplu. Semnificaţia lor este următoarea : • membrii privaţi ai clasei pot fi utilizaţi doar în interiorul clasei. ca în exemplele de mai sus. am definit funcţia print(). In definiţia unei clase. Complex c(1. 10. apoi definim un număr complex şi îi calculăm modulul. Datele şi funcţiile declarate private în clasa de bază nu pot fi utilizate în clasele derivate sau de obiecte de tipul claselor derivate.5. cout << "aria = " << trg.

dar nu în obiecte de tipul clasei Baza sau clase derivate. Clasa derivată moşteneşte membrii publici şi pretejaţi ai clasei de bază. în clasele derivate şi în obiectele de tipul clasei sau al claselor derivate. clasa derivată moşteneşte toţi membrii clasei de bază cu nivelul de acces avut în clasa de bază. Putem sumariza acum specificatorul de acces al variabilelor unui obiect în funcţie de specificatorii lor în clasa de bază şi specificatorul din definiţia clasei derivate. Variabila y poate fi utilizată în clasa Baza şi in clasele derivate.membrii publici ai clasei pot fi utilizaţi şi în afara clasei. Variabila x poate fi utilizată în clasa Baza. în clasele derivate şi în obiecte de tipul clasei Baza sau clase derivate din Baza. protected: int y. • protected spune că toţi membrii publici şi protejaţi ai clasei de bază sunt moşteniţi în clasa derivată ca membrii protejaţi. Membrii publici şi protejaţi ai clasei de bază apar ca şi cum ar fi declaraţi în clasa derivată. private : int v . Fie definiţia unei clase numită Baza class Baza { public: int x. specificatorul de acces în definiţia clasei derivate public protected private specificatorul de acces al variabilei în clasa de bază public protected private public protected acces interzis protected protected acces interzis private private acces interzis • Putem sumariza acum accesul la variabilele unui obiect în funcţie de specificatorii lor. La utilizarea specificatorului de acces public. Variabila v poate fi utilizată doar în interiorul clasei Baza. Specificatorul de acces din definiţia clasei derivate dă nivelul minim de acces pentru membrii moşteniţi din clasa de bază. Considerăm acum specificatorul de acces din definiţia clasei derivate : • public spune că toţi membrii publici şi protejaţi ai clasei de bază sunt moşteniţi în clasa derivată ca membrii publici sau protejaţi. specificatorul de acces al variabilei din tabelul de mai sus public protected private da da da da da nu membri ai aceleiaşi clase membri claselor derivate 163 . • private spune că toţi membrii publici şi protejaţi ai clasei de bază sunt moşteniţi în clasa derivată ca membrii privaţi. }.

dar numai variabila x poate fi utilizată de obiectele de tipul Deriv. }. cu tipul lor. Variabilele obiectului. In tabelul de mai jos se arată variabilele obiectului b. Considerăm clasa derivată din Baza definită cu specificatorul de acces protected class DerivProtected : protected Baza { /* declaratii din clasa Deriv */ public: int w.x. cout << b. Fie o clasă derivată din Baza cu specificatorul de acces public class DerivPublic : public Baza { /* declaratii din clasa Deriv */ public: int w. In acest caz variabilele x şi y au în clasa DerivPublic specificatorul de acces definit în clasa Baza. Variabila y nu poate fi utilizată de obiecte de tipul DerivPublic deoarece este declarată protected în clasa de bază.nemembri da nu nu Vom defini mai jos trei clase derivate din Baza cu specificatorii de acces public. In acest caz variabilele x şi y au în clasa DerivProtected specificatorul de acces protected. respectiv public pentru x şi protected pentru y. Considerăm acum clasa derivată din Baza definită cu specificatorul de acces private class DerivPrivate : private Baza { /* declaratii din clasa Deriv */ 164 . De exemplu. cu tipurile lor. în funcţia main() putem avea instrucţiunile int main() { DerivPublic b. protected şi private şi vom analiza specificatorii de acces în aceste clase ai variabilelor x şi y definite în clasa Baza..y. dar nu pot fi utilizată de obiecte de tipul DerivProtected. deoarece doar variabilele cu specificatorul de acces public pot fi utilizate de obiectele de tipul clasei. sunt cele din tabelul de mai jos. Ele pot fi utilizate de funcţiile definite în clasa DerivProtected. Fie obiectul DerivProtected c . }. } dar nu putem avea instrucţiunea cout << b. Ele pot fi utilizate de funcţiile definite în clasa Deriv.

putem apela explicit un constructor al clasei de bază pentru iniţializarea variabilelor. x. care are forma constructor_clasa_derivata (lista de parametri) : constructor_clasa_de_baza (lista de parametri) { /* definitia constructorului clasei derivate */ } Menţionăm că. se apelează destructorul clasei şi apoi destructorul clasei de bază. 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. constructorul implicit şi destructorul clasei de bază sunt întotdeauna apelaţi când un obiect de tipul clasei derivate este creat sau distrus. }.public: int w. exceptând constructorii. DerivProtected d. O clasă derivată moşteneşte membri clasei de bază. y : private DerivProtected c. Clasa Point va avea două câmpuri de tip întreg. ce sunt coordonatele punctului pe ecran. la distrugerea unui obiect de tipul unei clase derivate. In constructorul clasei derivate se apelează la început constructorul implicit al clasei de bază. cu tipurile lor. x şi y. doi constructori. sunt cele din tabelul de mai jos. o funcţie clear() ce pune la valoarea zero cele două coordinate şi o funcţie print() ce scrie pe ecran coordonatele punctului. y : protected w : public x. destructorul şi operatorul =. In acest caz toate variabilele x si y din clasa Baza au în clasa DerivPrivate specificatorul de acces private. Definiţia clasei Point este cea de mai jos 165 . w :public y : protected w : public x. DerivPublic b. Deşi constructorii şi destructorii nu sunt moşteniţi. Apelarea sa se face în lista de iniţializatori. Dacă este nevoie. Variabilele obiectului. Fie obiectul DerivPrivate d . ţinând cont de specificatorii de acces.

} void Point::print() { cout << “ x = “ << x << “. Culoarea va fi un câmp de tip întreg. int p2) { x = p1. y = 0. 166 . Point(){x = 0. } Definim acum o clasă Pixel care să descrie un pixel pe ecran. Clasa Pixel va defini funcţia clear() ce va pune la valoarea zero coordonatele şi culoarea obiectului tip Pixel şi funcţia print() ce va scrie pe ecran coordonatele şi culoarea pixelului. y = p2. } Point::Point(int p1. Point(int.” << “ y = “ << y << endl. int). void clear(). y.} void print(). y = 0.class Point { public: int x. Clasa Pixel va moşteni coordonatele punctului din clasa Point. void Point::clear() { x = 0. Un pixel este caracterizat de coordonatele sale şi de culoare. }.

color = 0. } In constructorul clasei derivate de mai sus am apelat constructorul clasei de bază în secţiunea : Pixel :: Pixel(int a. void clear(). Pixel::Pixel(int a. care va iniţializa coordonatele pixelului. void print(). int c) : Point(a. Pixel(int. b) void Pixel::clear() { Point::clear(). int b. cout << “culoarea : “ << color << endl. In definiţia constructorului clasei Pixel vom apela constructorul cu parametri al clasei Point. int c) : Point(a. int). int b.class Pixel : public Point { public: int color. } 167 . }. } void Pixel::print() { Point::print(). int. b) { color = c.

// afisaza coordonatele si culoarea utilizand un pointer Pixel * ptrx = & p. direct de către obiect.print(). direct de către obiect. return 0. color = 0. Vom crea un obiect pt tip Point şi vom afişa coordonatele lui. ptrp->print(). p.3 Funcţii virtuale. cout << "coordonatele unui punct" << endl. 200). // creaza un obiect de tipul Pixel si afisaza coordonatele si culoarea Pixel p(1. şi printr-o variabilă tip pointer. pt. 2. numele şi departamentul în cazul tuturor 168 . } deoarece ar fi însemnat o apelare recursivă a funcţiei clear(). cout << "coordonatele si culoarea unui pixel" << endl. 10. Vom crea apoi un obiect p tip Pixel şi vom afişa coordonatele şi culoarea apelând funcţia print(). // afisaza coordonatele punctului utilizand un pointer Point * ptrp = &pt.print(). şi printr-o variabilă tip pointer. apelând funcţia print(). } Rezultatul programului este cel de mai jos. 15).Menţionăm că nu puteam defini funcţia clear() a clasei Pixel astfel void Pixel::clear() { clear(). ptrx->print(). Un exemplu de utilizare a claselor Point şi Pixel este prezentat mai jos. Polimorfism Considerăm următorul exemplu. Vrem să definim o listă care să conţină informaţii despre angajaţii unei intreprinderi. int main() { // creaza un obiect de tipul Point si afisaza coordonatele lui Point pt(124.

Definiţia clasei Angajat este următoarea : class Angajat { protected: string nume. una pentru angajaţii simpli. Aceste informaţii vor fi şiruri de caractere. string dept. Vom defini apoi o clasă numită Manager. 169 . • o funcţie print() ce afişază variabilele nume. void print() {cout << "Nume : " << nume << "\n".angajaţilor. şi o clasă derivată ce va defini în plus un câmp pentru poziţia managerului. ce va defini câmpuri cu numele angajatului şi departamentul şi altă clasă pentru manageri ce va defini câmpuri cu numele angajatului. Reprezentarea acestor clase este cea de mai jos. • un constructor cu doi parametri pentru nume şi departament. } }. dept(d) {} . • un constructor cu trei parametri pentru nume. nume şi dept. ce moşteneşte clasa Angajat. cu numele persoanei şi al departamentului. departament şi poziţie. iar în cazul managerilor şi poziţia. public: Angajat(string s. este de a defini o clasă de bază ce conţine câmpuri cu numele angajatului şi departamentul. Vom defini o clasă de bază numită Angajat. dept şi poziţie. • o funcţie print() ce afişază aceste variabile. cout << "Departament : " << dept << "\n". string d) : nume(s). O soluţie este aceea de a defini două clase diferite. departamentul şi poziţia. ce conţine : • variabilă tip string pozitie cu poziţia managerului. ce conţine : • două variabile tip string. O altă soluţie. Un astfel de program este complicat deoarece trebuie să prelucreze separat cele două tipuri de obiecte. aplicată în continuare.

string p) : Angajat(s. void print() {Angajat::print(). şi într-o instrucţiune for apelăm funcţia print() a obiectelor for(int i = 0. Angajat * vect[2]. "proiectare". // apeleaza functia print() a obiectelor din vector for(int i = 0. vect[1] = &m1. i < 2. Manager m1("George". Presupunem că vrem să afişăm numele şi departamentul pentru toţi angajaţii şi. "proiectare". i < 2. string d. O soluţie este de a crea doi vectori cu pointeri. i++) vect[i]->print(). pozitie(p) {}.Definiţia clasei Manager este următoarea. vect[1] = &m1. unul de tipul Angajat şi altul de tip Manager cu adresele obiectelor respective şi de a le prelucra separat. public: Manager(string s. Această soluţie duce la programe complexe. Limbajul oferă următoarea posibilitate. Fie următoarea funcţie main() pentru rezolvarea problemei (necorectă). Manager m1("George". "sef"). "sef"). // memoreaza adresele obiectelor in vector vect[0] = &x1. Putem deci memora adresele obiectelor de tip Angajat şi Manager într-un vector cu pointeri de tip Angajat şi să apelăm funcţia print() a fiecărui obiect într-un ciclu. altul de tipul Manager şi un vector de doi pointeri de tipul Angajat Angajat x1("Alex". cout << " Pozitie : " << pozitie << "\n". Definim două obiecte. } Rezultatele sunt următoarele 170 . "proiectare"). Un pointer de tipul clasei de bază conţine adresa unui obiect de tipul clasei de bază sau de tipul unei clase derivate. în plus pentru manageri. class Manager : public Angajat { protected: string pozitie. Memorăm adresele celor două obiecte în componentele vectorului vect[0] = &x1. unul de tipul Angajat. "proiectare"). } }. Angajat * vect[2]. Programul este cel de mai jos int main() { Angajat x1("Alex". poziţia. i++) vect[i]->print(). return 0. d).

O funcţie virtuală are acelaşi nume în clasa de bază şi în toate clasele ce o moştenesc. } }. d) { 171 . # include <iostream> # include <string> using namespace std. } virtual void print() { cout << "nume : " << nume << "\n". nu se afişază poziţia. Problema acestei soluţii este următoarea. Funcţia apelată este determinată de tipul variabilei pointer. în cazul celui de-al doilea angajat. Vom defini funcţia print() din exemplul anterior virtuală şi în acest caz funcţia apelată este determinată de tipul obiectului. string p) : Angajat(s. în cazul obiectelor de tipul Manager. class Manager : public Angajat { protected: string pozitie. public: Manager(string s. deşi.Remarcăm că. string d. ar trebui să se apeleze funcţia clasei Manager. cout << " departament : " << dept << "\n". Putem face ca funcţia apelată să fie determinată de tipul obiectului şi nu de tipul variabilei pointer definind funcţia respectivă virtuală. dept = d. O funcţie este definită ca virtuală scriind cuvântul cheie virtual în faţa definiţiei funcţiei. Angajat. nu de tipul obiectului. public: Angajat(string s. In consecinţă. string dept. Programul complet este următorul. string d) { nume = s. class Angajat { protected: string nume. este apelată totdeauna funcţia print() a clasei de bază.

} }. cout << " pozitie : " << pozitie << "\n". Manager m1("Bob". deoarece se modifică după natura obiectului indicat de pointer. // apeleaza functia print() a obiectelor din vector for(int i = 0. Indicaţie. Rezultatul rulării programului este cel de mai jos. int main() { Angajat x1("Alex". este polimorfic. // memoreaza adresele obiectelor in vector vect[0] = &x1. “proiectare”). "proiectare". apelează funcţia print() din clasa corespunzătoare tipului obiectului. vect[1] = &m1. "proiectare"). astfel încât apelul vect[i]->print(). Menţionăm de asemenea că instrucţiunea Angajat x1(“Alex”. Orice clasă ce defineşte sau moşteneşte funcţii virtuale se numeşte polimorfă. producând rezultatul corect. Angajat * vect[2]. } Reamintim că funcţia print() este virtuală. "sef"). i++) vect[i]->print(). Să se înlocuiască şirurile de tip string din clasele Angajat şi Manager cu şiruri tip C.pozitie = p. Exerciţiu. } virtual void print() { Angajat::print(). este corectă deoarece constructorul copiere al clasei string are ca argument un şir de tip C sau de tip C++. i < 2. Definiţia clasei Angajat poate fi următoarea class Angajat { protected: 172 . return 0. Vom spune că apelul de funcţie vect[i]->print().

Definiţia constructorului Angajat poate fi următoarea Angajat::Angajat(char * s. Definiţiile funcţiilor sunt cele anterioare. char *). dept = new char[strlen(d) + 1]. funcţia clear().} }. } 173 . d). Point(){x = 0. y = 0. int p2) { x = p1. Noua definiţie a clasei Point este următoarea class Point { private: int x.” << “ y= “ << y << endl. char * dept. y = 0. void Point::clear() { x = 0. int). char * d) { nume = new char[strlen(s) + 1]. virtual void print(). y = p2. y. } Exemplu. şi funcţia print(). }. public: Angajat(char *. ce pune la valoarea zero variabilele. public: virtual void clear(). virtual void print(). vor fi declarate virtuale. Point(int. Vom redefini clasele Point şi Pixel astfel ca. strcpy(dept. Fie clasele Point şi Pixel definite anterior. Ele vor fi repetate aici. ce afişază valoarea variabilelor. s). } void Point::print() { cout << “x = “ << x << “.char * nume. strcpy(nume. } Point::Point(int p1.

// atribuie variabilei ptr adresa obiectului px de tip Pixel 174 . 15). b) { color = c.Clasa Pixel moşteneşte clasa Point. } void Pixel::print() { Point::print(). 2. Pixel::Pixel(int a. // afisaza coordonatele punctului cout << "coordonatele unui punct" << endl. int. Noua definiţie a clasei Pixel este următoarea class Pixel : public Point { private: int color. } In funcţia main() vom crea un obiect p de tip Point şi apoi un obiect px de tipul Pixel. public: virtual void print(). // creaza un obiect de tipul Pixel Pixel px(1. Constructorul ei apelează constructorul cu parametri al clasei Point. color = 0. int c) : Point(a. iar funcţiile clear() şi print() apelează funcţiile cu acelaşi nume din clasa Point. Vom scrie datele din aceste obiecte apelând funcţia print() printr-un pointer la clasa de bază. int main() { Point * ptr. 7). cout << “ culoare = “ << color << endl. int b. ptr->print(). virtual void clear(). // creaza un obiect de tipul Point Point p(2. int). // atribuie variabilei ptr adresa obiectului p de tip Point ptr = &p. Definiţiile funcţiilor sunt următoarele. } void Pixel::clear() { Point::clear(). Pixel(int. }.

constructorul şi desctructorul afişază un mesaj. } Rezultatul rulării programului este cel de mai jos. apelul de funcţie ptr->print(). Programul este următorul #include <iostream> using namespace std. deoarece se modifică după natura obiectului indicat de pointer. A doua instrucţiune ptr->print().4 Destructori virtuali La distrugerea unui obiect de tipul unei clase derivate este apelat destructorul clasei derivate şi apoi destructorul clasei de bază. In funcţia main() definim o variabilă pointer de tipul Baza şi îi dăm ca valoare adresa unui obiect de tipul Deriv creat cu operatorul new. return 0.} ~Baza(){cout << "Destructor Baza" << endl. apelează funcţia print() a clasei Point. după care ştergem obiectul creat cu instrucţiunea delete Baza * var = new Deriv().} }. ptr->print().ptr = &px. Prima instrucţiune ptr->print(). delete var. 175 . Fie programul de mai jos în care definim o clasă de bază numită Baza şi o clasă derivată numită Deriv. 10. apelează funcţia print() a clasei Pixel. // afisaza coordonatele si culoarea pixelului cout << "coordonatele si culoarea unui pixel" << endl. In ambele clase. este polimorfic. In consecinţă. class Baza { public: Baza(){cout << "Constructor Baza" << endl.

ca mai jos class Baza { public: Baza(){cout << "Constructor Baza" << endl. care este Baza.} }. char *argv[]) { Baza * var = new Deriv(). return 0. ce apelează şi destructorul clasei de bază. deoarece. şi nu de tipul obiectului memorat.} ~Deriv(){cout << "Destructor Deriv" << endl. funcţia ce se apelează este dată de tipul variabilei. este apelat destructorul clasei Baza şi nu al clasei Deriv. Rezultatul rulării programului este acum următorul Acum este apelat destructorul clasei derivate. } Rezultatul rulării programului este următorul După cum se observă. Pentru a apela destructorul clasei Deriv trebuie să declarăm destructorul clasei de bază de tip virtual. delete var.} virtual ~Baza(){cout << "Destructor Baza" << endl.} }.class Deriv : public Baza { public: Deriv(){cout << "Costructor Deriv" << endl. în cazul funcţiilor ce nu sunt virtuale. int main(int argc. 176 .

O aplicaţie a datelor statice este aceea de a număra obiectele existente de tipul clasei. dacă există obiecte de tipul clasei. }. int A::x = 0. X(){nb ++. chiar dacă nu există nici un obiect de tipul clasei. class A { public: static int x. { X c. Datele statice sunt la fel ca şi variabilele globale. b. pentru toate obiectele. Constructorul adună o unitate. sau A. int main() { X a.x Datele statice sunt iniţializate automat la zero. Acest lucru se poate realiza simplu. class X { public: static int nb.1 Date statice Obiectele de tipul unei clase au propriile variabile ce conţin valori ce dau starea fiecărui obiect. este necesar să existe o variabilă a clasei. Definirea se face scriind cuvântul cheie static la începutul declaraţiei variabilei.} }. Exemplu. # include <iostream> using namespace std. In funcţia main() vom crea obiecte şi vom afişa numărul lor. ca nume_obiect. Exemplu. definind acea variabilă de tip static.10. Valoarea iniţială a variabilei statice este zero.x sau. Pentru exemplul clasei definite mai sus. d. iar destructorul scade o unitate din variabila statică. 177 . Datele statice există.5 Date şi funcţii statice 10. dar în spaţiul de nume al clasei. cout << “exista “ << X::nb << “ obiecte” << endl. Uneori. int X::nb = 0. putem utiliza variabila statică x ca A::x. într-un singur exemplar. Datele de tip static se iniţializează în afara clasei. Reamintim că variabilele statice se iniţializează în afara clasei. Definiţia clasei este următoarea.5. Vom defini o clasă cu o variabilă statică ce va număra obiectele existente de tipul clasei.} ~X(){nb--.

ce crează un obiect de tipul Deriv1 sau Deriv2. După instrucţiunea X c. # include <iostream> using namespace std. Un exemplu de utilizare a funcţiilor statice este tehnica de programare ClassFactory. In ambele cazuri metoda se va numi oper(). 178 . După ieşirea din blocul interior există două obiecte. class Base { protected: float a. constructorii corespunzători şi o funcţie virtuală oper() ce nu efectuează nici un calcul. c şi d. Funcţiile statice pot fi apelate utilizând numele clasei sau al unui obiect de tipul clasei. Funcţiile statice sunt la fel ca şi funcţiile globale. obiectele c şi d fiind distruse la ieşirea din bloc.cout << “exista “ << X::nb << “ obiecte” << endl. a. b. Ea constă în următoarele. b. Definirea unei funcţii statice se face scriind cuvântul cheie static înaintea definiţiei funcţiei. } Rezultate afişate vor fi După instrucţiunea X a. Vrem ca în funcţie de valoarea unui parametru să obţinem un obiect care să facă produsul sau suma a două numere. există patru obiecte. d. 10.nb sau X::nb. Fie o clasă de bază numită Base şi două clase derivate din ea.5.2 Funcţii statice O clasă poate defini funcţii statice. b. } cout << “exista “ << X::nb << “ obiecte” << endl. există două obiecte. Exemplu. în funcţie de valoarea parametrului şi furnizează adresa obiectului creat ca pointer de tipul clasei de bază Base. Vrem să definim următoarele două clase : • o clasă cu o metodă ce calculează suma a două numere. • o clasă cu o metodă ce calculează produsul a două numere. putem adresa variabila nb ca X. dar în spaţiul de nume al clasei. 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. a şi b. Menţionăm că. Ele pot prelucra doar datele statice ale clasei. return 0. Acest lucru se face definind o clasă cu o funcţie statică. Deriv1 şi Deriv2. Vom defini o clasă de bază ce defineşte două variabile a şi b.

else return new Mul(x. y) {} virtual float oper() {return a * b. Implementarea funcţiei operfactory este următoarea Base * ClassFactory::operfactory(float x. float y. virtual float oper(){return 0. b = y. } Clasele derivate definesc funcţia virtuală oper() ce efectuează adunarea. float y) { a = x.} }. 179 . b = 0.} }. class Mul : public Base { public: Mul(float x. char c) { if(c == '+') return new Add(x. float). y) {} virtual float oper(){return a + b. respectiv produsul a două numere. In cazul clasei Add.} }. în cazul clasei Mul.public: Base() {a = 0. class ClassFactory { public: static Base * operfactory(float. char). Vom defini o clasă cu o metodă statică numită operfactory 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) cu adresa obiectului creat. }. }. float y) : Base(x. y). y).} Base(float. Ele au următoarele definiţii class Add : public Base { public: Add(float x. float y) : Base(x. float. Base::Base(float x.

35. return 0. } Rezultatul rulării programului este cel de mai jos. ClassFactory cf. cout << z << endl.Presupunem că vrem să calculăm suma a două numere. // calculeaza produsul numerelor z = base->oper(). '*'). // creaza un obiect care sa calculeze suma a doua numere base = cf. Apoi utilizează metoda oper() a obiectului la calculul sumei a două numere. cout << "suma numerelor " << x << " si " << y << endl. cout << z << endl. y. y = -2. In funcţia main() se crează un obiect de tipul Add apelând funcţia operfactory şi se memorează adresa obiectului într-un pointer de tipul clasei de bază.2. 180 . y. int main() { float z. '+'). Base * base. // creaza un obiect care sa calculeze produsul a doua numere base = cf. cout << "produsul numerelor " << x << " si " << y << endl. float x = 1. // calculeaza suma numerelor z = base->oper().operfactory(x.operfactory(x.

O serie de constante. In afară de aceste două obiecte. Definiţiile acestor clase se găsesc în bibliotecile <ifstream>. Funcţiile membre importante ale claselor sunt: • constructor fără parametri. Pentru lucrul cu fişiere de date limbajul C++ defineşte următoarele clase: • ofstream pentru operaţii de scriere de fişiere. iar obiectul cout o instanţă a clasei ostream. Parametrul filename este un şir de caractere cu numele fişierului. instanţă a uneia din clasele de mai sus. asociază obiectul creat cu fişierul ce va fi prelucrat. 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. mai sunt predefinite: obiectul cerr. (un stream). de tipul ostream. • ifstream pentru operaţii de citire din fişiere. Obiectul cin este o instanţă a clasei istream. Clasele istream şi ostream sunt definite în biblioteca <iostream>. utilizate de aceste clase. int mode). Diagrama moştenirii acestor clase este cea de mai jos. Aceste clase moştenesc clasele istream şi ostream.11 Fişiere tip C++ Limbajul C++ defineşte clasele istream şi ostream pentru operaţii intrare şi ieşire cu fişierele standard. • constructor cu parametrii funcţiei open().deschidere în citire ios::out – deschidere în scriere ios::binary – fişier binary Aceşti parametri se pot combina folosind operatorul | . Pentru prelucrarea unui fişier se crează un obiect. • fstream pentru operaţii de citire şi scriere a fişierelor. De exemplu. tastatura şi ecranul. <ofstream> şi <fstream>. pentru un fişier binar deschis în scriere. sunt definite în clasa ios. De obicei. Acest constructor crează un obiect şi apoi deschide fişierul. • funcţia membră open() cu prototipul open(char * filename. aceste două fişiere sunt tot ecranul. de tipul ostream. • funcţia void close(). care este fişierul de ieşire standard pentru erori şi obiectul clog. El poate avea valorile : ios::in . 181 . Al doilea parametru este opţional şi dă modul de deschidere. pentru înregistrarea diverselor mesaje.

1 Fişiere text Fişierele text sunt compuse din linii. Vom calcula valoarea expresiei e= cos 2 ( x) + x + sin( x) 1+ x pentru x cuprins în intervalul [0. return EXIT_FAILURE. # include <fstream> # include <iostream> # include <cmath> using namespace std. are valoarea adevărat dacă s-a detectat sfârşitul fişierului. • funcţia bool eof(). 11. i++) { // calculeaza expresia si scrie in fisier 182 . cin şi cout. Exemplu.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. if(!fil1. } // creaza fisierul double x. Vom scrie valorile calculate într-un fişier text cu numele rez.1. (din fişierul cin). i < 11.is_open()) { cout << “ Nu se poate crea fisierul “ << filename << endl. • funcţia bool is_open().închide un fişier. ofstream fil1. sfârşitul de fişier este indicat prin Ctrl+Z. 2] cu pasul 0. 11. care sunt şiruri formate din 0 sau mai multe caractere. for(int i = 0.txt. e. Operatorii de citire şi scriere sunt >> şi <<. Programul este următorul. După aceasta se apasă tasta return. Apoi. separate de caracterul ‘\n’. int main() { char * filename = “rez.open(filename).txt”. separate de caracterul ‘\t’. are valoarea adevărat dacă fişierul este deschis. 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. fil1. In fiecare linie vom scrie o pereche de valori x şi e. vom citi fişierul creat şi vom afişa rezulatate pe ecran.2. In cazul citirii datelor de la tastatură.

2. e = (cos(x) * cos(x) + x) / (1.2 Funcţii intrare / ieşire tip caracter Fişierele text sunt formate din linii ce conţin zero sau mai multe caractere.1. } fil2. } fil1. fil1 << x << ‘\t’ << e << endl.x = i * 0. if(!fil2. } Rezultatul rulării programului este cel de mai jos.open(filename. Pentru prelucrarea acestor fişiere sunt definite funcţii ce citesc sau scriu caractere şi linii. 11.is_open()) { cout << “ nu se poate deschide fisierul “ << filename << endl.close(). return EXIT_FAILURE. } // scrie antetul cout << "x" << '\t' << "e" << endl. fil2 >> x >> e. return EXIT_SUCCESS. while(!fil2.open(filename). 183 .open(filename. şi apoi în citire pentru afişarea rezultatelor cu instrucţiunea fil. fil2 >> x >> e. ios::in).close(). Exerciţiu. // citeste fisierul creat si afisaza rezulatele ifstream fil2. fil2. ios::out). adică şiruri de caractere terminate cu caracterul ‘\n’.eof()) { cout << x << ‘\t‘ << e << endl. Se va deschide la început streamul în scriere pentru crearea fişierului cu instrucţiunea fil. Să se rescrie programul de mai sus definind un singur obiect de tipul fstream fstream fil. urmate de caracterul ‘\n’.0 + fabs(x)) + sin(x).

Exemplu. ce scrie un caracter într-un fişier.get() . Vom copia un fişier în altul. Acelaşi lucru este valabil pentru operatorul << şi funcţia put(). 184 . Instrucţiunile c = fis. return EXIT_FAILURE.get(c) >> x. Ţinând cont de prototipul funcţiei get() ifstream& get(char&). rezultatul este o referinţă la un obiect tip ifstream. Reamintim că operatorul >> este un operator binar. este posibil să citim un caracter şi un întreg char c. ifstream& get(char&). ifstream fila(filename). unde fis este un obiect tip fstream. int x. operandul drept este o variabilă. Exemplu. Operandul stâng este un obiect tip ifstream. clasele istream şi ostream. Reamintim că. deoarece operandul stâng este o referinţă tip ifstream. } cout << endl << “Introduceti numele noului fisier” << endl. citesc un caracter dintr-un fişier. # include <iostream> # include <fstream> using namespace std. int main() { char filename [24]. Clasa ofstream are funcţia membră ofstream& put(char). if(!fila.is_open()) { cout << endl << “Fisierul “ << filename << “ nu exista “ << endl. definesc aceleaşi funcţii. şi fis.Fişierele text au indicator de poziţionare ce conţine numărul următorului caracter de scris sau de citit. Fie fis un obiect tip ifstream şi c o variabilă tip caracter. cin >> filename. citesc un caracter din streamul fis în variabila c. Acest indicator are valoarea zero la deschiderea fişierului şi apoi este modificat de instrucţiunile de citire sau scriere a fişierului. cout << “Introduceti numele fisierului de copiat” << endl. citind câte un caracter din primul fişier şi copiindu-l în al doilea şi vom calcula lungimea fişierului. rezultatul evaluării funcţiei get(char&). Funcţiile : int get(). Clasa ifstream are următoarele funcţii membre pentru citirea caracterelor.get(c) . cu instrucţiunea fis.

close(). main. } char car. nl++.cin >> filename. cout << "fisierul " << filename << “ are ” << nl << " caractere" << endl. Valoarea implicită a caracterului ch este ‘\n’. Citirea se opreşte după citirea a size-1 caractere sau la întâlnirea caracterului ch. return EXIT_SUCCESS.get(car) are valoarea fals la întâlnirea sfârşitului de fişier.close().eof()) { filb. puteam copia fişierul astfel while(fila. int size. citeşte cel mult size – 1 caractere în vectorul bloc. nl++. char ch). } Rezultatul rulării programului este cel de mai jos. filb. fila. fila.txt.is_open()) { cout << “Fisierul “ << filename << “ nu se poate crea” << endl. In acest exemplu fişierul sursă. } Pentru citirea unui şir de caractere se utilizează funcţia getline() a clasei ifstream.get(car). In acest caz funcţia are forma ifstream& getline(char * bloc.put(car). int size). return EXIT_FAILURE. } fila.get(car).put(car). In consecinţă. while(!fila.cpp este copiat în fişierul main. Funcţia ifstream& getline(char * bloc. int nl = 0. Menţionăm că expresia fila. if(!filb.get(car)) { filb. 185 . ofstream filb(filename).

close(). Vom ilustra utilizarea funcţiei getline la afişarea unui fişier pe ecran. Constructorul crează obiectul şi deschide fişierul. Fişierele au indicatori interni care dau adresa următorului octet de citit sau de scris. pentru indicatorul de citire şi respectiv seekp(int offset. pentru indicatorul următorului octet de scris. read(char * block.2 Fişiere binare Scrierea şi citirea datelor din fişierele binare se face cu funcţiile: write(char * block. cout << str << endl. Funcţia citeşte caractere în obiectul str de tip string până la întâlnirea caracterului ch. } fin.is_open()) { while(!fin. 186 . ifstream fin("test. } return 0. în care caz funcţia are forma istream& getline(istream& file. Primul parametru este adresa unui vector de caractere de unde sunt scrise datele sau unde sunt citite datele.Citirea unui şir de caractere dintr-un fişier tip text într-un obiect tip string se poate face şi cu funcţia globală istream& getline(istream& file. } In acest exemplu. unde file este un obiect tip istream. char ch) .txt"). int main() { string str. pentru indicatorul următorului octet de citit şi tellp(). # include <iostream> # include <fstream> # include <string> using namespace std. str). 11. if(fin.eof()) { getline(fin. string str). Al doilea parametru dă numărul de caractere de citit sau de scris. int size). int direction). Programul citeşte câte o linie şi o scrie pe ecran. Voloarea acestor indicatori este dată de funcţiile tellg(). se crează un obiect (stream) cu constructorul clasei ifstream cu parametrii funcţiei open(). Indicatorii de poziţie sunt modificaţi de funcţiile seekg(int offset. Valoarea implicită a caracterului ch este ‘\n’. int size). string str. int direction).

} // citeste si afisaza fisierul filb. după care citim fişierul şi îl afişăm pe ecran. ios::out | ios::binary). j < 10. testând sfarşitul de fişier după fiecare citire.eof()) { 187 . 11). j++) x[j] = ‘0’ + i.write(x. 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. } fila.is_open()) { cout << “Nu se poate citi fisierul “ << filename << endl.pentru indicatorul de scriere. // deschide fisierul filb. ofstream fila. 11). ifstream filb.open(filename.open(filename. while(!filb.close(). Primul bloc va conţine cractere ‘0’. } // creaza fisierul for(int i = 0. Vom crea un fişier binar cu 10 blocuri de câte 10 caractere fiecare. al doilea bloc caractere ‘1’. Parametrul offset dă valoarea cu care se modifică indicatorul. // deschide fisierul in creare fila.read(x. # include <iostream> # include <fstream> using namespace std. etc. Citirea fişierului se face după schema cunoscută. return EXIT_FAILURE. int main() { char filename[] = “fis. if(!filb. // scrie blocul in fisier fila. if(!fila.txt”.is_open()) { cout << “ Nu se poate crea fisierul “ << filename << endl. i < 10. ios::binary | ios::in). char x[11]. return EXIT_FAILURE. x[10] = 0. i++) { // creaza un bloc for(int j = 0.

// creaza fisierul filx. filb. al doilea bloc va conţine caractere ‘b’. for(i = 0.cout << x << endl. etc. modificând indicatorul de citire. if(!filx. } Rezultatul rulării programului este cel de mai jos. ios::out | ios::binary). } filb. conţinând şiruri de 10 caractere fiecare.close(). ofstream filx. Vom crea un fişier binar format din 26 de blocuri. return EXIT_SUCCESS. // scrie blocul in fisier filx. Exemplu. return EXIT_FAILURE. } int i. int main() { char filename[] = “fil. # include <iostream> # include <fstream> using namespace std. 11).open(filename.is_open()) { cout << “ Nu se poate crea fisierul “ << filename << endl. i < 26. 11). j++) x[j] = ‘a’ + i. char x[11]. j < 10. } 188 . Vom citi apoi fiecare al patrulea bloc.write(x.txt”. i++) { // creaza un bloc for(int j = 0. Primul bloc va conţine caractere ‘a’.read(x. x[10] = 0.

close(). vom citi din acest fişier o variabilă tip string şi vom scrie valoarea ei pe ecran. cout << x << endl.filx. 11). 11. separate de un spaţiu într-un fişier tip string. ios::binary | ios::in). } Rezultatul rulării programului este cel de mai jos. în afară de fişierele standard şi cele pe disc. 189 . i = i + 4) { // modifica indicatorul fisierului fily. Clasele definesc funcţia string str(). ifstream fily.is_open()) { cout << “Nu se poate citi fisierul “ << filename << endl. } // citeste si afisaza fisierul for(i = 0. ieşire şi respective intrare / ieşire tip string. i < 26.open(filename.close(). După fiecare operaţie vom afişa pe ecran şirul de caractere din fişierul tip string. Scrierea şi citirea se fac cu operatorii << şi >>.seekg(i * 11. return EXIT_SUCCESS. Clasele definesc constructori fară parametri şi constructori cu un parametru tip string. ios::beg). In cazul acestor fişiere scrierea / citirea datelor se face în / din şiruri tip string. return EXIT_FAILURE. if(!fily. respectiv double. care furnizează o copie a şirului din obiectul stream tip i/o/stringstream. fily. } fily. Definiţiile acestor clase se găsesc în biblioteca <sstream>. şi fişiere tip string.3 Fişiere text tip string Limbajul C++ defineşte. Vom scrie valoarea unor variabile de tip int. Limbajul defineşte clasele istringstream ostringstream stringstream pentru fişiere de intrare. Exemplu.read(x. // citeste si afisaza fisierul fily.

string t.# include <string> # include <iostream> # include <sstream> using namespace std.str() << endl. 190 . // scrie valoarea sirului continut in obiectul ss cout << “ss = ” << ss. // scrie valoarea variabilei t pe ecran cout << “t = ” << t << endl.42. Menţionăm că valoarea şirului conţinut într-un fişier de tip string nu este modificată de operatorul de citire >>. ‘\t’ sau ‘\n’ întâlnit. // scrie valoarea sirului continut in obiectul ss cout << “ss = ” << ss. operatorul << citeşte caractere până primul caracter spaţiu. // scrie valoarea variabilelor x si d separate de un spatiu in obiectul ss ss << x << “ ” << d. Variabila t va primi ca valoare prin citire şirul de caractere “12 “. // creaza un obiect stringstream ce contine un sir vid stringstream ss. double d = -3. la citirea unui şir de caractere. return 0. int main() { int x = 12.str() << endl. Reamintim că. } Valorile afişate pe ecran vor fi Variabilele tip int şi float sunt convertite în şir de caractere la scrierea în obiectul ss. // citeste valoarea variabilei tip string t din obiectul ss ss >> t.

Alt tip de erori sunt cele legate de operaţiile aritmetice : împărţirea prin zero. etc.12 Tratarea excepţiilor 12. depăşirea gamei de reprezentare a numerelor. ea este lansată cu instrucţiunea throw.. Instrucţiunea catch executată este cea pentru care tipul argumentului coincide cu tipul obiectului generat de instrucţiunea throw. care generează un obiect cu informaţii despre eroare. (o excepţie). Excepţia este obiectul cu informaţii despre eroare generat de instrucţiunea throw. Eroarea este prinsă (tratată) de instrucţiunea catch care are ca parametru obiectul cu informaţii despre eroare. sau un obiect instanţă a unei clase definită de programator.1 Excepţii In timpul execuţiei programului pot apărea diverse erori. generat de instrucţiunea throw. programul să apeleze automat o funcţie de tratare a erorii. o instrucţiune try poate fi urmată de mai multe instrucţiuni catch. Această funcţie are ca parametru un obiect cu informaţii despre eroare. char. double. Ea poate fi un obiect simplu de tip int. 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 } Atunci când apare o excepţie. Intr-un bloc pot aparea mai multe tipuri de excepţii. atunci când apare o eroare. etc. In consecinţă. Limbajul C++ are un mecanism de tratare a excepţiilor ce permite ca. Instrucţiunea pe care o urmărim pentru apariţia unei excepţii trebuie inclusă într-un bloc try. programul trebuie să execute instrucţiunea throw cu forma throw obiect. Tipurile argumentelor din instrucţiunile catch trebuie să fie diferite. Dacă apare o excepţie (o eroare). catch şi throw. Un tip de erori sunt cele asociate operaţiilor intrare/ ieşire : încercăm să citim un fişier inexistent. etc. Tratarea excepţiilor se face cu instrucţiunile try. numite în continuare excepţii. indici eronaţi. Fiecare tip de excepţie este tratată de o anumită instrucţiune catch. vrem să creăm un fişier pe disc dar discul este plin. 191 .

} catch(int a) { cout << "impartire prin zero linia : " << a << endl. cout << "x = " << x << " y = " << y << " x / y = " << z << endl. Blocul catch încearcă repararea erorii apărute. sau void abort(). Instrucţiunea throw va genera un obiect de tip int ce conţine numărul liniei unde a apărut eroarea. Programul este următorul.Exemplu. z = x / y. } Rezultatul rulării programului este cel de mai jos. trebuie ca tipul obiectului lansat de instrucţiunea throw să coincidă cu tipul obiectului specificat într-o instrucţiune catch. Prototipurile acestor funcţii se găsesc în biblioteca <cstdlib>. try { if(y == 0) throw __LINE__. • constanta __FILE__ conţine numele fişierului curent compilat. Preluarea tuturor excepţiilor 192 . int main() { int x = 2. pentru a termina programul. #include<cstdlib> # include <iostream> using namespace std. Dacă nu este posibil acest lucru. Tratarea unei excepţii împărţire prin zero. Pentru a prinde o excepţie. z. } return 0. Funcţia exit() termină programul dintr-o funcţie diferită de main(). Instrucţiunea throw va apărea într-un bloc try. astfel excepţia nu este prinsă. Limbajul defineşte două constante utile în cazul excepţiilor : • constanta __LINE__ conţine numărul liniei curente compilate. y = 0. Vom utiliza constanta __LINE__ în instrucţiunea throw pentru a semnala linia în care a apărut eroarea. se pot utiliza funcţiile : void exit(int).

} else return x / y. Numărul liniei unde apare eroarea este constanta __LINE__ . un obiect de tip string.2 Excepţii lansate de funcţii Intr-un bloc try pot exista apeluri la multe funcţii. Exemplu. throw string(“impartire prin zero ” + ss. ss << "linia : " << __LINE__ << " fisierul : " << __FILE__.str()). Vom defini o funcţie ce calculează câtul a două numere întregi şi lansează o excepţie. vezi exemplul anterior. int y) throw(string) { if(y == 0) { stringstream ss. Obiectul de tip string ce reprezintă mesajul este creat cu secvenţa de instrucţiuni : stringstream ss. în cazul nostru. Numele fişierului sursă este conţinut în constanta tip caracter __FILE__. Excepţiile lansate de o funcţie sunt precizate la definirea funcţiei în felul următor tip nume_functie(lista de parametri) throw (lista de tipuri) { // corpul functiei } Lista de tipuri dă tipurile de excepţii ce vor fi lansate de instrucţiunea throw. Tipul excepţiei lansate de funcţie este precizat în linia de definiţie a funcţiei şi este. un şir tip string ce va conţine mesajul “impartire prin zero linia : ” + numărul liniei + “fisierul : “ + numele fisierului sursa. ss << "linia : " << __LINE__ << " fisierul : " << __FILE__ . } 193 . int fun(int x. şi orice funcţie poate lansa excepţii.Dacă dorim ca un singur bloc catch să preia toate excepţiile unui bloc try vom scrie blocul catch astfel catch(…) { // trateaza exceptiile } 12. Programul este următorul # include <iostream> # include <string> #include <sstream> using namespace std. dacă numitorul este zero.

• funcţia virtuală what(). • operatorul = . care are ca rezultat un mesaj despre excepţie. după cum se va arăta în continuare. cu prototipul virtual char * what(). apelată cu un parametru eronat: indicele primului caracter din şirul adăugat depăşeşte indicele ultimului element din şirul adăugat. Funcţiile din clasele standard ale limbajului C++ generează excepţii de tipul exception sau de tipul unei clase standard derivate din clasa exception. __FILE__). try { // functie ce poate lansa o exceptie c = fun(a. Se va defini un şir ss de tip C şi se vor înscrie în el constantele __LINE__ şi __FILE__ cu funcţia sprintf() : char ss[50]. throw string(“impartire prin zero ” + string(ss)). sprintf(ss. b = 0. Exerciţiu. } catch(string mesaj) { cout << mesaj << endl. cout << a << “/” << b << “=” << c << endl. b). iar excepţia este prinsă în blocul catch corespunzător. Indicaţie. Prototipul funcţiei sprintf() este definit în biblioteca <cstdio>. Să se modifice funcţia fun() astfel ca şirul ss să fie de tip C.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 defineşte • un constructor implicit şi un constructor copiere : exception(). 12.int main() { int a = 10. c. Clasa se numeşte exception iar prototipul ei se află în biblioteca <exeception>. 194 . __LINE__. Vom prezenta un exemplu în care se prinde o eroare generată de o funcţie a clasei string: funcţia append. } Rezultatul rulării programului este cel de mai jos. Menţionăm că funcţia ce lansează o excepţie este apelată într-un bloc try. } return 0. "linia : %i fisierul : %s". exception(exception&).

un obiect de tip runtime_error.  excepţii intrare / ieşire ce apar la operaţii cu fişiere. Să rescriem funcţia care ce calculează câtul a două numere întregi şi lansează o excepţie. 4. Exemplu. în cazul 195 .append(str2. aceste excepţii sunt descrise de clasa ios::failure. aceste excepţii sunt descrise de clase ce moştenesc din clasa runtime_error. aceste excepţii sunt descrise de clase ce moştenesc direct din clasa excepţion. cout<<str1<<endl. Clasele logic_error.what() <<endl. runtime_error şi ios::failure extind direct clasa exception şi sunt extinse de alte clase pentru a descrie diverse tipuri de erori. cout << "Type: " << typeid(e). Tipul excepţiei lansate de funcţie este precizat în linia de definiţie a funcţiei şi este. Clasele logic_error şi runtime_error şi clasele ce le extind sunt definite în biblioteca standard <stdexcept>.name() <<endl. aceste excepţii sunt descrise de clase ce moştenesc din clasa logic_error. dacă numitorul este zero. } catch (exception &e) { cout << "Exception: " << e..  excepţii logice. str1. excepţii lansate când o excepţie nu corespunde nici unei clauze catch.  excepţii detectabile la execuţia programului. de exemplu depăşirea gamei inferioare sau superioare de reprezentare a numerelor în timpul unei operaţii aritmetice.#include <cstdlib> #include <iostream> #include <string> using namespace std. etc. string str2("test"). } return 0. Excepţiile lansate de funcţiile din bibliotecile standard ale limbajului C++ sunt grupate în patru categorii:  excepţii lansate de operatorul new când nu se poate aloca memoria cerută. de exemplu încercarea de a apela o funcţie cu un argument invalid. int main() { try { string str1("err"). } Rezultatul rulării programului este cel de mai jos. ele vor fi tratate într-un paragraf special. 3). care pot fi determinate înainte de execuţia programului.

int fun(int x. } catch(runtime_error& exm) { cout << exm.what() << endl. definiţi în clasa ios. throw runtime_error("impartire prin zero linia : " + ss. } return 0. b). ss << __LINE__.nostru. Programul este următorul. } else return x / y. cout << a << "/" << b << "=" << c << endl. } int main(int argc. int y) throw(runtime_error) { if(y == 0) { stringstream ss. ce dau starea curentă a streamului : • badbit poziţionat la unu la apariţia unei erori nereparabile (eroare intrare / ieşire pe disc). este un obiect de tipul runtime_error ce conţine un mesaj de eroare. } Rezultatul rulării ptogramului este cel de mai jos. #include <iostream> # include <stdexcept> #include <sstream> using namespace std. char *argv[]) { int a = 10. b = 0.4 Excepţii intrare / ieşire Orice obiect de tip stream conţine următorii biţi. try { // functie ce poate lansa o exceptie c = fun(a.str()). c. 12. 196 .

Putem permite lansarea excepţiilor cu funcţia int exceptions(int except). are valoarea true dacă bitul badbit are valoarea unu. De exemplu exceptions(ios::badbit | ios::failbit). #include <iostream> #include <fstream> #include <exception> using namespace std. // permite lansarea unei exceptii file. In cazul în care excepţiile sunt permise. şi funcţia virtuală const char * what(). are valoarea true dacă ceilalţi biţi au valoarea zero. Clasa de bază pentru excepţiile lansate de streamuri este clasa failure. Exemplu. Aceşti biţi sunt definiţi în clasa ios şi sunt poziţionaţi după fiecare operaţie de scriere sau citire. • bool eof(). • bool good(). are valoarea true dacă cel puţin unul din biţii badbit şi failbit are valoarea unu. definită în clasa ios. Implicit. încercăm să deschidem în citire un fişier inexistent. în urma unei operaţii intrare / ieşire. • goodbit poziţionat la unu atunci când nu există eroare. etc).failbit poziţionat la unu la apariţia unei erori reparabile (de exemplu citim o variabilă numerică.exceptions(ios::badbit | ios::failbit). vezi definiţia clasei exception. are valoarea true dacă bitul eofbit are valoarea unu. care moşteneşte din clasa exception. int main() { ifstream file. Se vor lansa excepţii în cazul apariţiei unei erori în care sunt poziţionaţi la unu biţii badbit şi failbit. • eofbit poziţionat la unu când s-a detectat sfârşitul de fişier la citire. Ea este o clasă internă a clasei ios şi defineşte constructorul failure(string). try { • 197 . Vom citi un fişier tip text şi vom afişa conţinutul lui pe ecran. dar streamul de intrare conţine litere. Ei pot fi testaţi cu următoarele funcţii definite în clasa ios : • bool bad(). unde except este o combinaţie a biţilor pentru care dorim să urmărim excepţii: ios::badbit ios::failbit Aceşti biţi se pot combina cu operatorul |. permite lansarea excepţiilor când biţii badbit şi failbit sunt poziţionaţi la unu. instrucţiunile de prelucrare a fişierului vor fi incluse într-un bloc try şi excepţiile sunt prinse în blocul catch corespunzător. Menţionăm că funcţia eof() a fost deja folosită la citirea fişierelor. erorile streamurilor nu lansează excepţii. • bool fail().

open(“rez. while(! file.char car.eof()) { cout << car.what() << endl.txt”). } catch(ios::failure e) { cout << “exceptie : “ << e. } file.close(). 198 .get(). car = file. } Blocul catch are ca parametru un obiect de tipul failure catch(ios::failure e) care este tipul excepţiilor lansate de streamuri.get(). car = file. } return 0. file.

Variabilele t1 şi t2 primesc ca valoare timpul curent. int tm_hour. /* time value */ Funcţia standard ce completează o variabilă de acest tip este time_t time(time_t * timeptr). Exemplu. sau printf(“%s\n”. t2. Exemplu. Instrucţiunile cout << ctime(&t1) << endl.h>. pentru programele C++. converteşte timpul conţinut într-o variabilă time_t în structura tm. int tm_mday. pentru programele C.[1.[0. Structura predefinită tm conţine următoarele informaţii struct tm { int tm_sec.[0.[0. }.1 Funcţii de timp Limbajele C şi C++ au funcţii predefinite pentru a obţine timpul curent.59] */ /* hours since midnight .23] */ /* day of the month . Fie instrucţiunile : time_t t1. int tm_isdst. int tm_year. int tm_wday. Prototipurile acestor funcţii se găsesc în bibliotecile <time.31] */ /* months since January .[0. Funcţia ctime converteşte timpul unei variabile de tip time_t într-un şir de caractere de forma luna ziua hh:mm:ss an Prototipul funcţiei este char* ctime(time_t * timeptr).6] */ /* days since January 1 . int tm_mon.[0. Tipul variabilelor ce memorează timpul este long int şi este redefinit ca time_t. ctime(&t1)).59] */ /* minutes after the hour . Funcţia struct tm * localtime(time_t * timeptr).13 Aplicaţii 13. Timpul este măsurat în secunde. Funcţia time are ca rezultat timpul în parametrul timeptr şi ca valoare. afişază timpul din variabila t1 ca mai jos. typedef long time_t.11] */ /* years since 1900 */ /* days since Sunday . t1 = time(&t2). Fie secvenţa de instrucţiuni 199 /* seconds after the minute .365] */ /* daylight savings time flag */ . respectiv <ctime>. int tm_yday. int tm_min.[0. de la data de 1 Ianuarie 1970 GMT ora 0. Exemplu.

t2. Prototipurile funcţiilor pentru lucrul cu fire de execuţie se află în biblioteca <process. t2 = localtime(&t1). 200 . ce dă data compilării fişierului sursă curent. Fiecare fir execută o anumită funcţie cu prototipul void f(void *). printf(“%d:%d:%d\n”. t1 = time(NULL). t2 = * ptrt.h>. Funcţia struct tm * gmtime(const time_t * timeptr). cout << t2. ptrt = localtime(&t1). unsigned int stack. Există două macroinstrucţiuni ce pot fi utilizate : • __DATE__ .tm_hour << ":" << t2. struct tm * ptrt. printf("TIME : %s \n". void * arglist). Funcţia time_t mktime(struct tm * t). 13. Parametrul funcţiei este un pointer de un tip nespecificat. t1 = time(NULL). Este posibil să cream programe cu mai multe fire de execuţie simultan. __DATE__ ). Instrucţiunile : printf("DATE : %s \n".tm_sec).tm_sec << endl. converteşte timpul dintr-o structură tip tm într-o variabilă time_t. __TIME__ ). Limbajul C defineşte următoarele funcţii pentru lucrul cu fire de execuţie : • funcţia unsigned long _beginthread(void (*f) (void *). struct tm t2. t2. Sistemul de operare alocă cuante de timp ale unităţii centrale pe rând fiecărui fir.2 Fire de execuţie Programele de până acum au fost procese cu un singur fir de execuţie (thread). Timpul afişat pe ecran este Vom rescrie secvenţa de program astfel time_t t1. are ca rezultat un pointer la o structură tm ce conţine timpul GMT. şir de caractere. t2.time_t t1.tm_min. şir de caractere.tm_hour. afişază cele două şiruri de caractere ca mai jos. • __TIME__ . struct tm t2.tm_min << ":" << t2. ce dă timpul compilării fişierului sursă curent.

In exemplul nostru dimensiunea stivei firelor de execuţie va fi zero iar lista de parametri pasaţi firelor va fi NULL. for(i = 0. Funcţiile executate de fire vor consta dintr-un ciclu în care se scrie un mesaj şi apoi se pune firul în aşteptare un număr de milisecunde. } return. Vom face un program care să lanseze în execuţie două fire. } // functie executata de al doilea fir void fthr2(void * arg) { int i.lansează în execuţie un fir. Al doilea parametru este dimensiunea unei stive ce poate fi utilizată de fir. i++) { printf("thread1\n"). } return. # include <stdio. Primul parametru este un pointer la funcţia ce va fi executată de fir. i < 5. for(i = 0. i++) { printf("thread2\n").h> # include <stdlib. Un fir se termină atunci când funcţia executată de fir se termină.h> // functie executata de primul fir void fthr1(void * arg) { int i. } 201 . Exemplu. i < 5. Ultimul parametru este o listă de argumente transmisă firului. _sleep(500). suspendă execuţia firului de pentru un număr de milisecunde dat de parametrul ms.h> # include <process. _sleep(1000). Parametrul void (*f) (void *) este interpretat astfel void (*f) (void *) void (*) (void *) void (void *) void • funcţia f este pointer la funcţie cu un parametru de tip void* de tip void _sleep(int ms).

3 Funcţia system Funcţia system() execută o comandă a sistemului de operare. char *argv[]) { system("dir"). NULL). ce afişază conţinutul unui director. Vom utiliza comanda dir pentru a afişa conţinutul directorului curent al aplicaţiei. } Execuţia primului fir se termină când se ajunge la instrucţiunea return a funcţiei fthr1. pentru programele C şi <cstdlib> pentru programele C++. Parametrul funcţiei este un şir de caractere ce conţine comanda. _beginthread(&fthr2. 0. Aceasta poate fi specificată ca &fthr1 sau fthr1 deoarece numele funcţiei este chiar adresa punctului de intrare în funcţie. După cum se vede fiecare fir este executat de cinci ori. system("PAUSE").int main() { _beginthread(fthr1. Programul este următorul. O comandă utilă este pause. Reamintim că primul parametru al funcţiei _beginthread() este adresa funcţiei ce va fi executată de fir. ce opreşte execuţia programului până când se apasă tasta Return. int main(int argc. return 0. O altă comandă utilă este dir. NULL). Prototipul funcţiei system() este definit în biblioteca <stdlib. 0.h>. Rezultatul rulării programului este prezentat mai jos. #include <cstdlib> #include <iostream> using namespace std. return EXIT_SUCCESS. Prototipul funcţiei este int system(const char * command) . } 202 . 13.

cpp. main. main. fişierul obiect rezultat al compilării. 203 .exe.o şi programul executabil.Rezultatul rulării programului este cel de mai jos. main. Se pot observa în directorul curent : fişierul sursă.

Algoritmele sunt funcţii ce se aplică asupra elementelor unui container. tipn> nume_obiect. Un exemplu de container sunt listele dublu înlănţuite reprezentate grafic ca mai jos. In această definiţie T1. tipn sunt tipurile actuale cu care este apelat constructorul clasei generice. Biblioteca de şabloane standard are trei componente : • containere. Clasa defineşte un câmp de tipul T ce va conţine numărul.. tipn> nume_obiect (parametrii). Clasele bibliotecii de şabloane standard sunt clase generice care au ca parametri tipul obiectelor manipulate. Aici tip1. o funcţie de acces. de exemplu sortarea elementelor unei liste sau ale unui vector. etc. Listele dublu înlănţuite pot fi parcurse în ambele sensuri. double. sau clase definite de programator. Elementele listelor pot fi sortate în ordine crescătoare sau descrescătoare. etc.14 Biblioteca de şabloane standard In activitatea de programare apar frecvent anumite structuri de date: liste simplu sau dublu înlănţuite. O clasă generică se defineşte cu instrucţiunea template cu forma template <class T1. 204 . stive. …. class Tn> class nume_clasa { // definitia clasei }. un constructor tip copiere. …. int. O reprezentare a clasei este cea de mai jos. …. Limbajul C++ are o bibliotecă cu clase special proiectate (Biblioteca de şabloane standard.. Standart Template Library. class T2. square().1 Clase generice Clasele din biblioteca de şabloane standard sunt clase generice. Exemplu. STL) pentru crearea şi manipularea acestor structuri. numite elementele containerului. …. 14. în care tipurile datelor şi funcţiilor sunt parametri. cozi. geta(). ce se pot utiliza la declararea de variabile şi funcţii membre ale clasei generice. şi o funcţie ce calculează pătratul numărului. Iteratorii permit parcurgerea elementelor unui container. In această reprezentare tipul T este parametrul clasei X. Un container este o structură de date ce conţine alte obiecte. vectori. Tn sunt tipuri parametri. Exemple de containere sunt vectorii. folosind iteratori. Să definim o clasă generică X ce poate calcula pătratul unui număr întreg sau real. • algoritme. listele. mulţimi. tip2. tip2. Un obiect de tipul unei clase generice se declară cu constructorul clasei cu următoarele forme: nume_clasa <tip1. Putem adăuga / şterge elemente în / din listă. nume_clasa <tip1. Ele pot fi tipuri simple. cu funcţiile existente în biblioteca de algoritme. …. şirurile tip string. • iteratori. T2. tip2. etc.

Definiţia clasei este următoarea # 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; } Rezultatul rulării programului este cel de mai jos.

Ca exerciţiu, vom repeta definiţia clasei generice X, cu funcţiile membre definite în afara clasei. In acest caz, definiţia template <class T> 205

trebuie repetată înaintea fiecărei definiţii de funcţie. #include <iostream> using namespace std; template <class T> class X { private: T a; public: X(T b); T square(); T geta(); }; template <class T> X<T>::X(T b) { a = b; } template <class T> T X<T>::square() { return a * a; } template <class T> T X<T>::geta() { return a; } Compilatorul utilizează definiţia clasei generice pentru a genera clasele necesare. In exemplul de mai sus, compilatorul generează două clase, una X_int şi una X_double.

14.2 Containere, iteratori şi algoritme generice
Un container este un obiect ce poate conţine alte obiecte, numite în continuare elemente ale containerului. Cel mai cunoscut tip de container este vectorul. Obiectele din container au acelaşi tip. Tipul obiectelor poate fi unul simplu, int, double, etc., sau unul structurat, şiruri tip string, sau obiecte ale unei clase definite de utilizator. Containerele permit inserarea şi ştergerea de obiecte. Ele au funcţii membre ce efectuează aceste operaţii. De regulă, funcţiile ce inserează obiecte în container, alocă şi memoria necesară. Menţionăm că, clasa obiectelor memorate într-un container, trebuie să aibă un constructor implicit (fără parametri), un constructor tip copiere şi operatorul =. O operaţie obişnuită efectuată asupra unui container este parcurgerea obiectelor din container (traversarea containerului). Parcurgerea elementelor containerului se face cu un iterator. Iteratorul este similar unui pointer, el indică un anumit obiect din 206

container. Operaţiile efectate asupra iteratorilor, indiferent de tipul containerului, sunt următoarele : • iniţializarea iteratorului la un anumit obiect din container, • furnizarea obiectului de la poziţia curentă a iteratorului, • modificarea obiectului de la poziţia curentă a iteratorului, • avansarea iteratorului la poziţia următoare din container. Clasele ce definesc iteratori au următorii operatori, ce realizează operaţiile de mai sus asupra iteratorilor : • 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 sau modificarea obiectului de la poziţia curentă a containerului indicată de iterator. In cele ce urmează vom prezenta vectorii, listele şi numerele complexe. Biblioteca de şabloane standard defineşte funcţii ce se pot aplica asupra containerelor. Cele mai importante funcţii sunt sort(), ce sortează elementele containerului, şi find(), care caută un element în container. Aceste funcţii vor fi prezentate pentru fiecare tip de container.

14.3 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. Ea este definită în biblioteca <vector>. Clasa defineşte următorii constructori : • constructorul implicit (fără parametri) vector(); crează un vector vid, • constructorul copiere vector(const vector& p); crează un vector în care copiază elementele vectorului p care este parametru, • constructor ce crează un vector cu n elemente vector(int n); Clasa defineşte destructorul ~vector() ; Fie T tipul componentelor vectorului (parametrul clasei generice). Clasa defineşte următoarele funcţii : • void push_back(const T&); adaugă un element la sfârşitul vectorului, • void pop_back(); şterge 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, • void clear(); şterge toate componentele vectorului, • operatorul = este operatorul de atribuire, • operatori de comparare a doi vectori , <, <=, = =, >, >=, != , element cu element.

207

Menţionăm că un vector poate avea două sau mai multe elemente egale. Elementele unui vector pot fi accesate în trei moduri : cu operatorul [] şi funcţia at(), arătate mai sus, şi cu iteratori. Aceste moduri vor fi exemplificate în continuare. Exemplu. Să creăm şi să listăm un vector cu componente întregi. # include <iostream> # include <vector> using namespace std; #include <string> int main() { // definim un vector cu componente siruri vector <string> v; // adauga 4 elemente v.push_back("abcd"); v.push_back("x+y"); v.push_back("1x2ycz"); v.push_back("29dx"); // afisaza componentele vectorului cout << “ elementele vectorului “ << endl ; for(int k = 0; k < v.size(); k++) cout << v[k] << “ “; cout << endl; return EXIT_SUCCESS; } Rezultatul rulării programului este cel de mai jos.

Menţionăm că, expresia v[k], putea fi înlocuită cu expresia v.at(k). La definirea obiectului v, se crează un vector vid. 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 alt element fictiv după ultimul element. In exemplul următor comparăm doi vectori, vec1 şi vec2. #include <cstdlib> #include <iostream> #include <vector> using namespace std;

208

vec1 > vec2. vec1. end(). permit compararea a doi iteratori. Clasa iterator defineşte următorii operatori : • operatorii ++ şi -. permite adresarea sau modificarea valorii de la poziţia curentă a containerului indicată de iterator. <.push_back(7). char *argv[]) { vector<int> vec1. primul element al vectorului vec2 este -3. = =. • operatorul de adresare indirectă. rezultatul programului este cel de mai jos. 209 . >=.push_back(-2). putem defini iteratori constanţi. vec1. else cout << "vec1 > vec2" << endl. Un iterator este asemenea unui pointer la un element al containerului. <=. vec2.push_back(8). Clasa vector defineşte funcţiile : begin().3. if(vec1 < vec2) cout << "vec1 < vec2" << endl.) se pot utiliza iteratori. >. Un obiect de tipul acestei clase se defineşte astfel vector<tip>::iterator nume_obiect. 14. In consecinţă.push_back(-3). Vom menţiona că există două tipuri de iteratori ce permit parcurgerea vectorilor. vec2.int main(int argc. vec2. Un iterator poate fi iniţializat la orice poziţie din container. Dacă parcurgem un container fără să modificăm elementele componente. Parcugerea unui vector în sens direct Clasa iterator este o clasă internă a containerului (în cazul nostru a clasei vector). return EXIT_SUCCESS. în sens direct şi respectiv în sens invers. listă. !=. } Primul element al vectorului vec1 este -2. else if(vec1 == vec2) cout << "vec1 = vec2" << endl.push_back(5). etc. *.1 Parcurgerea vectorilor cu iteratori Pentru parcurgerea componentelor unui container (vector.modifică iteratorul la următoarea / precedenta poziţie din container. • operatorii relaţionali. vector<int> vec2.

// se adauga 4 elemente v.begin(). vector <int>::iterator it. ce indică ultimul element al vectorului şi respectiv elementul fictiv ce precede primul element al vectorului.ce au ca rezultat un iterator. int main() { // definim un vector cu componente intregi vector <int> v. 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).push_back(-5).push_back(13). Clasa vector defineşte funcţiile : rbegin(). // afisaza componentele vectorului in ordine directa utilizand iteratori cout << "componentele in ordine directa" << endl. v. # include <iostream> # include <vector> using namespace std. 210 . ce indică primul element al vectorului şi respectiv elementul fictiv ce urmează după ultimul element al vectorului. v.end(). rend().push_back(7). it++) cout << *it << “ “. Un obiect de tipul acestei clase se defineşte astfel vector<tip>::reverse_iterator nume_obiect. Pentru vectorul anterior avem situaţia de mai jos Exemplu. it != v. Să parcurgem vectorul anterior utilizând iteratori. v. ce au ca rezultat un iterator.push_back(12). for(it = v.

iter1 != v.rbegin(). Prima funcţie şterge elementul din poziţia specificată de iteratorul pos. erase(iterator first. Inserarea valorii 25 ca prim element se face astfel it = v. ce inserează elementul val înaintea poziţiei specificate de iteratorul pos. v. După ştergerea sau inserarea unui element în vector. Creăm un vector v vector <int> v. Programul este următorul.erase(it). toţi iteratorii trebuie reiniţializaţi. return 0.3. cout << endl.begin(). Exemplu. Putem reprezenta parcurgerea vectorului în sens direct ca în figura de mai jos.2 Stergerea şi inserarea de elemente In afară de funcţia pop_back(). v. // afisaza conponentele vectorului in ordine inversa utilizand iteratori cout << "componentele in ordine inversa" << endl. clasa vector defineşte funcţia insert(iterator pos. Stergerea primului element al vectorului se face astfel it = v. } Rezultatul rulării programului este cel de mai jos. Fie vectorul din exemplul precedent.rend(). for(iter1 = v. 25). iter1++) cout << *iter1 << “ “. Programul de mai jos şterge primul element din vector şi apoi inserează valoarea 25 ca prim element al vectorului.insert(it. clasa vector defineşte şi funcţiile : erase(iterator pos).cout << endl. Afişarea elementelor vectorului se face cu o instrucţiune for 211 .begin(). In afară de funcţia push_back(). Fie it un iterator vector <int>::iterator it. iterator last). vector<int>::reverse_iterator iter1. care este apoi iniţializat ca în exemplul anterior. const T& val). 14. a doua funcţie şterge elementele între poziţiile first şi last.

i < 5.size(). cout << "vectorul modificat" << endl.begin(). i++) cout << v. int main(int argc. cout << endl.begin().push_back(i * i). 25). 212 . it = v.at(i) << " ".size().insert(it. i < v. i < v. i++) cout << v. v. for(i = 0. // insereaza un element in prima pozitie cout << “insereaza un element in prima pozitie” << endl. for(i = 1. i++) v.for(i = 0. // se adauga 4 elemente int i. for(i = 0.erase(it). cout << “vectorul modificat" << endl. i < v. char *argv[]) { // definim un vector cu componente intregi vector <int> v. for(i = 0.at(i) << " ".size(). return 0. cout << endl.at(i) << " ".size(). i++) cout << v. cout << endl. v. } Rezultatul rulării programului este cel de mai jos. // afisaza componentele vectorului cout << "vectorul initial" << endl. vector <int>::iterator it. i++) cout << v. it = v. i < v. Programul este următorul #include <iostream> #include <vector> using namespace std. // sterge primul element din vector cout << “sterge primul element din vector” << endl. cout << endl.at(i) << " ".

return 0.size(). iterator iter2).end()). # include <iostream> # include <vector> # include <algorithm> # include <string> using namespace std. i++) cout << vst. vst.push_back("ccd").size(). // afisaza vectorul sortat cout << "vectorul sortat" << endl.14. vst. for(i = 0. // afisaza vectorul initial cout << "vectorul initial" << endl. vector<string> vst. i++) cout << vst.push_back("abc"). i < vst.begin().3 Sortarea componentelor unui vector Biblioteca de şabloane standard defineşte funcţii de sortare a componentelor unui vector. Prima dintre acestea.at(i) << endl.at(i) << endl. Prototipul funcţiei sort() este definit în biblioteca <algorithm>. // sorteaza vectorul sort(vst. for(i = 0. int main() { int i. Exemplu. i < vst.push_back("mnp"). 213 . vst. vst. Vom crea un vector cu componente tip string şi îl vom sorta. vst. } Rezultatul rulării programului este cel de mai jos. funcţia sort() sortează elementele vectorului în ordine crescătoare şi are următorul prototip sort(iterator iter1.3.push_back("rst"). unde iteratorii iter1 şi iter2 dau primul element din vector şi ultimul element din vector ce vor fi sortaţi. Aceste funcţii sunt definite în biblioteca <algorithm>.

int n) { return k < n. unde iter1 şi iter2 sunt iteratori. T y). ce dau primul element din vector şi ultimul element din vector ce vor fi sortaţi. vezi exemplul de mai jos. int n) { return k > n. /* funcţie de comparare pentru sortarea elementelor vectorului in ordine crescatoare */ bool comp(int k. iar comp este o funcţie cu prototipul bool comp(T x. # include <iostream> # include <vector> # include <algorithm> using namespace std. Definind diferite funcţii comp putem sorta vectorul în ordine crescătoare sau descrescătoare. Vom crea un vector cu componente întregi şi îl vom sorta în ordine crescătoare şi apoi descrescătoare. Funcţia comp() are rezultatul true dacă x < y şi false în caz contrar. } /* funcţie de comparare pentru sortarea elementelor vectorului in ordine descrescatoare */ bool comp2(int k.A doua funcţie sort() are prototipul sort(iter1. iter2. Parametri x şi y sunt două elemente de tipul T al componentelor vectorului. Vom defini două funcţii de comparare pentru cele două cazuri. comp). Exemplu. Funcţia sort permută elementele x şi y dacă funcţia comp are rezultatul false. } 214 . Prototipul funcţiei sort() este definit în biblioteca <algorithm>.

v. const T& val).push_back(12). // creaza vectorul v. } Rezultatul rulării programului este cel de mai jos. for(i = 0. sort(v. funcţia are ca rezultat iteratorul ce indică 215 . // afisaza vectorul initial cout << “Vectorul initial parcurs in sens direct” << endl. comp). i < v.at(i) << “ “. i++) cout << v[i] << “ “.begin(). // sorteaza vectorul in ordine crescatoare cout << "Se sorteaza vectorul in ordine crescatoare" << endl. return 0. i++) // cout << v[i] << endl.size(). cout << endl. i++) cout << v[i] << “ “. for(i = 0. în intervalul dat de iteratorii first şi last.begin().int main() { vector<int> v. // afisaza vectorul sortat cout << "Vectorul sortat parcurs in sens direct \n". care caută elementul val printre elementele containerului. v.size(). comp2). cout << endl.3. i < v. v.push_back(4). v.end(). v. cout << endl. 14.size(). i < v. // afisaza vectorul sortat cout << "Vectorul sortat parcurs in sens direct \n". for(i = 0.end().push_back(-3). sort(v. iterator last. cout << v. // sorteaza vectorul in ordine descrescatoare cout << "Se sorteaza vectorul in ordine descrescatoare" << endl. int i.4 Căutarea unui element într-un vector Biblioteca de şabloane standard defineşte funcţia globală find() cu prototipul find(iterator first. Dacă elementul a fost găsit.push_back(12).

// se adauga 4 elemente int i.size(). v. char *argv[]) { // definim un vector cu componente intregi vector <int> v. 216 . i < v. Definim vectorul vector <int> v.v.end().at(i) << " ". Programul este următorul #include <iostream> #include <vector> using namespace std. în caz contrar funcţia are ca rezultat sfârşitul intervalului. cout << endl. care este apoi iniţializat. i < 5. iteratorul it se compară cu sfârşitul intervalului if(it != v. i = it . cout << "indicele elemnetului este " << i << endl. if(it != v. 4). Pentru a vedea dacă elementul a fost găsit.push_back(i * i).end().begin().prima apariţie a elementului. // afisaza componentele vectorului cout << "componentele vectorului" << endl.end()) { // componentul a fost găsit } Indicele elementului găsit se calculează ca int i = it – v. Funcţia find() va cauta valoarea 4 printre elementele vectorului it = find(v. v. 4).end()) { cout << "elementul " << *it << " a fost gasit" << endl. i++) v.begin(). Definim un iterator ce va conţine rezultatul căutării vector <int>::iterator it. int main(int argc. for(i = 0. } return 0. Vom exemplifica utilizarea funcţiei find() cu programul de mai jos.begin(). i++) cout << v. for(i = 1.begin(). // cauta un element vector <int>::iterator it. it = find(v.

copy(istream_iterator<int>(cin). first şi last dau primul şi ultimul element din containerul sursă. copy(vec. Iteratori pentru streamuri Iteratorii pot itera pe streamuri. iar C este un şir de caractere ce este scris pe stream după scrierea unui element. ostream_iterator<int>(cout.begin()). Fie vectorul cu zece componente de tip int vector<int> vec (10).begin(). Adaptorul pentru interfaţa cu streamuri de ieşire este ostream_iterator<T.begin()). istream_iterator<int>(). char *argv[]) { vector<int> vec(10). iterator last. iar prim dă primul element din containerul destinaţie. Adaptorul pentru interfaţa cu streamuri de intrare este istream_iterator<T> unde T este streamul de intrare. Vom exemplifica utilizarea acestor iteratori la copierea de numere întregi din streamul cin într-un vector de tip int. istream_iterator<int>(). iterator prim). Poziţia iteratorului ce corespunde sfârşitului de fişier al streamului de intrare este istream_iterator<int>() Vom copia elementele vectorului vec pe streamul de ieşire cout astfel copy(vec. vec. int main(int argc. In biblioteca de şabloane standard există adaptori pentru interfaţa cu streamuri.3. Vom copia elemente din streamul de intrare cin în vectorul vec astfel copy(istream_iterator<int>(cin). " ")).end(). pentru a citi sau scrie date. Programul este următorul #include <iostream> #include <vector> #include <algorithm> #include <iterator> using namespace std. ostream_iterator<int>(cout.end(). " ")). 217 . Funcţia are ca parametrii trei iteratori. şi scrierea componentelor vectorului în streamul cout. Prototipul funcţie este copy(iterator first. 14.begin(). C> unde T este streamul de ieşire. definiţi în biblioteca <iterator>. vec.5 Copierea unui container. Biblioteca de şabloane standard defineşte funcţia copy ce copiază un container în altul. Acest lucru este util pentru a citi / scrie elementele unui container dintr-un / pe un fişier. vec. vec.} Rezultatul rulării programului este cel de mai jos.

Programul este următorul. strcp. #include <iostream> #include <vector> #include <string> #include <iterator> using namespace std. } Rezultatul rulării programului este cel de mai jos. str. char *argv[]) { vector<string> str. // se afisaza strcp cout << "vectorul copiat" << endl. cout << endl. // se copiaza str in strcp copy(str. cout << "vectorul initial" << endl. " ")). este indicat prin Ctrl+Z.push_back("a+b").size()). ostream_iterator<string>(cout. str.push_back("3*2"). // se defineste un vector strcp de aceeasi lungime cu str vector<string> strcp(str. Exemplu. str. copy(strcp.end(). " ")). Vom iniţializa vectorul str cu elemente tip string şi vom copia elementele pe ecran cu un iterator pentru streamul cout.cout << endl. Reamintim că.begin().end(). } 218 . return 0. return 0. cout << endl. Vom copia vectorul str în vectorul strcp cu funcţia copy() şi vom copia elementele vectorului strcp pe ecran. Vom utiliza funcţia copy la copierea unui vector cu elemente de tip string în altul. ostream_iterator<string>(cout. tot cu un iterator pentru streamul cout. copy(str. str.begin(). sfârşitul streamului de intrare cin. int main(int argc. vezi figura cu rezultatul programului. după care se apasă tasta Enter. strcp.begin().push_back("m*n+p"). str.begin()).end().

Un obiect de tipul acestei clase se defineşte astfel list<tip>::iterator nume_obiect. • void clear(). 14. crează o listă vidă. Lista are elemente de acelaşi tip. • operatorul = este operatorul de atribuire. !=. O listă poate fi parcursă în sens direct sau în sens invers. • void pop_back(). Pentru parcurgerea directă a listelor clasa list defineşte funcţiile : begin(). • int size(). 219 . adaugă un element la sfârşitul listei. Operaţiile implementate de clasa iterator sunt cele prezentate în paragraful despre vectori. • T& back().şterge ultimul element din listă. crează o listă în care copiază elementele listei p. are valoarea true dacă lista este vidă. • operatori de comparare = =.Rezultatul rulaării programului este cel de mai jos. ştrerge toate elementele din listă. <=.1 Parcurgerea listelor Pentru parcurgerea componetelor unei liste se utilizează iteratori. Un iterator este asemenea unui pointer la un element al listei. Funcţiile definite de clasa list sunt următoarele : • void push_back(T&). <. 14. • void pop_front(). Clasa defineşte destructorul ~list() . are ca rezultat primul element din listă. • T& front(). • void push_front(T&). Fie T tipul elementelor listei. Clasa defineşte următorii constructori : • constructorul implicit list(). end(). • constructorul copiere list(const list& p). Parcurgerea listelor în ordine directă Pentru parcurgerea unei liste în ordine directă se utilizează clasa iterator care este o clasă internă a clasei list. >=.are ca rezultat ultimul element din listă. adică o listă ce poate fi parcursă în ambele sensuri. şterge primul element din listă.4. adaugă un element la începutul listei. • bool empty() . element cu element. Prototipul clasei este definit în biblioteca <list>. compară două liste. dă numărul de elemente din listă.4 Liste Clasa generică list implementează o listă dublu înlănţuită. >.

ce indică ultimul element al listei şi respectiv primul element al listei.sort(). Exemplu. // adauga elemente la sfarsitul si inceputul listei ls.begin(). rend(). ls. # include <iostream> # include <list> using namespace std. Un obiect de tipul acestei clase se defineşte ca list<tip>::reverse_iterator nume_obiect.push_front(4).2 Sortarea listelor Pentru sortarea în ordine directă a listelor. cout << endl. ls. 14.4. Operaţiile implementate de clasa iterator sunt cele prezentate în paragraful despre vectori. // sortarea elementelor listei in ordine crescatoare ls. iter++) cout << *iter << " ". Parcurgerea listelor în ordine inversă Pentru parcurgerea inversă a listelor se utilizează clasa reverse_iterator care este tot o clasă internă a clasei list.push_back(11). // parcurgerea listei sortate in sens direct cout << "-.lista initiala --\n". // parcurgerea listei initiale in sens direct cout << "-. iter != ls.lista sortata parcursa in sens direct --\n". o vom sorta ascendent şi vom afişa elementele listei sortate parcurgând-o în sens direct şi invers. cout << endl. for(iter = ls.end(). // parcurgerea listei sortate in sens invers 220 .sorteaza lista in ordine creascatoare --\n".ce au ca rezultat un iterator ce indică primul element al listei şi respectiv ultimul element al listei.push_front(12). ls.begin(). ce sortează elementele listei în ordine crescătoare. iter != ls. Vom crea o listă cu elemente întregi. for(iter = ls. cout << "-. int main() { list <int> ls.end(). list<int>::iterator iter. iter++) cout << *iter << " ". clasa list defineşte funcţia void sort(). Clasa list defineşte funcţiile : rbegin().push_back(7). ce au ca rezultat un iterator pentru parcurgerea în sens invers a listei.

// parcurgerea listei in ordine inversa cout << "lista parcursa in sens invers\n". ls.push_front(4).inversarea ordinii elementelor listei --\n". Vom exemplifica utilizarea acestei funcţii. ls.rbegin().begin(). for(iter = ls.4. iter != ls.cout << "-. return 0. list<int>::iterator iter. iter++) cout << *iter << endl. iter != ls.end(). // inversarea ordinii elementelor listei cout << "-. // parcurgerea listei in sens direct cout << "-. inversând elementele unei liste şi parcurgând-o în sens direct şi apoi invers. for(iter = ls. 221 .lista initiala --\n".begin(). ls.reverse(). iter1++) cout << *iter1 << " ".lista sortata parcursa in sens invers --\n". 14. iter++) cout << *iter << endl. int main() { list <int> ls.end(). cout << endl. // parcurgerea listei in ordine inversa cout << "lista parcursa in sens direct\n". ls.3 Inversarea ordinii elementelor listelor Pentru inversarea ordinii elementelor unei liste clasa list defineşte funcţia void reverse().push_back(7).push_front(12). for(iter1 = ls. # include <iostream> # include <list> using namespace std. iter1 != ls. ls.push_back(11). list<int>::reverse_iterator iter1. } Rezultatul rulării programului este cel de mai jos.rend().

rend(). const T& val). Vom afişa lista iniţială şi după fiecare modificare. lst. lst. list<string>::iterator. Prototipul funcţiei este void afisarelista(list<string>. Definim lista cu şiruri de caractere astfel list<string> lst. ce dau prima poziţie şi ultima poziţie din listă. iterator last). Prima funcţie şterge elementul din poziţia specificată de iteratorul pos. ce inserează elementul val înaintea poziţiei specificate de iteratorul pos. 14. a doua funcţie şterge elementele între poziţiile first şi last. iter1++) cout << *iter1 << endl.4. "xns"). } Rezultatul programului este cel de mai jos. toţi iteratorii trebuie iniţializaţi. Clasa list defineşte funcţia insert(iterator pos. Inserarea unui element în prima poziţie din listă se face astfel it = lst. Pentru aceasta.begin(). după fiecare ştergere sau inserare de elemente. şi vom insera un element în prima poziţie. ce are ca parametri o listă cu şiruri de caractere şi doi iteratori.insert(it. Vom crea o listă cu şiruri de caractere. Reamintim că. erase(iterator first. Stergerea primului element din listă se face astfel it = lst. După ştergerea sau inserarea unui element în vector. Iniţializăm apoi lista ca în exemplele anterioare.4 Inserarea şi ştergerea elementelor din liste Clasa list defineşte şi funcţiile : erase(iterator pos). list<string>::iterator).begin(). for(iter1 = ls.rbegin(). Exemplu. 222 . definim o funcţie afisarelista(). Definim un ierator ce va fi utilizat în funcţiile erase() şi insert() list<string>::iterator it. return 0.erase(it). iter1 != ls. iteratorii trebuie iniţializaţi.list<int>::reverse_iterator iter1. vom şterge primul element din listă.

it = lst. lst. cout << "insereaza un element la inceputul listei" << endl.end()).push_front("zma").insert(it.push_front("a*b"). lst.begin(). // sterge primul element din lista cout << "sterge primul element din lista" << endl. lst. list<string>::iterator den) { list<string>::iterator it. lst.Programul este următorul #include <iostream> #include <list> #include <string> using namespace std.push_back("abc").push_back("xyzp").begin(). // insereaza un element la inceputul listei it = lst. for(it = beg.lista modificata --" << endl.lista modificata --" << endl. afisarelista(lst. list<string>::iterator it. afisarelista(lst. it++) cout << *it << " ". lst. afisarelista(lst.end()). lst. cout << "-. "xns"). lst. char *argv[]) { list<string> lst. 223 . lst. lst. } int main(int argc. cout << "-. // initializeaza lista lst. list<string>::iterator beg. cout << endl.begin(). // afisaza lista initiala cout << "-.begin().lista initiala --" << endl. lst.erase(it).end()). /* functie ce afisaza lista */ void afisarelista(list<string> lst.begin().push_back("mzp"). lst. lst. it != den.

list2.4. list1. 224 . Funcţia are ca parametrii trei iteratori.begin(). } Rezultatul programului este cel de mai jos. copy(list1. iterator last.push_back("a12c"). se poate face cu funcţia copy() cu prototipul copy(iterator first.begin().push_back("n*m+p"). char *argv[]) { list<string> list1. cout << endl.5 Copierea listelor Copierea elementelor unei liste în altă listă. o copiem în lista list2 şi apoi copiăm lista list2 pe ecran. Cele două operaţii de copiere se fac cu funcţia copy().push_back("x2y3"). 14.return 0. iterator prim). list1. list1.end(). int main(int argc.end(). list1. copy(list2. iar prim dă primul element din containerul destinaţie. Vom exemplifica utilizarea funcţiei copy în exemplul următor în care creăm o listă cu numele list1. first şi last dau primul şi ultimul element din containerul sursă. #include <cstdlib> #include <iostream> #include <list> #include <string> #include <iterator> using namespace std. Programul este următorul. Pentru a copia list ape ecran utilizăm adaptorul ostream_iterator ca mai sus. return 0. sau în alt container. list1. " ")). ostream_iterator<string>(cout.begin()).push_back("abv"). list2. list2(10). } Rezultatul rulării programului este arătat mai jos.

vec. # include <string> # include <iostream> using namespace std.push_back("vector"). } Rezultatul rulării programului este cel de mai jos. lis. Exemplu. " ")). int main(int argc. return 0. #include <iostream> #include <vector> #include <list> #include <iterator> #include <string> using namespace std. end() . 225 . ostream_iterator<string>(cout.Vom prezenta un exemplu în care copiem elementele unui vector într-o listă şi apoi elementele listei pe ecran cu funcţia copy(). copy(vec. rend() . cout << endl. vec. vec. int main() { string str1(“abcdegh”). ce au ca rezultat un iterator pentru parcurgerea şirului în sens direct. vec.begin()).end().5 Parcurgerea şirurilor tip string cu iteratori Sirurile tip string pot fi parcurse cu iteratori la fel ca orice container.begin().push_back("lista").push_back("in"). char *argv[]) { vector<string> vec. Clasa string defineşte o clasă internă iterator şi funcţiile : begin().begin().end(). Vom defini un şir tip string şi îl vom parcurge în ambele sensuri folosind iteratori. vec. ce au ca rezultat un iterator pentru parcurgerea şirului în sens invers. copy(lis. lis.push_back("copiat"). list<string> lis(4). 14. Clasa string defineşte clasa internă reverse_iterator şi funcţiile : rbegin(). Programul este cel de mai jos.

+=. Biblioteca defineşte următorii operatori: • cei patru operatori aritmetici +.rend(). Prototipul acestei clase este definit în biblioteca <complex>. // parcurge sirul in sens invers string::reverse_iterator rii.// parcurge sirul in sens direct string::iterator ii.6 Numere complexe Biblioteca de şabloane standard defineşte clasa complex pentru lucrul cu numere complexe. -=. rii++) cout << *rii << " ". • operatorul de scriere <<. Pentru citirea cu operatorul >> numărul complex u + i v este introdus ca (u. 14. v) • operatorul de citire >>. /= Biblioteca defineşte următoarele funcţii matematice standard care au ca argumente numere complexe : asin sin exp pow acos cos log sqrt atan tan log10 Biblioteca defineşte următoarele funcţii: 226 .begin().rbegin(). ii != str1.sirul parcurs in sens direct --" << endl. Clasa defineşte următorii constructori: • Constructorul implicit fără parametri complex(). for(ii = str1. *= . Am putea deci defini iteratorul string::const_iterator ii. / . rii != str1. ii++) cout << *ii << " ". cout << "-.end(). • Constructorul cu parametri Complex(T u. • Constructorul copiere complex(const complex<T>&). cout << endl. return 0. * . cout << "-. v) Clasa defineşte următorii operatori de atribuire : • =. ce crează un număr complex u + iv. cout << endl. } Menţionăm că deoarece nu modificăm elementele şirului am putea utiliza iteratori constanţi. -. ce crează numărul complex 0 + i 0.sirul parcurs in sens invers --" << endl. tipul double sau float al perechii de numere reale ce definesc numărul complex. Numărul complex u + i v este scris de operatorul << ca (u. Clasa are ca parametru T. for(rii = str1. T v). • operatorii relaţionali = = şi !=.

5) In final se scrie partea reală a numărului complex cu cele două funcţii real() Programul este următorul. Numărul este introdus sub forma (2. b(2. are ca rezultat conjugatul numărului complex. } Rezultatul rulării programului este cel de mai jos. Se citeşte apoi numărul complex 2. cout << "abs(c) = " << abs(c) << endl.5i de la tastatură care este scris pe ecran. 1).real() << endl. Exemplu. T real(const complex<T>&). dă valoarea absolută (norma) a numărului complex. complex <T> conj(complex<T>&).5i.• • • • • • • T real(). prima este funcţie membră a clasei. dau partea reală a numărului complex. T abs(complex<T>&). apoi c = c + a şi valoarea absolută a lui c. dă pătratul valoarii absolute (normei) a numărului complex.5). c. cout << ”c = ” << c << endl. cout << "real a = " << real(a) << endl. T norm(complex<T>&).0. b = 2. // citeste si scrie un numar complex cin >> a. // scrie partea reala a numarului complex cout << "real a = " << a.0 – 3. T imag(const complex<T>&). calculează suma lor c = a + b. T imag().7. 227 . Fie un program ce defineşte numerele complexe a = 1 + i. c = a + b. -3. -3. cout << "c + a = " << c << endl. dau partea imaginară a numărului complex. int main() { complex <double> a(1. return 0. #include <iostream> #include <complex> using namespace std. prima este funcţie membră a clasei.7 – 3. c += a. cout << "a = " << a << endl.

228 .

9. Avem totdeauna 0 ≤ d i < r . ….. + b1 Vom scrie de unde deducem: q1 = q 2 2 + r1 r1 = b1 q 2 = bn −1 2 n −3 + .. 2. etc se pot obţine ca resturile impǎrţirilor repetate ale numǎrului N cu 2.. 2. D. Coeficientul bi este 0 sau 1. B=11. D=13. în sistemul octal 0. 2. Conversia zecimal-binarǎ Fie un numǎr natural reprezentat în sistemul binar N = ∑ bi 2 i = bn −1 2 n −1 + .. E. Cifrele sistemului binar se numesc biţi.. …. B. C.. …. 1. Fie numărul N = (d n −1 d n −2 . C=12... 7. iar în sistemul hexazecimal cifrele utilizate sunt: 0.Anexa 1. b1 . iar d i este cifra de pe poziţia i.d 0 ) r Mărimea numărului natural corespunzător este: N = d n −1 r n −1 + d n −2 r n − 2 + . In sistemul binar cifrele utilizate sunt 0 şi 1. 9. + 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ǎ. Din expresia de mai sus a numǎrului se observǎ cǎ cifrele bo . Reprezentarea informaţiilor A. resturile obţinute reprezintǎ chiar cifrele numǎrului binar.. 1. In sistemul zecimal cifrele utilizate sunt 0. In sistemul poziţional. Sǎ convertim numarul 14 din baza 10 în baza 2. 1. Exemplu. poziţia unei cifre determină ponderea cifrei în mărimea numărului. A.. 14 = q1 2 + r0 = 7 * 2 + 0 229 . + d 0 r 0 = ∑ d i r i i =0 n −1 unde: r>1 este baza. + b1 21 + b0 i =0 n −1 In partea dreaptă avem un polinom de puteri ale lui 2.1 Reprezentarea numerelor întregi în sistemul binar Un număr natural se reprezintǎ ca o colecţie de cifre. Vom nota: N = q0 şi vom scrie: q 0 = q1 2 + r0 de unde deducem: r0 = b0 q1 = bn −1 2 n − 2 + . n este numărul de cifre. F unde: A=10. E=14 şi F=15.

Avem deci: b0 = r0 = 0 7 = q 2 2 + r1 = 3 * 2 + 1 Avem: b1 = r1 = 1 3 = q3 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 Algoritmul de conversie a unui numǎr din baza 10 în baza 2 este urmǎtorul: 1. pentru reprezentarea în baza 8 se grupează câte 3 cifre binare. q 0 = N 2. 16. (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. Exemplu. Unitatea de bază a informaţiei în calculator este un octet sau byte. 10 şi 16 şi operaţii cu 230 . Numerele întregi se reprezintǎ în calculator pe 8. 32 sau 64 de biţi. Conversia din baza 10 în baza 8 sau 16 se face prin împǎrţiri repetate cu 8 şi respectiv 16. pentru reprezentarea sa în baza 16 se grupează câte 4 cifre binare. while qi ≠ 0 { q qi +1 = i 2 ri = qi %2 i = i +1 } Resturile obţinute sunt cifrele numǎrului binar. Având un numǎr în baza 2. Exemplu. ce cuprinde 8 cifre binare (biţi). 8. ( 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. primul rest fiind cifra cea mai puţin semnificativǎ. 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. i = 0 3.

El este 0 pentru numere pozitive şi 1 pentru numere negative.numere în aceste baze se poate folosi aplicaţia Calculator a sistemului de operare Windows. Baza 10 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Baza 2 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 Baza 8 0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17 Baza 16 0 1 2 3 4 5 6 7 8 9 a b c d e f Reprezentarea numerelor binare cu semn In cazul numerelor binare cu semn. bitul cel mai semnificativ este bitul de semn. un bit de semn şi 7 biţi ai numǎrului: Număr zecimal 13 -13 25 -7 127 -127 Reprezentare binarǎ 0000 1101 1000 1101 0001 1001 1000 0111 0111 1111 1111 1111 Reprezentare hexazecimalǎ 0D 8D 19 87 7F FF 231 . 2. Vom considera numere întregi reprezentate pe 8 biţi. Reprezentarea în mǎrime şi semn Numǎrul pozitiv X se reprezintǎ ca: X = 0 * 2 n −1 + ∑ ai 2 i i =0 n −2 Numǎrul negativ X se reprezintǎ ca: X = −1 * 2 n −1 + ∑ ai 2 i i =0 n−2 Exemple. Există trei reprezentǎri ale numerelor binare cu semn. 8 şi 16. In final prezentăm în tabela de mai jos numerele de la 0 la 15 reprezentate în bazele 10.

127]. Exemple de numere reprezentate în complement faţǎ de 2 pe un octet. Avem relaţia: 232 . Număr zecimal 13 -13 -7 127 -127 Reprezentare binarǎ 0000 1101 1111 0011 1111 1001 0111 1111 1000 0001 Reprezentare hexazecimalǎ 0D F3 F9 7F 81 Menţionǎm cǎ în calculatoare numerele întregi se reprezintǎ în general în complement faţǎ de 2. Reprezentarea în complement faţǎ de 1 Numǎrul pozitiv X se reprezintǎ în complement faţǎ de 1 ca: X = 0 * 2 n −1 + ∑ ai 2 i i =0 n −2 Numǎrul negativ X se reprezintǎ ca: X = −1 * 2 n −1 + ∑ a i 2 i i =0 n −2 unde: ai = 1 − ai . Primul bit va fi interpretat ca şi coeficientul lui − 2 n −1 . Putem scrie formula de reprezentare a numerelor binare în mǎrime şi semn ca: X = (−1)a n −1 2 n −1 + ∑ ai 2 i i =0 n −2 unde coeficientul a n −1 are valoarea 0 sau 1. Considerăm formula ce reprezintǎ un număr negativ în complement faţǎ de 2. Pentru a reprezenta un numǎr negativ în complement faţǎ de 1 complementǎm toate cifrele lui. Număr zecimal 15 -15 -19 19 Reprezentare binarǎ 0000 1111 1111 0000 1000 1100 0001 0011 Reprezentare hexazecimalǎ 0F F0 8C 13 Reprezentarea numerelor binare în complement faţǎ de 2 Numerele pozitive se reprezintǎ în complement faţǎ de 2 ca: X = 0 * 2 n −1 + ∑ ai 2 i i =0 n −2 Numerele negative se reprezintǎ în complement faţǎ de 2 ca: X = −1 * 2 n −1 + ∑ a i 2i + 1 i =0 n−2 unde: a i = 1 − ai . Exemple de numere reprezentate în complement faţǎ de 1 pe un octet.Gama numerelor întregi reprezentabile pe un octet în mǎrime şi semn este [-127.

Regula este evidentǎ din formula de reprezentare a numerelor pozitive.∑2 i =0 n −2 i + 1 = 2 n −1 n−2 i =0 n −2 i =0 In consecinţǎ. La deplasarea numărului binar bitul de semn rǎmane neschimbat. Exemple. Cazul numerelor pozitive. In acelaşi fel se aratǎ cǎ regula este valabilǎ pentru împǎrţirea cu 2. putem scrie: − 1 * 2 n −1 + ∑ (1 − ai )2 i + 1 = −1 * 2 n −1 + 2 n −1 − ∑ ai 2 i = −∑ ai 2 i i =0 n −2 A. • reamintim ca bitul de semn rǎmâne neschimbat.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. Numǎr zecimal -28 -14 -56 Numǎr binar 1110 0100 1111 0010 1100 1000 Numǎr hexazecimal E4 F2 C8 233 . Se deplaseazǎ toate cifrele numărului. iar cifrele adaugate sunt zerouri. Exemplu. Număr zecimal 28 14 56 Număr binar 0001 1100 0000 1110 0011 1000 Număr hexazecimalǎ 1C 0E 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. Fie numărul pozitiv X = 0 * 2 n −1 + ∑ ai 2 i i =0 n −2 şi fie numărul înmulţit cu 2 Y = 2 * X = 0 * 2 n −1 + ∑ bi 2 i i =0 n−2 de unde se deduce: bi +1 = ai b0 = 0 Am presupus cǎ prin înmulţirea cu 2 a numǎrului X nu apare depǎsire. Să deplasǎm numărul 28 cu o cifrǎ binarǎ la dreapta şi la stânga. • la deplasarea la dreapta cifrele adaugate sunt unuri.

Sǎ efectuăm adunarea: 12.35 0 0. Exemple de reprezentare a numerelor reale.48+0.(3) = 0.1283 2 Vom prezenta un exemplu în care se pierd cifrele cele mai puţin semnificative din rezultat.1248 *10 2 0.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).3333333 * 10 0 Pentru mǎrirea preciziei calculelor se cautǎ ca prima cifră din mantisǎ sǎ fie diferitǎ de zero (vezi exemplul de mai sus). în unele cazuri se pierd cifrele mai puţin semnificative din numǎr. Numerele în această formă se numesc normalizate.A. b este baza iar e este exponent sau caracteristicǎ. Aducerea numǎrului mai mic la exponentul celui mai mare poate duce la pierderea cifrelor cel mai puţin semnificative din mantisǎ.45 = 0.23755 * 10 3 234 ..035 = −0.35 * 10 −1 Mantisa f are totdeauna un număr finit de cifre şi în plus: f <1 0.55 + 0. Sǎ adunǎm numerele: 237.1248 2 iar al doilea 0.3333333333. Numǎrul 1 = 0.000425 Numerele sunt reprezentate astfel: 0. pentru exemplificare vom considera cǎ mantisa are şapte cifre semnificative. 0. Exemplu. Deoarece mantisa are un numǎr finit de cifre.. Exemplu.23755 3 0. 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.035 2 şi se adunǎ mantisele.35 Primul numǎr este reprezentat ca: 0.35 2 -1 In continuare.3245 -0.1248 2 0.3245 * 10 2 − 0.35 * 10 0 Pentru adunare se aduc numerele la acelaşi exponent 0. 3 se reprezintǎ ca: 0. 32. Pentru exemplificare vom considera baza zece.

La exponent se adaugǎ totdeauna 235 .425 -3 0. Pentru a nu avea douǎ semne.02345678 3 După scǎderea celor două numere obţinem: 0. Exemplu. Presupunem acum că în unitatea de calcul se lucreazǎ cu o mantisǎ cu opt cifre semnificative. Operaţiile se fac deci cu o mantisǎ mai lungǎ.1034567 3 0.425 * 10 −3 La adunare. cele mai puţin semnificative. mantisa celui de-al doilea numǎr este deplasatǎ la dreapta cu şase cifre. 127].2345678 2 Dacǎ mantisa are doar şapte cifre semnificative. Domeniul exponentului este [-127.2375504 3 Observǎm că s-au pierdut douǎ cifre.07999992 3 iar dupǎ normalizare obţinem rezultatul corect: 0. 63] se adaugǎ valoarea 63 astfel încât gama exponentului va fi [0.0. Fie de efectuat operaţia de scădere: 103.0234567 3 Rezultatul scăderii celor douǎ numere este: 0. In calculatoarele personale numerele reale se reprezintǎ în virgulǎ mobilǎ în felul urmǎtor R = semn * 1. 0. 0.mantisa * 2 exponent In reprezentarea în virgulǎ mobilă scurtǎ (simpla precizie) numǎrul se reprezinta pe 32 biţi. dacǎ gama exponentului este [-63.0000004 3 Rezultatul adunǎrii este: 0.08 3 şi dupǎ normalizare obtinem rezultatul 80. se adaugǎ un numǎr constant la exponent astfel ca sǎ fie totdeauna pozitiv. Dupǎ efectuarea unei operaţii aritmetice se cautǎ ca prima cifra din mantisǎ sǎ fie diferită de zero. Operaţia de scǎdere se face aducând numerele la acelaşi exponent şi scǎzând mantisele. dupǎ care se reţin şapte cifre din rezultat. când deplasǎm la dreapta cu o cifrǎ mantisa celui de-al doilea număr. La înmulţire se adunǎ exponenţii şi se înmulţesc mantisele.10345670 3 0. se pierd doua cifre semnificative din al doilea numǎr la aducerea la acelaşi exponent. unul pentru numǎr şi altul pentru exponent. La împǎrţire se scad exponenţii şi se împart mantisele. De exemplu. în unitatea de calcul mantisa mai are încǎ o cifrǎ. pentru o precizie maximǎ a rezultatului (normalizare). 126]. pierdem o cifră semnificativǎ.45678 Numerele se reprezintǎ astfel: 0. In acest caz cele douǎ numere sunt 0.23755 3 0.4567 – 23. Pentru creşterea preciziei operaţiilor. Considerând lungimea mantisei de şapte cifre.7999992 2 Mentionǎm cǎ exponentul poate fi pozitiv sau negativ.

1023]. Gama numerelor reale ce se pot reprezenta este (−10 307 . cifra 0 are codul hexazecimal 30. Domeniul exponentului este [-1023. De exemplu caracterul A are codul hexazecimal 41.254].10 307 ) A. In consecinţă. Exemple. Fie numărul R = 1. deci exponentul are domeniul [0. In reprezentarea în virgulǎ mobilă lungǎ (dublǎ precizie) numǎrul se reprezintă pe 64 biţi.4 Reprezentarea caracterelor Unul dintre sistemele de reprezentare a caracterelor este codul ASCII în care fiecare caracter este reprezentat pe un octet.0 * 2 −1 .0 * 2 0 . 2046]. Bitul de semn are valoarea 0 pentru numere pozitive şi 1 pentru cele negative. primii nouă biti din număr sunt 001111111 Numărul R va fi reprezentat în hexazecimal ca 3F800000. etc.valoarea 127 astfel încat exponentul are domeniul [0.5 = 1.0 = 1. La exponent se adaugǎ cantitatea 1023. Bitul de semn este zero iar exponentul este -1+127=126. Bitul de semn este bitul 63. caracterul a are codul hexazecimal 61. Fir numărul R = 0. Gama numerelor reale ce se pot reprezenta în acest format este (−10 37 . exponentul ocupǎ biţii 23-30 iar mantisa biţii 0-22. numărul este ± ∞ Dacǎ exponentul este 255 şi mantisa este diferitǎ de zero numărul este NaN (not a number). y\x 0 1 2 3 4 5 6 7 8 9 10 0 Nul Soh Stx Etx Eot Enq Ack Bel Bs Ht Lf 1 Dle Dc1 Dc2 Dc3 Dc4 Nak Syn Elb Can Em Sub 2 ! “ # $ % & ‘ ( ) * 3 0 1 2 3 4 5 6 7 8 9 : 4 A B C D E F G H I J 5 P Q R S T U V W X Y Z 6 a b c d e f g h i j 7 p q r s t u v w x y z 236 . Valoarea NaN apare atunci când efectuǎm urmatoarele operaţii ∞−∞ 0*∞ 0/0 ∞/∞ sau când un operand este NaN. Primii nouă biţi din număr sunt 001111110 Numărul se reprezintă în hexazecimal ca 3F000000. Bitul de semn este zero iar exponentul 0+127.10 37 ) iar mantisa corespunde la opt cifre zecimale. Menţionǎm cǎ dacǎ exponentul este 255 iar mantisa este 0. Bitul de semn ocupă bitul 31. Codurile ASCII ale caracterelor sunt prezentate în tabela de mai jos. exponentul ocupǎ biţii 52-62 iar mantisa ocupă biţii 0-51.

Un alt sistem de reprezentare a caracterelor este UNICODE.1 Codul ASCII Codul zecimal unui caracter este 16*x+y. 237 . De exemplu caracterul A are codul zecimal 65 sau.11 12 13 14 15 Vt Ff Cr So Si Esc Fs Gs Rs Us + . în varianta UNICODE16 fiecare caracter este reprezentat pe 2 octeţi. echivalent. / : < = > ? K L M N O [ \ ] ^ _ k l m n o { | } ~ del Tabelul 1. . codul hexazecimal 41.

Priorităţile şi asociativitatea operatorilor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Operator :: [ ] ( ) -> .Anexa 2. ++ -. Asociativitate stânga stânga dreapta dreapta stânga stânga stânga stânga stânga stânga stânga stânga stânga stânga dreapta dreapta stânga 238 .! ~ sizeof (tip) new delete * & (tip) */% +<< >> < <= > >= = = != & ^ | && || ?: = += -= *= /= %= <<= >>= &= | = ^= .+ .

Anexa 3. Tipul complex în limbajul C
Limbajul C defineşte tipul complex pentru lucrul cu numere complexe. Un număr complex este o pereche ordonată de două numere reale. In limbajul C vom avea numere complexe constituite din două numere reale tip float şi separat, numere complexe constituite din două numere reale tip double. Un număr complex (o constantă complexă) are forma a+b*I unde a şi b sunt numere reale sau întregi ce definesc numărul complex. Forma instrucţiunilor de declarare a variabilelor de tip complex este complex double listă de variabile; complex float listă de variabile; Lista de variabile este formată din nume de variabile separate de virgule. Putem defini, de exemplu, variabilele de tip complex următoare complex float x, y, z ; complex double m, n, p ; Variabilele de tip complex pot fi iniţializate la definirea lor. La iniţializare se utilizează constante complexe cu forma de mai sus sau cu forma {a + b * I} unde a şi b sunt cele două numere reale ce definesc constanta complexă. Exemplu. Să definim variabilele complexe x = 2.35 – 7.4i şi y = -12.5 + 4.3i. Instrucţiunea de definire a acestor variabile este complex double x = 2.35 – 7.4 * I, y = -12.5 + 4.3 * I ; sau complex double x = {2.35 – 7.4 * I}, y = {-12.5 + 4.3 * I} ; Operaţiile cu numere complexe sunt cele cunoscute, +, -, * şi /. Priorităţile şi asociativitatea operatorilor sunt tot cele cunoscute. Expresiile se scriu după regulile cunoscute. In scrierea expresiilor se pot utiliza ca termeni variabile şi numere întregi, reale şi complexe. Scrierea numerelor complexe se face cu instrucţiunea printf, în care vom defini doi specificatori de conversie pentru fiecare număr complex, primul pentru partea reală, al doilea pentru partea imaginară. Pentru lucrul cu numere complexe trebuie să includem biblioteca antet <complex.h>. Ca prim exemplu, definim două variabile complexe, d1 şi d2, şi calculăm expresiile d1 * d2 şi d1 / d2 + 1. Variabila d1 este iniţializată la definirea di, în instrucţiunea complex, la valoarea 2.35 – 3.45i, variabila d2 este iniţilizată în program la valoarea 4.3 - 2.5i -3.57 + 2.95i. Programul este următorul. #include <stdio.h> #include <stdlib.h> #include <complex.h> int main(int argc, char *argv[]) { // definirea de variabile complexe complex double d1 = 2.35 - 3.45 * I, d2, d3; d2 = 4.3 - 2.5 * I -3.57 + 2.95 * I; d3 = d1 * d2; 239

printf(" d1 = %6.2f %6.2f\n d2 = %6.2f %6.2f\n d1 + d2 = %6.2f %6.2f\n", d1, d2, d3); d3 = d1 / d2 + 1; printf(" d1 = %6.2f %6.2f\n d2 = %6.2f %6.2f\n d1 / d2 + 1 = %6.2f %6.2f\n", d1, d2, d3); return 0; } Rezultatul rulării programului este cel de mai jos.

Ca un alt exemplu, definim două variabile complexe, a1 şi a2, calculăm produsul lor, a3=a1*a2 şi afişăm rezultatul pe ecran. Programul este următorul. #include <stdio.h> #include <stdlib.h> #include <complex.h> int main(int argc, char *argv[]) { complex double a1 = {2.75 + 3.2 * I}, a2 = {-1 + 3.42 * I}, a3; printf("a1 = (%6.2f %6.2f)\n", a1); printf("a2 = (%6.2f %6.2f)\n", a2); a3 = a1 * a2; printf("a1 * a2 = (%6.2f %6.2f)\n", a3); return 0; } Rezultatul rulării programului este cel de mai jos.

Biblioteca <complex.h> include funcţiile uzuale pentru lucrul cu numere complexe. Există funcţii separate pentru numere complexe ce constau din două numere de tip double şi respective din numere de tip float. Funcţiile pentru operaţiile cu numere complexe formate dintr-o pereche de două numere tip double sunt următoarele: 240

creal cimag carg cabs conj

csin ccos casin cacos catan

cexp clog csqrt cpow

Toate funcţiile de mai sus au un argument număr complex, cu excepţia funcţiei cpow ce are două argumente, numere complexe formate dintr-o pereche de două numere tip double. Funcţiile pentru operaţiile cu numere complexe cu sunt formate dintr-o pereche de două numere tip float au numele de mai sus urmate de litera f. Vom exemplifica utilizarea acestor funcţii calculând −1 , apoi partea reală, partea imaginară, argumentul şi modulul numărului complex 2.46 -6.25i. Programul este cel de mai jos. #include <stdio.h> #include <stdlib.h> #include <complex.h> int main(int argc, char *argv[]) { // complex double a = {-1 + 0 * I}, b, c = {2.46 - 6.25 * I}; complex double a = -1 + 0 * I, b, c = 2.46 - 6.25 * I; double cr, ci; // calcul sqrt(-1) b = csqrt(a); // b = csqrt(-1); // b = csqrt(-1 + 0 * I); printf("a = (%6.2f, %6.2f)\n", a); printf("b = (%6.2f, %6.2f)\n", b); // calcul partea reala si imaginara a unui numar complex cr = creal(c); ci = cimag(c); printf("c = (%6.2f, %6.2f)\n", c); printf("real(c) = %6.2f, imag(c) = %6.2f\n", cr, ci); // calcul argumentul si modulul unui numar complex cr = carg(c); ci = cabs(c); printf("arg(c) = %6.2f, abs(c) = %6.2f\n", cr, ci); return 0; } In program am iniţializat variabilele folosind cele două forme arătate mai sus. Calculul valorii −1 se poate face cu una din formele b = csqrt(a); b = csqrt(-1); b = csqrt(-1 + 0 * I); 241

unde variabila complexă a este iniţializată la valoarea -1. Rezultatul rulării programului este cel de mai jos.

In exemplul următor vom citi un număr complex x de la tastatură şi vom calcula x2 şi xi. Vom utiliza numere complexe formate din două numere reale tip float. Vom citi pertea reală şi cea imaginară cu instrucţiunea scanf. Pentru ridicarea la putere vom utiliza funcţia cpow. Programul este următorul. #include <stdio.h> #include <stdlib.h> #include <complex.h> int main(int argc, char *argv[]) { complex float x, y; float xr, xi; scanf("%f %f", &xr, &xi); x = xr + xi * I; printf("x = %f %f\n", crealf(x), cimagf(x)); y = cpow(x, 2); printf("x ^ 2 = %f %f\n", crealf(y), cimagf(y)); y = cpow(x, 1 * I); printf("x ^ I = %f %f\n", crealf(y), cimagf(y)); return 0; } Rezultatul rulării programului este cea de mai jos.

Vom exemplifica acum calculul valorii, modulului şi argumentului funcţiei de o variabilă complexă 10 ( 0.05 s + 1) f ( s) = ( s + 1)( 0.01s + 1) (0.01 2 s 2 + 0.01s + 1) pentru s luând valori în intervalul [0, 14i] cu pasul 0.5i. Programul este cel de mai jos.

242

2f %6. imagft = cimag(ft). argft). // s = i * 0. realft.5 * I). realft = creal(ft).01 * s + 1).01 * s * s + 0. printf("%6. absft. imags = cimag(s). imags. absft.2f %6. 243 . pas = {0. ft = 10 * (0.#include <stdio. for(i = 1. char *argv[]) { complex float ft. float realft.01 * s + 1) / (0.h> #include <complex.5 * I. int i. imagft.2f %6. argft. // s = i * (0. } Se va remarca modul de calcul al variabilei complexe s. imags.h> int main(int argc.01 * 0. absft = cabs(ft). i <= 28.2f %6. printf(" omega real imag abs arg\n").2f \n". argft = carg(ft). i++) { s = i * pas. complex float s. Rezultatul rulării programului este cel de mai jos.05 * s + 1) / (s + 1) / (0. } return 0.h> #include <stdlib.5 * I}. imagft.

244 .