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 .............................................................................................. 10
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 - - ........................................................................................... 23
1.16 Operaţii cu numere întregi la nivel de bit ........................................................ 26
1.16.1 Operatori de deplasare ............................................................................... 26
1.16.2 Operaţii logice la nivel de bit .................................................................... 28
2 Structuri de control fundamentale ............................................................................. 31
2.1 Algoritme ............................................................................................................ 31
2.2 Expresii relaţionale ............................................................................................. 32
2.3 Expresii booleene ............................................................................................... 33
2.4 Operatorul if ....................................................................................................... 34
2.5 Operatorul switch ............................................................................................... 37
2.6 Operatorul ? ........................................................................................................ 38
2.7 Operatorul while ................................................................................................. 38
2.8 Operatorul do-while ........................................................................................... 41
2.9 Operatorul for ..................................................................................................... 42
2.10 Operatorul , ...................................................................................................... 46
3 Funcţii ........................................................................................................................ 48
3.1 Funcţii standard .................................................................................................. 48
3.2 Definirea funcţiilor ............................................................................................. 49
3.3 Prototipuri de funcţii .......................................................................................... 51
3.4 Compilarea separată a funcţiilor ......................................................................... 52
3.5 Funcţii cu parametri tablouri ............................................................................. 53
3.6 Supraîncărcarea funcţiilor .................................................................................. 58
3.7 Transmiterea parametrilor către funcţii .............................................................. 60
3.8 Recursivitatea ..................................................................................................... 65
3.9 Funcţii generice .................................................................................................. 66
3.10 Funcţii C standard de manipulare a caracterelor .............................................. 67
4 Pointeri şi referinţe .................................................................................................... 70
4.1 Pointeri ............................................................................................................... 70
4.1.1 Declararea variabilelor tip pointerilor ......................................................... 71
4.2 Referinţe ............................................................................................................. 75
4.3 Pointeri la funcţii ................................................................................................ 78
4.4 Interpretarea instrucţiunilor ce conţin pointeri ................................................... 79
4.5 Pointeri şi tablouri unidimensionale ................................................................... 81
4.6 Şiruri tip C .......................................................................................................... 85
2
4.7 Pointeri şi tablouri multidimensionale .............................................................. 88
4.8 Parametrii funcţiei main. Parametrii liniei de comandă ..................................... 92
4.9 Alocarea dinamică a memoriei .......................................................................... 93
5 Fişiere tip C ............................................................................................................. 102
5.1 Fişiere tip text ................................................................................................... 103
5.1.1 Funcţii intrare / ieşire cu format ................................................................ 104
5.1.2 Funcţii intrare / ieşire tip caracter .............................................................. 106
5.2 Fişiere text tip şir de caractere .......................................................................... 110
5.3 Fişiere binare .................................................................................................... 112
6 Structuri tip C şi uniuni ........................................................................................... 117
6.1 Structuri ............................................................................................................ 117
6.2 Uniuni ............................................................................................................... 120
7 Clase ....................................................................................................................... 122
7.1 Definirea unei clase .......................................................................................... 123
7.1.1 Definirea unei clase ................................................................................... 123
7.1.2 Pointerul this .............................................................................................. 128
7.1.3 Spaţii de nume ........................................................................................... 128
7.2 Constructori şi destructori ................................................................................ 129
7.2.1 Constructori ............................................................................................... 129
7.2.2 Destructori ................................................................................................. 133
7.3 Funcţii prietene ................................................................................................. 134
7.4 Determinarea tipului unei expresii ................................................................... 136
8 Siruri tip C++ ........................................................................................................... 139
8.1 Clasa string ....................................................................................................... 139
9 Supraîncărcarea operatorilor ................................................................................... 143
9.1 Supraîncărcarea operatorului de atribuire ........................................................ 144
9.2 Supraîncărcarea operatorilor aritmetici ............................................................ 146
9.3 Supraîncărcarea operatorilor << şi >> .............................................................. 148
10 Moştenirea ............................................................................................................. 151
10.1 Pointeri la obiecte. Operatorii new şi delete .................................................. 151
10.2 Moştenirea .................................................................................................... 155
10.2.1 Definirea unei clase derivate ................................................................... 155
10.2.2 Specificatorii de acces ............................................................................. 157
10.3 Funcţii virtuale. Polimorfism ........................................................................ 163
10.4 Destructori virtuali ........................................................................................ 170
10.5 Date şi funcţii statice ..................................................................................... 171
10.5.1 Date statice .............................................................................................. 171
10.5.2 Funcţii statice .......................................................................................... 173
11 Fişiere tip C++ ....................................................................................................... 176
11.1 Fişiere text ..................................................................................................... 177
11.1.1 Funcţii intrare / ieşire cu format .............................................................. 177
11.1.2 Funcţii intrare / ieşire tip caracter ............................................................ 179
11.2 Fişiere binare ................................................................................................. 181
11.3 Fişiere text tip string ...................................................................................... 184
12 Tratarea excepţiilor ................................................................................................ 186
12.1 Excepţii .......................................................................................................... 186
12.2 Excepţii lansate de funcţii ............................................................................. 188
12.3 Excepţii standard ........................................................................................... 189
12.4 Excepţii intrare / ieşire .................................................................................. 191
13 Aplicaţii ................................................................................................................ 194
3
13.1 Funcţii de timp ............................................................................................... 194
13.2 Fire de execuţie ............................................................................................. 195
13.3 Funcţia system ............................................................................................... 197
14 Biblioteca de şabloane standard ............................................................................ 199
14.1 Clase generice ................................................................................................ 199
14.2 Containere, iteratori şi algoritme generice ................................................... 201
14.3 Vectori ........................................................................................................... 202
14.3.1 Parcurgerea vectorilor cu iteratori ........................................................... 204
14.3.2 Stergerea şi inserarea de elemente .......................................................... 206
14.3.3 Sortarea componentelor unui vector ........................................................ 208
14.3.4 Căutarea unui element într-un vector ...................................................... 210
14.3.5 Copierea unui container. Iteratori pentru streamuri ................................ 212
14.4 Liste ............................................................................................................... 214
14.4.1 Parcurgerea listelor .................................................................................. 214
14.4.2 Sortarea listelor ........................................................................................ 215
14.4.3 Inversarea elementelor listelor ................................................................ 216
14.4.4 Inserarea şi ştergerea elementelor din liste .............................................. 217
14.4.5 Copierea listelor ...................................................................................... 219
14.5 Parcurgerea şirurilor tip string cu iteratori .................................................... 220
14.6 Numere complexe .......................................................................................... 221
Anexa 1. Reprezentarea informaţiilor ....................................................................... 224
A.1 Reprezentarea numerelor întregi în sistemul binar ......................................... 224
A.2 Deplasarea numerelor binare cu semn ........................................................... 228
A.3 Reprezentarea numerelor reale ....................................................................... 229
A.4 Reprezentarea caracterelor ............................................................................. 231
Anexa 2. Priorităţile şi asociativitatea operatorilor ................................................... 233
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
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
9
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.
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
10
î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
11 % 5 = 1 11 / 5 = 2
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.
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)
11
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,
• 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
12
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};
char c[4] = {‘a’, ‘b’, ‘c’, ‘x’};
Ultimul tablou este reprezentat în memorie astfel
a b c x
13
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
double a[10], b;
int i, j, y[3][4];
14
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:
Considerăm o variabilă x şi o expresie e. Instrucţiunea
x op= e;
este echivalentă cu
15
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
# 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
ş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>
16
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”;
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.
17
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:
sau
18
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 ?
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:
• 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
19
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
• 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ăman la valoarea
prescrisă pană sunt modificaţi.
Exemplu. Fie instrucţiunile
int x ;
cin >> x ;
cout << hex << x << endl ;
cout << x << endl ;
20
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;
}
Menţionăm că funcţia main() returnează valoarea 0 cu instrucţiunea
return 0;
Rezultatul rulării programului este cel de mai jos.
21
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
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.
22
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.
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 :
23
• 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: se incrementează sau decrementează
valoarea variabilei x cu unu; 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
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;
24
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 : se utilizează în calcule valoarea
variabilei x (neincrementate sau nedecrementate). 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 :
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 :
25
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
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
26
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.
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
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 şi vom afişa rezultatele în bazele 10 şi 16.
27
# 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
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
28
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ă.
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
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 ;
Reamintim că operatorii <<, >>, &, | şi ~ sunt operatori aritmetici.
29
Pentru a selecta unui număr de biţi dintr-o număr întreg se defineşte un şir de biţi
numit mască şi 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.
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;
}
Rezultatul rulării programului este cel de mai jos.
Să se explice de ce, în cazul al doilea, masca este 0x3ff;
30
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.
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ă.
31
• Operatorul for se utilizează atunci când numărul de execuţii ale unei acţiuni
este dinainte cunoscut. Operatorul are o variabilă de control ce se poate
modifica la fiecare iteraţie. Forma acestui operator este
for (i = instrucţiune1; condiţie; instrucţiune2)
s;
Operatorul include o instrucţiune1 ce specifică valoarea iniţială a variabilei
de control, şi o instrucţiune2 ce poate modifica valoarea variabilei de control
după fiecare iteraţie. Acţiunea s se execută atât timp cât expresia booleană
condiţie are valoarea adevărat. Operatorul for este echivalent cu următoarele
instrucţiuni
instrucţiune1;
while(condiţie)
s;
instrucţiune2;
Menţionăm că testul condiţiei se face înainte de execuţia acţiunii s.
Operatorii prezentaţi se numesc structuri de control fundamentale. In construcţia unui
program structurile de control şi structurile de date sunt inseparabile. Structurile
fundamentale de date sunt
• structuri simple: numere întregi, reale, caractere.
• structuri complexe tablouri, fişiere.
Tablourile au un număr cunoscut de elemente. Ele se prelucrează de regulă cu
operatorul for. Fişierele secvenţiale au un număr de elemente necunoscut în avans. Ele
se prelucrează de regulă cu operatorul while.
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ă
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.
32
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.
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) ˅
33
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
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(expresie)
S1;
else
S2;
34
Dacă expresie este diferită de zero se execută instrucţiunea S1 altfel instrucţiunea S2.
Operatorul if poate avea şi o formă simplificată
if(expresie)
S;
In care se execută instrucţiunea S dacă expresie are valoare diferită de zero.
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.
35
Instrucţiunile din expresia operatorului if pot fi instrucţiuni compuse, sau blocuri de
instrucţiuni, adică un şir de instrucţiuni cuprinse între acolade. Ca exemplu, fie
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 execiţie a programului este cel de mai jos.
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).
36
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 :
37
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 (expresie)
S;
38
instrucţiunea S se execută atâta timp cât expresia este diferită de zero (are valoarea
adevărat). Instrucţiunea S poate fi o instrucţiune compusă, formată dintr-o secvenţă de
instrucţiuni, cuprinse între acolade, { şi }.
Vom exemplifica utilizarea acestui operator calculând suma elementelor unui vector a
cu 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;
}
Putem rescrie partea de calcul din program astfel
s = 0;
i = 0;
while(i < 4)
{
s += a[i];
i++;
}
sau
39
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;
cout << "media este : " << media << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
40
2.8 Operatorul do-while
Operatorul do-while este tot un operator repetitiv. Acest operator are forma
do
S
while (expresie);
Instrucţiunea S se execută atâta timp cât expresia este diferită de zero (are valoarea
adevărat). Instrucţiunea S din diagrama sintactică poate fi o secvenţă de instrucţiuni.
Instrucţiunea S poate fi o instrucţiune simplă sau o instrucţiune compusă, scrisă î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
{
n = n * i;
i = i + 1;
}
while(i < 6);
41
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; expresie2; expresie3)
S
Operatorul for execută repetat o instrucţiunea S, atât timp cât expresie2 este diferită
de zero. expresie1 are rolul de a iniţializa variabila de control. expresie3 are rolul de
a modifica valoarea variabilei de control. Instrucţiunea S, executată repetat, poate fi o
42
instrucţiune simplă sau o instrucţiune compusă ce constă dintr-o secvenţă de
instrucţiuni între acolade, { şi }.
Exemplu. Vom calcula valoarea expresiei x x e
x
+ +2 pentru x cuprins între unu şi
doi 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.
43
Menţionăm că instrucţiunea for se putea scrie astfel
for(i = 0; i < 6; i++)
sau
for(i = 0; i < 6; ++i)
Un alt mod de a calcula valorile expresiei de mai sus este 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, y;
cout << setw(4) << "x" << '\t' << setw(5) << "y" << endl;
for(x = 1; x <= 2; x = x + 0.2)
{
y = exp(x) + 2 * x + sqrt(x);
cout << setw(4) << x << '\t' << setw(5) << y << endl;
}
return 0;
}
Exemplul următor calculează suma componentelor pare şi impare ale unui vector x cu
şase elemente. Programul pseudocod este următorul
s1 = 0;
s2 = 0;
for( i = 0; i < 6; i=i+1)
if (i % 2 = = 0)
s1 = s1 +
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;
int i;
float x[10] = {1, -12, 23.2, 0.44, 3.4, -2.3};
for(i = 0; i < 6; i = i + 1)
if(i % 2 = = 0)
s1 = s1 + x[i];
44
else
s2 = s2 + x[i];
cout << “suma elementelor pare este “ << s1 << endl
<< “suma elementelor impare este “ << s2 << endl;
return 0;
}
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;
for(cnt = 1; cnt <= n; ++cnt)
{
cout << "dati un numar : ";
cin >> x;
if(x < 0)
continue;
sum = sum + x;
m = m + 1;
}
45
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;
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)
46
cout << str << " este palindrom" << endl;
else
cout << str << " nu este palindrom" << endl;
return 0;
}
Rezultatul rulării programului este prezentat mai jos.
47
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
Limbajul are multe funcţii standard, matematice, de prelucrare a caracterelor, etc. Mai
înainte am prezentat cele mai utile funcţii matematice. Vom prezenta acum câteva
funcţii de prelucrare a caracterelor. Funcţia strlen() cu prototipul
int strlen(char x[]);
dă lungimea unui şir de caractere. Prototipul acestei funcţii se află în biblioteca
<cstdlib>. Funcţia isdigit() cu prototipul
int isdigit(int c);
are o valoare diferită de zero dacă c este o cifră, ‘0’, ‘1’, …, ‘9’ şi zero în caz contrar.
Funcţia isalpha() cu prototipul
int isalpha(int c);
are o valoare diferită de zero dacă c este o literă, ‘a’, …, ‘z’, ‘A’, … ‘Z’ şi zero în caz
contrar. Prototipurile acestor funcţii se află în biblioteca header <cctype>.
Vom exemplifica utilizarea acestor funcţii cu un program ce citeşte un şir de caractere
de la tastatură şi numără cifrele din şir. Programul este următorul
#include <cstdlib>
#include <iostream>
using namespace std;
int main()
{
char a[100];
cout << "introduceti un sir" << endl;
cin >> a;
int n = strlen(a);
int k = 0, i;
for(int i = 0; i < n; i++)
if(isdigit(a[i]))
k++;
cout << "in sir sunt " << k << " cifre" << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
48
Funcţiile strlen() şi isdigit() au câte un parametru şi calculează o valoare. Apelarea lor
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, funcţia isdigit() este apelată în expresia testată de instrucţiunea if. Apelarea
funcţiilor şi transmiterea valorii calculate de funcţie în punctual din program unde este
apelată este prezentată schematic 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.
49
/* functie ce calculeaza suma a doua numere reale */
double suma (double x, double y)
{
double z ;
z = x + y ;
return z ;
}
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);
50
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.
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. Parametrii actuali de ieşire sunt
variabile.
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().
51
#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()
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.
52
Fişierul main.cpp
#include <iostream>
using namespace std;
#include "max.h"
int main(int argc, char *argv[])
{
double a, b, c ;
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. Pentru a defini un
parametru formal ca vector, scriem numele vectorului urmat de paranteze drepte. La
apelarea unei funcţii cu parametri vectori, scriem numele vectorului, fără paranteze
sau indici, ca parametru actual. 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.
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
53
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++)
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.
/* testarea functiei suma */
int main()
{
float x[5] = {11.3, -2.67, 0.34, -2.5, 14};
float z;
// calculeaza suma componentelor
z = suma(x, 5);
// scrie componentele vectorului
cout << “suma componentelor vectorului “ << endl;
for(i = 0, i < 5; i++)
cout << x[i] << “\t”;
cout << endl;
// scrie rezultatul
cout << “este “ << z << endl;
return 0;
}
Funcţia este apelată cu instrucţiunea
z = suma(x, 5);
După cum am spus mai sus, când un parametru al unei funcţii este tablou, la apelarea
funcţiei scriem doar numele tabloului, 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
54
= 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
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;
55
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]
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.
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.
*/
56
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;
}
/*
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
*/
57
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);
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 diferiţi 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;
58
}
int main()
{
int a = 29, b = 47, c = -32;
cout << "min " << a << "," << b << " este " << min(a, b) << endl;
cout << "min " << a << "," << b << ", " << c << " este "
<< 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
59
double min(double a, double b, double c)
{
double x = a < b? 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);
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ă. Dacă în corpul funcţiei modificăm
aceşti parametri, valoarea lor se modifică în programul apelant (în stivă este
adresa acestor parametri). In consecinţă, pentru ca un parametru al unei
funcţii să fie parametru de ieşire, el trebuie transmis prin adresă
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ţă
Există două moduri de a defini parametri transmişi prin adresă:
• utilizarea parametrilor tip referinţă,
• utilizare parametrilor tip pointer.
60
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ă.
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
61
La prima apelare a funcţiei stiva este
După prima execuţie a funcţiei valorile variabilelor a şi b în program sunt
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ţă).
62
• 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
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.
63
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

