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

2 Variabile Numele unei variabile este format din cifre. Tipul unei variabile este făcut explicit cu o declaraţie de tip. litere şi caracterul _ (underscore). Literele mari sunt diferite de cele mici. variabile şi expresii 1. primul bit este utilizat pentru semn. not Pentru fiecare tip există constante predefinite ce dau limitele minimă şi maximă ale domeniului de valori. pentru tipul întreg se adaugă restul împărţirii a două numere întregi. Diagrama sintactică este cea de mai jos. Tipurile predefinite în limbajul C++ sunt cele din tabelul 1.* /. 5 . In cazul tipului bool valoarea false este reprezentată prin zero iar true prin unu. simbolizat %. 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. Menţionăm că. De exemplu abc şi Abc sunt două nume de variabile diferite. operaţiile tipului real sunt + . şi începe totdeauna cu o literă sau caracterul _. pentru tipurile char şi int. 1.Partea I Programarea procedurală 1 Constante. Instrucţiunea de declarare a tipului are forma tip listă de variabile.1 Tipuri fundamentale In matematică variabilele se clasifică după tipul lor. Tabelul 1. ceilalţi biţi sunt biţi ai numărului. or. Lista de variabile este formată din nume de variabile separate de virgule. 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.true 16 7 7 31 31 Operaţii +-*/% +-*/ +-*/ +-*/% +-*/% and.

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

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

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

enum CLRX {aaa. defineşte tipul CLRX şi trei constante aaa. 1. 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 . 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.14 • enumerări. Directiva define ce defineşte o constantă are forma # define nume valoare unde valoare este interpretată de preprocessor ca un şir de caractere. ccc}. directiva # define PI 3. Constante booleene constante booleene sunt true şi false şi se reprezintă prin valorile unu şi respective zero. bbb. Ele se definesc sub forma L’\xhhhh’ unde h reprezintă o cifră hexazecimală. Preprocesorul citeşte diversele directive şi modifică programul sursă corespunzător acestora. Compilatoarele limbajelor C şi C++ au un preprocessor care modifică programul sursă înainte de compilare. De exemplu.14 are ca efect înlocuirea numelui PI în program cu şirul de caractere 3. bbb.• constante tip caracter UNICODE corespund tipului wchar_t. O enumerare defineşte constante întregi sau un tip întreg şi valorile asociate acelui tip.6 Constante cu nume In program putem defini constante cu nume în următoarele feluri • utilizarea directivei define .

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

Funcţia pow are prototipul double pow(double a. se poate schimba doar semnul câtului şi restului. Exemple de expresii aritmetice şi scrierea lor sunt prezentate mai jos. Apelarea unei funcţii se face scriind numele funcţiei ca termen într-o expresie urmat în paranteze de parametrii actuali. iar în cazul operanzilor de tip întreg. Operatorii sunt + .7 Expresii aritmetice Expresiile aritmetice sunt formate din constante.* şi / pentru operanzi reali. ( şi ). Toate funcţiile de mai sus au argumente de tip double şi rezultatul de tip double. Pentru gruparea termenilor se folosesc paranteze rotunde. 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). Restul împărţirii a două numere întregi.1. Vom presupune că variabilele din aceste expresii au fost declarate în prealabil de tip double şi au primit valori. şi % (restul împărţirii a două numere întregi). variabile şi funcţii. 11 . a şi b. De exemplu. iar funcţia ceil(x) calculează valoarea  x  (cel mai mic număr întreg mai mare ca x). 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. double b) şi calculează expresia ab. şi nu valoarea lor absolută.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

