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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

respectiv sau (or).Rezultatul rulării programului este prezentat în continuare. In final reamintim că operatorul = este operator de atribuire. Aceşti operatori se definesc folosind tabelele de adevăr. 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. 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.3 Expresii booleene Operatorii booleeni ai limbajelor C şi C++ sunt. 2. 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 . şi (and). în ordinea priorităţilor ! && | | care reprezintă operatorii nu (not). Operatorul nu este operator unar.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Definiţia clasei este următoarea # include <iostream> using namespace std; template <class T> class X { private: T a; public: X(T b) {a = b;} T square() {return a * a;} T geta() {return a;} }; Să definim obiecte de tipul X, ce conţin elemente de tip int sau double, utilizând clasa generică X. int main() { // crează un obiect cu şablonul <int> X<int> n(2); cout << "patratul valorii " << n.geta() << " este " << n.square() << endl; // crează un obiect cu şablonul <double> X<double> d(3.14); cout << "patratul valorii " << d.geta() << " este " << d.square() << endl; return 0; } Rezultatul rulării programului este cel de mai jos.

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

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

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

container. Operaţiile efectate asupra iteratorilor, indiferent de tipul containerului, sunt următoarele : • iniţializarea iteratorului la un anumit obiect din container, • furnizarea obiectului de la poziţia curentă a iteratorului, • modificarea obiectului de la poziţia curentă a iteratorului, • avansarea iteratorului la poziţia următoare din container. Clasele ce definesc iteratori au următorii operatori, ce realizează operaţiile de mai sus asupra iteratorilor : • operatorii ++ şi -- modifică iteratorul la următoarea / precedenta poziţie din container, • operatorii relaţionali, = =, !=, <, <=, >, >=, permit compararea a doi iteratori, • operatorul de adresare indirectă, *, permite adresarea sau modificarea obiectului de la poziţia curentă a containerului indicată de iterator. In cele ce urmează vom prezenta vectorii, listele şi numerele complexe. Biblioteca de şabloane standard defineşte funcţii ce se pot aplica asupra containerelor. Cele mai importante funcţii sunt sort(), ce sortează elementele containerului, şi find(), care caută un element în container. Aceste funcţii vor fi prezentate pentru fiecare tip de container.

14.3 Vectori
Clasa generică vector implementează vectori cu elemente de un anumit tip. Un obiect de tip vector are un număr iniţial de componente şi dimensiunea lui creşte dacă este nevoie. Clasa vector are ca parametru tipul componentelor vectorului. Ea este definită în biblioteca <vector>. Clasa defineşte următorii constructori : • constructorul implicit (fără parametri) vector(); crează un vector vid, • constructorul copiere vector(const vector& p); crează un vector în care copiază elementele vectorului p care este parametru, • constructor ce crează un vector cu n elemente vector(int n); Clasa defineşte destructorul ~vector() ; Fie T tipul componentelor vectorului (parametrul clasei generice). Clasa defineşte următoarele funcţii : • void push_back(const T&); adaugă un element la sfârşitul vectorului, • void pop_back(); şterge ultimul element al vectorului, • int size(); dă numărul de componente ale vectorului, • bool empty(); are valoarea true dacă vectorul este vid, • operatorul [] şi funcţia T& at(int) selectează un element al vectorului, • T& front(); are ca rezultat primul component al vectorului, • T& back();are ca rezultat ultimul component al vectorului, • void clear(); şterge toate componentele vectorului, • operatorul = este operatorul de atribuire, • operatori de comparare a doi vectori , <, <=, = =, >, >=, != , element cu element.

207

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

Menţionăm că, expresia v[k], putea fi înlocuită cu expresia v.at(k). La definirea obiectului v, se crează un vector vid. După execuţia instrucţiunilor push_back(), vectorul este următorul

şi are dimensiunea 4. Menţionăm că, vectorul are un element fictiv înaintea primului element şi alt element fictiv după ultimul element. In exemplul următor comparăm doi vectori, vec1 şi vec2. #include <cstdlib> #include <iostream> #include <vector> using namespace std;

208

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

228 .

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

q 0 = N 2. Pentru conversii de numere între bazele 2. ce cuprinde 8 cifre binare (biţi). Exemplu. pentru reprezentarea în baza 8 se grupează câte 3 cifre binare. Conversia din baza 10 în baza 8 sau 16 se face prin împǎrţiri repetate cu 8 şi respectiv 16. pentru reprezentarea sa în baza 16 se grupează câte 4 cifre binare. 16. Numerele întregi se reprezintǎ în calculator pe 8. (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. 32 sau 64 de biţi. Având un numǎr în baza 2. primul rest fiind cifra cea mai puţin semnificativǎ. 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. 10 şi 16 şi operaţii cu 230 . Conversia unui numǎr din baza 8 în baza 2 se face convertind fiecare cifrǎ octalǎ prin 3 cifre binare. Exemplu. 8. ( 28) 10 = (11100) 2 = ( 34) 8 Pentru verificare ( 34) 8 = (3 * 81 + 4)10 = ( 28) 10 Conversia unui număr din baza 16 în baza 2 se face reprezentând fiecare cifrǎ hexazecimalǎ prin 4 cifre binare. Având un număr în baza 2.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. i = 0 3.

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

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

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

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

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

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

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

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

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

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

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

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

creal cimag carg cabs conj

csin ccos casin cacos catan

cexp clog csqrt cpow

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

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

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

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

242

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

244 .