·
·
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;
64
}
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};
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 i;
}
// 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
65
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
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;
}
66
Rezultatul rulării programului este cel de mai jos.
Exerciţiu. Să se definească o clasă generică ce permută două variabile de acelaşi tip.
3.10Funcţii C standard de manipulare a caracterelor
Funcţiile ce manipulează caractere tip ASCII sunt un exemplu de funcţii standard ale
limbajului C. Prototipurile lor se află în biblioteca <cctype>.
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 (‘*’) ‘*’
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[]);
Prototipul ei se găseşte în biblioteca <cstring>.
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
67
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;
}
Rezultatul rulării programului este următorul.
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[]);
68
double atof(char s[]);
care convertesc un şir de caractere într-un număr întreg şi respectiv real. Prototipurile
acestor funcţii se află în biblioteca <cstdlib>. 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;
// 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.
69
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
70
4.1.1 Declararea variabilelor tip pointerilor
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
71
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.
72
// 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.
73
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
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
char *pc, y[7] ;
74
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.
75
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.
76
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;
77
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.
78
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ă
79
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
80
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[])
{
81
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
elemente de vectoriului 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;
82
// 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 x vectorului ş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 :
83
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()
{
84
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 Ş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 biblioteca <cstring>, 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++.
Vom exemplifica acum utilizarea funcţiilor de manipulare a caracterelor ASCII
tipărind caracterele alfabetice, alfanumerice, cifrele, literele mici şi mari ale codului
ASCII.
85
Aceste funcţii au fost prezentate anterior şi prototipurile lor din biblioteca <cctype>
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> 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;
/*
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)
if((*fn)(c))
cout << c;
cout << endl;
return;
}
Următorul program testează această funcţie.
86
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
void copy(char * s1, char * s2)
{
int len = strlen(s2);
for(i = 0; i < len + 1; i++)
s1[i] = s2[i];
return;
}
O altă variantă este
void copy(char * s1, char * s2)
{
int i;
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]
87
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
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
88
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()
{
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;
89
}
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;
for(i = 0; i < 2; i++)
{
y = x[i];
cout << "adresa lui x[" << i << "] = " << y << endl;
}
return 0;
}
90
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.
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
int x[2][3];
int i, j;
91
// 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.
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
92
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 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>.
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
93
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.
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 **
94
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 ;
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
// 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;
95
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;

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++)
{
96
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++)
cout << setw(3) << *(*(z + i) + j) << " ";
cout << endl;
}
return 0;
}
Rezultatul rulării programului este cel de mai jos.
97
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 *
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++)
98
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.
Limbajul C++ permite alocarea memoriei necesară unei variabile cu operatorul new
cu formele
new tip
pentru alocarea de memorie pentru un scalar şi
new tip[expresie întreagă]
pentru alocarea de memorie pentru un vector cu un număr de componente dat de
expresie întreagă.
De exemplu următoarele instrucţiuni alocă memorie prntu 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
delete prt;
pentru eliberarea memoriei allocate unei variabile scalare şi
delete [] ptr;
pentru eliberarea memoriei alocate unui vector. 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
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
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()
99
{
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()
{
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];
100
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**.
101
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 mumele
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
102
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.
103
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ă.
104
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);
In cazul instrucţiunilor printf specificatorii de conversie dau formatul de scriere a
varibilelor, în timp ce restul caracterelor 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. Vom scrie un program care calculează valoarea unei expresii
x x
x x
ln
) sin( 2 ) 2 cos( 1
+
+ +
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");
105
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”;
ş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
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
Funcţia
int fgetc(FILE * stream);
106
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 streamul specificat de stream. Funcţia returnează caracterul scris
sau constanta EOF în caz de eroare.
Funcţia
int putchar(int c);
scrie caracterul c in streamul stdout.
Funcţia
int ungetc(int c, FILE * stream);
pune caracterul c în streamul de intrare.
Următoarele funcţii citesc sau scriu linii din fişiere tip text.
Funcţia
char * fgets(char * s, int n, FILE * stream);
citeşte cel mult n – 1 caractere în vectorul s. Ea se opreşte dacă întâlneşte caracterul
‘\n’ sau sfârşitul de fişier. 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.
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)
}
107
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");
return 0;
}
// citeste cate un caracter până la sfarsitul fisierului
// citeste un caracter
car = fgetc(file);
while(car != EOF)
{
// numara caracterul citit
nb = nb + 1;
// citeste un caracter
car = fgetc(file);
}
fclose(file);
printf("fisierul %s contine %d octeti\n", name, nb);
return 0;
}
Rezultatul rulării proghramului este cel de mai jos.
Un alt mod de a rezolva această problemă va fi arătat ulterior.
108
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 0;
}
// citeste numele noului fisier
printf(“introduceti numele noului fisier\n”);
scanf(“%s”, nume2);
fout = fopen(nume2, "w");
if(fout == NULL)
{
printf("fisierul %s nu se poate crea", nume2);
return 0;
}
// copiaza fisierul
int c;
c = fgetc(fin);
while(c != EOF)
{
fputc(c, fout);
c = fgetc(fin);
}
fclose(fin);
fclose(fout);
return 0;
}
Rezultatul rulării programului este cel de mai jos.
109
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()
{
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.
110
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;
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.
111
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.
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)
{
112
printf("nume de fisier eronat\n");
return 0;
}
// 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 0;
}
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.
#include <stdio.h>
int main()
{
FILE * fil;
int i, j;
char x[15];
// deschide fisierul in creare
fil = fopen(“fil.txt”, “wb”);
if(fil == NULL)
{
printf(“fisierul nu se poate crea\n”);
return 0;
}
for(i = 0; i < 10; i++)
{
// creaza un bloc
for(j = 0; j < 14; ++j)
x[j] = ‘0’ + i ;
x[14] = 0;
// scrie blocul in fisier
113
fwrite(x, 15, 1, fil);
}
// inchide fisierul
fclose(fil);
// deschide fisierul in citire
fil = fopen(“fil.txt”, “rb”);
if(fil == NULL)
{
printf(“fisierul nu se poate deschide\n”);
return 0;
}
int xct;
// citeste un sir
xct = fread(x, 15, 1, fil);
while(xct != 0)
{
// scrie sirul pe ecran (in streamul stdout)
printf(“%s\n”, x);
// citeste un sir
xct = fread(x, 15, 1, fil);
}
// inchide fisierul
fclose(fil);
return 0;
}
Rezultatul rulării programului este cel de mai jos.
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;
114
int i, j;
char x[10];
// deschide fisierul in creare
fil = fopen(“fil”, “wb”);
if(fil == NULL)
{
printf("fisierul nu se poate crea\n");
return 0;
}
for(i = 0; i < 15; i++)
{
// creaza un bloc
for(j = 0; j < 9; ++j)
x[j] = ‘a’ + i + j;
x[9] = 0;
// scrie blocul in fisier
fwrite(x, 10, 1, fil);
// scrie sirul pe ecran (in fluxul stdout)
puts(x);
}
// inchide fisierul
fclose(fil);
// deschide fisierul in citire
fil = fopen(“fil”, “rb”);
if(fil == NULL)
{
printf("fisierul nu se poate citi\n");
return 0;
}
printf("fisierul citit\n");
// citeste sirurile pare
for(i = 0; i < 15; i += 2)
{
// pozitioneaza indicatorul fisierului 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 0;
}
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);
115
în loc de a-l poziţiona în raport cu începutul fişierului.
Rezultatul rulării programului este cel de mai jos.
116
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 date
de tipuri diferite. Datele dintr-o structură vor fi numite componente sau câmpuri.
Definirea unei structuri se face cu instrucţiunea
struct nume {lista de declaraţii de tip };
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 . 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.
117
#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)
{
vector c;
118
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
(*pc).real = cos(1.23);
119
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
aplicaţii cu multe variabile ale căror valori nu sunt necesare simultan. Instrucţiunea de
definire a unei uniuni este
120
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));
121
Partea II. Programarea orientată obiect
7 Clase
Pentru a rezolva o problemă trebuie să creăm un model al problemei. Procesul de
modelare se numeste abstractizare. In acest proces trebuie să definim :
• datele afectate
• operaţiile identificate de problemă.
Abstractizarea este structurarea problemei în entităţi definind datele şi operaţiile
asociate acestor entităţi. Tipurile de date abstracte pe care le definim au următoarele
proprietăţi :
• definesc o structura a datelor
• datele sunt accesibile prin operaţii bine definite. Setul de operaţii definite se
numeşte interfaţă. Operaţiile interfeţei sunt singurul mecanism de acces la
structura de date.
Definiţiile pe care le vom 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) 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 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 (apelarea unei functii).
Un mesaj este o cerere către un obiect ca el să invoce una din metodele lui.
Mesajul conţine :
- numele metodei,
- argumentele metodei.
• Metoda. O metoda este asociată unei clase. Un obiect invoca o metodă ca
reacţie la primirea unui mesaj.
Clasa este un tip de date definit de utilizator. Clasa defineşte variabile şi funcţii ce
prelucrează aceste variabile.
Există o notaţie pentru tipurile de date abstracte pe care le definim
Nume clasă
Date
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
122
clasei numite destructori. Valorile proprietăţilor dau starea obiectului la un moment
dat.
Datele şi funcţiile membre ale clasei pot fi publice, private sau protejate. Membri
publici ai clasei pot fi utilizaţi de orice funcţie din program. Membri privaţi ai clasei
pot fi utilizaţi doar de funcţiile membre ale clasei. Membri protejaţi vor fi prezentaţi
ulterior. Implicit, toate datele şi funcţiile unei clase sunt private.
Principiile programării orientate obiect sunt următoarele
• Primul principiu al programării orientate obiect este încapsularea datelor.
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 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.
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
123
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 ;.
Datele, (câmpurile), membre ale clasei sunt m şi n şi ele vor memora constantele m şi
n din funcţia 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
informaţiilor. Funcţiile membre ale clasei, (metodele) 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 member, (metodele), pot fi definite în
interiorul clasei sau în afara ei. Funcţiile definite în interiorul clasei sunt funcţii inline
, compilatorul generează direct textul lor în program, şi nu un apel la funcţie.
In limbajul C++ avem posibilitatea de a grupa anumite nume în spaţii de nume.
Prin definiţie, datele şi funcţiile unei clase constituie un spaţiu de nume ce are
numele clasei. Operatorul de rezoluţie :: arată că un nume de dată sau de funcţie
aparţine unui spaţiu de nume. Operatorul de rezoluţie :: se utilizează pentru a
defini o funcţie membru a clasei în afara clasei.
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)
{
124
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);
}
// scrierea 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;
}
Apelul unei funcţii a clasei de către un obiect se face conform următoarei diagrame
sintactice
Utilizarea unui câmp al un obiect se face conform următoarei diagrame sintactice
125
Declararea unui obiect se face în acelaşi fel cu declararea tipurilor standard.
Reamintim că un obiect este o instanţă a unei clase. Diagrama sintactică pentru
definirea obiectelor este cea de mai jos
La declararea unui obiect se apelează un constructor. Orice obiect are propriile
variabile.
Vom exemplifica utilizarea clasei definind funcţia liniară x + 1şi calculând valoarea ei
în punctual 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
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.
126
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 header şi definiţia funcţiei
main() într-un fişier separat care să includă 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];
127
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
}
128
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
129
Line::Line(int a, int 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 aceiaşi parametrii ca un alt obiect. De exemplu instrucţiunea
X a(b);
declară un obiect a de tipul X şi îi atribuie ca valoare obiectul b. Dacă nu definim
un constructor copiere, el este definit de compilator. Menţionăm cuvântul cheie
const din definiţia constructorului copiere. El interzice modificarea
argumentului în timpul copierii.
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&);
};
130
Vom prezenta doar definiţiile constructorilor, definiţiile celorlalte funcţii sunt cele
anterioare.
// 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();
}
131
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ă definiţia funcţiei f putea fi
Line f(const Line r);
deoarece funcţia nu modifică parametrul r.
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.
132
# 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
~ nume_clasa(){/* 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:
133
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 scrise vor fi:
Menţionăm că toate variabilele locale unei funcţii sau unui bloc sunt create intr-o stivă
la intrarea în funcţie sau bloc şi sunt şterse din stivă la ieşirea din funcţie sau bloc.
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 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:
134
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;
}
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:
135
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.
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>
136
#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.
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.
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;
137
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.
138
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
139
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);
140
Primele două funcţii adaugă şirul str la sfârşitul şirului. A treia funcţie adaugă la
sfârşitul şirului caracterele ş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;
141
getline(cin, str);
produc citirea unei linii de caractere din fişierul de intrare cin.
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.
142
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
functie de forma
tip operator semn (parametri) {/* corpul funcţiei */}
unde semn este operatorul dorit +,-, *, / , [], (), <<, >>, =, etc. Operatorii
supraîncărcaţi au aceeaşi prioritate ca cei originali şi acelaşi număr de parametri.
Menţionăm că orice operator supraîncărcat poate fi definit ca funcţie membră a clasei
sau ca funcţie globală.
Exemplu. Să supraîncărcăm operatorul + pentru clasa Line, care să adune două funcţii
liniare. Operatorul ce va fi 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;
}
A se compara această funcţie cu funcţia sum definită anterior. Funcţia are doi
parametri, funcţiile de adunat, 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;
}
Putem defini operatorul ca funcţie prietenă a clasei. In definiţia clasei vom scrie
instrucţiunea
friend Line operator+ (Line, Line);
In acest caz definiţia funcţiei este
Line operator+( Line x, Line y)
{
Line temp(x.m + y.m, x.n + y.n);
return temp;
}
143
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 (int, int);
Line (const Line & x);
Line ();
void print();
void invert();
Line operator+( Line); // functia are un singur parametru
private:
double m;
double n;
};
Implementarea funcţiei este
Line Line::operator+( Line x)
{
Line temp(m + x.m, n + x.n);
return temp;
}
Exemplu. Fie instrucţiunea
Line a(1, 3), b(4, -3), c;
Instrucţiunile
c = a + b;
şi
c = a.operator+(b);
sunt echivalente.
9.1 Supraîncărcarea operatorului de atribuire
Considerăm clasa definită anterior în care definim constructorul implicit,
constructorul copiere şi operatorul de atribuire. Menţionăm că operatorul de atribuire
este automat definit pentru orice clasă. Supraîncărcarea lui aici este doar un exerciţiu.
O definiţie incorectă a operatorului de atribuire este următoarea
# include <iostream>
using namespace std;
class Line
{
144
private:
double m, n;
public:
Line ();
Line (const Line &);
void operator = (const Line &);
};
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:
145
Line o();
Line o(const Line o&);
Line & operator = (const Line &);
};
Prototipul operatorului de atribuire al unei clase T este
T& operator = (const T&);
Operatorul are ca rezultat o referinţă la obiectul ce apelează operatorul. Pentru aceasta
se utilizează pointerul this. Implementarea corectă a operatorului de atribuire
supraîncărcat este următoarea.
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.2 Supraîncărcarea operatorilor aritmetici
Limbajul ne dă posibilitatea de a defini funcţii care să efectueze operaţiile standard
+,-, etc., pentru operanzi ce sunt obiecte ale unor clase (operator overloading). Un
operator poate fi definit ca o funcţie globală sau ca o funcţie membru a clasei. Vom
exemplifica supraîncărcarea operatorului + pentru o clasă ce descrie numere
complexe. Clasa va defini două varibile de tip float, real şi imag, pentru partea reală şi
cea imaginară a numărului complex şi doi constructori, constructorul implicit fără
parametri şi un constructor care să iniţializeze variabilele real şi imag. O reprezentare
a clasei este cea de mai jos.
Complex
float real;
float imag;
Complex ();
Complex (float, float);
Vom exemplifica pentru început supraîncărcarea operatorului + pentru clasa Complex
în cazul în cazul în care operatorul este o funcţie globală. Funcţia are numele
operator+ şi doi parametri de tip Complex, numerele complexe ce se adună. Tipul
funcţiei este Complex. Definiţia funcţiei este
Complex operator+(Complex, Complex);
Definiţia clasei Complex este cea de mai jos.
class Complex
{
private:
float real;
146
float imag;
public:
Complex();
Complex (float, float);
};
Problema scrierii funcţiei este aceea că datele clasei Complex sunt declarate private şi
nu pot fi utilizate direct de funcţie, care este globală. La fel ca într-un capitol anterior,
o soluţie este de a scrie două funcţii de acces membre ale clasei, de exemplu :
float getreal();
float getimag();
care au ca rezultat variabilele real şi imag ale clasei. Această soluţie rămâne ca
exerciţiu. O altă soluţie este următoarea. Pentru ca o funcţie externă să aibe acces la
membrii privaţi ai clasei, ea trebuie declarată în definiţia clasei ca funcţie
prietenă, friend. Definiţia unei funcţii prietene este
friend tip nume_funcţie ( parametri);
In consecinţă, definiţia clasei Complex va fi
class Complex
{
private:
float real;
float imag;
public:
Complex();
Complex (float, float);
friend Complex operator+(Complex &, Complex &);
};
Definiţia funcţiei va fi cea de mai jos.
Complex operator+(Complex& x, Complex& y)
{
Complex temp;
temp.real = x.real + y.real;
temp.imag = x.imag + y.imag;
return temp;
}
Trebuie ca în definiţia clasei să includem un constructor implicit, fără parametri
Complex();
Constructorul implicit este apelat la declararea obiectului temp
Complex temp;
în funcţia operator+. Constructorul implicit nu mai este generat automat de compilator
deoarece am definit un constructor.
Vom exemplifica acum supraîncărcarea operatorului + pentru clasa Complex în cazul
în care operatorul este definit ca o funcţie membră a clasei. Numele funcţiei este
operator+. O funcţie membru a clasei este apelată de un obiect. In consecinţă, vom
scrie
c = a.operator+(b);
147
sau, echivalent
c = a + b;
De accea, atunci când operatorul este o funcţie membră a clasei, funcţia
corespunzătoare are un singur parametru, operandul drept, şi este apelată de operandul
stâng. Fie definiţia clasei Complex în care definim şi funcţia operator+ ca funcţie
membră
class Complex
{
private:
float real;
float imag;
public:
Complex();
Complex (float, float);
Complex operator+ (Complex);
};
Implementarea constructorilor este
Complex::Complex(float x, float y)
{
real = x;
imag = y;
}
Complex::Complex()
{
real = 0;
imag = 0;
}
Implementarea operatorul + este următoarea
Complex Complex::operator+(Complex p)
{
Complex temp;
temp.real = real + p.real;
temp.imag = imag + p.imag;
return temp;
}
Exerciţiu. Să se scrie implementarea operatorilor de scădere, -, înmulţire, * şi
împărţire, / a două numere complexe.
9.3 Supraîncărcarea operatorilor << şi >>
Operatorul << este numit operator de inserţie, el insereaza caractere într-un stream.
Operatorul >> este numit operator de extracţie, el extrage caractere dintr-un stream.
Toate funcţiile de inserţie au forma
148
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 Complex. In
definiţia clasei definim funcţia
friend ostream& operator << (ostream& stream, Complex x);
Implementarea funcţiei este
ostream& operator << (ostream& stream, Complex x)
{
stream << "(" << x.real << "," << x.imag << ")";
return stream;
}
Exemplu. Operatorul de extracţie >> pentru clasa Complex este supraîncărcat astfel
friend istream& operator >> (istream& stream, Complex& x);
Implementarea lui este următoarea
istream& operator >> (istream& stream, Complex& x)
{
stream >> x.real >> x.imag;
return stream;
}
Când un operator este membru al unei clase, operandul stâng (transmis prin this) este
cel care apeleaza operatorul. Operatorii << şi >> pe care i-am definit nu pot fi membri
ai clasei deoarece operandul stâng ostream, respectiv istream, nu 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 Complex este
class Complex
{
private:
float real;
float imag;
public:
Complex();
Complex (float, float);
Complex operator+ (Complex);
friend ostream& operator << (ostream&, Complex&);
149
friend istream& operator >> (istream&, Complex&);
};
Definiţia funcţiilor mrmbre ale clasei este cea de mai sus.
Putem utiliza operatorii definiţi astfel
int main()
{
Complex a(1.0, 1.0);
Complex b(0.0, 1.0);
Complex c;
c = a + b;
// echivalent putem scrie
// c = a.operator+(b);
cout << “a = “ << a << endl;
cout << “b = “ << b << endl;
cout << “c = “ << c << endl;
return 0;
}
Rezultatul programului este cel de mai jos.
Reamintim că, trebuie ca în definiţia clasei să includem un constructor implicit, fără
parametri, de exemplu cu definiţia
Complex() {real = 0; imag = 0;}
Constructorul implicit nu mai este generat automat de compilator deoarece am definit
un constructor. Constructorul implicit este apelat la declararea obiectului temp
Complex temp;
în funcţia operator+ şi a obiectului c în funcţia main().
Complex c;
Acest constructor iniţializează variabilele obiectului la valoarea 0.
Reamintim că orice clasă include un operator implicit de atribuire = care are ca
parametru un obiect de tipul clasei.
150
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 la o clasă este
tip * identificator;
unde tip este numele clasei, iar identificator este numele variabilei tip pointer.
Variabila tip pointer poate fi iniţializată, ca orice variabilă tip pointer, folosind
operatorul de calcul al adresei &.
Fie p un pointer la un obiect. Un câmp al obiectului este adresat astfel
(*p).membru
Prin definiţie, această scriere se prescurtează
p->membru
O funcţie membră a clasei se apelează astfel
(*p).funcţie(parametri);
Prin definiţie, această scriere se prescurtează astfel
p->funcţie(parametri);
Operatorii . şi -> au aceeaşi prioritate ca operatorii () şi [].
Exemplu. 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, " <<
" m = " << m << " n = " << n << endl;
151
}
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;
// defineste un pointer la obiect
Line * pl;
pl = &d;
// apeleaza functiile prin pointer
pl->print();
cout << "f(" << a << ")= " << pl->value(a) << endl;
// apeleaza functiile direct
d.print();
cout << "f(" << a << ")= " << d.value(a) << endl;
return 0;
}
Datele afişate pe ecran vor fi
functia f(x) = m * x + n, m = 1, n = 0
f(1.5) = 1.5
f(1.5) = 1.5
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
cu operatorul new şi vom calcula f(1.5). Funcţia main() corespunzătoare este cea de
mai jos.
int main()
{
// defineste un pointer la obiect
Line * pl;
152
// 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 cu operatorul new. Pentru aceasta, clasa trebuie să aibă
un constructor fără parametri. De exemplu, putem crea un vector cu zece obiecte de
tip Line
Line * vect;
vect = new Line[10];
Acest vector se şterge cu operatorul delete cu forma
delete [] vect;
Exemplu. Vom crea vectori cu 2 componente cu obiecte tip Line şi vom afişa pe ecran
parametrii obiectelor create. Fie pentru început definiţia clasei Line.
# 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;
}
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();
153
sau, echivalent
pv[0]->print();
Definim apoi o variabilă pointer de tip Line, pvx. Ea este iniţializată cu adresa unui
vector ce conţine obiecte de tipul Line :
Line * pvx = new Line[2];
Menţionăm că, în acest caz, este apelat constructorul fără parametri al clasei. In acest
vector memorăm nişte obiecte de tip Line :
pvx[0] = Line(1, -1) ;
pvx[1] = Line(2, -2) ;
Apelarea funcţiei print() se face astfel. Obiectul din al doilea component alvectorului
pvx este pvx[1] sau *(pvx + 1), deci funcţia print() se apelează
(pvx[1]).print();
sau
(*(pvx + 1)).print();
sau, conform celor dintr-un capitol anterior
(pvx + 1)->print();
Obiectul pvx este şters în final cu instrucţiunea
delete []
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();
Line * pvx = new Line[2];
pvx[0] = Line(1, -1);
pvx[1] = Line(2, -2);
// scrie obiectele
for(i = 0; i < 2; i++)
(*(pvx + i)).print();
// (pvx[i]).print();
// (pvx + i)->print();
delete [] pvx;
return 0;
}
Rezultatul apelării programului este cel de mai jos.
154
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 membrii 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;}
};
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.
155
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 scrie 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 scrie numărul complex şi un constructor cu parametri.
class Complex : public Baza
{
public:
double abs() {return sqrt(a * a + b * b);}
void print() {cout << "(u, v) = "; Baza::print();}
Complex(double x, double y) : Baza(x, y) {}
};
Complex
double abs() ;
void print() ;
Complex(double, double) ;
156
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.
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,
• 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
{
157
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 derivate. 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
nemembri da nu nu
Fie o clasă derivată din Baza cu specificatorul de acces public
class DerivPublic : public Baza
{
/* declaratii din clasa Deriv */
public:
int w;
158
};
In acest caz variabilele x şi y au în clasa Deriv 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. 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;
Variabila x poate fi utilizată de obiecte de tipul Deriv deoarece este definită de tipul
public în clasa de bază, dar variabila y nu poate fi utilizată de obiecte de tipul Deriv
deoarece este declarată protected în clasa de bază. In tabelul de mai jos se arată
variabilele obiectului b, publice, protejate şi private.
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 Deriv specificatorul de acces protected. Ele
pot fi utilizate de funcţiile definite în clasa Deriv, dar nu pot fi utilizată de obiecte de
tipul Deriv. Fie obiectul
DerivProtected c ;
Variabilele publice, protejate şi private ale obiectului 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 */
public:
int w;
} ;
In acest caz toate variabilele x si y din clasa Baza au în clasa Deriv specificatorul de
acces private. Fie obiectul
DerivPrivate d ;
Variabilele publice, protejate şi private ale obiectului sunt cele din tabelul de mai jos.
159
public
protected
private
DerivPublic b ; DerivProtected c ; DerivPrivate 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 nu sunt
moşteniţi, constructorul implicit şi destructorul clasei de bază sunt totdeauna apelaţi
când un obiect de tipul clasei derivate este creat sau distrus.
In constructorul clasei derivate se apelează la început constructorul implicit al clasei
de bază. Dacă este nevoie, putem apela explicit un constructor al clasei de bază pentru
iniţializarea variabilelor. Apelarea sa se face în secţiunea : care are forma
constructor_clasa_derivata (lista de parametri)
: constructor_clasa_de_baza (lista de parametri)
{
/* definitia constructorului clasei derivate */
}
Menţion că, la distrugerea unui obiect de tipul unei clase derivete, 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
class Point
{
public:
int x, y;
void clear();
Point(int, int);
Point(){x = 0; y = 0;}
void print();
};
160
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.
class Pixel : public Point
{
public:
int color;
void clear();
Pixel(int, int, int);
void print();
};
161
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;
}
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;
162
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 clasă care să conţină informaţii
despre angajaţii unei intreprinderi, numele şi departamentul în cazul tuturor
angajaţilor, iar în cazul managerilor şi poziţia. Aceste informaţii vor fi şiruri de
caractere.
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 şi o funcţie print() ce afişază aceste variabile.
Vom defini o clasă numită Manager ce moşteneşte clasa Angajat ce conţine o
variabilă tip string pozitie cu poziţia managerului, un constructor cu trei parametri
pentru nume, departament şi poziţie şi o funcţie print() ce afişază variabilele nume,
dept şi poziţie. Reprezentarea acestor clase este cea de mai jos
163
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"; }
};
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
164
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("Bob", "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("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;
}
Rezultatele sunt următoarele
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 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;
165
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)
{
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
166
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:
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;
167
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;
}
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();
168
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
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();
169
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;}
};
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
170
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ă.
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,
171
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;
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.
172
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;
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;}
};
173
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);
};
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;
174
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.
175
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 consola.
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.
Clasele definesc un constructor fără parametri.
Funcţiile membre importante ale claselor sunt:
• 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.
• clasele definesc şi un constructor cu parametrii funcţiei open(). Acest
constructor crează un obiect şi apoi deschide fişierul,
• funcţia
176
void close();
î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 0;
}
// creaza fisierul
int i;
double x, e;
177
for(i = 0; i < 11; i++)
{
// calculeaza expresia si scrie in fisier
x = i * 0.2;
e = (cos(x) * cos(x) + x) / (1.0 + fabs(x)) + sin(x);
fil1 << x << ‘\t’ << e << endl;
}
fil1.close();
// citeste fisierul creat si afisaza rezulatele
ifstream fil2;
fil2.open(filename);
if(!fil2.is_open())
{
cout << “ nu se poate deschide fisierul “ << filename << endl;
return 0;
}
// scrie antetul
cout << "x" << '\t' << "e" << endl;
fil2 >> x >> e;
while(!fil2.eof())
{
cout << x << ‘\t‘ << e << endl;
fil2 >> x >> e;
}
fil2.close();
return 0;
}
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);
178
11.1.2 Funcţii intrare / ieşire tip caracter
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 0;
}
cout << endl << “Introduceti numele noului fisier” << endl;
cin >> filename;
ofstream filb(filename);
179
if(!filb.is_open())
{
cout << “Fisierul “ << filename << “ nu se poate crea” << endl;
return 0;
}
char car;
int nl = 0;
fila.get(car);
while(!fila.eof())
{
filb.put(car);
nl++;
fila.get(car);
}
fila.close();
filb.close();
cout << "fisierul " << filename << “ are ” << nl << " caractere" <<
endl;
return 0;
}
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. 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);
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) ;
180
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);
pentru indicatorul de scriere. Parametrul offset dă valoarea cu care se modifică
indicatorul. Parametrul direction are valorile :
ios::beg – relativă la începutul fişierului
181
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 0;
}
int i, j;
// creaza fisierul
for(i = 0; i < 10; i++)
{
// creaza un bloc
for(j = 0; j < 10; j++)
x[j] = ‘0’ + i;
x[10] = 0;
// scrie blocul in fisier
fila.write(x, 11);
}
fila.close();
ifstream filb;
// deschide fisierul
filb.open(filename, ios::binary | ios::in);
if(!filb.is_open())
{
cout << “Nu se poate citi fisierul “ << filename << endl;
return 0;
}
182
// citeste si afisaza fisierul
filb.read(x, 11);
while(!filb.eof())
{
cout << x << endl;
filb.read(x, 11);
}
filb.close();
return 0;
}
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 0;
}
int i, j;
for(i = 0; i < 26; i++)
{
// creaza un bloc
for(j = 0; j < 10; j++)
x[j] = ‘a’ + i;
183
x[10] = 0;
// scrie blocul in fisier
filx.write(x, 11);
}
filx.close();
ifstream fily;
// citeste si afisaza fisierul
fily.open(filename, ios::binary | ios::in);
if(!fily.is_open())
{
cout << “Nu se poate citi fisierul “ << filename << endl;
return 0;
}
// citeste si afisaza fisierul
for(i = 0; i < 26; i = i + 4)
{
// modifica indicatorul fisierului
fily.seekg(i * 11, ios::beg);
fily.read(x, 11);
cout << x << endl;
}
fily.close();
return 0;
}
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 şi 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
184
valoarea ei pe ecran. După fiecare operaţie vom afişa pe ecran şirul de caractere din
fişierul tip string.
# 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 >>.
185
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. Al tip de
erori sunt cele legate de operaţiile aritmetice : împărţirea prin zero, indici eronaţi,
depăşirea gamei de reprezentare a numerelor, etc. Limbajul C++ are un mecanism de
tratare a excepţiilor ce permite ca, atunci când apare o eroare, (o excepţie), programul
să apeleze automat o funcţie de tratare a erorii. Această funcţie are ca parametru un
obiect cu informaţii despre eroare. Tratarea excepţiilor se face cu instrucţiunile try,
catch şi throw. Instrucţiunea pe care o urmărim pentru apariţia unei excepţii trebuie
inclusă într-un bloc try. Dacă apare o excepţie (o eroare), ea este lansată cu
instrucţiunea throw, care generează un obiect cu informaţii despre eroare. 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 apare 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.
Exemplu. Tratarea unei excepţii împărţire prin zero. Instrucţiunea throw va genera un
obiect de tip int ce conţine linia unde a apărut eroarea. Limbajul defineşte două
constante utile în cazul excepţiilor :
186
• 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
Dacă dorim ca un singur bloc catch să preia toate excepţiile unui bloc try vom scrie
blocul catch astfel
187
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;
}
int main()
{
int a = 10, b = 0, c;
try
188
{
// 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 = şi 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 character din
şirul adăugat depăşeşte indicele ultimului element din şirul adăugat.
#include <cstdlib>
#include <iostream>
#include <string>
using namespace std;
189
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
nostru, este un obiect de tipul runtime_error ce conţine un mesaj de eroare. Programul
este următorul.
#include <iostream>
# include <stdexcept>
190
#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),
• 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 :
191
• 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
{
char car;
file.open(“rez.txt”);
car = file.get();
while(! file.eof())
{
cout << car;
car = file.get();
}
192
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.
193
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
194
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 susrsă curent,
• __TIME__ , şir de caractere, ce dă timpul compilării fişierului susrsă curent.
Instrucţiunile :
printf("DATE : %s \n", __DATE__ );
printf("TIME : %s \n", __TIME__ );
afişază cele două şiruri de caractere ca

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);
195
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);
196
}
return;
}
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");
197
system("PAUSE");
return EXIT_SUCCESS;
}
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.
198
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().
199
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>
200
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ă.
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
container. Operaţiile efectate asupra iteratorilor, indiferent de tipul containerului, sunt
următoarele :
201
• 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.
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 =.
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.
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.
202
Menţionăm că un vector poate avea două sau mai multe elemente egale. Clasa vector
este definită în biblioteca <vector>.
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;
int main()
{
// definim un vector cu componente intregi
vector <int> v;
// adauga 4 elemente
v.push_back(12);
v.push_back(-5);
v.push_back(7);
v.push_back(13);
// afisaza componentele vectorului
int k;
cout << “ elementele vectorului “ << endl ;
for(k = 0; k < v.size(); k++)
cout << v[k] << “ “;
cout << endl;
return 0;
}
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 altul, după ultimul element.
In exemplul următor comparăm doi vectori, vec1 şi vec2.
#include <cstdlib>
#include <iostream>
#include <vector>
using namespace std;
203
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();
204
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 << “ “;
205
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();
206
v.insert(it, 25);
Afişarea elementelor vectorului se face cu o instrucţiune for
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;
207
return 0;
}
Rezultatul rulării programului este cel de mai jos.
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;
}
208
Rezultatul rulării programului este cel de mai jos.
A doua funcţie sort() are următorul prototip
sort(iter1, iter2, comp);
unde iter1 şi iter2 sunt iteratori, ce dau primul element din vector şi ultimul element
din vector ce vor fi sortaţi, iar comp este o funcţie cu prototipul
bool comp(T x, T y);
Parametri x şi y sunt două elemente de tipul T al componentelor vectorului. Funcţia
comp() are rezultatul true dacă x < y. 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;
}
int main()
209
{
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);
210
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ă
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;
211
}
return 0;
}
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;
212
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, " "));
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.
Vom exemplifica utilizarea funcţiei 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
vetcorul 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());
213
// se afisaza strcp
cout << "vectorul copiat" << endl;
copy(strcp.begin(), strcp.end(), ostream_iterator<string>(cout, " "));
cout << endl;
return 0;
}
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ă