j = j + 1. d = x[i++]. i = i + 1. expresia b++ are valoarea 3 (valoarea neincrementată a variabilei) şi apoi se incrementează b. Avem rezultatul a=3 b=4 Exemplu. Fie secvenţa de instrucţiuni de mai jos : int i. -3.a = b. In consecinţă. double x[4] = {2. cout << “j = “ << j << endl. 14. Exemplu. 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.1 şi j=2 26 .4 şi i=2 Fie acum secvenţa de instrucţiuni următoare : int j. cout << “j = “ << j++ << endl. j = 1. cout << “j = “ << j << endl. d = x[++j]. double d. Expresia i++ are valoarea 1 şi apoi se incrementează i. double d. Instrucţiunea d = x[++j]. corespunde secvenţei de instrucţiuni : j = j + 1.4e1. i = 1. -3. Avem deci d = 14.4e1. 14.4.1 0}. corespunde secvenţei de instrucţiuni : d = x[i].4. Instrucţiunea d = x[i++]. d = x[j].1 0}. cout << endl. Fie instructiunile : int j = 1. In consecinţă expresia ++j are valoarea 2 şi avem d = -3. double x[4] = {2. b = b + 1.

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

# include <iostream> using namespace std. Reamintim că. return 0. // deplasează variabila a la stanga cu un bit x = x << 1. 1.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. // deplasează variabila b la dreapta cu 3 pozitii b = b >> 3. 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ă. /* uitlizarea operatorilor de deplasare */ int main() { int x = 10. cout << “b = “ << dec << b << “ “ << hex << showbase << b << endl.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 .16. pentru scrierea sau citirea unui număr întreg în instrucţiunile cout sau cin în diferite baze. cout << “x deplasat la stanga cu 1 bit = “ << dec << x << “ “ << hex << showbase << x << endl. Vom încheia acest paragraf cu un program care deplasează numere întregi şi afişază valoarea lor în zecimal şi hexazecimal. int b = 50. cout << “b deplasat la dreapta cu 3 biti = “ << dec << b << “ “ << hex << showbase << b << endl. cout << “x = “ << dec << x << “ “ << hex << showbase << x << endl. } Rezultatele rulării programului sunt prezentate în figura de mai jos.

| ^ sunt operatori binari. b. Expresiile cu acest operator au forma următoare Operatorul ~ complementează fiecare bit din expresia întreagă. Fie următoarea secvenţă de program : int a. Operatorii &. la calculul valorii c = a & b.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 &. b = 0xabcd. c. 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. d = a | b. a = 0xf000. e. d. | şi ~ sunt operatori aritmetici. Rezultatele sunt c = 0xa000 d = 0xfbcd e = 0x5bcd In primul caz. 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 . Exemple. e = a ^ b. c = a & b. vom exemplifica doar calculul expresiei 0xf & 0xa = 0xa Rezultatul se obţine conform calculului de mai jos. Operatorul ~ este operator unar.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

j++) cout << setw(6) << a[i][j] << " ". j < NCOL. for(i = 0. i < NLIN. return. rdmat(y). } /* 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. char *argv[]) { double x[NLIN][NCOL]. j < NCOL. cout << endl. } return. j. rdmat(x). j++) c[i][j] = a[i][j] + b[i][j]. z[NLIN][NCOL]. i < NLIN. j. cout << "introduceti a doua matrice pe linii" << endl. // citeste elementele matricelor x si y cout << "introduceti prima matrice pe linii" << endl. i++){ for(j = 0. */ void addmat(double a[NLIN][NCOL]. i++) for(j = 0. 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. for(i = 0. y[NLIN][NCOL]. 63 . double b[NLIN][NCOL]. } /* program pentru testarea functiilor */ int main(int argc.

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

double b). double min(double. cout << "min " << a << ". // functia main() int main() { double a = 68. return (x < c? x : c).5. # include <iostream> using namespace std. c) << endl. b. b) << endl. b). } Rezultatul programului este cel de mai jos. double y) { return (x < y? x: y)." << b << ". return 0. b. return 0. respectiv trei numere reale după funcţia main. double. Vom declara mai întâi prototipurile celor două funcţii şi vom defini funcţia main. } // calculul minimului a trei numere reale double min(double a. b = -77. 65 . c) << endl.<< min(a. double)." << b << " este " << min(a.3. c = 32. 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. double b. double).29. cout << "min " << a << ". // calculul minimului a doua numere reale double min(double x. " << c << " este " << min(a. } Vom defini acum cele două funcţii. double c) { double x = min(a. // defineste prototipurile celor doua functii min() double min(double.

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

b = “ << b << endl. şi atunci când acest parametru este modificat în corpul funcţiei el este modificat direct în programul apelant (este parametru de ieşire). b = “ << b << endl. 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. } int main() { int a = 20. b). 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 . la apelarea funcţiei se va pune în stivă adresa acestui parametru. b = “ << b << endl. b = 30. f(a. int& y) { x = 52. // functie ce modifica valorile parametrilor // primul parametru este transmis prin valoare. doarece nu este transmis prin referinţă. return. f(2*a + 3. } Rezultate afişate sunt cele de mai jos. return 0. cout << “a = “ << a << “ . al doilea prin adresa # include <iostream> using namespace std. Primul parametru va fi transmis prin valoare iar celălalt prin adresă. y = 65. cout << “a = “ << a << “ . void f(int x.Dacă un parametru al funcţiei este tip referinţă. b). cout << “a = “ << a << “ . Menţionăm că primul parametru se modifică doar în stivă şi nu în programul apelant.

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

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

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

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

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

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

Fie de exemplu instrucţiunea int a. 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). O instrucţiune de atribuire de forma a = b + c. şi se memorează conţinutul registrului acumulator la adresa variabilei a (în cazul nostru adresa 1000). 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. In cazul unui tablou se alocă memorie fiecărui component al tabloului.1 Pointeri Un pointer este o variabilă ce conţine adresa unei alte variabile. dacă n este o variabilă de tip int. 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. După cum vom vedea. Compilatorul alocă zone de memorie variabilelor ca mai jos Compilatorul crează o tabelă cu numele variabilelor şi adresele lor în memorie. Limbajele C şi C++ permit definirea unor variabile ce conţin adrese ale altor variabile. b. aceste variabile se utilizează la transmiterea parametrilor funcţiilor prin adresă. ci doar adrese. Acestea au tipul pointer şi referinţă. iar pn este o variabilă de tipul pointer la int. Pointerii sunt utilizaţi şi la prelucrarea tablourilor. 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). ce are valoarea 3. O instrucţiune de atribuire de forma a=b. 4. De exemplu. parametrii transmişi prin adresă pot fi parametri de ieşire ai funcţiilor. putem reprezenta cele două variabile astfel 74 . c. ce conţine adresa lui n. In programul generat de compilator nu există nume de variabile.

expresia &x este adresa lui x. Variabilele tip pointer pot fi iniţializate doar cu adrese. 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. int * pn.4. *pk. (Variabila pn a fost iniţializată în prealabil cu adresa lui n). De exemplu. De exemplu int * pn. Dacă x este o variabilă oarecare. *pv. Putem scrie de exemplu int n. n= 14. Imaginea memoriei este următoarea Tabela cu adrese creată de compilator poate fi k n pn pk Instrucţiunile k = n. 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 .1. Pentru a calcula adresa unei variabile se utilizează operatorul &. // initializeaza pn cu adresa lui n Variabilele de tip pointer pot fi iniţializate la declararea lor. şi pn = &n. k = *pn. putem scrie int n. sunt echivalente. defineşte o variabilă tip pointer la int ce poate conţine adresa unei variabile de tip int. int * pn. Instrucţiunea int u. Fie de exemplu instrucţiunile int k. defineşte o variabilă u de tip int şi o variabilă pv de tip pointer la int. k primeşte valoarea 14.1 Declararea variabilelor tip pointer O variabilă de tip pointer se defineşte cu instrucţiunea tip * nume. *pn = &n.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Pointerii sunt adresele unor şiruri de caractere ce corespund argumentelor liniei de comandă. 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 .9 Alocarea dinamică a memoriei Alocarea memoriei unei variabile scalare sau tablou se face astfel : • de către compilator. Ca exemplu fie un program ce tipăreşte argumentele liniei de comandă. Parametrii liniei de comandă Prototipul funcţiei main() utilizat până acum este int main(). iar argc este dimensiunea acesui vector. 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.4. i < argc. 4. int main(int argc. 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. de la linia de comandă  aplicatie. return 0. char * argv[]). char * argv[]) { int i. for(i = 0. ++i) cout << argv[i] << endl.8 Parametrii funcţiei main. Funcţia main poate avea şi următorul prototip int main(int argc. } Un mod de a furniza parametri liniei de comandă este de a rula programul într-o fereastră DOS. Primul şir de caractere este chiar numele programului.exe parametru1 parametru2 … parametrun Alt mod este de a introduce aceşti parametri într-o casetă de dialog a mediul de programare.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

i += 2) { // pozitioneaza indicatorul fisierului in raport cu inceputul fisierului fseek(fil. fputs(“\n”. // citeste un sir fread(x.return EXIT_FAILURE. 121 . Rezultatul rulării programului este cel de mai jos. } printf("fisierul citit\n"). 10. // scrie sirul pe ecran (in fluxul stdout) fputs(x. SEEK_CUR). SEEK_SET). (long)(i * 10). stdout). } // inchide fisierul fclose(fil). } 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. return EXIT_SUCCESS. stdout). // citeste sirurile pare for(i = 0. (long)(10). i < 15. fil). 1. în loc de a-l poziţiona în raport cu începutul fişierului.

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

int main() { struct person p1.nume). printf("varsta : %d\n". int varsta. strcpy(p1. "popescu").h> typedef struct { double cmpa. return 0.h> struct person { char nume[64].h> #include <string. p1.#include <stdio. p2. char prenume[64]. vector y) { 123 . p2. strcpy(p1. p2. vector addcmp(vector x. p2.prenume. p2 = p1. Reamintim că. } Rezultatul rulării programului este prezentat mai jos.nume. Vom declara variabile de acest tip în funcţia main şi în funcţia ce adună vectorii. double cmpb. } vector.varsta = 50.prenume). Funcţia va avea ca parametri două structuri şi ca rezultat o structură. "ioan"). 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. Vom construi o funcţie care să adune doi vectori. # include <stdio. printf("nume : %s\n". }. 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.varsta). printf("prenume : %s\n".

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

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

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

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

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

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

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

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

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

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

putem defini următorul spaţiu de nume namespace general { int a. Toate clasele. • Putem utiliza directiva using pentru a declara doar anumite simboluri din spaţiul de nume #include <iostream> using std::cout.De exemplu. cout << endl.2 Constructori şi destructori 7. 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. în general. Clasa precedentă avea un constructor Line (int. obiectele şi funcţiile din bibliotecile C++ standard sunt definite în spaţiul std. } 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. De exemplu. Această directivă are forma using namespace identificator. 7. Constructorul are acelaşi nume cu al clasei şi nu are nici un tip. using std::endl. b. • Utilizând numele spaţiului # include <iostream> …………………………………… std::cout << std::endl. obiectele trebuie să iniţializeze variabilele lor la declararea obiectului. limbajul are o sintaxă specială numită listă de iniţializatori. Deoarece constructorii au. obiectul cout corespunzător streamului de ieşire standard poate fi utilizat astfel: • Folosind directiva using namespace # include <iostream> using namespace std. De exemplu. int).2. rolul de a iniţializa variabilele 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. …………………………………… cout << endl. vezi Anexa 2.1 Constructori In general. Constructorul anterior se poate scrie 134 .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

trebuie ca în definiţia clasei să includem un constructor implicit. 154 . care are ca parametru un obiect de tipul clasei. Reamintim că. fără parametri. de exemplu cu definiţia Line() {m = 0. b. c. orice clasă include un operator implicit de atribuire =.Reamintim că. 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.

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

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

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

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

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

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

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

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

în clasele derivate şi în obiectele de tipul clasei sau al claselor derivate. dar nu în obiecte de tipul clasei Baza sau clase derivate. clasa derivată moşteneşte toţi membrii clasei de bază cu nivelul de acces avut în clasa de bază. }. 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. Fie definiţia unei clase numită Baza class Baza { public: int x. 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 . protected: int y. 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. La utilizarea specificatorului de acces public.membrii publici ai clasei pot fi utilizaţi şi în afara clasei. Clasa derivată moşteneşte membrii publici şi pretejaţi ai clasei de bază. Variabila v poate fi utilizată doar în interiorul clasei Baza. Variabila x poate fi utilizată în clasa Baza. • 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ă. • 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 : int v . Membrii publici şi protejaţi ai clasei de bază apar ca şi cum ar fi declaraţi în clasa derivată. 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. în clasele derivate şi în obiecte de tipul clasei Baza sau clase derivate din Baza.

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

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

int). } Point::Point(int p1. Un pixel este caracterizat de coordonatele sale şi de culoare. y = p2.class Point { public: int x. Clasa Pixel va moşteni coordonatele punctului din clasa Point. Point(int. void Point::clear() { x = 0. y. Culoarea va fi un câmp de tip întreg. Point(){x = 0.” << “ y = “ << y << endl. }. int p2) { x = p1. void clear(). 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 .} void print(). } Definim acum o clasă Pixel care să descrie un pixel pe ecran. y = 0. } void Point::print() { cout << “ x = “ << x << “. y = 0.

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

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

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

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

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

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

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

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

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

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

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

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

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

cout << "produsul numerelor " << x << " si " << y << endl. Apoi utilizează metoda oper() a obiectului la calculul sumei a două numere.Presupunem că vrem să calculăm suma a două numere. '+'). Base * base. // creaza un obiect care sa calculeze produsul a doua numere base = cf.operfactory(x.2. y.35. cout << "suma numerelor " << x << " si " << y << endl. cout << z << endl. return 0. int main() { float z. float x = 1. 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ă. 180 . ClassFactory cf. y.operfactory(x. cout << z << endl. '*'). } Rezultatul rulării programului este cel de mai jos. // calculeaza suma numerelor z = base->oper(). // creaza un obiect care sa calculeze suma a doua numere base = cf. // calculeaza produsul numerelor z = base->oper().

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

228 .

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

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

In final prezentăm în tabela de mai jos numerele de la 0 la 15 reprezentate în bazele 10. bitul cel mai semnificativ este bitul de semn. 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. 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 . El este 0 pentru numere pozitive şi 1 pentru numere negative. 8 şi 16. 2. Există trei reprezentǎri ale numerelor binare cu semn. Vom considera numere întregi reprezentate pe 8 biţi. 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.numere în aceste baze se poate folosi aplicaţia Calculator a sistemului de operare Windows.

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. 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 2 pe un octet. 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. Considerăm formula ce reprezintǎ un număr negativ în complement faţǎ de 2. Avem relaţia: 232 . 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 . Pentru a reprezenta un numǎr negativ în complement faţǎ de 1 complementǎm toate cifrele lui. 127]. 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. Primul bit va fi interpretat ca şi coeficientul lui − 2 n −1 .

iar cifrele adaugate sunt zerouri. Numǎr zecimal -28 -14 -56 Numǎr binar 1110 0100 1111 0010 1100 1000 Numǎr hexazecimal E4 F2 C8 233 . Regula este evidentǎ din formula de reprezentare a numerelor pozitive. • la deplasarea la dreapta cifrele adaugate sunt unuri. 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.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. In acelaşi fel se aratǎ cǎ regula este valabilǎ pentru împǎrţirea cu 2. Exemplu. La deplasarea numărului binar bitul de semn rǎmane neschimbat. Se deplaseazǎ toate cifrele numărului. 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 i =0 n −2 i + 1 = 2 n −1 n−2 i =0 n −2 i =0 In consecinţǎ. Exemple. 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. Cazul numerelor pozitive.

Aducerea numǎrului mai mic la exponentul celui mai mare poate duce la pierderea cifrelor cel mai puţin semnificative din mantisǎ. Deoarece mantisa are un numǎr finit de cifre.1248 *10 2 0.3245 -0. 3 se reprezintǎ ca: 0.3333333333. Exemplu. Sǎ efectuăm adunarea: 12. 0. b este baza iar e este exponent sau caracteristicǎ.A.48+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.35 * 10 0 Pentru adunare se aduc numerele la acelaşi exponent 0. Exemplu.23755 3 0.1283 2 Vom prezenta un exemplu în care se pierd cifrele cele mai puţin semnificative din rezultat.(3) = 0.35 * 10 −1 Mantisa f are totdeauna un număr finit de cifre şi în plus: f <1 0.55 + 0.35 2 -1 In continuare.035 2 şi se adunǎ mantisele.23755 * 10 3 234 .035 = −0.. pentru exemplificare vom considera cǎ mantisa are şapte cifre semnificative. Numǎrul 1 = 0.1248 2 iar al doilea 0. Numarul real R este pus sub forma R = f * be unde: f este un număr subunitar (mantisa). 32.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).35 Primul numǎr este reprezentat ca: 0.1248 2 0. Numerele în această formă se numesc normalizate. Sǎ adunǎm numerele: 237.35 0 0.000425 Numerele sunt reprezentate astfel: 0.45 = 0.3 Reprezentarea numerelor reale Numerele reale se reprezintǎ în calculator în virgulǎ mobilǎ. Pentru exemplificare vom considera baza zece.. Exemple de reprezentare a numerelor reale. în unele cazuri se pierd cifrele mai puţin semnificative din numǎr.3245 * 10 2 − 0.

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

Valoarea NaN apare atunci când efectuǎm urmatoarele operaţii ∞−∞ 0*∞ 0/0 ∞/∞ sau când un operand este NaN.10 37 ) iar mantisa corespunde la opt cifre zecimale.0 * 2 −1 . exponentul ocupǎ biţii 23-30 iar mantisa biţii 0-22. 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 . Primii nouă biţi din număr sunt 001111110 Numărul se reprezintă în hexazecimal ca 3F000000. In reprezentarea în virgulǎ mobilă lungǎ (dublǎ precizie) numǎrul se reprezintă pe 64 biţi.0 = 1. Menţionǎm cǎ dacǎ exponentul este 255 iar mantisa este 0. numărul este ± ∞ Dacǎ exponentul este 255 şi mantisa este diferitǎ de zero numărul este NaN (not a number). Exemple. caracterul a are codul hexazecimal 61.5 = 1. Bitul de semn este zero iar exponentul 0+127.4 Reprezentarea caracterelor Unul dintre sistemele de reprezentare a caracterelor este codul ASCII în care fiecare caracter este reprezentat pe un octet.10 307 ) A. De exemplu caracterul A are codul hexazecimal 41. cifra 0 are codul hexazecimal 30. 2046]. Bitul de semn este zero iar exponentul este -1+127=126. Bitul de semn are valoarea 0 pentru numere pozitive şi 1 pentru cele negative.254]. In consecinţă. exponentul ocupǎ biţii 52-62 iar mantisa ocupă biţii 0-51. 1023].0 * 2 0 . Fir numărul R = 0. Codurile ASCII ale caracterelor sunt prezentate în tabela de mai jos. Gama numerelor reale ce se pot reprezenta în acest format este (−10 37 . La exponent se adaugǎ cantitatea 1023. Bitul de semn este bitul 63. Bitul de semn ocupă bitul 31. etc. Gama numerelor reale ce se pot reprezenta este (−10 307 .valoarea 127 astfel încat exponentul are domeniul [0. Domeniul exponentului este [-1023. deci exponentul are domeniul [0. Fie numărul R = 1. primii nouă biti din număr sunt 001111111 Numărul R va fi reprezentat în hexazecimal ca 3F800000.

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

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

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

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

244 .

Sign up to vote on this title
UsefulNot useful