214
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();
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();
215
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
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 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
216
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";
list<int>::reverse_iterator iter1;
for(iter1 = ls.rbegin(); iter1 != ls.rend(); iter1++)
cout << *iter1 << endl;
return 0;
}
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);
Programul este următorul

#include <iostream>
#include <list>
#include <string>
217
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());

return 0;
}
Rezultatul programului este cel de mai jos.
218
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.
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.
219
#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”);
// parcurge sirul in sens direct
220
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:
• T real();
221
• 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.
222
223
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
224
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 ·
In acelaşi fel obţinem:
( )
2 10
) 1010 ( 10 ·
( ) ( )
2 10
1011 11 ·
( ) ( )
2 10
1100 12 ·
( ) ( )
2 10
1101 13 ·
( ) ( )
2 10
1111 15 ·
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 · ·
225
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
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
226
-13 1000 1101 8D
25 0001 1001 19
-7 1000 0111 87
127 0111 1111 7F
-127 1111 1111 FF
Gama numerelor întregi reprezentabile pe un octet în mǎrime şi semn este [-127, 127].
Putem scrie formula de reprezentare a numerelor binare în mǎrime şi semn ca:


·


+ − ·
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
227
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:
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.
228
Numǎr zecimal Numǎr binar Numǎr hexazecimal
-28 1110 0100 E4
-14 1111 0010 F2
-56 1100 1000 C8
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
0.3245 2
-0.35 -1
0.1248 2
0.35 0
229
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
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].
0.23755 3
0.425 -3
230
In calculatoarele personale numerele reale se reprezintǎ în virgulǎ mobilǎ în felul
urmǎtor
R = semn * 1.mantisa * 2
exponent
In reprezentarea în virgulǎ mobilă scurtǎ (simpla precizie) numǎrul se reprezinta pe 32
biţi. Domeniul exponentului este [-127, 127]. La exponent se adaugǎ totdeauna
valoarea 127 astfel încat exponentul are domeniul [0,254]. Bitul de semn are valoarea
0 pentru numere pozitive şi 1 pentru cele negative. Bitul de semn ocupă bitul 31,
exponentul ocupǎ biţii 23-30 iar mantisa biţii 0-22. Gama numerelor reale ce se pot
reprezenta în acest format este
) 10 , 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
231
6 Ack Syn & 6 F V f v
7 Bel Elb ‘ 7 G W g w
8 Bs Can ( 8 H X h x
9 Ht Em ) 9 I Y i y
10 Lf Sub * : J Z j z
11 Vt Esc + : K [ k {
12 Ff Fs , < L \ l |
13 Cr Gs - = M ] m }
14 So Rs . > N ^ n ~
15 Si Us / ? O _ o del
Tabelul 1.1 Codul ASCII
Codul zecimal unui caracter este 16*x+y. De exemplu caracterul A are codul zecimal
65 sau, echivalent, codul hexazecimal 41.
Un alt sistem de reprezentare a caracterelor este UNICODE. în varianta UNICODE-
16 fiecare caracter este reprezentat pe 2 octeţi.
232
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
233

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..............................................................................................10 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 - -...........................................................................................23 1.16 Operaţii cu numere întregi la nivel de bit........................................................26 1.16.1 Operatori de deplasare...............................................................................26 1.16.2 Operaţii logice la nivel de bit....................................................................28 2 Structuri de control fundamentale.............................................................................31 2.1 Algoritme............................................................................................................31 2.2 Expresii relaţionale.............................................................................................32 2.3 Expresii booleene...............................................................................................33 2.4 Operatorul if.......................................................................................................34 2.5 Operatorul switch...............................................................................................37 2.6 Operatorul ?........................................................................................................38 2.7 Operatorul while.................................................................................................38 2.8 Operatorul do-while...........................................................................................41 2.9 Operatorul for.....................................................................................................42 2.10 Operatorul ,......................................................................................................46 3 Funcţii........................................................................................................................48 3.1 Funcţii standard..................................................................................................48 3.2 Definirea funcţiilor.............................................................................................49 3.3 Prototipuri de funcţii..........................................................................................51 3.4 Compilarea separată a funcţiilor.........................................................................52 3.5 Funcţii cu parametri tablouri .............................................................................53 3.6 Supraîncărcarea funcţiilor..................................................................................58 3.7 Transmiterea parametrilor către funcţii..............................................................60 3.8 Recursivitatea.....................................................................................................65 3.9 Funcţii generice..................................................................................................66 3.10 Funcţii C standard de manipulare a caracterelor..............................................67 4 Pointeri şi referinţe....................................................................................................70 4.1 Pointeri...............................................................................................................70 4.1.1 Declararea variabilelor tip pointerilor.........................................................71 4.2 Referinţe.............................................................................................................75 4.3 Pointeri la funcţii................................................................................................78 4.4 Interpretarea instrucţiunilor ce conţin pointeri...................................................79 4.5 Pointeri şi tablouri unidimensionale...................................................................81 4.6 Şiruri tip C..........................................................................................................85

2

4.7 Pointeri şi tablouri multidimensionale ..............................................................88 4.8 Parametrii funcţiei main. Parametrii liniei de comandă.....................................92 4.9 Alocarea dinamică a memoriei..........................................................................93 5 Fişiere tip C.............................................................................................................102 5.1 Fişiere tip text...................................................................................................103 5.1.1 Funcţii intrare / ieşire cu format................................................................104 5.1.2 Funcţii intrare / ieşire tip caracter..............................................................106 5.2 Fişiere text tip şir de caractere..........................................................................110 5.3 Fişiere binare....................................................................................................112 6 Structuri tip C şi uniuni...........................................................................................117 6.1 Structuri............................................................................................................117 6.2 Uniuni...............................................................................................................120 7 Clase .......................................................................................................................122 7.1 Definirea unei clase..........................................................................................123 7.1.1 Definirea unei clase...................................................................................123 7.1.2 Pointerul this..............................................................................................128 7.1.3 Spaţii de nume...........................................................................................128 7.2 Constructori şi destructori................................................................................129 7.2.1 Constructori...............................................................................................129 7.2.2 Destructori.................................................................................................133 7.3 Funcţii prietene.................................................................................................134 7.4 Determinarea tipului unei expresii...................................................................136 8 Siruri tip C++...........................................................................................................139 8.1 Clasa string.......................................................................................................139 9 Supraîncărcarea operatorilor...................................................................................143 9.1 Supraîncărcarea operatorului de atribuire........................................................144 9.2 Supraîncărcarea operatorilor aritmetici............................................................146 9.3 Supraîncărcarea operatorilor << şi >>..............................................................148 10 Moştenirea.............................................................................................................151 10.1 Pointeri la obiecte. Operatorii new şi delete..................................................151 10.2 Moştenirea....................................................................................................155 10.2.1 Definirea unei clase derivate...................................................................155 10.2.2 Specificatorii de acces.............................................................................157 10.3 Funcţii virtuale. Polimorfism........................................................................163 10.4 Destructori virtuali........................................................................................170 10.5 Date şi funcţii statice.....................................................................................171 10.5.1 Date statice..............................................................................................171 10.5.2 Funcţii statice..........................................................................................173 11 Fişiere tip C++.......................................................................................................176 11.1 Fişiere text.....................................................................................................177 11.1.1 Funcţii intrare / ieşire cu format..............................................................177 11.1.2 Funcţii intrare / ieşire tip caracter............................................................179 11.2 Fişiere binare.................................................................................................181 11.3 Fişiere text tip string......................................................................................184 12 Tratarea excepţiilor................................................................................................186 12.1 Excepţii..........................................................................................................186 12.2 Excepţii lansate de funcţii.............................................................................188 12.3 Excepţii standard...........................................................................................189 12.4 Excepţii intrare / ieşire..................................................................................191 13 Aplicaţii ................................................................................................................194

3

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

4

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

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

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

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

defineşte tipul CLRX şi trei constante aaa.6 Constante cu nume In program putem defini constante cu nume în următoarele feluri • utilizarea directivei define . De exemplu. ccc}. O enumerare defineşte constante întregi sau un tip întreg şi valorile asociate acelui tip. directiva # define PI 3. prima are valoarea zero şi fiecare constantă următoare are o valoare mărită cu unu : aaa = 0 bbb = 1 ccc = 2 In program constantele sunt înlocuite cu valorile lor. defineşte tipul cstval şi constantele de acest tip 9 .14 • enumerări. Preprocesorul citeşte diversele directive şi modifică programul sursă corespunzător acestora.• constante tip caracter UNICODE corespund tipului wchar_t. Ele se definesc sub forma L’\xhhhh’ unde h reprezintă o cifră hexazecimală. bbb. enum CLRX {aaa. Compilatoarele limbajelor C şi C++ au un preprocessor care modifică programul sursă înainte de compilare. Directiva define ce defineşte o constantă are forma # define nume valoare unde valoare este interpretată de preprocessor ca un şir de caractere.14 are ca efect înlocuirea numelui PI în program cu şirul de caractere 3. 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. ccc aferente acestui tip. nn = 2 + 3 * 4}. bbb. cc = -1. Constante booleene constante booleene sunt true şi false şi se reprezintă prin valorile unu şi respective zero. De exemplu enum cstval {ppp = 2. dd. Valoarea fiecărei constante poate fi specificată printr-o expresie întreagă constantă. 1.

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

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).5) 11 .5 (a*cos(x)*cos(x)+b*sin(x))/(2.75 + x e x + e −2 x 5 log( x + 2) + ln 1 + cos( x ) ( x + y) a + 3. double b) şi calculează expresia ab. a * cos 2 ( x) + b * sin( x) 2. iar funcţia ceil(x) calculează valoarea  x  (cel mai mic număr întreg mai mare ca x). Exemple de expresii aritmetice şi scrierea lor sunt prezentate mai jos. se defineşte astfel a % b = a – (a / b) * b De exemplu 19 % 4 = 3 19 / 4 = 4 11 % 5 = 1 11 / 5 = 2 Pentru gruparea termenilor se folosesc paranteze rotunde. Toate funcţiile de mai sus au argumente de tip double şi rezultatul de tip double. De exemplu. a+3. Apelarea unei funcţii se face scriind numele funcţiei ca termen într-o expresie urmat în paranteze de parametrii actuali.împărţirii a două numere întregi). a şi b. Restul împărţirii a două numere întregi. Funcţia pow are prototipul double pow(double a. Vom presupune că variabilele din aceste expresii au fost declarate în prealabil de tip double şi au primit valori.75+fabs(x)) (exp(x)+exp(-2*x))/5 log10(fabs(x)+2)+log(fabs(1+cos(x))) pow(x+y. ( şi ).

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

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

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

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

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

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

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

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

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

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

In această etapă sunt ataşate programului funcţiile din biblioteci. Editorul de legături generează un program executabil din toate fişierele obiect. sau o operaţie intrare/ieşire. • In etapa a treia programul excutabil este încărcat în memorie şi executat. etc.1. • Etapa a doua este editarea legăturilor.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. El este un fişier cu extensia exe. Din fiecare fişier sursă rezultă un fişier obiect. el generează doar o secvenţă de apel la funcţie. Fiecare fişier sursă este compilat separat. etc. expresia sizeof (char) are valoarea 1. 22 . • Prima etapă este compilarea programului. Atunci când compilatorul întâlneşte un apel de funcţie (de exemplu sin. Fie instrucţiunea double a[5]. deoarece tipul expresiei este double. In această etapă programul este verificat pentru erori sintactice. In cazul unui tablou rezultatul este numărul total de octeţi ocupat de tablou. Expresia sizeof(a) are valoarea 40 iar expresia sizeof(a) / sizeof(a[0] dă numărul elementelor tabloului a. expresia sizeof(int) are ca rezultat valoarea 4. Funcţiile respective sunt precompilate în biblioteci speciale şi programul editor de legături ataşează aceste funcţii programului obiect. double m. operatorul sizeof dă numărul de octeţi ocupat de rezultatul expresiei. cos. In cazul cand operandul este o expresie. 1.13 Execuţia unui program Reamintim etapele de execuţie a unui program. 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. Expresia sizeof(x + m) are valoarea 8. Fie de exemplu instrucţiunile int x.).

.Operatorul ++ incrementează o variabilă întreagă cu valoarea unu. float : . // scrie dimensiunea rezultatului unei expresii cout << “\texpresia a[0]+b: ” << sizeof(a[0]+ b) << ‘\n’. putea fi scrisă ca cout << ‘\t’ << ”int: “ << sizeof(int) << endl.. cout << “\tdouble: “ << sizeof(double) << endl..... sau endl.. ‘\t’. Şirului de caractere “\tint: “ duce la scrierea caracterului ‘\t’ şi a şirului ”int: “. Aceşti operatori pot fi : 23 . // scrie dimensiunea tipurilor standard cout << ”\tint: “ << sizeof(int) << endl. Instrucţiunea cout << ”\tint: “ << sizeof(int) << endl. } Rezultatul rulării programului este prezentat mai jos.. cout << “\tfloat: “ << sizeof(float) << endl. 1. // scrie dimensiunea unui vector cout << “\tvectorul float a[10]: “ << sizeof(a) << endl. int main() { float a[10]. cout << “\tchar: “ << sizeof(char) << endl. b. char : .decrementează o variabilă întreagă cu unu. Vrem ca rezultatele să fie afişate pe ecran astfel: Numarul de octeti utilizati int : . Vom scrie un program care să afişeze numărul de octeţi utilizaţi pentru memorarea tipurilor fundamentale. operatorul .15 Operatorii ++ şi . expresia a[0]+b : . cout << “Numarul de octeti utilizati:” << endl... double : ….Exemplu.. Programul este următorul : // dimensiunea tipurilor standard # include <iostream> using namespace std. 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] : …. Menţionăm că este echivalent dacă în instrucţiunea cout utilizăm caracterul ‘\n’.. sau şirul de caractere “\n”.

In acelaşi fel. valoarea incrementată sau decrementată se utilizează mai departe în calcule. f(x). Modul de execuţie a expresiei este următorul: se incrementează sau decrementează valoarea variabilei x cu unu. x = i. 24 . --x postfix x++. cout << “i = “ << ++i << endl. x. k. f(x). După execuţia instrucţiunii x = ++i variabilele au valorile i = 2 şi x = 2. x.Expresiile ++x şi x++ reprezintă instrucţiunea x=x+1 iar expresiile . Instrucţiunea x = ++i. Exemplu.. Fie declaraţia de variabile int i = 1. reprezintă scrierea prescurtată a expresiilor: x = x – 1. ++x. k = j. i = i + 1.• • prefix ++x. cout << “i = “ << i << endl. --x O expresie de forma f(++x). reprezintă scrierea prescurtată a secvenţei de instrucţiuni j = j – 1. expresia f(--x). Instrucţiunea k = --j. Valorile afişate vor fi i=1 i=2 i=2 deoarece a doua instructiune cout este echivalentă cu instrucţiunile cout << “i = “. Fie secventa de instrucţiuni int i = 1.reprezintă instrucţiunea x=x–1 Cazul operatorilor prefix. După execuţia instrucţiunii variabilele au valorile j = 2 şi k = 2. Exemplu. este scrierea prescurtată a expresiilor: x = x + 1. reprezintă scrierea prescurtată a secvenţei de instrucţiuni i = i + 1. cout << “i = “ << i << endl.-x şi x. Exemplu. Fie declaraţia de variabile int j = 3.

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

Exemplu.4.j=1 j=1 j=2 deoarece a doua instrucţiune cout este echivalentă cu instrucţiunile : cout << “j = “ << j. Instrucţiunea d = x[i++].4 şi i=2 Fie acum secvenţa de instrucţiuni următoare : int j. corespunde secvenţei de instrucţiuni : d = x[i]. 14.4e1. corespunde secvenţei de instrucţiuni : j = j + 1.16 Operaţii cu numere întregi la nivel de bit 1. -3. De exemplu. i = 1.1 0}. j = 1. cout << endl. prin deplasarea numărului 7 la dreapta cu un bit se obţine rezultatul 3. d = x[j]. Instrucţiunea d = x[++j]. Avem deci d = 14. Deplasarea la dreapta a unui întreg cu un bit reprezintă câtul împărţirii acelui număr cu doi.4. j = j + 1. Expresia i++ are valoarea 1 şi apoi se incrementează i. d = x[i++]. double x[4] = {2. -3. 14. d = x[++j].16.4e1. In consecinţă expresia ++j are valoarea 2 şi avem d = -3.1 Operatori de deplasare Deplasarea la stânga a unui întreg cu un bit reprezintă înmulţirea acelui număr cu doi.1 şi j=2 1. i = i + 1. 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. double x[4] = {2. Expresia de deplasare a unui număr întreg are forma 26 . double d. double d. Fie secvenţa de instrucţiuni de mai jos : int i.1 0}.

se scrie prescurtat x >>= n . b = a << 4. Z şi R care să aibe valorile 1. 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. (La deplasarea la dreapta se propagă bitul de semn. Expresia din dreapta dă numărul de biţi cu care se face deplasarea. Limbajele C şi C++ au operatorii de atribuire <<= şi >>= pentru scrierea prescurtată a instrucţ iunilor ce conţin operatorii << şi >>. Fie instrucţiunile de mai jos int a = 0xff. enum {X = 1. int y = 0xff. 27 . Variabila j va primi valoarea 3. la deplasarea la stânga se adaugă zerouri). R = X << 3}. 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ă. c = y >> 4. iar instrucţiunea x = x >> n . Z = Y << 1. Reamintim că 7 / 2 = 3. se scrie prescurtat x <<= n . Ca un alt exemplu. Deplasarea se face după regulile deplasării numerelor binare cu semn. să definim constantele X. Exemple. Variabila x primeşte valoarea 3 (câtul înpărţirii lui 7 la 2 este 3). Vom încheia acest paragraf cu un program care deplasează numere întregi şi afişază valoarea lor în zecimal şi hexazecimal. int c. Numărul a. reprezentat pe patru octeţi. Vom exemplifica utilizarea operatorilor de deplasare deplasând două variabile întregi la stânga şi la dreapta şi vom afişa rezultatele în bazele 10 şi 16. Fie instrucţiunea int x = 7 >> 1. 4. Y. Instrucţiunea x = x << n .Rezultatul expresiei din stânga este numărul întreg ce va fi deplasat. vezi anexa. 8. folosind instrucţiunea enum. i si j şi secvenţa de instrucţiuni : i = 7. Care este valoarea numerelor 0xf şi 0xff0 în zecimal? Fie două variabile întregi. Dacă expresia de deplasat este de tipul unsigned biţii adăugaţi sunt zerouri. 2. int b. Y = X << 1. j = i >> 1.

int b = 50. } Rezultatele rulării programului sunt prezentate în figura de mai jos.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 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 &.16. Expresiile cu aceşti operatori logici au forma următoare 28 .# include <iostream> using namespace std. | ^ sunt operatori binari. cout << “x deplasat la stanga cu 1 bit = “ << dec << x << “ “ << hex << showbase << x << endl. // deplasează variabila b la dreapta cu 3 pozitii b = b >> 3. cout << “b = “ << dec << b << “ “ << hex << showbase << b << endl. return 0. /* uitlizarea operatorilor de deplasare */ int main() { int x = 10. cout << “x = “ << dec << x << “ “ << hex << showbase << x << endl. cout << “b deplasat la dreapta cu 3 biti = “ << dec << b << “ “ << hex << showbase << b << endl. 1. // deplasează variabila a la stanga cu un bit x = x << 1.

Operatorul ~ este operator unar. c. Fie următoarea secvenţă de program : int a. d. De exemplu expresia x=x&a. |= şi ^= pentru scrierea prescurtată a instrucţiunilor de atribuire ce conţin operatori &. e. &.Operaţiile sunt aplicate asupra fiecărei perechi de biţi din cei doi operanzi. vom exemplifica doar calculul expresiei 0xf & 0xa = 0xa Rezultatul se obţine conform calculului de mai jos. De ce? Limbajele C şi C++ au operatorii &=. e = a ^ b. b = 0xabcd. d = a | b. Se scrie prescurtat x &= a . | şi ~ sunt operatori aritmetici. la calculul valorii c = a & b. b. Expresiile cu acest operator au forma următoare Operatorul ~ complementează fiecare bit din expresia întreagă. | şi ^. 29 . Rezultatele sunt c = 0xa000 d = 0xfbcd e = 0x5bcd In primul caz. a = 0xf000. >>. Reamintim că operatorii <<. Exemple. c = a & b. 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 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.

Vrem ca în variabila întreagă b să selectăm ultimul octet. Să se explice de ce. Pentru a selecta cifrele hexazecimale dintr-un număr întreg vom utiliza ca mască valorile 0xf. } Rezultatul rulării programului este cel de mai jos. 0xf0. 0x2. return 0. pentru a selecta biţii unei cifre hexazecimale vom utiliza ca mască valorile 0x1. cout << “ultimii 8 biti inversati din a = “ << hex << showbase << b << endl. 0x4 şi 0x8. în cazul al doilea. b. etc. cout << “a = “ << hex << showbase << a << endl. Exemplu. 30 . b = a & 0x3ff. Pentru a selecta cifrele octale dintr-un număr întreg vom utiliza ca mască valorile 07. apoi ultimii 10 biţi şi în final primii 4 biţi ai variabilei a. Fie variabila întreagă a = 0x6dbc. int main() { int a = 0x6dbc. // selecteaza ultimul octet din a b = a & 0xff. cout << “primii 4 biti din a = “ << hex << showbase << b << endl. 070. etc. masca este 0x3ff. // pastram primii opt biti si inversam ultimii opt biti din a b = a ^ 0x00ff. // selectează primii patru biti din a b = a & 0xf000. cout << “ultimul octet din a = “ << hex << showbase << b << endl. // selecteaza ultimii 10 biti din a cout << “ultimii 10 biti din a = “ << hex << showbase << b << endl.Pentru a selecta unui număr de biţi dintr-o număr întreg se defineşte un şir de biţi numit mască şi se efectuează operaţia & între numărul iniţial şi mască. Inversarea unor biţi din variabila a este prezentată în finalul exemplului. 0xf00. 0700. De exemplu. // operatii logice la nivel de bit # include <iostream> using namespace std.

. Aceşti operatori se caracterizează prin faptul că au o singură intrare şi o singură ieşire. Menţionăm că în cazul operatorului do acţiunea s se execută cel puţin o dată. Operatorii anteriori sunt suficienţi pentru a descrie clasele de calcule ce se pot descompune într-un număr cunoscut de acţiuni. Fiecare acţiune trebuie să poată fi executată într-un interval finit de timp. else s2. 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. Orice algoritm se poate descompune într-un număr finit de etape. condiţie este o expresie booleană care are valoarea advărat sau fals. în timp ce pentru operatorul while este posibil ca acţiunea s să nu se execute niciodată. s2. Acţiunea s se execută atâta timp cât expresia booleană condiţie are valoarea adevărat. ……….2 Structuri de control fundamentale 2. 31 . i = 2: s2.1 Algoritme Programele reprezintă formulări concrete ale unor algoritme ce prelucrează structuri de date. 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. s2. 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. sau if (condiţie) s1. In funcţie de valoarea lui i se execută una dintre acţiunile s1. indiferent de conţinutul său. while (condiţie) In acest caz acţiunea s se execută atâta timp cât condiţie are valoarea adevărat. Fiecare operator este interpretat în şirul de calcule ca o singură acţiune. Acţiunea s1 se execută când condiţie are valoarea adevărat. Un algoritm este format dintr-un şir finit de acţiuni bine definite şi neambigue pentru rezolvarea unei probleme. 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. … sn. i = n: sn. • Operatorul switch este generalizarea operatorului if switch(i) i = 1: s1. • Operatorul do are forma do s. sn.

Fişierele secvenţiale au un număr de elemente necunoscut în avans. for i = 1. Forma acestui operator este for (i = instrucţiune1. în ordinea priorităţilor <. Ele se prelucrează de regulă cu operatorul while. Exemplu. i = i + 1 s = s * i.Operatorul for se utilizează atunci când numărul de execuţii ale unei acţiuni este dinainte cunoscut. != O expresie relaţională are următoarea formă Rezultatul evaluării unei expresii relaţionale este fals sau adevărat (false sau true). Tablourile au un număr cunoscut de elemente. i <= n. Structurile fundamentale de date sunt • structuri simple: numere întregi. In construcţia unui program structurile de control şi structurile de date sunt inseparabile. • structuri complexe tablouri. b = 3. Algoritmul de calcul pentru n! care este definit ca n!= ∏ i i =1 n • s = 1. >. Operatorii relaţionali ai limbajelor C şi C++ sunt. while(condiţie) s. Ele se prelucrează de regulă cu operatorul for. Menţionăm că testul condiţiei se face înainte de execuţia acţiunii s. In tabela de mai jos sunt prezentate exemple de expresii relaţionale şi rezultatul evaluării lor. Fie următoarele instrucţiuni de atribuire int a = 2. fişiere. Priorităţile operatorilor aritmetici sunt mai mari decât ale operatorilor relaţionali. Operatorii prezentaţi se numesc structuri de control fundamentale. condiţie. caractere. Exemple. şi o instrucţiune2 ce poate modifica valoarea variabilei de control după fiecare iteraţie. Operatorul are o variabilă de control ce se poate modifica la fiecare iteraţie. Operatorul include o instrucţiune1 ce specifică valoarea iniţială a variabilei de control.2 Expresii relaţionale O operaţie definită pentru tipurile de date fundamentale este compararea. reale. 2. >= = =. 32 . c = 6. Operatorul for este echivalent cu următoarele instrucţiuni instrucţiune1. <=. instrucţiune2. Acţiunea s se execută atât timp cât expresia booleană condiţie are valoarea adevărat. instrucţiune2) s.

Fie următoarea instrucţiune de declarare a unei variabile de tip bool bool r. Variabila r poate avea valorile true sau false. Operatorul nu este operator unar.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. x false false true true y false true false true x && y false false false false x||y false true true true x !x false true true false Rezultatul evaluării unei expresii booleene este true sau false. respectiv true. în ordinea priorităţilor ! && | | care reprezintă operatorii nu (not). r = a – b >= 2. iar operatorul = = este operator de comparare. r = a * a != -b. respectiv sau (or).3 Expresii booleene Operatorii booleeni ai limbajelor C şi C++ sunt. In cursurile de logică operatorii booleeni se notează astfel: nu (not) şi (and) sau (or) ¬ ˄ ˅ 33 . In final reamintim că operatorul = este operator de atribuire. Aceşti operatori se definesc folosind tabelele de adevăr. Pentru afişarea valorii booleene ca true. Putem avea următoarele instrucţiuni de atribuire r = a + b = = c. şi (and). La scrierea valorii unei variabile booleene valoarea afişată este 0 sau 1. iar expresia a==b este o expresia relaţională care are ca rezultat valoarea true sau valoarea false. respectiv false se utilizează manipulatorul boolalpha. 2. Instrucţiunea a=b atribuie variabilei a valoarea variabilei b. după cum variabila are valoarea false.

4 Operatorul if Acest operator execută o anumită instrucţiune în funcţie dacă o anumită condiţie este îndeplinită sau nu. else S2.! ~ 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.Exemple de expresii booleene şi scrierea lor sunt prezentate mai jos : a∧b a & &b ¬(a ∨ b) !(a || b) a ∧b∨c a & &b || c ¬ a ∨ ¬b !a ||!b a ∧ (b ∨ c ) a & &(b || c) Pentru scrierea simplă a expresiilor booleene sunt importante două teoreme numite legile lui DeMorgan ¬(a ∨ b) = ¬a ∧ ¬b ¬(a ∧ b) = ¬a ∨ ¬b care se demonstrează cu ajutorul tabelelor de adevăr. In final menţionăm că operatorii booleeni and şi or sunt • comutativi a∨b = b∨a a∧b = b∧a • asociativi la stânga a ∨ b ∨ c = ( a ∨ b) ∨ c a ∧ b ∧ c = ( a ∧ b) ∧ c • distributivi a ∨ (b ∧ c ) = ( a ∨ b) ∧ (a ∨ c) a ∧ (b ∨ c ) = ( a ∧ b) ∨ (a ∧ c) In final prezentăm prioritatea (precedenţa) şi asociativitatea operatorilor Operator []() ++ -. Forma operatorului if este if(expresie) S1. 34 .+ .

cin >> n >> m. Operatorul if poate avea şi o formă simplificată if(expresie) S. cin >> n >> d. cout << “ maximul dintre “ << m << “ si “ << n << “ este ”. if(n > m) cout << n << endl. Un alt exemplu este calculul maximului a două numere întregi citite de la tastatură. cout << “introduceti doi intregi : “ << endl. vom calcula expresia n % d. else cout << m << endl. In care se execută instrucţiunea S dacă expresie are valoare diferită de zero. return 0. int main() { int n. 35 . m. Pentru a testa dacă un întreg n este divizibil cu un alt întreg d. int main() { int n. restul împărţirii lui n la d. d.Dacă expresie este diferită de zero se execută instrucţiunea S1 altfel instrucţiunea S2. Cei doi întregi se citesc de la tastatură. // calculul maximului a doua numere intregi # include <iostream> using namespace std. if(n % d == 0) cout << n << “ este divizibil cu “ << d << endl. } Un exemplu de execuţie a programului este dat mai jos. cout << “introduceti doi intregi : “. return 0. } Un exemplu de rulare a programului este prezentat mai jos. else cout << n << “ nu este divizibil cu “ << d << endl. # include <iostream> using namespace std. Ca prim exemplu vom scrie un program care să testeze dacă un întreg este divizibil cu altul.

adică un şir de instrucţiuni cuprinse între acolade. se execută instrucţiunea if(e2). if(x < y) { int temp = x. Ca exemplu. în caz contrar se execută instrucţiunea if(e3).Instrucţiunile din expresia operatorului if pot fi instrucţiuni compuse. y = temp. return 0. Dacă expresia booleană e1 are valoarea adevărat. De exemplu. x = y. // ordonarea descrescatoare a două numere intregi citite de la tastatura # include <iostream> using namespace std. } Un exemplu de execiţie a programului este cel de mai jos. int main() { int x. cin >> x >> y. else s2. putem avea instrucţiunea compusă if (e1) if (e2) s1. else if(e3) s3.” << y << endl. e2. (este diferită se zero). 36 . e3 sunt expresii booleene. Menţionăm că o instrucţiune if poate conţine alte instrucţiuni if. } cout << «numerele ordonate descrescator : « << x << “. cout << “introduceti doi intregi : “. unde e1. y. fie ordonarea descrescătoare a două numere întregi citite de la tastatură. else s4. sau blocuri de instrucţiuni.

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

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

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

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

8 Operatorul do-while Operatorul do-while este tot un operator repetitiv. n = 1. i = 1. Instrucţiunea S poate fi o instrucţiune simplă sau o instrucţiune compusă. Instrucţiunea S din diagrama sintactică poate fi o secvenţă de instrucţiuni. Exemplu. i = i + 1. Acest operator are forma do S while (expresie). 41 . i = i + 1. { şi }.2. while (i < 6) Programul corespunzător este următorul # include <iostream> using namespace std. Programul pseudocod este următorul. n. i=1 do n = n * i. Instrucţiunea S se execută atâta timp cât expresia este diferită de zero (are valoarea adevărat). /* calculul valorii 5! */ int main() { int i. n = 1. scrisă între acolade. do { n = n * i. } while(i < 6). 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.

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

…. care scrie şirurile de caractere x şi y pe câte 4. respectiv 5 coloane. cout << setw(4) << "x" << '\t' << setw(5) << "y" << endl. } return 0. i = i + 1) { x = 1 + 0. i = i + 1) { x = 1 + 0. int main() { int i. for(i = 0. In program rezultatele sunt scrise de asemenea separate de tab. …. i < 6.2 * i y = e x + 2x + x } Vrem ca rezultatul să fie afişat în două coloane cu antetul x şi y. Pentru a prescrie numărul de coloane al unui câmp se utilizează funcţia setw() din biblioteca <iomanip>. y = exp(x) + 2 * x + sqrt(x). O primă variantă de program pseudocod este următorul for (i = 0. 43 .2. …. Programul este următorul # include <iostream> # include <cmath> # include <iomanip> using namespace std. double x. Rezultatul rulării programului este cel de mai jos. Vom calcula valoarea expresiei e x + 2 x + x pentru x cuprins între unu şi doi cu pasul 0. ca mai jos. separate de caracterul ‘\t’ (tab). cout << setw(4) << x << '\t' << setw(5) << y << endl. i < 6. Exemplu. x y …. { şi }. } Pentru a scrie antetul tabelului cu valori am utilizat instrucţiunea cout << setw(4) << "x" << '\t' << setw(5) << "y" << endl.2 * i.instrucţiune simplă sau o instrucţiune compusă ce constă dintr-o secvenţă de instrucţiuni între acolade. y.

Menţionăm că instrucţiunea for se putea scrie astfel for(i = 0; i < 6; i++) sau for(i = 0; i < 6; ++i) Un alt mod de a calcula valorile expresiei de mai sus este 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, y; cout << setw(4) << "x" << '\t' << setw(5) << "y" << endl; for(x = 1; x <= 2; x = x + 0.2) { y = exp(x) + 2 * x + sqrt(x); cout << setw(4) << x << '\t' << setw(5) << y << endl; } return 0; } Exemplul următor calculează suma componentelor pare şi impare ale unui vector x cu şase elemente. Programul pseudocod este următorul s1 = 0; s2 = 0; for( i = 0; i < 6; i=i+1) if (i % 2 = = 0) s1 = s1 + xi else s2 = s2 + xi 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 #include <iostream> using namespace std; /* calculul sumei componentelor pare si impare ale unui vector */ int main() { float s1 = 0, s2 = 0; int i; float x[10] = {1, -12, 23.2, 0.44, 3.4, -2.3}; for(i = 0; i < 6; i = i + 1) if(i % 2 = = 0) s1 = s1 + x[i];

44

else s2 = s2 + x[i]; cout << “suma elementelor pare este “ << s1 << endl << “suma elementelor impare este “ << s2 << endl; return 0; } 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; for(cnt = 1; cnt <= n; ++cnt) { cout << "dati un numar : "; cin >> x; if(x < 0) continue; sum = sum + x; m = m + 1; }

45

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; 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) 46

cout << str << " este palindrom" << endl; else cout << str << " nu este palindrom" << endl; return 0; } Rezultatul rulării programului este prezentat mai jos.

47

exp. i. 3. 48 . Funcţia isalpha() cu prototipul int isalpha(int c). ….1 Funcţii standard Limbajul are multe funcţii standard. etc. int n = strlen(a). for(int i = 0. log10. cos. Programul este următorul #include <cstdlib> #include <iostream> using namespace std. } Rezultatul rulării programului este cel de mai jos. int k = 0. return 0. limbajele de programare permit definirea de funcţii care să realizeze anumite calcule. ‘z’. dă lungimea unui şir de caractere. etc. cout << "in sir sunt " << k << " cifre" << endl.3 Funcţii Limbajele de programare au funcţii predefinite ce corespund funcţiile matematice uzuale : sin. Prototipul acestei funcţii se află în biblioteca <cstdlib>. matematice. i < n. Atunci când un grup de instrucţiuni se utilizează în mai multe locuri din program. In plus. Vom exemplifica utilizarea acestor funcţii cu un program ce citeşte un şir de caractere de la tastatură şi numără cifrele din şir. are o valoare diferită de zero dacă c este o cifră. ‘A’. …. Vom prezenta acum câteva funcţii de prelucrare a caracterelor. ‘a’. Funcţiile pot fi grupate în biblioteci şi utilizate în diverse programe. ‘1’. aceste instrucţiuni se plasează într-o funcţie care este apelată unde este necesară. Funcţia strlen() cu prototipul int strlen(char x[]). Descompunerea unui program în module are avantaje. i++) if(isdigit(a[i])) k++. ‘9’ şi zero în caz contrar. Un program constă din una sau mai multe funcţii. Prototipurile acestor funcţii se află în biblioteca header <cctype>. de prelucrare a caracterelor. Mai înainte am prezentat cele mai utile funcţii matematice. ‘0’. Funcţiile pot fi testate separat şi programele sunt mai clare. … ‘Z’ şi zero în caz contrar. Una dintre funcţii are numele main şi execuţia programului începe cu această funcţie. cin >> a. Funcţia isdigit() cu prototipul int isdigit(int c). are o valoare diferită de zero dacă c este o literă. log. cout << "introduceti un sir" << endl. int main() { char a[100]. In acest fel programele mari pot fi formate din module.

parametrii ei vor fi două variabile de tip double. 49 . tipn reprezintă tipurile parametrilor arg1. …. 3. Instrucţiunea return termină totodată şi execuţia funcţiei. Exemplu. ce descriu calculele efectuate de funcţie. Numele funcţiei va fi suma. Apelarea lor se face scriind numele funcţiei urmat în paranteze de argumentul funcţiei într-o expresie. Corpul funcţiei este format dintr-o secvenţă de instrucţiuni între acolade. …. funcţia isdigit() este apelată în expresia testată de instrucţiunea if. Parametrii din definiţia funcţiei se numesc parametri formali. …. In corpul funcţiei vom defini o variabilă de tip double. tip2 arg2. 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. Funcţia strlen() este apelată în membrul drept al unei instrucţiuni de atribuire. tip2. 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). z. Tipul acestei expresii trebuie să fie acelaşi cu tipul funcţiei. furnizate de un program care apelează funcţia. • numefuncţie este numele funcţiei ales de programator.Funcţiile strlen() şi isdigit() au câte un parametru şi calculează o valoare. în care vom calcula rezultatul. valoarea calculată de funcţie va fi de tip double. arg2. Orice funcţie are un nume şi parametri sau argumente. x şi y. argn.2 Definirea funcţiilor O funcţie calculează o valoare pe baza valorilor argumentelor sale. • lista de parametri (argumente ale funcţiei) are forma : tip1 arg1. { şi }. Să definim o funcţie care să calculeze suma a două numere reale de tip double. Apelarea funcţiilor şi transmiterea valorii calculate de funcţie în punctual din program unde este apelată este prezentată schematic mai jos.

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

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

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

Prin definiţie. c .h // definitia functiei max() double max (double x. Valoarea calculată va fi ataşată numelui funcţiei. cout << " max(a.h" int main(int argc. valoarea calculată de funcţie. cout << " a = " .5 Funcţii cu parametri tablouri Funcţiile pot avea ca parametri tablouri. #include "max. Exemplu. b) = " << c << endl . char *argv[]) { double a. matrice. return 0. } Fişierul max. scriem numele vectorului. condiţii asupra parametrilor de intrare. cin >> a . ca parametru actual. scriem numele vectorului urmat de paranteze drepte. vectori.cpp #include <iostream> using namespace std. atunci când un parametru este un tablou. double y) { if( x > y) return x . parametrul formal tablou este un parametru de ieşire. (de intrare. Vom scrie o funcţie care să calculeze suma componentelor unui vector a cu elemente numere reale de tip float. la apelarea funcţiei elementele vectorului sunt iniţializate. în stivă se pune adresa tabloului. Definiţia funcţiei este următoarea /* funcţie ce calculeaza suma componentelor unui vector float suma(float a[]. modificarea apare şi în programul ce a apelat funcţia. else return y . c = max(a. de ieşire). fără paranteze sau indici. Dacă elementele tabloului sunt modificate în funcţie. cin >> b . etc. b) . După cum am spus mai sus. etc. Parametrii funcţiei vor fi vectorul a şi dimensiunea sa. Pentru a defini un parametru formal ca vector. cout << " b = " . Vom presupune că. In consecinţă. parametrul formal vector are forma float a[] Definiţia funcţiei va fi precedată de un comentariu ce descrie parametrii funcţiei. } 3. b.Fişierul main. int n). La apelarea unei funcţii cu parametri vectori. Parametri de intrare 53 .

/* testarea functiei suma */ int main() { float x[5] = {11. trebuie testată. } O funcţie. In exemplul anterior definiţia funcţiei este scrisă înainte de utilizarea ei în funcţia main. float z.34. cout << endl.5. i = i++) s = s + a[i]. Căutarea binară într-o listă ordonată crescător. 5). cum s-a arătat mai sus. // scrie componentele vectorului cout << “suma componentelor vectorului “ << endl. In acest caz. i < 5. for(i = 0. // scrie rezultatul cout << “este “ << z << endl. Fie indicii ls şi ld domeniului de căutare în vector. int n) { int i. 14}. } Funcţia este apelată cu instrucţiunea z = suma(x. -2. Componentele vectorului sunt afişate pe ecran separate de caracterul tab pe un rând iar rezultatul pe rândul următor. // calculeaza suma componentelor for (i = 0. Iniţial ls 54 . i++) cout << x[i] << “\t”. float s = 0. După cum am spus mai sus. este posibil de a scrie definiţia unei funcţii după funcţia main(). return s.3. 0. fără indici sau paranteze drepte. când un parametru al unei funcţii este tablou. -2. înainte de definiţia funcţiei main() trebuie să scriem prototipul funcţiei. Reamintim că.67. Vrem să testăm dacă valoarea z se află printre componentele vectorului.a – vector n – dimensiunea vectorului a Iesire suma – suma componentelor vectorului a Preconditie : elementele vectorului a sunt initializate */ float suma(float a[]. Un program simplu ce testează funcţia de mai sus este următorul. return 0. Exemplu. i < n. odată scrisă. // calculeaza suma componentelor z = suma(x. Fie un vector x cu n elemente reale ordonate crescător. la apelarea funcţiei scriem doar numele tabloului. 5).

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ă.09}. int n. etc.. condiţiile asupra parametrilor.77. i. ld = n . else ld = i .vector cu elemente sortate crescator n . double z) { int ls. } Un program de testare a funcţiei este următorul. = -1 daca z nu exista in lista. Preconditie : x[0] <= x[1] <= . 55 . la apelarea funcţiei. 4. int n.2. if(x[i] < z) ls = i +1. 3.1] */ int find (double x[].dimensiunea lui x z . 1. z = 4. } return -1. 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ă.valoarea cautata find = indicele elementului z in lista daca exista. double z) Parametri de intrare : x . <= x[n ..14. 5. double z. int main() { double a[6] = {-2.35.2. ls = 0.1. parametrul vector este scris ca numele tabloului.= 0 şi ld =n-1. definiţia funcţiei este precedată de un comentariu ce descie funcţia.1. Reamintim că. ld. parametrii. Funcţia ce implementează acest algoritm este următoarea /* Cautarea binara int find (double x[]. while(ls <= ld) { i = (ls + ld) / 2. fără indici sau paranteze drepte. 7. if(x[i] == z) return i. int k.12.

z). • o funcţie ce scrie elementele unei matrice pe ecran. if(k < 6) cout << "elementul " << z << " are rangul " << k << endl.k = find(a.2. 6. Matricele pot avea oricâte linii şi de coloane. ce vor fi prescrise în program cu două directive define. 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ă. #include <iostream> #include <iomanip> # define NLIN 2 # define NCOL 2 using namespace std. return 0.14 5. iteraţia 1 2 3 ls 0 3 3 ld 5 5 3 i 2 4 3 x[i] 3. else cout << "elementul " << z << " nu este in lista" << endl.12 4. /* 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. Fiecare funcţie este precedată de un comentariu ce descrie parametrii şi operaţia realizată de funcţie.2 Rezultatul rulării programului este cel de mai jos. } Tabela de mai jos prezintă iteraţiile algoritmului pentru z = 4. */ 56 . Reamintim că primul element din vector are indicele zero. • o funcţie ce calculează suma a două matrice.

} return. i < NLIN. j < NCOL. j. for(i = 0. j++) cin >> a[i][j]. cout << endl. double c[NLIN][NCOL]) { int i. j++) c[i][j] = a[i][j] + b[i][j]. } /* 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 < NCOL. double b[NLIN][NCOL]. i++) for(j = 0. return. i < NLIN. } /* program pentru testarea functiilor */ 57 . return. j. } /* 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. for(i = 0. j < NCOL. for(i = 0.void rdmat(double a[NLIN][NCOL]) { int i. j. i < NLIN. i++) for(j = 0. i++){ for(j = 0. j++) cout << setw(6) << a[i][j] << " ". */ void addmat(double a[NLIN][NCOL].

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

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

parametrii actuali şi variabilele locale ale funcţiei sunt memorate într-o stivă. In cazul transmiterii unui parametru prin valoare. • utilizare parametrilor tip pointer. variabilă Argumentul este o variabilă sau expresie In general o funcţie poate calcula mai multe valori. în tabelul de mai jos. Definirea parametrilor tip referinţă Există două moduri de a defini parametri transmişi prin adresă: • utilizarea parametrilor tip referinţă. 3. 60 . în stivă se pune chiar valoarea parametrului.7 Transmiterea parametrilor către funcţii La apelarea unei funcţii. valoarea lor se modifică în programul apelant (în stivă este adresa acestor parametri). el trebuie transmis prin adresă Vom rezuma proprietăţile celor două moduri de transmitere a parametrilor către funcţii. double c). double b. In consecinţă. • Cazul parametrilor transmişi prin adresă. Dacă în corpul funcţiei modificăm aceşti parametri. double c) { double x = a < b? a: b. pentru ca un parametru al unei funcţii să fie parametru de ieşire. double b). Celelalte valori vor fi asociate unor parametri de ieşire. return (x < c? x : c). parametrii transmişi prin valoare sunt parametri de intrare (nu pot fi parametri de ieşire ai funcţiei). double min(double a. în cazul transmiterii prin adresă în stivă se pune adresa parametrului. Dacă în corpul funcţiei modificăm aceşti parametri. valoarea lor se modifică doar în stivă şi nu în programul apelant.double min(double a. • Cazul parametrilor transmişi prin valoare. double b. 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ă. Există două moduri de a transmite parametrii către funcţii: prin valoare şi prin referinţă (adresă). care vor fi obigatoriu transmişi prin adresă. } Menţionăm că prototipurile celor două funcţii puteau fi scrise respectiv double min(double a. In consecinţă. Una dintre valori este transmisă la punctul de apelare de instrucţiunea return.

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. f(a. la apelarea funcţiei se va pune în stivă adresa acestui parametru. b = 30. cout << “a = “ << a << “ . b). parametrul formal int x. b = “ << b << endl. Valorile variabilelor a şi b înainte de prima apelare a funcţiei sunt 61 . cout << “a = “ << a << “ . b). Referinţa la un astfel de parametru are tipul T&. void f(int x. y = 65. Primul parametru va fi transmis prin valoare iar celălalt prin adresă. De exemplu. parametrul formal int& x este transmis prin adresă. b = “ << b << endl. doarece nu este transmis prin referinţă. // functie ce modifica valorile parametrilor // primul parametru este transmis prin valoare. Fie un parametru de un tip T al unei funcţii. } int main() { int a = 20. şi atunci când acest parametru este modificat în corpul funcţiei el este modificat direct în programul apelant (este parametru de ieşire). f(2*a + 3. Menţionăm că primul parametru se modifică doar în stivă şi nu în programul apelant. } Rezultate afişate sunt cele de mai jos. Vom prezenta acum doar parametrii tip referinţă. int& y) { x = 52. b = “ << b << endl.Referinţele şi variabilele tip pointer vor fi prezentate pe larg în capitolul următor. return. return 0. este transmis funcţiei prin valoare. Dacă un parametru al funcţiei este tip referinţă. al doilea prin adresa # include <iostream> using namespace std. cout << “a = “ << a << “ .

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

-3.73. } Rezultatul rulării programului este cel de mai jos. Tipul funcţiei este void deoarece toate valorile calculate de funcţie sunt asociate unor parametrii de ieşire. b. return 0. 3). c. cout << endl. 5. /* Calculul sumei a doi vectori void sumvect(double x[]. Exemplu. • 63 . return. i++) cout << c[i] << " ". în stivă se pune adresa primului element al tabloului. cout << "vectorul suma" << endl. for(int i = 0. double z[]. Vectorul sumă va fi z. 6. Valoarea lui actuală este rezultatul evaluării unei constante. Argumentul transmis este o variabilă din programul apelant.15. modificarea unui element al tabloului în funcţie produce modificarea lui în programul ce a apelat funcţia.21}. int n) . i < n. parametri formali tablouri pot fi parametri de ieşire. for(i = 0. In consecinţă. sumvect(a. -3. } Un program ce testează funcţia de mai sus este următorul int main() { double a[3] = {1. double y[]. double c[3]. z Parametrii de iesire : z – vector. i++) z[i] = x[i] + y[i]. Menţionăm că în cazul în care un parametru este un tablou. z = x + y Preconditii : Parametrii de intrare sunt initializati */ void sumvect(double x[].92}. y. variabile sau expresii în programul apelant.25. b[3] = {0. int n) { int i. Vom defini o funcţie ce calculează suma a doi vectori x şi y de numere reale de dimensiune n. double y[]. Parametrii de intrare : x – vector y – vector n – dimensiunea vectorilor x. • un parametru tip referinţă este un parametru de ieşire. i < 3.un parametru transmis prin valoare este un parametru de intrare.29. double z[].

m = s / n. i < n. 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[]. /* Calculul mediei si dispersiei componentelor unui vector void md(float x[]. // calculeaza media for(i = 0. i < n. float& d). unde x este vectorul de numere de dimensiune n pentru care calculăm media m şi dispersia d. float& m. Valoarea medie a elementelor şirului este 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. int n. float& m. for(i = 0. m şi d sunt asociate unor parametri. return. float& d) { float s = 0. Prototipul funcţiei va fi void md(float x[]. float& m. #include <iostream> using namespace std. i++) d = d + (x[i] – m) * (x[i] – m). d = d / (n – 1). i++) s = s + x[i]. xn . x2 . // calculeaza dispersia d = 0. int n.Vom încheia acest paragraf definind o funcţie care să calculeze media şi dispersia unui şir de numere x1. int n. Deoarece valorile calculate. Media şi dispersia vor fi asociate unor parametri de ieşire ai funcţiei. 64 . float& d). int i. funcţia are tipul void.

se execută instrucţiunea s = n * fact(n – 1) adică se apelează încă o dată funcţia. if(n = = 1) s = 1. dispersia. int main() { float a[4] = {1. for(int i = 1. i <= n. 4. media.} Un program ce testează funcţia de mai sus este următorul. Vom exemplifica acest lucru cu o funcţie care să calculeze valoarea n! în variantă recursivă şi nerecursivă. adică o funcţie se poate apela pe ea însăşi. ++n) s = s * i. return s. cout << “ media = “ << media << “ dispersia = “ << dispersia << endl. float media. 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. 3. md(a. 1.5. 3. fact(2). -2. Varianta nerecursivă n!= ∏ i i =1 n // calculul factorialului int fact(int n) { int s = 1. return 0. } Rezultatul rulării programului este cel de mai jos. După apelul fact(3) stiva este n=3 s=? Deoarece n este diferit de unu. return i.33}.2e-1.34. dispersia). Stiva de 65 . } Varianta recursivă 1 n =1  n!=  n * (n − 1)! n > 1 // calculul factorialului int fact(int n) { int s. else s = n * fact(n – 1).

fb = -9. şi stiva devine n=3 s=? n=2 s=? N=1 S=1 După această ultimă apelare se ajunge la instrucţiunea return. valoarea 6. } Apelarea unei funcţii generice se face astfel nume_funcţie <tip. cout << “ maximul dintre “ << a << “ si “ << x << “ este “ << c << endl. 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. variabilele corespunzând acestei apelări se strerg din stivă n=3 s=6 După care se obţine rezultatul final. Definirea unei funcţii generice se face cu instrucţiunea template <typename identificator. 3.43.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. T b) { return (a > b ? a : b). fb) << endl. int main() { // maximul a doua numere intregi int a = 15. după care variabilele corespunzând ultimei apelări a funcţiei se sterg din stivă. putem calcula maximul a două numere întregi sau reale astfel.n=3 s=? n=2 s=? Se apelează încă o dată funcţia. cout << “maximul dintre “ << fa << “ si “ << fb << “ este “ << maxval<float>(fa. return. Stiva devine n=3 s=? n=2 s=2 Din nou se ajunge la instrucţiunea return. De exemplu. } 66 . c = maxval<int>(a. // maximul a doua numere reale float fa = 3. tip. x = 20.3. c. x). …>(parametri). fact(1).

Funcţie int isalnum (int c). const char s2[]). ‘\n’. int toupper(int c). int isalpha (int c). Să se definească o clasă generică ce permută două variabile de acelaşi tip. sau > 0. Rezultatul este un număr < 0. “bcd”) strcmp(“xyz”. Funcţia int strcmp(const char s1[]. 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. int isupper (int c). int isdigit (int c). int islower (int c). tolower(‘A’) tolower (‘a’) tolower (‘*’) ‘a’ ‘a’ ‘*’ O funcţie utilă la prelucrarea şirurilor este strlen() care dă lungimea unui şir de caractere. int isxdigit(int c).Rezultatul rulării programului este cel de mai jos.10 Funcţii C standard de manipulare a caracterelor Funcţiile ce manipulează caractere tip ASCII sunt un exemplu de funcţii standard ale limbajului C. 3. “xyz”) strcmp(“abcd”. Funcţie int tolower(int c). Descriere Test dacă un caracter este alfanumeric Test dacă un caracter este alfabetic Test dacă un caracter este o cifră zecimală Test dacă un caracter este o cifră hexazecimală Test dacă un caracter este literă mică Test dacă un caracter este literă mare Test dacă un caracter este spaţiu (‘ ‘. până la primul caracter diferit. Prototipurile lor se află în biblioteca <cctype>. Următoarele funcţii convertesc literele mari în litere mici şi invers. după cum sunt caracterele diferite comparate. Exemple de utilizare a funcţiei strcmp sunt prezentate în tabelul de mai jos strcmp(“abc”. “abc”) <0 =0 >0 "abc" < "bcd" deoarece ‘a’ < ‘b’ şirurile sunt egale şirul “abcd” este mai lung 67 . Exerciţiu. int isspace (int c). fără caracterul ‘\0’ terminal. compară cele două şiruri. = 0. Prototipul ei se găseşte în biblioteca <cstring>. caracter cu caracter. ‘\t’) Funcţiile au un rezultat diferit de zero dacă argumentul este conform descrierii funcţiei. Prototipul acestei funcţii este int strlen(char[]).

i < n.Vom scrie o funcţie care să numere literele mici. i < n. # include <iostream> # include <cstring> # include <cctype> using namespace std. // calculul numarului de litere mici. cout << “sirul contine : “ << cnt << “ cifre” << endl. // numara literele mici din sir cnt = 0. cnt. int i. cout << “sirul contine : “ << cnt << “ litere mici” << endl. cin >> x. cout << “introduceti un sir”. Exerciţiu. return 0. Alte funcţii ce prelucrează şiruri de caractere sunt int atoi(char s[]). 68 . i++) if(isupper(x[i])) cnt++. cout << “sirul contine : “ << cnt << “ litere mari” << endl. i++) if(isdigit(x[i])) cnt = cnt + 1. Să se scrie un program care să convertească literele mici ale unui şir citit de la tastatură în litere mari. // calculeaza lungimea sirului n = strlen(x). } Rezultatul rulării programului este următorul. Programul este următorul. // numara cifrele din sir cnt = 0. i++) if(islower(x[i])) cnt = cnt + 1. literele mari şi cifrele dintr-un şir citit de la tastatură. i < n. litere mari si cifre dintr-un sir int main() { char x[50]. for(i = 0. for(i = 0. for(i = 0. // numara literele mari din sir cnt = 0. n.

z = x * y. int main() { char s1[] = “-123”. // converteste sirurile in numere x = atoi(s1). care convertesc un şir de caractere într-un număr întreg şi respectiv real. cout << “x * y = “ << z << endl. } Rezultatul rulării programului este cel de mai jos. y = atof(s2). # include <cstdlib> # include <iostream> using namespace std. double y. char s2[] = “1. return 0 .22e-1”. Vom converti aceste şiruri în numere şi vom efectua produsul lor. z. int x.double atof(char s[]). Un exemplu de utilizare a acestor funcţii poate fi următorul. Prototipurile acestor funcţii se află în biblioteca <cstdlib>. Fie două şiruri ce conţin numere. // scrie numerele si produsul lor cout << “x = “ << x << “ y = “ << y << endl. // scrie sirurile cout << “sirul s1 : ” << s1 << endl << “sirul s2 : ” << s2 << endl. 69 .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

for(i = 0. prtcars(“islower”. prtcars(“isxdigit”. i++) . &isdigit). i < len + 1. return. return. &isupper). prtcars(“isupper”. Se execută instrucţiunea de atribuire s1[i] = s2[i] 87 . O primă variantă este void copy(char * s1. &isalpha). prtcars(“isalnum”. char * s2). } Condiţia care se testează pentru execuţia instrucţiunii for este (s1[i]=s2[i]) != ‘\0’. &isxdigit). } Rezultatul rulării programului este cel de mai jos. char * s2) { int i. } O altă variantă este void copy(char * s1.int main() { prtcars(“isdigit”. i++) s1[i] = s2[i]. (s1[i] = s2[i]) != ‘\0’. Ea este executată astfel. &islower). Prototipul ei va fi void copy(char * s1. unde şirul s2 este sursa iar şirul s1 este destinaţia. char * s2) { int len = strlen(s2). for(i = 0. prtcars(“isalpha”. &isalnum). Vom exemplifica utilizarea pointerilor la tablouri unidimensionale construind o funcţie care copiază un şir în altul (ca şi funcţia standard strcpy). return 0.

return. } care este o scriere condensată a variantei anterioare. char * s2) { while(*s1++ = *s2++) . x[0] şi x[1]. O altă variantă utilizează pointeri în locul elementelor de tablouri şi faptul că şirurile tip C sunt terminate prin zero. s2++. în cazul nostru s1[i]. x[1][2] El este considerat ca un vector cu 2 elemente.7 Pointeri şi tablouri multidimensionale Tablourile se definesc conform urmăroarei diagrame sintactice Elementele tablourilor sunt memorate pe linii. 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). tabloul int x[2][3]. Reamintim că operatorul de atribuire are ca rezultat valoarea atribuită. De exemplu. care este caracterul de terminare al şirului s2. Operatorul [] selectează un element al unui tablou (este operator de selecţie). El este un operator binar. void copy(char * s1. este compus din elementele : x[0][0]. void copy(char * s1. 4. x[0][1]. } Expresia testată de către instrucţiunea while este *s1 = *s2 Rezultatul evaluării expresiei este valoarea *s1 (un caracter copiat în şirul s1). fiecare element fiind un vector cu trei elemente de tip int.după care rezultatul s1[i] este comparat cu valoarea ‘\0’. } return. x[1][0]. In final putem scrie varianta următoare. x[0][2]. x[1][1]. char * s2) { while(*s1 = *s2) { s1++. operandul stâng 88 .

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

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

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

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

adică un pointer de tip nespecificat. Vom aloca un vector cu 10 componente întregi. 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. Alocarea de memorie se poate face cu funcţia malloc() cu prototipul void * malloc(int size). Primul şir de caractere este chiar numele programului. După alocare. unde size este numărul de octeţi de alocat. int main(int argc. Prototipul acestor funcţii se află în biblioteca <cstdlib>. 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. Ca exemplu fie un program ce tipăreşte argumentele liniei de comandă. 4. şi el trebuie convertit în pointer la tipul alocat. for(i = 0. de la linia de comandă  aplicatie. Exemplu. i < argc. } Un mod de a furniza parametri liniei de comandă este de a rula programul într-o fereastră DOS. unde p este o variabilă tip pointer ce conţine adresa zonei de memorie ce va fi eliberate. ++i) cout << argv[i] << endl. In program definim o variabilă v de tip pointer la întreg ce va conţine adresa memoriei alocate de funcţia malloc().exe parametru1 parametru2 … parametrun Alt mod este de a introduce aceşti parametri într-o casetă de dialog a mediul de programare. îl vom iniţializa şi îl vom scrie pe ecran. Tipul funcţiei este void*. vectorul poate fi utilizat 93 . Pointerii sunt adresele unor şiruri de caractere ce corespund argumentelor liniei de comandă. char * argv[]) { int i.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. Eliberarea memoriei alocate se face cu funcţia void free(p). iar argc este dimensiunea acesui vector. return 0.9 Alocarea dinamică a memoriei Alocarea memoriei unei variabile scalare sau tablou se face astfel : • de către compilator.

x[0][1]. Rezultatul rulării programului este cel de mai jos. x[0] şi x[1]. aşa cum am spus mai sus int * Matricea x constă deci dintr-un vector cu două componente al căror tip este int *. Vectorul x[0] de exemplu. } Instrucţiunea int * v = (int *) malloc(10 * sizeof(int)). poate fi şters. ce corespund celor două linii ale matricei. j++) { cout << *(v + j) << “ “. j < 10. x[0][2]. // elibereaza memoria return 0. for(j = 0. int main() { int j. dar vectorul v[10] este alocat de compilator şi nu poate fi şters. int * v = (int *) malloc(10 * sizeof(int)). Definiţia acestei matrice este int x[2][3] . Pentru a determina tipul variabilei pointer ce va conţine memoria alocată cu funcţia malloc(). alocă un vector cu 10 componente de tipul int.pentru calcule. Instrucţiunea converteşte pointerul furnizat de funcţia malloc() la tipul (int *). vom analiza cazul unei matrice x cu elemente întregi cu două linii şi trei coloane. La sfârşitul programului. // cout << v[j] << “ “. conţine elementele x[0][0]. Tipul vectorilor x[0] şi x[1] este. } for(j = 0. # include <iostream> # include <cstdlib> using namespace std. alocat cu funcţia malloc. deci tipul variabilei x va fi int ** 94 . v[j] = 2 * j. Matricea x este formată din doi vectori. j++) { // *(v + j) = 2 * j. în timp ce vectorul v[10]. memoria alocată trebuie eliberată cu funcţia free(). } cout << endl. Ea are acelaşi efect cu instrucţiunea int v[10]. j < 10. In continuare vom arăta cum se alocă memorie pentru o matrice. free(v).

Vom afişa adresele liniilor matricei x cu cele două expresii echivalente. i < 2. Definim în final o variabilă z de tip pointer ce va fi iniţializat cu adresa vectorului y. z[i] + j şi *(z + i) + j // afisaza adresele elementelor matricei cout << "adresele elementelor matricei" << endl. x[0] şi x[1] . 2. z[i] şi *(z + i) // afisaza adresele liniilor matricei x cout << "adresele liniilor matricei" << endl. i++) { for(j = 0. {-4. } // afisaza adresele elementelor matricei cout << "adresele elementelor matricei" << endl.Prezentăm mai jos un program în care am definit matricea int x[2][3] = {{1. z = y. Vom afişa adresele liniilor matricei. In continuare prezentăm secvenţe din program în care utilizăm variabila z pentru a afişa adresele liniilor matricei. y[1] = x[1]. i < 2. i++) { cout << "adresa lui x[" << i << "] : " << *(z + i) << endl. y[0] = x[0]. -5. Definim un vector y cu două elemente de tipul int * ce va fi iniţializat cu adresele celor două linii ale matricei x. int * y[2]. 95 . adresele elementelor matricei şi elementele matricei utilizând o variabilă tip pinter. i++) { cout << "adresa lui x[" << i << "] : " << z[i] << endl. for(i = 0. 3}. Tipul variabilei z va fi int ** int ** z . j < 3. adresele elementelor matricei şi valorile elementelor matricei x. -6}}. } // afisaza adresele liniilor matricei x cout << "adresele liniilor matricei" << endl. i < 2. for(i = 0. for(i = 0. } Afişăm apoi adresele elementelor matricei x cu cele două expresii echivalente. j++) cout << "adresa lui x[" << i << "][" << j << "] : " << z[i] + j << endl.

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

} // afisaza elementele matricei x pe linii cout << "elementele matricei" << endl. for(i = 0. i < 2. } // afisaza adresele elementelor matricei x cout << "adresele elementelor matricei" << endl. cout << endl. j < 3. } // afisaza adresele liniilor matricei x cout << "adresele liniilor matricei" << endl.cout << "adresa lui x[" << i << "] : " << z[i] << endl. 97 . i++) { for(j = 0. i < 2. i++) { for(j = 0. j < 3. i < 2. i++) { for(j = 0. j++) cout << "adresa lui x[" << i << "][" << j << "] : " << *(z + i) + j << endl. } Rezultatul rulării programului este cel de mai jos. j < 3. j < 3. for(i = 0. j++) cout << setw(3) << z[i][j] << " ". cout << endl. for(i = 0. i < 2. j++) cout << setw(3) << *(*(z + i) + j) << " ". i++) { for(j = 0. } // afisaza elementele matricei x pe linii cout << "elementele matricei" << endl. } // afisaza adresele elementelor matricei x cout << "adresele elementelor matricei" << endl. i++) { cout << "adresa lui x[" << i << "] : " << *(z + i) << endl. i < 2. j++) cout << "adresa lui x[" << i << "][" << j << "] : " << z[i] + j << endl. for(i = 0. } return 0. for(i = 0.

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

Instrucţiunea float *pf = new float[10]. Variabila ptr conţine adresa memoriei alocată mai înainte cu new. iat variabila pf conţine adresa vectorului. De exemplu următoarele instrucţiuni alocă memorie prntu o variabilă scalară double * ptr. Operatorii new şi delete sunt utili la operaţii cu vectori. } Prima funcţie malloc() alocă memorie pentru un vector cu nlin elemente de tipul int*. Memoria alocată cu operatorul new poate fi eliberată cu operatorul delete cu formele delete prt. ptr = new double. int main() 99 . Rezultatul rulării programului este arătat mai jos. Exemplu. j < ncol. *ptr = 1. i < nlin. alocă un vector de 10 componente de tip float. A doua funcţie malloc() alocă memorie pentru un vector cu ncol elemente de tipul int. pentru eliberarea memoriei alocate unui vector. i++) { for(j = 0. Limbajul C++ permite alocarea memoriei necesară unei variabile cu operatorul new cu formele new tip pentru alocarea de memorie pentru un scalar şi new tip[expresie întreagă] pentru alocarea de memorie pentru un vector cu un număr de componente dat de expresie întreagă. Primele două instrucţiuni puteau fi scrise double *ptr = new double. Exemplu. cout << endl.z[i][j] = i * j. for(i = 0.7. Un vector alocat cu new poate fi şters cu operatorul delete delete [] pf. pentru eliberarea memoriei allocate unei variabile scalare şi delete [] ptr. îl vom iniţializa şi vom afişa pe ecran componentele lui. Operatorul new alocă memoria corespunzătoare tipului de variabilă şi are ca rezultat adresa zonei de memorie alocată. j++) cout << z[i][j] << '\t'. } return 0. Vom aloca un vector cu 10 componente întregi.

return 0. j < 10. for(i = 0. cout << "introduceti # de linii ". } Rezultatul rulării programului este cel de mai jos. # include <iostream> # include <cstdlib> using namespace std. Pentru tipul variabilelor utilizate în program vezi exemplu anterior. ncol. Instrucţiunea int * v = new int[10]. dar vectorul v[10] alocat cu new poate fi şters. } cout << endl. unde nlin şi ncol se vor citi de la tastatură. for(j = 0. j++) { cout << v[j] << “ “. are acelaşi efect cu instrucţiunea int x[10]. Vom aloca o matrice cu nlin linii şi ncol coloane. j++) { *(v + j) = j. cout << "introduceti # de coloane ". j. Exemplu. cin >> ncol. vectorul v[10] alocat de compilator nu poate fi şters. i < nlin. int j. } for(j = 0. int nlin. cu elemente întregi. delete [] v. v = new int * [nlin]. cin >> nlin. int main() { int i. i++) v[i] = new int[ncol].{ int * v = new int[10]. Elementele matricei vor primi aceleaşi valori ca în exemplul anterior. int ** v. j < 10. 100 . // cout << *(v + j) << “ “. // v[j] = j.

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

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

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

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

etc. printf(“\n numarul zecimal : %d\n numarul hexazecimal : %x\n”. a şirului “numarul zecimal” şi a valorii variabilei i. două pentru partea subunitară.1f pentru variabila x şi %6. scanf(“%x”. float x. are ca rezultat scrierea caracterului ‘\n’. (trecerea la un nouă linie). Exemplu.txt". instrucţiunea printf(“\n numarul zecimal : %d\n numarul hexazecimal : %x\n”.Exemplu. printf(“introduceti un numar hexazecimal\n”). "w").h> #include <math. parametrii funcţiei scanf sunt de tip pointer (sunt parametri de ieşire). In consecinţă.txt. &i). Formatul se va încheia cu un caracter ‘\n’. In cazul instrucţiunilor printf specificatorii de conversie dau formatul de scriere a varibilelor. i. De exemplu.h> int main() { int i. z. #include <stdio.2.2f pentru expresie. Valorile expresiei vor fi scrise pe ecran şi într-un fişier cu numele rez. i). } Rezultatul programului este cel de mai jos. Să citim un număr hexazecimal de la tastatură şi să-l scriem în format zecimal şi hexazecimal pe ecran. Valorile vor fi separate de un caracter tab. // scrie antetul pe ecran si in fisier printf(" x \t f(x) \n").h> int main () { FILE * fp. Specificatorii de format vor fi %4. #include<stdio. i. cea a lui z ocupă 6 caractere. în timp ce restul caracterelor sunt scrise în fişier. Vom scrie un program care calculează valoarea unei expresii 1 + cos(2 x) + 2 sin( x) x + ln x pentru valori ale lui x cuprinse între 1 şi 2 cu pasul 0. cu un caracter pentru partea subunitară. Reamintim că. Valoarea lui x va ocupa 4 caractere. int i. 105 . la apelarea funcţiei scanf în program utilizăm adresa variabilei i scanf(“%x”. ‘\t’. fp = fopen("rez. i). &i).

şi instrucţiunile de scriere devin printf(format. x[i]). x[1] …. x. } Rezultatul rulării programului este cel de mai jos. 4. Vom afişa componentele vectorului sub forma Element Valoare x[0] …. x. fprintf(fp.1f \t %6.12.98.1f \t %6.2. x.1. i < 6.. i++) { z = (1 + cos(2 * x) + 2 * sin(x)) / (x + log(fabs(x))).1}. z).1f \t %6. int i. fprintf(fp. return 0. x. 0. Consideram un vector cu 5 componente numere reale. " %4. // scrie componentele vectorului for(i = 0. printf(“Element\tValoare”). # include <stdio. i. x = x + 0.2f \n"..2f \n".fprintf(fp. z). } 5.h> int main() { double x[5] = {1.23. printf(" %4. z). z). i < 5. return 0. In programul anterior puteam defini un şir de caractere cu specificatorii de format char * format = “ %4.2 Funcţii intrare / ieşire tip caracter Funcţia int fgetc(FILE * stream). } fclose(fp). 5. i++) printf(“x[%d] \t\t%d\t”. // calculeaza valorile expresiei si scrie aceste valori in fisiere for(i = 0. x = 1. 106 . " x \t f(x) \n").2f \n”. Exemplu. format.0. -23.

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(). Presupunem că utilizăm o funcţie read(file) ce citeşte câte un bloc de date din fişier. Funcţia returnează adresa vectorului s. funcţia returnează valoarea EOF care este o constantă predefinită în biblioteca <stdio.h>. Funcţia scrie şi caracterul ‘\n’ după ce a scris şirul s. în timp ce funcţia puts scrie caracterul ‘\n’ în fişierul stdout după şir. Dacă se întâlneşte sfârşitul de fişier. Funcţia int puts(char * s). pune caracterul c în streamul de intrare. fputs(“\n”. FILE * stream). Funcţia int ungetc(int c. Următoarele funcţii citesc sau scriu linii din fişiere tip text. Funcţia int getchar(). FILE * stream).citeşte un caracter din fişierul de intrare şi avansează indicatorul de poziţionare al fişierului cu valoarea unu. Ea este echivalentă cu funcţia fgetc(stdin). Funcţia returnează caracterul scris sau constanta EOF în caz de eroare. Schema principială de citire a unui fişier secvenţial este următoarea. Ea se opreşte dacă întâlneşte caracterul ‘\n’ sau sfârşitul de fişier. la scrierea în fişierul stdout cu funcţia fputs vom scrie fputs(x. scrie şirul de caractere s (terminat cu ‘\0’) în fluxul stdout. citeşte cel mult n – 1 caractere în vectorul s. După ultimul caracter citit se adaugă caracterul ‘\0’ în vectorul s. FILE * stream). La întâlnirea sfârşitului de fişier sau la o eroare funcţia returnează valoarea NULL. stdout). Funcţia int putchar(int c). citeşte un caracter din streamul stdin. int n. In consecinţă. Funcţia int fputs(const char * s. Caracterul ‘\0’ nu este scris. scrie caracterul c în streamul specificat de stream. scrie caracterul c in streamul stdout. scrie şirul de caractere s (terminat cu ‘\0’) în fluxul stream. Funcţia char * fgets(char * s. Reamintim că funcţia fputs nu scrie caracterul ‘\n’ în fişier. stdout). Caracterul ‘\0’ nu este scris. read(file) while(! feof(file)) { // prelucreaza blocul citit read(file) } 107 . Funcţia int fputc(int c. pentru a afişa câte un şir de caractere pe rând. Caracterul ‘\n’ este introdus în şirul citit. FILE * stream).

} Rezultatul rulării proghramului este cel de mai jos. return 0. şi va deschide fişierul în citire. char car.Exemplu. int nb = 0. // citeste un caracter car = fgetc(file). printf("fisierul %s contine %d octeti\n". } fclose(file). Programul va citi numele unui fişier de la tastatură. name). } // citeste cate un caracter până la sfarsitul fisierului // citeste un caracter car = fgetc(file). 108 . name. return 0. "r"). // deschide fisierul in citire file = fopen(name. while(car != EOF) { // numara caracterul citit nb = nb + 1. /* calculul dimensiunii unui fisier */ #include <stdio. // test daca fisierul exista if(file == NULL) { printf("nume de fisier eronat\n"). FILE* file. Vom exemplifica utilizarea acestor funcţii cu un program care să calculeze dimensiunea în octeţi a unui fişier. scanf("%s".h> int main() { char name[64]. Un alt mod de a rezolva această problemă va fi arătat ulterior. 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. nb). printf("introduceti numele fisierului\n").

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

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

111 . d1. int x = 10. char c1[10]. # include <stdio.34. // scrie pe ecran fisierul tip sir printf("%s\n". d. &x1. int x1. &d1. In consecinţă. // scrie valorile variabilelor pe ecran printf("x = %d d = %f c = %s\n". c).h> int main() { char s[100]. x1. Exemplu. &x1. c1). // scrie variabilele initiale in fisierul tip sir sprintf(s. // scrie pe ecran variabilele initiale printf("x = %d d = %f c = %s\n". c). într-un fişier tip şir de caractere char s[100] . "%d %lf %s".Reamintim că la citirea unei variabile tip double se utilizează specificatorul de conversie %lf. return 0. double d1. char c1[10]. // citeste valorile variabilelor din fisierul tip sir sscanf(s.34. Vom apoi citi valorile din vectorul s şi vom iniţializa alte variabile int x1. c1). } Pentru citirea variabilei d1 de tip double am utilizat specificatorul de conversie %lf. d. Valorile scrise pe ecran cu cele trei instrucţiuni printf() sunt cele de mai jos. şi vom scrie valorile lor pe ecran. char c[] = "abc". Fie variabilele int x = 10. Menţionăm că parametrii funcţiei sscanf sunt de tip pointer (sunt parametri de ieşire). la apelarea funcţiei sscanf în program utilizăm adresele variabilelor x1 şi d1 sscanf(s. double d = -2. Vom scrie valorile acestor variabile. x. x. "%d %lf %s". double d = -2. &d1. char c[] = "abc". s). "%d %f %s". separate de spaţii. c1). double d1.

size_t size. printf("introduceti numele fisierului\n"). Exemplu. size_t nmb.h> // fisiere text C. scanf("%s". size_t size. Aceste funcţii pot citi sau scrie unul sau mai multe blocuri de date. Funcţia size_t fread(void * ptr. name). int main() { char name[256]. FILE* file. 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. scrie în vectorul ptr cel mult nmb blocuri de dimensiune size în fişierul stream. Funcţia size_t fwrite(const void * ptr.5. FILE * stream). FILE * stream). Funcţia long ftell(FILE* stream). In cazul întâlnirii sfarşitului de fişier. funcţia returnează valoarea 0. Lungimea fişierului va fi valoarea indicatorului de poziţie. Indicatorul de poziţie al fişierului este avansat cu numărul de octeţi citiţi. if(file == NULL) { 112 . Funcţia returnează numărul efectiv de blocuri citite. int nb = 0. Funcţia returnează numărul efectiv de blocuri scrise. următoarea instrucţiune poate fi o operaţie de citire sau scriere. // deschide fisierul in citire file = fopen(name. long int offset. size_t nmb. 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. Noua valoare a indicatorului fişierului este obţinută adăugând valoarea offset la poziţia specificată de whence care poate fi SEEK_SET începutul fişierului SEEK_CUR poziţia curentă a indicatorului SEEK_END sfârşitul fişierului După o instrucţiune fseek. citeşte în vectorul ptr cel mult nmb blocuri de dimensiune size din fişierul stream.3 Fişiere binare Pentru scrierea şi citirea de date din fişiere binare există funcţiile fread şi fwrite. Calculul lungimii fiserului // prin modificarea indicatorului de pozitie. "r"). #include <stdio. Indicatorul de poziţie al fişierului este avansat cu numărul de octeţi scrişi. int whence). are ca rezultat valoarea indicatorului de poziţie al fişierului.

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

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

“rb”). ++j) x[j] = ‘a’ + i + j. } // inchide fisierul fclose(fil). } Menţionăm că puteam avansa indicatorul fişierului la sfârşitul ciclului for cu 10 octeţi în raport cu poziţia curentă ca mai jos // avanseaza indicatorul fisierului cu 10 in raport cu pozitia curenta fseek(fil. SEEK_SET). fil). // scrie sirul pe ecran (in fluxul stdout) puts(x). 115 . // deschide fisierul in citire fil = fopen(“fil”. } // inchide fisierul fclose(fil). i++) { // creaza un bloc for(j = 0. if(fil == NULL) { printf("fisierul nu se poate crea\n"). // deschide fisierul in creare fil = fopen(“fil”. } for(i = 0.int i. return 0. fil). 10. (long)(10). return 0. j < 9. if(fil == NULL) { printf("fisierul nu se poate citi\n"). i < 15. fputs(“\n”. i < 15. return 0. char x[10]. 10. 1. SEEK_CUR). // citeste un sir fread(x. 1. // scrie sirul pe ecran (in fluxul stdout) fputs(x. } printf("fisierul citit\n"). i += 2) { // pozitioneaza indicatorul fisierului in raport cu inceputul fisierului fseek(fil. stdout). (long)(i * 10). j. stdout). “wb”). // scrie blocul in fisier fwrite(x. // citeste sirurile pare for(i = 0. x[9] = 0.

Rezultatul rulării programului este cel de mai jos.în loc de a-l poziţiona în raport cu începutul fişierului. 116 .

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

#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) { vector c;

118

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 (*pc).real = cos(1.23);

119

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 aplicaţii cu multe variabile ale căror valori nu sunt necesare simultan. Instrucţiunea de definire a unei uniuni este 120

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));

121

El este unic identificat prin nume şi defineşte o stare reprezentată de valorile atributelor la un moment dat. Setul de operaţii definite se numeşte interfaţă. Un obiect invoca o metodă ca reacţie la primirea unui mesaj. Clasa defineşte variabile şi funcţii ce prelucrează aceste variabile. Există o notaţie pentru tipurile de date abstracte pe care le definim Nume clasă Date Operaţii In programe creăm variabile de tipul unor clase care se numesc obiecte.numele metodei. Partea de definire se numeşte interfaţă. Interacţiunea este bazată pe mesaje trimise de la un obiect la altul cerând destinatarului să aplice o metodă asupra lui (apelarea unei functii). Intr-o clasă există o parte de definire a datelor şi operaţiilor şi o parte de implementare. Programarea orientată obiect 7 Clase Pentru a rezolva o problemă trebuie să creăm un model al problemei. Obiectele unei clase sunt create şi iniţializate de funcţii membre ale clasei special definite pentru acest scop numite constructori. Clasa defineşte variabile (numite şi câmpuri sau atribute sau proprietăţi) şi funcţii (numite şi metode) care implementează structura de date şi operaţiile tipului abstract. . Vom spune că obiectele sunt instanţe sau realizări ale unor clase. Instanţele clasei se numesc obiecte. Un obiect este o instanţă a unei clase (a unui tip abstract).Partea II. O metoda este asociată unei clase. distruse şi care interacţionează. Abstractizarea este structurarea problemei în entităţi definind datele şi operaţiile asociate acestor entităţi.argumentele metodei. • Metoda. Un program este o multime de obiecte create. Operaţiile interfeţei sunt singurul mecanism de acces la structura de date. Mesajul conţine : . In acest proces trebuie să definim : • datele afectate • operaţiile identificate de problemă. Clasa este un tip de date definit de utilizator. Obiectele sunt distruse când nu mai sunt necesare de funcţii membre ale 122 . Clasa defineşte deci proprietăţile şi comportarea unei mulţimi de obiecte. 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. Procesul de modelare se numeste abstractizare. Ea asigură detaliile de implementare pentru structura de date şi pentru operaţii. Definiţiile pe care le vom utiliza sunt următoarele : • Clasa este o reprezentare a unui tip de date abstracte. • Mesaje. Un mesaj este o cerere către un obiect ca el să invoce una din metodele lui. Comportarea unui obiect este definită de metodele ce se pot aplica asupra lui. • Obiect.

Apoi această clasă este moştenită de alte clase particulare. Acest principiu cere definirea unei clase generale ce conţine caracteristicile comune ale mai multor elemente. Valorile proprietăţilor dau starea obiectului la un moment dat. Când apelăm funcţia respectivă se apelează versiunea corespunzătoare tipului obiectului. iar membru este o declaraţie de funcţie membru sau dată a clasei. Un număr complex este o pereche ordonată de numere reale. private sau protejate.clasei numite destructori. Acesta cere combinarea datelor şi metodelor într-o singură structură de date. • Polimorfismul. • Moştenirea. Exemplu. Clasa va avea o funcţie print() ce afişază parametrii m şi n ai obiectului.1. Avem funcţii cu acelaşi nume în clasa de bază şi în clasele derivate. In exemplele următoare vom adopta următoarea regulă : numele claselor vor începe cu litere mari. Implicit. Datele şi funcţiile membre ale clasei pot fi publice. 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. Membri privaţi ai clasei pot fi utilizaţi doar de funcţiile membre ale clasei. toate datele şi funcţiile unei clase sunt private. 7. identificator este numele clasei. Implicit toţi membri clasei sunt privaţi. fiecare adaugă doar elementele proprii.1 Definirea unei clase Diagrama sintactică a definiţiei unei clase este Clasele sunt definite utilizând cuvântul cheie class. Clasa care este moştenită se numeşte clasă de bază sau superclasă. Funcţiile din clasele derivate redefinesc operaţiile necesare claselor derivate. Reprezentarea acestei clase este cea de mai jos. Principiile programării orientate obiect sunt următoarele • Primul principiu al programării orientate obiect este încapsularea datelor. Numere complexe. 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.1 Definirea unei clase 7. protected şi private. Acest principiu asigurară ascunderea structurii de date cu ajutorul unei interfeţe pentru adresarea datelor. funcţia inversă şi integrala definită. Specificatorii de acces sunt public. Membri publici ai clasei pot fi utilizaţi de orice funcţie din program. iar numele câmpurilor şi metodelor cu litere mici. iar clasele care moştenesc se numesc clase derivate sau subclase. 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. Membri protejaţi vor fi prezentaţi ulterior. Line 123 .

public: void assign(double. Ea crează obiecte şi iniţializează variabilele acestora.. datele şi funcţiile unei clase constituie un spaţiu de nume ce are numele clasei. Vom defini metodele clasei Line în afara definiţiei clasei. n. Ele sunt de tip double şi sunt declarate private. Interzicerea accesului din afara clasei este un principiu al programării orientate obiect numit încapsularea informaţiilor. In acest fel ele vor fi accesibile doar funcţiilor membre ale clasei. urmate de . void value(double). Funcţia Line() este constructor. Prin definiţie. double). Line(double. Toate funcţiile sunt declarate publice şi vor putea fi utilizate de obiecte în afara clasei. void invert(). In limbajul C++ avem posibilitatea de a grupa anumite nume în spaţii de nume. double n. double). Funcţiile definite în interiorul clasei sunt funcţii inline . membre ale clasei sunt m şi n şi ele vor memora constantele m şi n din funcţia descrisă de clasă. Definiţia clasei este inclusă între acolade { şi }. şi nu un apel la funcţie. (câmpurile). void assign(). Funcţiile membre ale clasei. Operatorul de rezoluţie :: se utilizează pentru a defini o funcţie membru a clasei în afara clasei. compilatorul generează direct textul lor în program. Line (double. invert(). void print(). double b) { 124 . }. Definiţia clasei este următoarea /* clasa Line descrie functia f(x) = m * x + n */ class Line { private: double m. double intgrl(double. double). (metodele) sunt assign(). value(). ca mai jos : // atribuirea de valori variabilelor m si n void Line::assign(double a.double m. Funcţiile member. pot fi definite în interiorul clasei sau în afara ei. (metodele). ce atribuie valori variabilelor m şi n. double intgrl(double. ce dă valoarea funcţiei într-un punct. ce inversează funcţia şi print(). double). ce scrie variabilele m şi n şi intgrl() ce calculează integrala definită a funcţiei. Operatorul de rezoluţie :: arată că un nume de dată sau de funcţie aparţine unui spaţiu de nume. Datele. void print(). void invert(). double). Numele clasei este Line. double value(double).

} Apelul unei funcţii a clasei de către un obiect se face conform următoarei diagrame sintactice Utilizarea unui câmp al un obiect se face conform următoarei diagrame sintactice 125 .n * temp. } // calculul valorii functiei in punctul x double Line::value(double x) { return m * x + n.a * a) / 2 + n * (b . } // constructor cu parametri Line::Line(double a.m = a. " << " m = " << m << " n = " << n << endl.a). } // calculul integralei definite double Line::intgrl(double a. } // scrierea parametrilor pe ecran void Line::print() { cout << "functia f(x) = m * x + n. n = b.0 / m. temp = 1. double b) { m = a. m = temp. double b) { return m * (b * b . } // calculul functiei inverse void Line::invert() { double temp. n = . n = b.

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

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

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

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

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

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

print(). 132 . obiectul s se copiază în stivă utilizând constructorul copiere. 3) când se execută instrucţiunea return s. Exemplu.Constructorul copiere este apelat ori de câte ori : • un obiect este copiat. Presupunem clasa Line definită anterior. Line y(x).invert(). Exemplu. return 0. // se apeleaza constructorul copiere // scrie numărul z z. Definitia funcţiei f() este următoarea Line f(Line r) { Line s = r. 2) când se apelează funcţia z = f(y). Tabloul definit poate fi prelucrat. Menţionăm că definiţia funcţiei f putea fi Line f(const Line r). • un obiect este returnat ca valoare de o funcţie. • un obiect este transmis prin valoare unei funcţii (obiectul este copiat în stivă). de exemplu Line x[20]. // scrie numarul x x. Pentru aceasta. de exemplu putem avea instrucţiunile Line a(1. Vom crea un vector cu două componente de tipul Line şi vom afişa parametri m şi n ai obiectelor. Parametrul funcţiei f (r în definiţia funcţiei) este transmis prin valoare. return s. Putem defini tablouri de obiecte în mod obişnuit. deci în stivă se pune o copie a obiectului creată cu constructorul copiere. Fie o funcţie globală f care are ca parametru un obiect de tip Line şi ca rezultat un obiect de tip Line. // se apeleaza constructorul copiere Line z. x[5] = a.print(). deoarece funcţia nu modifică parametrul r. clasa trebuie să definească un constructor fără parametri. // se apeleaza constructorul copiere } int main() { Line x(11. care este inversul obiectului ce este parametrul funcţiei. 22). z = f(y). s. } Constructorul copiere este apelat 1) atunci când se declară obiectul Line y(x). 2).

class Line { private: double m. 7. i < 2. double b) : m(a). Line() : m(0). Destructorul nu are tip şi nici parametri. v[0] = Line(1. v[1] = Line(2. n. } Menţionăm că am definit un constructor fără parametri. Pentru a vedea cum sunt apelaţi constructorii şi destructorul.2. " << " m = " << m << " n = " << n << endl. for(i = 0. -2). Line(double a. Blocul este orice şir de instrucţiuni între acolade.print(). compilatorul generează unul.# include <iostream> using namespace std. 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. -1). return 0. }. Diagrama sintactică a destructorului este următoarea ~ nume_clasa(){/* corpul destructorului*/} Dacă el nu este definit explicit. void Line::print() { cout << "functia f(x) = m * x + n. { şi } din interiorul unei funcţii. care este apelat la definirea vectorului de obiecte tip Line Line v[2]. int i. n(b) {}. i++) v[i]. fie următoarea clasă în care constructorul şi destructorul scriu un mesaj.2 Destructori Destructorul unei clase este apelat automat când un obiect este distrus. class Test { public: 133 . Line(). Fiecare clasă are un singur destructor. public: void print(). n(0) {}. în funcţia main(). } int main() { // creaza un vector cu obiecte de tipul Line Line v[2].

Există două moduri de a rezolva această problemă. Test y. Aceste funcţii se numesc funcţii de acces şi sunt de regulă publice. cout << “iesire blocul 1” << endl.} ~Test() {cout << “obiectul este distrus” << endl. } { cout << “intrare blocul 2” << endl.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. Test x. double getn(). In fiecare bloc creăm câte un obiect. int main() { { cout << “intrare blocul 1” << endl.} }. void setn(double). nu putem face acest lucru direct. Fie două blocuri de instrucţiuni în funcţia main. Definiţiile funcţiilor getm() şi setm() pot fi: 134 . } } Mesajele scrise vor fi: Menţionăm că toate variabilele locale unei funcţii sau unui bloc sunt create intr-o stivă la intrarea în funcţie sau bloc şi sunt şterse din stivă la ieşirea din funcţie sau bloc. In cazul clasei Line putem defini funcţii membre ale clasei care furnizează parametrii m şi n : double getm(). cout << “iesire blocul 2” << endl. şi funcţii care modifică aceşti parametric : void setm(double). 7. In prima metodă se definesc funcţii care să modifice şi să furnizeze valorile datelor unui obiect.Test() {cout << “obiectul este creat” << endl. Dacă datele sunt declarate private.

void invert().getm(). double d = y. ele au fost declarate const.getn(). class Line { public: Line (double. double intgrl(double. Line ().getn(). Line temp(a + c. Prototipul funcţiei va fi Line sum(const Line x. friend Line sum(const Line x. Parametrii x şi y sunt cele două funcţii liniare ce sunt adunate. double). double c = y. Vom defini o funcţie globală care să adune două funcţii liniare. ea trebuie declarată în definiţia clasei ca funcţie prietenă. Line (const Line & x). Line y) { double a = x. double).double Line::getm() { return m. Noua definiţie a clasei Line este următoarea. deoarece ele sunt declarate private în definiţia funcţiei. Deoarece obiectele nu vor fi modificate. double value(double). double b = x. } O altă soluţie este următoarea. } Exemplu. b + d). } void Line::setm(double x) { m = x. const Line y). Vom utiliza funcţiile getm() şi getn() pentru a obţine valoarea lor. f 1 ( x) = m1 x + n1 şi f 2 ( x ) = m2 x + n2 . Implementarea funcţiei este următoarea Line sum(Line x. return temp. void print(). void assign(double. const Line y). Pentru ca o funcţie externă să aibe acces la membri privaţi ai clasei. Suma lor va fi funcţia f ( x ) = ( m1 + m2 ) x + (n1 + n2 ) . friend.getm(). double). private: 135 . Considerăm două funcţii liniare. In implementarea funcţiei nu putem folosi direct variabilele m şi n. Diagrama sintactică a definiţiei unei funcţii prietene este friend tip nume_funcţie ( parametri).

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.

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>

136

#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.

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. 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;

137

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.

138

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 139

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

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

int main() { string str1 = "abcdg". produc citirea unei linii de caractere din fişierul de intrare cin.insert(0.insert(2. return 0. Un program ce exemplifică utilizarea funcţiei insert este cel de mai jos care inserează repetat un şir inaintea unui şir dat. string str2 = "xy". str2). cout << str1 << endl. str1. 142 . cout << str1 << endl. str1. str). str2). cout << str1 << endl. #include <iostream> #include <string> using namespace std. } Rezultatul rulării programului este cel de mai jos.insert(2. str1. cout << str1 << endl. "123").getline(cin.

Putem utiliza funcţia definită astfel int main() { Line x(2.getm(). <<. y(3. Să supraîncărcăm operatorul + pentru clasa Line. Operatorii supraîncărcaţi au aceeaşi prioritate ca cei originali şi acelaşi număr de parametri. *. []. double c = y. z = x + y. In definiţia clasei vom scrie instrucţiunea friend Line operator+ (Line. care să adune două funcţii liniare. Operatorii limbajului pot fi definiţi şi pentru tipurile nou create. z.n + y. / . return 0. creăm un nou tip.getn(). return temp. x.m + y.print(). funcţiile de adunat. etc.9 Supraîncărcarea operatorilor Operatorii limbajului C++ sunt predefiniţi pentru tipurile fundamentale: int. In acest caz definiţia funcţiei este Line operator+( Line x. } Putem defini operatorul ca funcţie prietenă a clasei. z. Când definim o clasă nouă. Line operator+( Line x.n). char. =. Menţionăm că orice operator supraîncărcat poate fi definit ca funcţie membră a clasei sau ca funcţie globală. -5). Line y) { Line temp(x. deoarece este o funcţie globală. 3). Line temp(a + c. etc. double.getm(). Această operaţie se numeşte supraîncărcarea operatorilor (operator overloading). >>.m. double d = y. Funcţia are doi parametri.-. } 143 . Line y) { double a = x. (). double b = x. return temp. Exemplu. Operatorul ce va fi definit ca o funcţie globală este următorul.getn(). } A se compara această funcţie cu funcţia sum definită anterior. Line). b + d). Pentru a supraîncarca un operator definim o functie de forma tip operator semn (parametri) {/* corpul funcţiei */} unde semn este operatorul dorit +.

Fie instrucţiunea Line a(1. double n. // functia are un singur parametru private: double m. Fie a. Instrucţiunile c = a + b. funcţia corespunzătoare are un singur parametru.n). class Line { 144 . Line (). c obiecte de tip Line. Implementarea funcţiei este Line Line::operator+( Line x) { Line temp(m + x.Al doilea mod de a defini operatorul + este ca funcţie membră a clasei. void print(). echivalent c = a + b. b(4. şi este apelată de operandul stâng. void invert(). 9. operandul drept. -3). n + x.1 Supraîncărcarea operatorului de atribuire Considerăm clasa definită anterior în care definim constructorul implicit. b. Supraîncărcarea lui aici este doar un exerciţiu. constructorul copiere şi operatorul de atribuire. sau. }. Definiţia clasei Line cu operatorul + este class Line { public: Line (int. De accea.m. } Exemplu. sunt echivalente. atunci când operatorul este o funcţie membră a clasei. 3).operator+(b).operator+(b). c. şi c = a. Menţionăm că operatorul de atribuire este automat definit pentru orice clasă. Line (const Line & x). int). O definiţie incorectă a operatorului de atribuire este următoarea # include <iostream> using namespace std. Line operator+( Line). Putem scrie c = a. O funcţie membră a clasei este apelată de un obiect. return temp.

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

Vom exemplifica supraîncărcarea operatorului + pentru o clasă ce descrie numere complexe.Line o(). // rezultatul funcţiei este referinta obiectului ce a apelat operatorul return *this. Prototipul operatorului de atribuire al unei clase T este T& operator = (const T&). Complex float real. n = r. real şi imag. O reprezentare a clasei este cea de mai jos. z(2. Tipul funcţiei este Complex. Line o(const Line o&). 146 . Definiţia funcţiei este Complex operator+(Complex.-.m. etc. Line & Line::operator = (const Line & r) { // obiectul ce a apelat operatorul primeste valoarea obiectului r m = r. numerele complexe ce se adună. pentru partea reală şi cea imaginară a numărului complex şi doi constructori.. Funcţia are numele operator+ şi doi parametri de tip Complex. x = y = z.n. y. float imag. Complex (). Pentru aceasta se utilizează pointerul this. 3). class Complex { private: float real. }.2 Supraîncărcarea operatorilor aritmetici Limbajul ne dă posibilitatea de a defini funcţii care să efectueze operaţiile standard +. Complex). pentru operanzi ce sunt obiecte ale unor clase (operator overloading). float). Vom exemplifica pentru început supraîncărcarea operatorului + pentru clasa Complex în cazul în cazul în care operatorul este o funcţie globală. Line & operator = (const Line &). 9. Definiţia clasei Complex este cea de mai jos. Complex (float. constructorul implicit fără parametri şi un constructor care să iniţializeze variabilele real şi imag. Implementarea corectă a operatorului de atribuire supraîncărcat este următoarea. } Putem scrie acum instrucţiunile Line x. Un operator poate fi definit ca o funcţie globală sau ca o funcţie membru a clasei. Operatorul are ca rezultat o referinţă la obiectul ce apelează operatorul. Clasa va defini două varibile de tip float.

return temp. Constructorul implicit este apelat la declararea obiectului temp Complex temp. care au ca rezultat variabilele real şi imag ale clasei.imag + y. float imag. în funcţia operator+. float getimag().real + y. Complex &). Această soluţie rămâne ca exerciţiu. float). }. friend. temp. Complex& y) { Complex temp. de exemplu : float getreal(). Complex operator+(Complex& x. } Trebuie ca în definiţia clasei să includem un constructor implicit. O funcţie membru a clasei este apelată de un obiect. public: Complex(). fără parametri Complex(). o soluţie este de a scrie două funcţii de acces membre ale clasei. temp. Numele funcţiei este operator+. ea trebuie declarată în definiţia clasei ca funcţie prietenă.float imag. Complex (float. 147 . friend Complex operator+(Complex &. Definiţia unei funcţii prietene este friend tip nume_funcţie ( parametri). }. Pentru ca o funcţie externă să aibe acces la membrii privaţi ai clasei. Complex (float. La fel ca într-un capitol anterior. Definiţia funcţiei va fi cea de mai jos. O altă soluţie este următoarea. definiţia clasei Complex va fi class Complex { private: float real. float).imag.real = x. In consecinţă.operator+(b). Problema scrierii funcţiei este aceea că datele clasei Complex sunt declarate private şi nu pot fi utilizate direct de funcţie. In consecinţă.imag = x. public: Complex(). care este globală. Constructorul implicit nu mai este generat automat de compilator deoarece am definit un constructor. Vom exemplifica acum supraîncărcarea operatorului + pentru clasa Complex în cazul în care operatorul este definit ca o funcţie membră a clasei.real. vom scrie c = a.

float y) { real = x. Complex operator+ (Complex). float imag.imag. echivalent c = a + b. el extrage caractere dintr-un stream. 9. } Implementarea operatorul + este următoarea Complex Complex::operator+(Complex p) { Complex temp. Operatorul >> este numit operator de extracţie. public: Complex(). Implementarea constructorilor este Complex::Complex(float x. -. imag = 0. / a două numere complexe. Fie definiţia clasei Complex în care definim şi funcţia operator+ ca funcţie membră class Complex { private: float real.3 Supraîncărcarea operatorilor << şi >> Operatorul << este numit operator de inserţie. Toate funcţiile de inserţie au forma 148 . return temp. } Complex::Complex() { real = 0. el insereaza caractere într-un stream.real = real + p. atunci când operatorul este o funcţie membră a clasei.real. înmulţire. } Exerciţiu. operandul drept. De accea.sau. }. funcţia corespunzătoare are un singur parametru.imag = imag + p. temp. Să se scrie implementarea operatorilor de scădere. imag = y. * şi împărţire. temp. şi este apelată de operandul stâng. Complex (float. float).

float). In definiţia clasei definim funcţia friend ostream& operator << (ostream& stream. Supraîncărcarea operatorului de inserţie << pentru clasa Complex. Complex&). Ultima instrucţiune este return stream. definiţia clasei Complex este class Complex { private: float real. cum sa arat mai înainte friend tip nume_funcţie ( parametri). Complex& x) { stream >> x. pentru ca o funcţie externă să aibe acces la membrii privaţi ai clasei. In consecinţă. Complex& x). friend ostream& operator << (ostream&. Operatorul de extracţie >> pentru clasa Complex este supraîncărcat astfel friend istream& operator >> (istream& stream. operandul stâng (transmis prin this) este cel care apeleaza operatorul. Complex (float. 149 . return stream. respectiv istream. public: Complex(). Al doilea este obiectul ce trebuie inserat. Complex x).imag << ")". } Primul parametru este o referinţă la streamul de ieşire. Reamintim că. tip obiect) { // corpul functiei return stream.ostream& operator << (ostream& stream. Cu aceasta. Exemplu. nu sunt un membri ai clasei. } Exemplu. float imag.real >> x. Definiţia unei funcţii prietene este. return stream. Implementarea lui este următoarea istream& operator >> (istream& stream.real << "." << x.imag. Complex x) { stream << "(" << x. ea trebuie declarată în definiţia clasei ca funcţie prietenă friend. Complex operator+ (Complex). } Când un operator este membru al unei clase. Operatorii << şi >> pe care i-am definit nu pot fi membri ai clasei deoarece operandul stâng ostream. aceşti operatori vor fi funcţii externe. Implementarea funcţiei este ostream& operator << (ostream& stream.

Complex&). trebuie ca în definiţia clasei să includem un constructor implicit. return 0. Complex b(0. de exemplu cu definiţia Complex() {real = 0.0). Reamintim că orice clasă include un operator implicit de atribuire = care are ca parametru un obiect de tipul clasei. fără parametri.0). Acest constructor iniţializează variabilele obiectului la valoarea 0.0. }. Putem utiliza operatorii definiţi astfel int main() { Complex a(1.operator+(b).} Constructorul implicit nu mai este generat automat de compilator deoarece am definit un constructor. 1. cout << “c = “ << c << endl. 150 . } Rezultatul programului este cel de mai jos. Complex c. Reamintim că.0. cout << “a = “ << a << endl. imag = 0. c = a + b. Complex c. Constructorul implicit este apelat la declararea obiectului temp Complex temp. // echivalent putem scrie // c = a.friend istream& operator >> (istream&. cout << “b = “ << b << endl. în funcţia operator+ şi a obiectului c în funcţia main(). 1. Definiţia funcţiilor mrmbre ale clasei este cea de mai sus.

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

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

n. class Line { private: double m. cout << "f(" << a << ")= " << pl->value(a) << endl. Line() : m(0). vect = new Line[10]. clasa trebuie să aibă un constructor fără parametri. double a = 1. indicat de componentul pv[0] al vectorului pv. Apelarea funcţiei print() se face conform definiţiei de mai sus. putem crea un vector cu zece obiecte de tip Line Line * vect. Putem crea vectori de obiecte cu operatorul new. " << " m = " << m << " n = " << n << endl. } Pentru început definim un vector de pointeri. // apeleaza functiile prin pointer pl->print(). n(0) {}. De exemplu. 0) . 0). este *pv[0].5. deci funcţia print() se apelează (*pv[0]). De exemplu. n(b) {}. Vom crea vectori cu 2 componente cu obiecte tip Line şi vom afişa pe ecran parametrii obiectelor create. Componentele vectorului pv sunt iniţializate cu adresele unor obiecte create cu operatorul new pv[0] = new Line(-1.print(). pv[1] = new Line(-2. 2) . adresa primului obiect. pv[2] de tipul Line : Line * pv[2] . return 0.// creaza un obiect cu operatorul new pl = new Line (1. 1) . double b) : m(a). // distruge obiectul delete pl. }. void Line::print() { cout << "functia f(x) = m * x + n. Acest vector se şterge cu operatorul delete cu forma delete [] vect. 153 . public: void print(). # include <iostream> using namespace std. Line(double a. Fie pentru început definiţia clasei Line. } In programul de mai sus puteam scrie Line *pl = new Line(1. Pentru aceasta. Exemplu.

-1).print(). // scrie obiectele for(i = 0. Apelarea funcţiei print() se face astfel. conform celor dintr-un capitol anterior (pvx + 1)->print(). sau (*(pvx + 1)).print(). } Rezultatul apelării programului este cel de mai jos. // pv[i]->print(). (pvx[i]). Line * pv[2]. i++) (*(pvx + i)). i < 2.sau. deci funcţia print() se apelează (pvx[1]). Definim apoi o variabilă pointer de tip Line. 1). 2). -2) . return 0. In acest vector memorăm nişte obiecte de tip Line : pvx[0] = Line(1. pvx[1] = Line(2.print(). pvx[0] = Line(1. în acest caz. este apelat constructorul fără parametri al clasei. Menţionăm că. pvx[1] = Line(2.print(). i++) (*pv[i]). // // 154 . // scrie obiectele for(i = 0. pv[1] = new Line(-2. (pvx + i)->print(). i < 2. Line * pvx = new Line[2]. Obiectul din al doilea component alvectorului pvx este pvx[1] sau *(pvx + 1). pv[0] = new Line(-1. sau. echivalent pv[0]->print(). Ea este iniţializată cu adresa unui vector ce conţine obiecte de tipul Line : Line * pvx = new Line[2]. -2). delete [] pvx. pvx.print(). -1) . Obiectul pvx este şters în final cu instrucţiunea delete [] Programul este următorul int main() { int i.

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

Clasa Triunghi defineşte o funcţie aria() ce calculează aria triunghiului. Baza (double.} Complex(double x. Triunghi double aria() . v) = ". Constructorul clasei Triunghi apelează constructorul clasei Baza pentru a iniţializa variabilele a şi b. In clasa Complex. class Triunghi : public Baza { public: double aria() {return a * b / 2. Triunghi(double.} void print() {cout << "(u. deoarece simpla apelare cu instrucţiune print() ar fi însemnat apelare recursivă a funcţiei print() din clasa Triunghi. o funcţie print() ce scrie valorile bazei şi înălţimii şi un constructor cu parametri. a va fi baza. y) {} }. double y) : Baza(x. double b. } Triunghi(double x. double) . inaltimea) = ".} void print(){cout << "(baza. Definiţia acestei clase este cea de mai jos. class Complex : public Baza { public: double abs() {return sqrt(a * a + b * b). a şi b vor fi partea reală şi partea imaginară a numărului complex. Complex(double.Baza double a. Complex double abs() . y) {} }. Apelarea funcţiei print() din clasa de bază se face cu operatorul de rezoluţie :: Baza ::print() . double). double) . In clasa Triunghi. o funcţie print() ce scrie numărul complex şi un constructor cu parametri. double y) : Baza(x. 156 . void print() . iar b înălţimea. Clasa Complex defineşte o funcţie abs() ce calculează modulul numărului complex. void print() . void print(). Funcţia print() a clasei Triunghi apelează funcţia print() a clasei de bază pentru a scrie valorile a şi b. Baza::print(). Baza::print().

v)| = " <<c. 10. dar nu pot fi utilizate de obiecte.2 Specificatorii de acces Vom prezenta acum semnificaţia cuvântului cheie acces din definiţia clasei derivate. Pentru a utiliza clasele derivate de mai sus. • membrii publici ai clasei pot fi utilizaţi şi în afara clasei.a << endl. Reamintim că am definit variabilele clasei de bază h şi b de tipul protected. Membrii publici şi protejaţi ai clasei de bază apar ca şi cum ar fi declaraţi în clasa derivată. specificatorii de acces sunt : public. • menbrii protejaţi ai clasei pot fi utilizaţi şi în clasele derivate. 1).Pentru a scrie numărul complex. în clasele derivate şi în obiectele de tipul clasei sau al claselor derivate. cout << "aria = " << trg. De exemplu. Fie definiţia unei clase numită Baza class Baza { 157 . Datele şi funcţiile declarate private în clasa de bază nu pot fi utilizate în clasele derivate sau de obiecte de tipul claselor derivate. funcţia print() a clasei Complex apelează funcţia print() a clasei de bază cu operatorul de rezoluţie ca mai sus.5. creăm un triunghi şi îi calculăm aria. In definiţia unei clase. } Rezultate afişate sunt cele de mai jos. Clasa derivată moşteneşte membrii publici şi pretejaţi ai clasei de bază. apoi definim un număr complex şi îi calculăm modulul. Constructorul clasei Complex apelează constructorul clasei Baza pentru a iniţializa variabilele a şi b. cout << "|(u. trg. pentru a afişa valorile variabilelor a şi b am definit funcţia print(). nu putem scrie în funcţia main() instrucţiunea cout << trg.2. int main() { Triunghi trg(2. Complex c(1. c. Din acest motiv.aria() << endl. Datele şi funcţiile declarate protected în clasa de bază pot fi utilizate în clasele derivate. 1). Semnificaţia lor este următoarea : • membrii privaţi ai clasei pot fi utilizaţi doar în interiorul clasei.print(). private şi protected. return 0.print().abs() << endl.

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 derivate. specificatorul de acces al variabilei din tabelul de mai sus public protected private da da da da da nu da nu nu membri ai aceleiaşi clase membri claselor derivate nemembri Fie o clasă derivată din Baza cu specificatorul de acces public class DerivPublic : public Baza { /* declaratii din clasa Deriv */ public: int w. private : int v . Variabila x poate fi utilizată în clasa Baza. Variabila v poate fi utilizată doar în interiorul clasei Baza. Variabila y poate fi utilizată în clasa Baza şi in clasele derivate. 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. clasa derivată moşteneşte toţi membrii clasei de bază cu nivelul de acces avut în clasa de bază. }. La utilizarea specificatorului de acces public. • 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. 158 . protected: int y.public: int x. Specificatorul de acces din definiţia clasei derivate dă nivelul minim de acces pentru membrii moşteniţi din clasa de bază. dar nu în obiecte de tipul clasei Baza sau clase derivate. 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.

protejate şi private ale obiectului sunt cele din tabelul de mai jos. dar nu pot fi utilizată de obiecte de tipul Deriv. respectiv public pentru x şi protected pentru y. Variabila x poate fi utilizată de obiecte de tipul Deriv deoarece este definită de tipul public în clasa de bază. deoarece doar variabilele cu specificatorul de acces public pot fi utilizate de obiectele de tipul clasei. }. Considerăm acum clasa derivată din Baza definită cu specificatorul de acces private class DerivPrivate : private Baza { /* declaratii din clasa Deriv */ public: int w. dar numai variabila x poate fi utilizată de obiectele de tipul Deriv. 159 . Considerăm clasa derivată din Baza definită cu specificatorul de acces protected class DerivProtected : protected Baza { /* declaratii din clasa Deriv */ public: int w. cout << b. In tabelul de mai jos se arată variabilele obiectului b. Variabilele publice. Ele pot fi utilizate de funcţiile definite în clasa Deriv. } dar nu putem avea instrucţiunea cout << b.y. In acest caz toate variabilele x si y din clasa Baza au în clasa Deriv specificatorul de acces private. }. publice. protejate şi private ale obiectului sunt cele din tabelul de mai jos. Ele pot fi utilizate de funcţiile definite în clasa Deriv. In acest caz variabilele x şi y au în clasa Deriv specificatorul de acces definit în clasa Baza. Fie obiectul DerivPrivate d .x. protejate şi private. Variabilele publice. dar variabila y nu poate fi utilizată de obiecte de tipul Deriv deoarece este declarată protected în clasa de bază. în funcţia main() putem avea instrucţiunile int main() { DerivPublic b.}. Fie obiectul DerivProtected c . In acest caz variabilele x şi y au în clasa Deriv specificatorul de acces protected. De exemplu.

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

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

2. int main() { // creaza un obiect de tipul Point si afisaza coordonatele lui Point pt(124. } In constructorul clasei derivate de mai sus am apelat constructorul clasei de bază în secţiunea : Pixel :: Pixel(int a. Vom crea un obiect pt tip Point şi vom afişa coordonatele lui. int c) : Point(a. cout << "coordonatele unui punct" << endl. 200). şi printr-o variabilă tip pointer. pt. } Menţionăm că nu puteam defini funcţia clear() a clasei Pixel astfel void Pixel::clear() { clear(). Un exemplu de utilizare a claselor Point şi Pixel este prezentat mai jos. care va iniţializa coordonatele pixelului. Vom crea apoi un obiect p tip Pixel şi vom afişa coordonatele şi culoarea apelând funcţia print(). // afisaza coordonatele punctului utilizand un pointer Point * ptrp = &pt. ptrp->print(). 15). b) void Pixel::clear() { Point::clear(). Pixel::Pixel(int a. 162 . cout << “culoarea : “ << color << endl. int b. b) { color = c. direct de către obiect. cout << "coordonatele si culoarea unui pixel" << endl.In definiţia constructorului clasei Pixel vom apela constructorul cu parametri al clasei Point. direct de către obiect. // creaza un obiect de tipul Pixel si afisaza coordonatele si culoarea Pixel p(1. apelând funcţia print(). } void Pixel::print() { Point::print(). } deoarece ar fi însemnat o apelare recursivă a funcţiei clear().print(). color = 0. şi printr-o variabilă tip pointer. int c) : Point(a. color = 0. int b.

Vrem să definim o clasă care să conţină informaţii despre angajaţii unei intreprinderi. 10. return 0.print(). ptrx->print(). departamentul şi poziţia. este de a defini o clasă de bază ce conţine câmpuri cu numele angajatului şi departamentul. Aceste informaţii vor fi şiruri de caractere. Un astfel de program este complicat deoarece trebuie să prelucreze separat cele două tipuri de obiecte. şi o clasă derivată ce va defini în plus un câmp pentru poziţia managerului. O altă soluţie. // afisaza coordonatele si culoarea utilizand un pointer Pixel * ptrx = & p. Vom defini o clasă numită Manager ce moşteneşte clasa Angajat ce conţine o variabilă tip string pozitie cu poziţia managerului. Reprezentarea acestor clase este cea de mai jos 163 . un constructor cu doi parametri pentru nume şi departament şi o funcţie print() ce afişază aceste variabile. departament şi poziţie şi o funcţie print() ce afişază variabilele nume. Vom defini o clasă de bază numită Angajat ce conţine două variabile tip string. numele şi departamentul în cazul tuturor angajaţilor. 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. iar în cazul managerilor şi poziţia. nume şi dept.p. O soluţie este aceea de a defini două clase diferite. Polimorfism Considerăm următorul exemplu. dept şi poziţie. } Rezultatul programului este cel de mai jos. un constructor cu trei parametri pentru nume.3 Funcţii virtuale. aplicată în continuare. cu numele persoanei şi al departamentului.

void print() {Angajat::print(). Limbajul oferă următoarea posibilitate. public: Angajat(string s. class Manager : public Angajat { protected: string pozitie. O soluţie este de a crea doi vectori cu pointeri. poziţia.Definiţia clasei Angajat este următoarea class Angajat { protected: string nume. string dept. string p) : Angajat(s. 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() {cout << "Nume : " << nume << "\n". string d) : nume(s). cout << " Pozitie : " << pozitie << "\n". } }. cout << "Departament : " << dept << "\n". public: Manager(string s. Presupunem că vrem să afişăm numele şi departamentul pentru toţi angajaţii şi. pozitie(p) {}. Fie următoarea funcţie 164 . d). Această soluţie duce la programe complexe. Un pointer de tipul clasei de bază conţine adresa unui obiect de tipul clasei de bază sau de tipul unei clase derivate. în plus pentru manageri. unul de tipul Angajat şi altul de tip Manager cu adresele obiectelor respective şi de a le prelucra separat. dept(d) {} . string d. } }.

şi într-o instrucţiune for apelăm funcţia print() a obiectelor for(int i = 0. // memoreaza adresele obiectelor in vector vect[0] = &x1. "proiectare"). } Rezultatele sunt următoarele Remarcăm că în cazul celui de-al doilea angajat nu se afişază poziţia. 165 . "proiectare". i < 2. # include <iostream> # include <string> using namespace std. este apelată totdeauna funcţia print() a clasei Angajat. i < 2. "proiectare". O funcţie este definită ca virtuală scriind cuvântul cheie virtual în faţa definiţiei funcţiei. i++) vect[i]->print(). return 0. Manager m1("Bob". "proiectare"). Definim două obiecte. Programul complet este următorul. O funcţie virtuală are acelaşi nume în clasa de bază şi în toate clasele ce o moştenesc. nu de tipul obiectului. i++) vect[i]->print(). Vom defini funcţia print() din exemplul anterior virtuală şi în acest caz funcţia apelată este determinată de tipul obiectului. In consecinţă. deşi. în cazul obiectelor de tipul Manager. Manager m1("Bob". Putem face ca funcţia apelată să fie determinată de tipul obiectului şi nu de tipul variabilei pointer definind funcţia respectivă virtuală. Problema acestei soluţii este următoarea. "sef"). vect[1] = &m1. vect[1] = &m1. Angajat * vect[2]. unul de tipul Angajat. // apeleaza functia print() a obiectelor din vector for(int i = 0. "sef"). ar trebui să se apeleze funcţia clasei Manager.main() pentru rezolvarea problemei (necorectă). Memorăm adresele celor două obiecte în componentele vectorului vect[0] = &x1. Programul este cel de mai jos int main() { Angajat x1("Alex". altul de tipul Manager şi un vector de doi pointeri de tipul Angajat Angajat x1("Alex". Funcţia apelată este determinată de tipul variabilei pointer. Angajat * vect[2].

"sef"). public: Manager(string s. } Reamintim că funcţia print() este virtuală. string p) : Angajat(s. // memoreaza adresele obiectelor in vector vect[0] = &x1. } }. string dept. string d) { nume = s. return 0. int main() { Angajat x1("Alex". Manager m1("Bob". cout << " departament : " << dept << "\n". astfel încât apelul 166 . Angajat * vect[2]. i++) vect[i]->print(). d) { pozitie = p. string d. } virtual void print() { cout << "nume : " << nume << "\n". vect[1] = &m1. "proiectare". } }. } virtual void print() { Angajat::print().class Angajat { protected: string nume. dept = d. public: Angajat(string s. cout << " pozitie : " << pozitie << "\n". // apeleaza functia print() a obiectelor din vector for(int i = 0. i < 2. class Manager : public Angajat { protected: string pozitie. "proiectare").

vect[i]->print(). public: Angajat(char *. Menţionăm de asemenea că instrucţiunea Angajat x1(“Alex”. producând rezultatul corect. Vom spune că apelul de funcţie vect[i]->print(). strcpy(nume. apelează funcţia print() din clasa corespunzătoare tipului obiectului. deoarece se modifică după natura obiectului indicat de pointer. Definiţia clasei Angajat poate fi următoarea class Angajat { protected: char * nume. Noua definiţie a clasei Point este următoarea class Point { private: int x. ce pune la valoarea zero variabilele. Să se înlocuiască şirurile de tip string din clasele Angajat şi Manager cu şiruri tip C. Orice clasă ce defineşte sau moşteneşte funcţii virtuale se numeşte polimorfă. 167 . dept = new char[strlen(d) + 1]. Rezultatul rulării programului este cel de mai jos. char * dept. y. Exerciţiu. strcpy(dept. d). Fie clasele Point şi Pixel definite anterior. s). Indicaţie. şi funcţia print(). vor fi declarate virtuale. virtual void print(). }. Vom redefini clasele Point şi Pixel astfel ca. char *). char * d) { nume = new char[strlen(s) + 1]. “proiectare”). este polimorfic. } Exemplu. Definiţia constructorului Angajat poate fi următoarea Angajat::Angajat(char * s. funcţia clear(). este corectă deoarece constructorul copiere al clasei string are ca argument un şir de tip C sau de tip C++. ce afişază valoarea variabilelor.

y = 0. y = 0. } Clasa Pixel moşteneşte clasa Point. int). int p2) { x = p1. virtual void print(). Pixel::Pixel(int a. Point(){x = 0. iar funcţiile clear() şi print() apelează funcţiile cu acelaşi nume din clasa Point. Constructorul ei apelează constructorul cu parametri al clasei Point. int). Ele vor fi repetate aici. Noua definiţie a clasei Pixel este următoarea class Pixel : public Point { private: int color. Definiţiile funcţiilor sunt următoarele. b) { color = c. 168 . int c) : Point(a. public: virtual void print().public: virtual void clear().” << “ y= “ << y << endl. } void Pixel::clear() { Point::clear(). Point(int. Pixel(int. int. } Point::Point(int p1. int b. } void Point::print() { cout << “x = “ << x << “. y = p2. }. virtual void clear().} }. Definiţiile funcţiilor sunt cele anterioare. void Point::clear() { x = 0.

15). // atribuie variabilei ptr adresa obiectului p de tip Point ptr = &p. // atribuie variabilei ptr adresa obiectului px de tip Pixel ptr = &px. // afisaza coordonatele punctului cout << "coordonatele unui punct" << endl. Prima instrucţiune ptr->print(). // creaza un obiect de tipul Point Point p(2. A doua instrucţiune ptr->print(). 7). } Rezultatul rulării programului este cel de mai jos.color = 0. } void Pixel::print() { Point::print(). Vom scrie datele din aceste obiecte apelând funcţia print() printr-un pointer la clasa de bază. 169 . } In funcţia main() vom crea un obiect p de tip Point şi apoi un obiect px de tipul Pixel. ptr->print(). // creaza un obiect de tipul Pixel Pixel px(1. 2. apelează funcţia print() a clasei Point. // afisaza coordonatele si culoarea pixelului cout << "coordonatele si culoarea unui pixel" << endl. ptr->print(). int main() { Point * ptr. return 0. cout << “ culoare = “ << color << endl.

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

1 Date statice Obiectele de tipul unei clase au propriile variabile ce conţin valori ce dau starea fiecărui obiect. Definirea se face scriind cuvântul cheie static la începutul declaraţiei variabilei.} virtual ~Baza(){cout << "Destructor Baza" << endl. în cazul funcţiilor ce nu sunt virtuale. este apelat destructorul clasei Baza şi nu al clasei Deriv. Rezultatul rulării programului este acum următorul Acum este apelat destructorul clasei derivate. dar în spaţiul de nume al clasei. într-un singur exemplar. şi nu de tipul obiectului memorat. funcţia ce se apelează este dată de tipul variabilei. care este Baza. 171 . ce apelează şi destructorul clasei de bază. Datele statice sunt la fel ca şi variabilele globale. 10. Pentru a apela destructorul clasei Deriv trebuie să declarăm destructorul clasei de bază de tip virtual. este necesar să existe o variabilă a clasei. Datele statice există chiar dacă nu există nici un obiect de tipul clasei. deoarece.5. sau A. ca mai jos class Baza { public: Baza(){cout << "Constructor Baza" << endl. }.După cum se observă. Acest lucru se poate realiza simplu. int A::x = 0. Uneori. definind acea variabilă de tip static.} }. Exemplu. Pentru exemplul clasei definite mai sus.5 Date şi funcţii statice 10. putem utiliza variabila statică x ca A::x.x sau. Datele de tip static se iniţializează în afara clasei. pentru toate obiectele. class A { public: static int x.

Valoarea iniţială a variabilei statice este zero. cout << “exista “ << X::nb << “ obiecte” << endl. b. iar destructorul scade o unitate din variabila statică.nb sau X::nb. După instrucţiunea X c. { X c. O aplicaţie a datelor statice este aceea de a număra obiectele existente de tipul clasei.} ~X(){nb--. c şi d. cout << “exista “ << X::nb << “ obiecte” << endl. a. In funcţia main() vom crea obiecte şi vom afişa numărul lor. Exemplu. 172 . Menţionăm că. ca nume_obiect. După ieşirea din blocul interior există două obiecte.dacă există obiecte de tipul clasei. Vom defini o clasă cu o variabilă statică ce va număra obiectele existente de tipul clasei. } Rezultate afişate vor fi După instrucţiunea X a. există patru obiecte. int main() { X a. Constructorul adună o unitate. X(){nb ++. d.x Datele statice sunt iniţializate automat la zero. # include <iostream> using namespace std. } cout << “exista “ << X::nb << “ obiecte” << endl. Definiţia clasei este următoarea. class X { public: static int nb. int X::nb = 0. b. return 0. obiectele c şi d fiind distruse la ieşirea din bloc.} }. putem adresa variabila nb ca X. a şi b. Reamintim că variabilele statice se iniţializează în afara clasei. există două obiecte. d. b.

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. Ea constă în următoarele. float y) : Base(x. b = y. dar în spaţiul de nume al clasei. In cazul clasei Add. constructorii corespunzători şi o funcţie virtuală oper() ce nu efectuează nici un calcul. Vom defini o clasă de bază ce defineşte două variabile a şi b. în funcţie de valoarea parametrului şi furnizează adresa obiectului creat ca pointer de tipul clasei de bază Base. Exemplu. Deriv1 şi Deriv2. Vrem să definim următoarele două clase : • o clasă cu o metodă ce calculează suma a două numere. Ele pot prelucra doar datele statice ale clasei. float).5. float y) { a = x. public: Base() {a = 0.10. Acest lucru se face definind o clasă cu o funcţie statică. • o clasă cu o metodă ce calculează produsul a două numere. Base::Base(float x. Fie o clasă de bază numită Base şi două clase derivate din ea. # include <iostream> using namespace std. b = 0. Un exemplu de utilizare a funcţiilor statice este tehnica de programare ClassFactory. virtual float oper(){return 0. Funcţiile statice sunt la fel ca şi funcţiile globale.} }. Definirea unei funcţii statice se face scriind cuvântul cheie static înaintea definiţiei funcţiei.2 Funcţii statice O clasă poate defini funcţii statice. class Base { protected: float a. Ele au următoarele definiţii class Add : public Base { public: Add(float x. respectiv produsul a două numere. ce crează un obiect de tipul Deriv1 sau Deriv2. în cazul clasei Mul.} Base(float. 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 pot fi apelate utilizând numele clasei sau al unui obiect de tipul clasei. y) {} virtual float oper(){return a + b.} }. } Clasele derivate definesc funcţia virtuală oper() ce efectuează adunarea. In ambele cazuri metoda se va numi oper(). 173 . b.

y). y). 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. 174 . Implementarea funcţiei operfactory este următoarea Base * ClassFactory::operfactory(float x. Apoi utilizează metoda oper() a obiectului la calculul sumei a două numere. float x = 1. // creaza un obiect care sa calculeze suma a doua numere base = cf. Base * base. float y) : Base(x. int main() { float z. Presupunem că vrem să calculăm suma a două numere. }. '+'). float y. y. y = -2. float. // calculeaza suma numerelor z = base->oper(). cout << "suma numerelor " << x << " si " << y << endl. char). }. y) {} virtual float oper() {return a * b.35.2. In funcţia main() se crează un obiect de tipul Add apelând funcţia operfactory şi se memorează adresa obiectului într-un pointer de tipul clasei de bază. ClassFactory cf. char c) { if(c == '+') return new Add(x. class ClassFactory { public: static Base * operfactory(float.operfactory(x. cout << z << endl. else return new Mul(x.class Mul : public Base { public: Mul(float x.} }.

175 .operfactory(x. return 0. '*'). cout << z << endl.cout << "produsul numerelor " << x << " si " << y << endl. } Rezultatul rulării programului este cel de mai jos. // calculeaza produsul numerelor z = base->oper(). y. // creaza un obiect care sa calculeze produsul a doua numere base = cf.

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

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

fil2 >> x >> e. } Rezultatul rulării programului este cel de mai jos. Să se rescrie programul de mai sus definind un singur obiect de tipul fstream fstream fil. e = (cos(x) * cos(x) + x) / (1.open(filename.open(filename. ios::out). 178 .close().for(i = 0.2. fil1 << x << ‘\t’ << e << endl. fil2 >> x >> e. // citeste fisierul creat si afisaza rezulatele ifstream fil2. Exerciţiu. } fil2.is_open()) { cout << “ nu se poate deschide fisierul “ << filename << endl. } fil1.close().open(filename). return 0. i < 11. fil2. şi apoi în citire pentru afişarea rezultatelor cu instrucţiunea fil. ios::in). return 0. } // scrie antetul cout << "x" << '\t' << "e" << endl. while(!fil2.eof()) { cout << x << ‘\t‘ << e << endl. Se va deschide la început streamul în scriere pentru crearea fişierului cu instrucţiunea fil.0 + fabs(x)) + sin(x). i++) { // calculeaza expresia si scrie in fisier x = i * 0. if(!fil2.

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

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

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

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

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

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

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

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

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

Obiectul de tip string ce reprezintă mesajul este creat cu secvenţa de instrucţiuni : stringstream ss. throw string(“impartire prin zero ” + ss. un obiect de tip string. 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__ .str()). try 188 . int fun(int x. vezi exemplul anterior. Vom defini o funcţie ce calculează câtul a două numere întregi şi lansează o excepţie. ss << "linia : " << __LINE__ << " fisierul : " << __FILE__. Excepţiile lansate de o funcţie sunt precizate la definirea funcţiei în felul următor tip nume_functie(lista de parametri) throw (lista de tipuri) { // corpul functiei } Lista de tipuri dă tipurile de excepţii ce vor fi lansate de instrucţiunea throw.catch(…) { // trateaza exceptiile } 12. Numele fişierului sursă este conţinut în constanta tip caracter __FILE__. ss << "linia : " << __LINE__ << " fisierul : " << __FILE__ . şi orice funcţie poate lansa excepţii. c. Exemplu. int y) throw(string) { if(y == 0) { stringstream ss. } int main() { int a = 10. } else return x / y. 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.2 Excepţii lansate de funcţii Intr-un bloc try pot exista apeluri la multe funcţii. b = 0. Programul este următorul # include <iostream> # include <string> #include <sstream> using namespace std.

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

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

b = 0. } return 0. ce dau starea curentă a streamului : • badbit poziţionat la unu la apariţia unei erori nereparabile (eroare intrare / ieşire pe disc). 12. } else return x / y.what() << endl. • goodbit poziţionat la unu atunci când nu există eroare. char *argv[]) { int a = 10. } Rezultatul rulării ptogramului este cel de mai jos. Aceşti biţi sunt definiţi în clasa ios şi sunt poziţionaţi după fiecare operaţie de scriere sau citire. } catch(runtime_error& exm) { cout << exm. dar streamul de intrare conţine litere. c. definiţi în clasa ios. try { // functie ce poate lansa o exceptie c = fun(a. Ei pot fi testaţi cu următoarele funcţii definite în clasa ios : 191 . cout << a << "/" << b << "=" << c << endl.4 Excepţii intrare / ieşire Orice obiect de tip stream conţine următorii biţi. ss << __LINE__.str()). int y) throw(runtime_error) { if(y == 0) { stringstream ss. int fun(int x. încercăm să deschidem în citire un fişier inexistent. throw runtime_error("impartire prin zero linia : " + ss. b). • eofbit poziţionat la unu când s-a detectat sfârşitul de fişier la citire. etc). } int main(int argc.#include <sstream> using namespace std. • failbit poziţionat la unu la apariţia unei erori reparabile (de exemplu citim o variabilă numerică.

Clasa de bază pentru excepţiile lansate de streamuri este clasa failure. instrucţiunile de prelucrare a fişierului vor fi incluse într-un bloc try şi excepţiile sunt prinse în blocul catch corespunzător.get(). Vom citi un fişier tip text şi vom afişa conţinutul lui pe ecran. int main() { ifstream file. • bool eof(). } 192 • • .get(). Menţionăm că funcţia eof() a fost deja folosită la citirea fişierelor. car = file. are valoarea true dacă bitul badbit are valoarea unu. are valoarea true dacă cel puţin unul din biţii badbit şi failbit are valoarea unu. Ea este o clasă internă a clasei ios şi defineşte constructorul failure(string). care moşteneşte din clasa exception. Putem permite lansarea excepţiilor cu funcţia int exceptions(int except). în urma unei operaţii intrare / ieşire.eof()) { cout << car. try { char car. file. şi funcţia virtuală const char * what().exceptions(ios::badbit | ios::failbit). while(! file. De exemplu exceptions(ios::badbit | ios::failbit).bool bad(). are valoarea true dacă bitul eofbit are valoarea unu. // permite lansarea unei exceptii file. permite lansarea excepţiilor când biţii badbit şi failbit sunt poziţionaţi la unu. Se vor lansa excepţii în cazul apariţiei unei erori în care sunt poziţionaţi la unu biţii badbit şi failbit. car = file. Implicit. • bool good(). erorile streamurilor nu lansează excepţii. #include <iostream> #include <fstream> #include <exception> using namespace std. are valoarea true dacă ceilalţi biţi au valoarea zero.txt”). Exemplu. vezi definiţia clasei exception. In cazul în care excepţiile sunt permise. bool fail(). definită în clasa ios.open(“rez. 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 |.

what() << endl. } Blocul catch are ca parametru un obiect de tipul failure catch(ios::failure e) care este tipul excepţiilor lansate de streamuri.close(). } return 0.file. 193 . } catch(ios::failure e) { cout << “exceptie : “ << e.

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

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

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

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

o şi programul executabil.exe.system("PAUSE"). main. 198 .cpp. Se pot observa în directorul curent : fişierul sursă. fişierul obiect rezultat al compilării. } Rezultatul rulării programului este cel de mai jos. main. return EXIT_SUCCESS. main.

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

Definiţia clasei este următoarea # include <iostream> using namespace std. Să definim obiecte de tipul X.geta() << " este " << n. template <class T> class X { private: T a. utilizând clasa generică X.} T geta() {return a. public: X(T b) {a = b. cout << "patratul valorii " << n. definiţia template <class T> 200 . vom repeta definiţia clasei generice X.geta() << " este " << d. } Rezultatul rulării programului este cel de mai jos. return 0.14). ce conţin elemente de tip int sau double.} T square() {return a * a. In acest caz.square() << endl. cout << "patratul valorii " << d.square() << endl. Ca exerciţiu. int main() { // crează un obiect cu şablonul <int> X<int> n(2).} }. cu funcţiile membre definite în afara clasei. // crează un obiect cu şablonul <double> X<double> d(3.

indiferent de tipul containerului. Cel mai cunoscut tip de container este vectorul. Operaţiile efectate asupra iteratorilor. etc. compilatorul generează două clase.trebuie repetată înaintea fiecărei definiţii de funcţie. funcţiile ce inserează obiecte în container. T geta(). T square().. O operaţie obişnuită efectuată asupra unui container este parcurgerea obiectelor din container (traversarea containerului). Ele au funcţii membre ce efectuează aceste operaţii. In exemplul de mai sus. numite în continuare elemente ale containerului. public: X(T b). } template <class T> T X<T>::square() { return a * a. int. Containerele permit inserarea şi ştergerea de obiecte. }. el indică un anumit obiect din container. Parcurgerea elementelor containerului se face cu un iterator.2 Containere. template <class T> X<T>::X(T b) { a = b. una X_int şi una X_double. şiruri tip string. sau unul structurat. sau obiecte ale unei clase definite de utilizator. template <class T> class X { private: T a. De regulă. Tipul obiectelor poate fi unul simplu. iteratori şi algoritme generice Un container este un obiect ce poate conţine alte obiecte. 14. sunt următoarele : 201 . Iteratorul este similar unui pointer. } Compilatorul utilizează definiţia clasei generice pentru a genera clasele necesare. Obiectele din container au acelaşi tip. } template <class T> T X<T>::geta() { return a. alocă şi memoria necesară. double. #include <iostream> using namespace std.

Clasele ce definesc iteratori au următorii operatori. • furnizarea obiectului de la poziţia curentă a iteratorului. • avansarea iteratorului la poziţia următoare din container. Menţionăm că clasa obiectelor memorate într-un container trebuie să aibă un constructor implicit (fără parametri). • operatori de comparare a doi vectori . 14. Cele mai importante funcţii sunt sort(). şi find(). Clasa defineşte următoarele funcţii : • void push_back(const T&). are valoarea true dacă vectorul este vid. • operatorul de adresare indirectă. Un obiect de tip vector are un număr iniţial de componente şi dimensiunea lui creşte dacă este nevoie. • void clear(). Clasa defineşte destructorul ~vector() . • bool empty(). != .• iniţializarea iteratorului la un anumit obiect din container. = =. crează un vector vid. adaugă un element la sfârşitul vectorului. • T& front(). crează un vector în care copiază elementele vectorului p care este parametru. = =. dă numărul de componente ale vectorului. Biblioteca de şabloane standard defineşte funcţii ce se pot aplica asupra containerelor. ce sortează elementele containerului. permite adresarea sau modificarea obiectului de la poziţia curentă a containerului indicată de iterator. ce realizează operaţiile de mai sus asupra iteratorilor : • operatorii ++ şi -. <=. • constructorul copiere vector(const vector& p). <. • int size(). >=. Aceste funcţii vor fi prezentate pentru fiecare tip de container. !=. • operatorul [] şi funcţia T& at(int) selectează un element al vectorului.3 Vectori Clasa generică vector implementează vectori cu elemente de un anumit tip. listele şi numerele complexe.modifică iteratorul la următoarea / precedenta poziţie din container. >. In cele ce urmează vom prezenta vectorii. Fie T tipul componentelor vectorului (parametrul clasei generice). 202 . Clasa defineşte următorii constructori : • constructorul implicit (fără parametri) vector(). • void pop_back(). • T& back(). Clasa vector are ca parametru tipul componentelor vectorului. are ca rezultat primul component al vectorului. şterge ultimul element al vectorului. <. • modificarea obiectului de la poziţia curentă a iteratorului. >. care caută un element în container. element cu element. • constructor ce crează un vector cu n elemente vector(int n). un constructor tip copiere şi operatorul =. • operatorul = este operatorul de atribuire. <=. >=. • operatorii relaţionali. şterge toate componentele vectorului. *.are ca rezultat ultimul component al vectorului. permit compararea a doi iteratori.

k++) cout << v[k] << “ “. v. for(k = 0. arătate mai sus. } Rezultatul rulării programului este cel de mai jos. Să creăm şi să listăm un vector cu componente întregi. // afisaza componentele vectorului int k. după ultimul element. La definirea obiectului v. cout << endl. Elementele unui vector pot fi accesate în trei moduri : cu operatorul [] şi funcţia at(). // adauga 4 elemente v. return 0. înaintea primului element şi altul. In exemplul următor comparăm doi vectori. Menţionăm că. #include <cstdlib> #include <iostream> #include <vector> using namespace std. Aceste moduri vor fi exemplificate în continuare. v.push_back(12). se crează un vector vid. int main() { // definim un vector cu componente intregi vector <int> v.Menţionăm că un vector poate avea două sau mai multe elemente egale. v.push_back(7). Exemplu.push_back(-5). k < v. cout << “ elementele vectorului “ << endl .at(k). vectorul este următorul şi are dimensiunea 4. # include <iostream> # include <vector> using namespace std. vec1 şi vec2. După execuţia instrucţiunilor push_back(). Menţionăm că.push_back(13).size(). şi cu iteratori. putea fi înlocuită cu expresia v. 203 . Clasa vector este definită în biblioteca <vector>. expresia v[k]. vectorul are un element fictiv.

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

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

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

push_back(i * i). for(i = 0. i < v.erase(it). for(i = 0. // sterge primul element din vector cout << “sterge primul element din vector” << endl. char *argv[]) { // definim un vector cu componente intregi vector <int> v. 25).size(). cout << "vectorul modificat" << endl. v. cout << endl. cout << endl.insert(it.size(). v.begin(). i++) cout << v. for(i = 0. int main(int argc. i++) cout << v. 207 . it = v. for(i = 1. i < 5. vector <int>::iterator it.at(i) << " ". cout << “vectorul modificat" << endl.at(i) << " ". i++) cout << v. cout << endl. i < v. i < v.at(i) << " ".v. Programul este următorul #include <iostream> #include <vector> using namespace std. i++) v. // insereaza un element in prima pozitie cout << “insereaza un element in prima pozitie” << endl. cout << endl.at(i) << " ". // afisaza componentele vectorului cout << "vectorul initial" << endl. Afişarea elementelor vectorului se face cu o instrucţiune for for(i = 0.size().size().begin(). // se adauga 4 elemente int i. i++) cout << v. i < v. 25). it = v.insert(it.

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

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

size(). } Rezultatul rulării programului este cel de mai jos. // creaza vectorul v.push_back(-3). v. // sorteaza vectorul in ordine crescatoare cout << "Se sorteaza vectorul in ordine crescatoare" << endl. sort(v. cout << v. i++) cout << v[i] << “ “.size(). iterator last. return 0. for(i = 0.begin(). // sorteaza vectorul in ordine descrescatoare cout << "Se sorteaza vectorul in ordine descrescatoare" << endl. for(i = 0. sort(v. // afisaza vectorul sortat cout << "Vectorul sortat parcurs in sens direct \n". 210 .at(i) << “ “. cout << endl.{ vector<int> v. i < v. // afisaza vectorul initial cout << “Vectorul initial parcurs in sens direct” << endl. v.push_back(12). for(i = 0.push_back(4). v. int i.push_back(12). comp). i < v. // afisaza vectorul sortat cout << "Vectorul sortat parcurs in sens direct \n". const T& val).end().3. comp2).size(). cout << endl. v. 14.begin().end(). cout << endl. v.4 Căutarea unui element într-un vector Biblioteca de şabloane standard defineşte funcţia globală find() cu prototipul find(iterator first. i++) // cout << v[i] << endl. i++) cout << v[i] << “ “. i < v.

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

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

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

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

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

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

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

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()); return 0; } Rezultatul programului este cel de mai jos.

218

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.

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. 219

#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”); // parcurge sirul in sens direct 220

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: • T real(); 221

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

223 .

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

Având un număr în baza 2. q 0 = N 2. 32 sau 64 de biţi. Unitatea de bază a informaţiei în calculator este un octet sau byte. ( 28) 10 = (11100) 2 = ( 34) 8 225 . Exemplu. while qi ≠ 0 { q qi +1 = i 2 ri = qi %2 i = i +1 } Resturile obţinute sunt cifrele numǎrului binar. primul rest fiind cifra cea mai puţin semnificativǎ. ce cuprinde 8 cifre binare (biţi). 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. Având un numǎr în baza 2. i = 0 3. pentru reprezentarea în baza 8 se grupează câte 3 cifre binare. Exemplu. pentru reprezentarea sa în baza 16 se grupează câte 4 cifre binare.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 In acelaşi fel obţinem: (10) 10 = (1010) 2 (11) 10 = (1011) 2 (12) 10 = (1100) 2 (13) 10 = (1101) 2 (15) 10 = (1111) 2 Algoritmul de conversie a unui numǎr din baza 10 în baza 2 este urmǎtorul: 1. Conversia din baza 10 în baza 8 sau 16 se face prin împǎrţiri repetate cu 8 şi respectiv 16.

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. 8 şi 16. bitul cel mai semnificativ este bitul de semn. 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. un bit de semn şi 7 biţi ai numǎrului: Număr zecimal 13 Reprezentare binarǎ 0000 1101 Reprezentare hexazecimalǎ 0D 226 . 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. 2. Există trei reprezentǎri ale numerelor binare cu semn. Vom considera numere întregi reprezentate pe 8 biţi. El este 0 pentru numere pozitive şi 1 pentru numere negative. Conversia unui numǎr din baza 8 în baza 2 se face convertind fiecare cifrǎ octalǎ prin 3 cifre binare. 10 şi 16 şi operaţii cu 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. Pentru conversii de numere între bazele 2.

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. Exemple de numere reprezentate în complement faţǎ de 1 pe un octet. Primul bit va fi interpretat ca şi coeficientul lui − 2 n −1 .-13 25 -7 127 -127 1000 1101 0001 1001 1000 0111 0111 1111 1111 1111 8D 19 87 7F FF 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. Exemple de numere reprezentate în complement faţǎ de 2 pe un octet. 127]. 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 2 i + 1 i =0 n−2 unde: a i = 1 − ai . Număr zecimal 13 -13 -7 Reprezentare binarǎ 0000 1101 1111 0011 1111 1001 Reprezentare hexazecimalǎ 0D F3 F9 227 . 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: − 1* 2 n −1 + ∑ (1 − ai )2 + 1 = −1 * 2 i i =0 n −2 n −1 +2 n −1 A. Exemplu. Regula este evidentǎ din formula de reprezentare a numerelor pozitive. Se deplaseazǎ toate cifrele numărului. 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. Să deplasǎm numărul 28 cu o cifrǎ binarǎ la dreapta şi la stânga. • la deplasarea la dreapta cifrele adaugate sunt unuri.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. Exemple. iar cifrele adaugate sunt zerouri. • reamintim ca bitul de semn rǎmâne neschimbat. 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.127 -127 0111 1111 1000 0001 7F 81 Menţionǎm cǎ în calculatoare numerele întregi se reprezintǎ în general în complement faţǎ de 2. Avem relaţia: ∑2 i =0 n −2 i + 1 = 2 n −1 − ∑ a i 2 = −∑ a i 2 i i i =0 i =0 n−2 n −2 In consecinţǎ. In acelaşi fel se aratǎ cǎ regula este valabilǎ pentru împǎrţirea cu 2. Cazul numerelor pozitive. 228 . Considerăm formula ce reprezintǎ un număr negativ în complement faţǎ de 2. La deplasarea numărului binar bitul de semn rǎmane neschimbat.

Numǎrul 1 = 0.1248 2 iar al doilea 0.Numǎr zecimal -28 -14 -56 Numǎr binar 1110 0100 1111 0010 1100 1000 Numǎr hexazecimal E4 F2 C8 A.1283 2 0.035 = −0.3245 * 10 2 − 0.45 = 0.(3) = 0.3 Reprezentarea numerelor reale Numerele reale se reprezintǎ în calculator în virgulǎ mobilǎ.3245 -0..35 Primul numǎr este reprezentat ca: 0. Exemplu. 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. Pentru exemplificare vom considera baza zece. 0. b este baza iar e este exponent sau caracteristicǎ.3333333333.1248 *10 2 0.3333333 * 10 0 Pentru mǎrirea preciziei calculelor se cautǎ ca prima cifră din mantisǎ sǎ fie diferitǎ de zero (vezi exemplul de mai sus). în unele cazuri se pierd cifrele mai puţin semnificative din numǎr. 3 se reprezintǎ ca: 0. pentru exemplificare vom considera cǎ mantisa are şapte cifre semnificative.48+0. Sǎ efectuăm adunarea: 12. Aducerea numǎrului mai mic la exponentul celui mai mare poate duce la pierderea cifrelor cel mai puţin semnificative din mantisǎ.035 2 şi se adunǎ mantisele.. Exemple de reprezentare a numerelor reale. 32.35 * 10 0 Pentru adunare se aduc numerele la acelaşi exponent 0. Numarul real R este pus sub forma R = f * be unde: f este un număr subunitar (mantisa).35 0 229 . Exemplu.35 * 10 −1 Mantisa f are totdeauna un număr finit de cifre şi în plus: f <1 0.1248 2 0. Deoarece mantisa are un numǎr finit de cifre.35 2 -1 In continuare. Numerele în această formă se numesc normalizate.

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

Fie numărul R = 1. La exponent se adaugǎ totdeauna valoarea 127 astfel încat exponentul are domeniul [0. deci exponentul are domeniul [0. Bitul de semn este zero iar exponentul 0+127.10 307 ) A. La exponent se adaugǎ cantitatea 1023.0 = 1. exponentul ocupǎ biţii 23-30 iar mantisa biţii 0-22. Bitul de semn este bitul 63. In consecinţă. Fir numărul R = 0. numărul este ± ∞ Dacǎ exponentul este 255 şi mantisa este diferitǎ de zero numărul este NaN (not a number). exponentul ocupǎ biţii 52-62 iar mantisa ocupă biţii 0-51. Bitul de semn ocupă bitul 31. Domeniul exponentului este [-1023. In reprezentarea în virgulǎ mobilă lungǎ (dublǎ precizie) numǎrul se reprezintă pe 64 biţi. Valoarea NaN apare atunci când efectuǎm urmatoarele operaţii ∞−∞ 0*∞ 0/0 ∞/∞ sau când un operand este NaN. y\x 0 1 2 3 4 5 0 Nul Soh Stx Etx Eot Enq 1 Dle Dc1 Dc2 Dc3 Dc4 Nak 2 ! “ # $ % 3 0 1 2 3 4 5 4 A B C D E 5 P Q R S T U 6 a b c d e 7 p q r s t u 231 .0 * 2 0 .0 * 2 −1 .mantisa * 2 exponent In reprezentarea în virgulǎ mobilă scurtǎ (simpla precizie) numǎrul se reprezinta pe 32 biţi. primii nouă biti din număr sunt 001111111 Numărul R va fi reprezentat în hexazecimal ca 3F800000. 1023].10 37 ) iar mantisa corespunde la opt cifre zecimale. Bitul de semn este zero iar exponentul este -1+127=126. cifra 0 are codul hexazecimal 30. Domeniul exponentului este [-127. Exemple. Gama numerelor reale ce se pot reprezenta în acest format este (−10 37 . 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. Gama numerelor reale ce se pot reprezenta este (−10 307 . Codurile ASCII ale caracterelor sunt prezentate în tabela de mai jos.5 = 1. 2046]. 127].254]. De exemplu caracterul A are codul hexazecimal 41. Bitul de semn are valoarea 0 pentru numere pozitive şi 1 pentru cele negative. caracterul a are codul hexazecimal 61. etc.In calculatoarele personale numerele reale se reprezintǎ în virgulǎ mobilǎ în felul urmǎtor R = semn * 1.4 Reprezentarea caracterelor Unul dintre sistemele de reprezentare a caracterelor este codul ASCII în care fiecare caracter este reprezentat pe un octet.

232 . De exemplu caracterul A are codul zecimal 65 sau.1 Codul ASCII Codul zecimal unui caracter este 16*x+y. echivalent. în varianta UNICODE16 fiecare caracter este reprezentat pe 2 octeţi. codul hexazecimal 41. Un alt sistem de reprezentare a caracterelor este UNICODE.6 7 8 9 10 11 12 13 14 15 Ack Bel Bs Ht Lf Vt Ff Cr So Si Syn Elb Can Em Sub Esc Fs Gs Rs Us & ‘ ( ) * + . / 6 7 8 9 : : < = > ? F G H I J K L M N O V W X Y Z [ \ ] ^ _ f g h i j k l m n o v w x y z { | } ~ del Tabelul 1. .

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

Sign up to vote on this title
UsefulNot useful