P. 1
CPP

CPP

|Views: 1,208|Likes:
Published by saltaretzul

More info:

Published by: saltaretzul on Jan 10, 2012
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as DOC, PDF, TXT or read online from Scribd
See more
See less

07/20/2013

pdf

text

original

Sections

  • 1.3Modificatori de tip
  • 1.6Constante cu nume
  • 1.7Expresii aritmetice
  • 1.9Instrucţiunea de atribuire
  • 1.10 Prototipuri de funcţii. Biblioteci de prototipuri
  • 1.11 Operaţii de intrare / ieşire
  • 1.12 Funcţia main
  • 1.13 Execuţia unui program
  • 1.14 Operatorul sizeof
  • 1.15 Operatorii ++ şi - -
  • 1.16 Operaţii cu numere întregi la nivel de bit
  • 1.16.1 Operatori de deplasare
  • 1.16.2 Operaţii logice la nivel de bit
  • 2Structuri de control fundamentale
  • 2.1 Algoritme
  • 2.2 Expresii relaţionale
  • 2.3 Expresii booleene
  • 2.4 Operatorul if
  • 2.5 Operatorul switch
  • 2.6 Operatorul ?
  • 2.7 Operatorul while
  • 2.8 Operatorul do-while
  • 2.9 Operatorul for
  • 2.10 Operatorul ,
  • 3Funcţii
  • 3.1Funcţii standard
  • 3.1.1Funcţii C standard de manipulare a caracterelor
  • 3.2Definirea funcţiilor
  • 3.3Prototipuri de funcţii
  • 3.4Compilarea separată a funcţiilor
  • 3.5Funcţii cu parametri tablouri
  • 3.6Supraîncărcarea funcţiilor
  • 3.7Transmiterea parametrilor către funcţii
  • 3.8Recursivitatea
  • 3.9Funcţii generice
  • 4Pointeri şi referinţe
  • 4.1.1Declararea variabilelor tip pointer
  • 4.3Pointeri la funcţii
  • 4.4Interpretarea instrucţiunilor ce conţin pointeri
  • 4.5Pointeri şi tablouri unidimensionale
  • 4.6Poineri şi şiruri tip C
  • 4.7Pointeri şi tablouri multidimensionale
  • 4.8Parametrii funcţiei main. Parametrii liniei de comandă
  • 4.9 Alocarea dinamică a memoriei
  • 5Fişiere tip C
  • 5.1Fişiere tip text
  • 5.1.1Funcţii intrare / ieşire cu format
  • 5.1.2Funcţii intrare / ieşire tip caracter
  • 5.2Fişiere text tip şir de caractere
  • 5.3Fişiere binare
  • 6Structuri tip C şi uniuni
  • 6.1Structuri
  • 7Clase
  • 7.1Definirea unei clase
  • 7.1.1Definirea unei clase
  • 7.1.2Pointerul this
  • 7.1.3Spaţii de nume
  • 7.2Constructori şi destructori
  • 7.2.1Constructori
  • 7.2.2Destructori
  • 7.3Funcţii prietene
  • 7.4Determinarea tipului unei expresii
  • 8Siruri tip C++
  • 9Supraîncărcarea operatorilor
  • 9.1Supraîncărcarea operatorilor aritmetici
  • 9.2Supraîncărcarea operatorului de atribuire
  • 9.3Supraîncărcarea operatorilor << şi >>
  • 10Moştenirea
  • 10.1 Pointeri la obiecte. Operatorii new şi delete
  • 10.2 Moştenirea
  • 10.2.1 Definirea unei clase derivate
  • 10.2.2 Specificatorii de acces
  • 10.3 Funcţii virtuale. Polimorfism
  • 10.4 Destructori virtuali
  • 10.5 Date şi funcţii statice
  • 10.5.1 Date statice
  • 10.5.2 Funcţii statice
  • 11Fişiere tip C++
  • 11.1 Fişiere text
  • 11.1.1 Funcţii intrare / ieşire cu format
  • 11.1.2 Funcţii intrare / ieşire tip caracter
  • 11.2 Fişiere binare
  • 11.3 Fişiere text tip string
  • 12Tratarea excepţiilor
  • 12.1 Excepţii
  • 12.2 Excepţii lansate de funcţii
  • 12.3 Excepţii standard
  • 12.4 Excepţii intrare / ieşire
  • 13Aplicaţii
  • 13.1 Funcţii de timp
  • 13.2 Fire de execuţie
  • 13.3 Funcţia system
  • 14Biblioteca de şabloane standard
  • 14.1 Clase generice
  • 14.2 Containere, iteratori şi algoritme generice
  • 14.3 Vectori
  • 14.3.1 Parcurgerea vectorilor cu iteratori
  • 14.3.2 Stergerea şi inserarea de elemente
  • 14.3.3 Sortarea componentelor unui vector
  • 14.3.4 Căutarea unui element într-un vector
  • 14.3.5 Copierea unui container. Iteratori pentru streamuri
  • 14.4 Liste
  • 14.4.1 Parcurgerea listelor
  • 14.4.2 Sortarea listelor
  • 14.4.3 Inversarea ordinii elementelor listelor
  • 14.4.4 Inserarea şi ştergerea elementelor din liste
  • 14.4.5 Copierea listelor
  • 14.5 Parcurgerea şirurilor tip string cu iteratori
  • 14.6 Numere complexe
  • Anexa 1. Reprezentarea informaţiilor
  • A.1 Reprezentarea numerelor întregi în sistemul binar
  • A.2 Deplasarea numerelor binare cu semn
  • A.3 Reprezentarea numerelor reale
  • A.4 Reprezentarea caracterelor
  • Anexa 2. Priorităţile şi asociativitatea operatorilor
  • Anexa 3. Tipul complex în limbajul C

CPP

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

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

iar tipul unsigned short int are domeniul de
valori [0 …2
16
). Tipul wchar_t este tipul unsigned short int predefinit.
• long
1.4 Operatorul typedef
Operatorul typedef permite să definim tipuri de date bazate pe tipuri existente. Forma
instrucţiunii este
typedef tip-existent tip-nou
Exemple. Instructiunea
typedef float REAL32
defineşte tipul REAL32 ce poate fi utilizat în locul tipului float.
In limbajul C nu există tipul predefinit bool. Putem să definim tipul bool folosind
tipul char cu instrucţiunea
typedef char bool
1.5 Constante
Tipurile de constante din limbaj corespund tipurilor de date.
Constante întregi
Constantele întregi sunt numere întregi scrise în bazele 8, 10 sau 16.
• constantele zecimale sunt numere întregi reprezentate în baza 10. Ele nu pot
începe cu cifra 0. Exemple de constante zecimale sunt :
275, -325, +12
• constante hexazecimale sunt numere întregi reprezentate în baza 16. Ele încep
cu 0x sau 0X. Cifrele hexazecimale sunt 0, …, 9 şi A … F sau a … f. Exemple
de constante hexazecimale :
0x1E sau 0x1e sau 0X1e sau 0X1E care reprezintă valoarea zecimală
30
-0x2f sau -0x2F, sau 0x02F reprezintă valoarea zecimală -47.
• constante octale sunt numere întregi reprezentate în baza 8. Ele încep
obligatoriu cu cifra 0. Exemple de constante octale :
016 reprezintă numărul zecimal 14 (să se arate că (14)
10
= (16)
8
)
-014 reprezintă numărul zecimal -12 (să se arate că (-12)
10
= (-14)
8
).
Constante reale
Constantele reale sunt numere reale în simplă sau dublă precizie. Partea subunitară
este separată de cea întreagă prin punct zecimal.
• constante reale în virgulă mobilă scurtă. Exemple :
1.5 -4.23 27.0
Constantele reale pot avea exponent, un număr întreg ce reprezintă puterea lui
zece cu care se înmulţeşte constanta. De exemplu numărul 2.31*10
1
se scrie ca
2.31e1 sau 2.31e+1 sau +2.31E1
Numărul real -41.2* 10
-1
se scrie ca şi constantă reală
-41.2e-1 sau -4.12
7
• constante reale în virgulă mobilă lungă. Se scriu după aceleaşi reguli ca şi
constantele reale în virgulă mobilă scurtă şi sunt urmate de sufixul L sau l. De
exemplu, numărul real 1,5 se scrie ca şi constantă în virgulă mobilă lungă
1.5L sau 15e-1L
Constante de tip caracter
• constante de tip caracter reprezintă un caracter al codului ASCII pe un octet.
Caracterele se scriu între apostrofuri. De exemplu, caracterul ASCII a se scrie
‘a’ sau echivalent, ca şi constante întregi, 0x61 sau 97
Caracterul ASCII A se scrie
‘A’ sau echivalent, 0x41 sau 65.
Caracterul 1 în codul ASCII este
‘1’ sau echivalent, 0x31 sau 49.
Un alt mod de a defini constante tip caracter este de a le scrie sub forma ‘\xhh’
unde h este o cifră hexazecimală. Numărul hexazecimal hh reprezintă
echivalentul hexazecimal al caracterului în codul ASCII. De exemplu,
caracterele ‘a’ şi ‘A’ se reprezintă ca ‘\x61’ şi respective ‘\x41’. Caracterul ‘1’
se reprezintă ca ‘\x31’. Caracterele speciale ale codului ASCII se reprezintă
folosind secvenţa de evitare
‘\n’ CR
‘\r’ LF
‘\t’ tab orizontal
‘\”’ ghilimele
‘\’’ apostrof
‘\\’ backslash
‘\?’ semnul intrebării
‘\0’ null (valoarea 0 pe un octet)
• constante tip şir de caractere. Ele reprezintă un şir de caractere ASCII scris
între ghilimele. De exemplu
“abcd”
Un şir de caractere este reprezentat de codurile ASCII ale caracterelor pe câte
un octet, urmate de un octet ce conţine valoarea 0. Sirul anterior se reprezintă
ca
a b c d \0
Caracterele speciale se reprezintă în interiorul unui şir folosind secvenţa de
evitare. De exemplu
“ab\”c”
“a\\bc”
“abc\n”
Menţionăm diferenţa între constanta tip caracter ‘a’ şi constanta tip şir de
caractere “a”. Constanta tip caracter ‘a’ se reprezintă pe un singur octet,
constanta “a” se reprezintă pe doi octeţi.
8
• constante tip caracter UNICODE corespund tipului wchar_t. Ele se definesc
sub forma L’\xhhhh’ unde h reprezintă o cifră hexazecimală.
Constante booleene
constante booleene sunt true şi false şi se reprezintă prin valorile unu şi respective
zero.
1.6 Constante cu nume
In program putem defini constante cu nume în următoarele feluri
• utilizarea directivei define . Compilatoarele limbajelor C şi C++ au un
preprocessor care modifică programul sursă înainte de compilare.
Preprocesorul citeşte diversele directive şi modifică programul sursă
corespunzător acestora. Directiva define ce defineşte o constantă are forma
# define nume valoare
unde valoare este interpretată de preprocessor ca un şir de caractere. De
exemplu, directiva
# define PI 3.14
are ca efect înlocuirea numelui PI în program cu şirul de caractere 3.14
• enumerări. O enumerare defineşte constante întregi sau un tip întreg şi valorile
asociate acelui tip. Instrucţiunea enum de definire a unor constante întregi are
forma
Instrucţiunea enum de declarare a unui tip întreg şi a valorilor asociate lui are
forma
De exemplu,
enum CLRX {aaa, bbb, ccc};
defineşte tipul CLRX şi trei constante aaa, bbb, ccc aferente acestui tip, prima
are valoarea zero şi fiecare constantă următoare are o valoare mărită cu unu :
aaa = 0
bbb = 1
ccc = 2
9
In program constantele sunt înlocuite cu valorile lor. Valoarea fiecărei
constante poate fi specificată printr-o expresie întreagă constantă. De exemplu
enum cstval {ppp = 2, dd, cc = -1, nn = 2 + 3 * 4};
defineşte tipul cstval şi constantele de acest tip
ppp = 2
dd = 3
cc = -1
nn = 14
Instructiunea
enum BAZA {BIN = 2, HEX = 16, OCT = 8, DEC = 10};
defineşte tipul BAZA şi constantele aferente
BIN = 2
HEX = 16
OCT = 8
DEC = 10
Putem defini variabile corespunzând tipului definit prin enum. Aceste
variabile pot primi ca valori doar constantele definite în instrucţiunea enum.
Fie de exemplu tipul enumx definit mai jos
enum enumx {ena, enb, enc};
Instrucţiunea următoare defineşte variabila z de tipul enumx
enumx z;
Putem atribui o valoare variabilei z astfel
z = ena;
Instrucţiunea enum are şi o formă mai simplă
enum {listă de nume};
care defineşte doar constante şi nu un tip. Orice constantă definită cu
instrucţiunea enum poate fi utilizată ca orice constantă întreagă.
• utilizarea cuvântului cheie const. O asemenea instrucţiune are forma
diagramelor sintactice de mai jos

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

se scrie ca
(a – b) / (a + b)
iar expresia
2
2
2
* *
m
x c x b a
+
+ +
se scrie ca
(a + b * x + c * x * x) / (2 + m*m)
Expresia
b
c
c
b
a
+
se scrie ca
a / (b / c + c / b)
Funcţiile matematice standard uzuale ale limbajelor C şi C++ sunt cele de mai jos
acos cos exp ceil fabs pow
asin sin log floor sqrt
atan tan log10
Funcţia floor(x) calculează valoarea ¸ ]
x
(cel mai mare număr întreg cuprins în x), iar
funcţia ceil(x) calculează valoarea 1
x
(cel mai mic număr întreg mai mare ca x).
Toate funcţiile de mai sus au argumente de tip double şi rezultatul de tip double.
Funcţia pow are prototipul
double pow(double a, double b)
şi calculează expresia a
b
. Apelarea unei funcţii se face scriind numele funcţiei ca
termen într-o expresie urmat în paranteze de parametrii actuali. Exemple de
expresii aritmetice şi scrierea lor sunt prezentate mai jos. Vom presupune că
variabilele din aceste expresii au fost declarate în prealabil de tip double şi au primit
valori.
11
x
x b x a
+
+
75 . 2
) sin( * ) ( cos *
2
(a*cos(x)*cos(x)+b*sin(x))/(2.75+fabs(x))
5
2x x
e e

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

Menţionăm în final că, tipurile char, short int şi variantele lor unsigned char, unsigned
short int şi float sunt doar pentru memorare. Calculele se efectuează doar cu variabile
de tip int, long int şi double.
Valoarea unei expresii poate fi convertită într-un tip diferit dacă este nevoie.
Există două forme ale expresiilor de conversie a tipurilor.
Prima formă a expresiei de conversie de tip este
(tip) expresie
De exemplu, dacă i este o variabilă întreagă cu valoarea 7 şi f este o variabilă de tip
double cu valoarea 3.47
int i = 7 ;
double = 3.47 ;
expresia
(i + f) % 2
nu este corectă deoarece expresia in parantezele rotunde are tipul double. Expresia
(int)(i + f) % 2
are tipul int şi este corectă, rezultatul ei este 5.
In limbajul C++ este posibilă încă o formă de convertire a expresiilor, cu forma
tip(expresie)
De exemplu, expresia anterioară se poate scrie
int(i + f) % 2
Menţionăm că se pot face conversii între toate tipurile standard existente în limbaje.
Conversia de la un tip real la un tip întreg se face prin trunchiere, valoarea 3.71
convertită la int este 3, iar valoarea -3.4 convertită la int este -3.
1.8 Tablouri
Un tablou este o mulţime de elemente de acelaşi tip. Tablourile sunt tipuri structurate
simple. Instrucţiunea de declarare a unui tablou cu o dimensiune este următoarea
tip nume [ număr întreg] ;
unde număr întreg reprezintă numărul de elemente ale tabloului. Elementele tabloului
sunt
nume[0], nume[1], …, nume[număr întreg – 1]
De exemplu instrucţiunea
int a[10];
declară un vector cu zece elemente de tip int. Elementele vectorului sunt
a[0], a[1], …, a[9].
Dimensiunea unui tablou este fixată la declararea sa şi nu se poate schimba.
Un tablou poate fi iniţializat la declararea sa scriind valorile elementelor între acolade,
{}, şi separate de virgule. Exemple de instrucţiuni ce declară tablouri şi le atribuie
valori
double x[3] = {1.32, -2.15, 4.45};
13
char c[4] = {‘a’, ‘b’, ‘c’, ‘x’};
Ultimul tablou este reprezentat în memorie astfel
a b c x
In cazul în care lista de valori este mai scurtă decât numărul de elemente declarate,
ultimele elemente sunt iniţializate cu zero. In cazul iniţializării unui tablou, la
declararea lui putem omite numărul de elemente al tabloului. De exemplu, putem scrie
char x[] = {‘#’, ‘>’, ‘m’};
Compilatorul calculează dimensiunea tabloului din numărul de valori utilizate pentru
iniţializare. Instrucţiunea precedentă este echivalentă cu instrucţiunea
char x[3] = {‘#’, ‘>’, ‘m’};
Menţionăm că un vector de caractere poate fi iniţializat cu o constantă tip şir de
caractere. De exemplu, putem scrie
char s[] = “abc”;
Reamintim că o constantă şir de caractere este terminată printr-un octet 0, deci
vectorul declarat are 4 componente, ‘a’, ‘b’, ‘c’ şi ‘\0’ si este reprezentat în memorie
ca
‘a’ ‘b

‘c’ ‘\0’
Instrucţiunea precedentă este echivalentă cu oricare dintre instrucţiunile următoare:
char s[4] = “abc”;
char s[4] = {‘a’, ‘b’, ‘c’ , ‘\0’};
char s[4] = {0x61, 0x62, 0x63, 0};
char s[4] = {97, 98, 99, 0} ;
Un tablou poate avea oricâte dimensiuni. Diagrama sintactică a declaraţiei unui tablou
este următoarea
De exemplu, instrucţiunea
float b[7][3]
declară o matrice cu şapte linii şi trei coloane. Elementele matricei sunt :
b[0][0] b[0][1] b[0][2]
… … …
b[6][0] b[6][1] b[6][2]
Elementele tablourilor sunt memorate pe linii. Un tablou cu mai multe dimensiuni
poate fi de asemenea iniţializat la declararea sa. Fie de definit matricea m cu elemente
întregi, pozitive
1
]
1

¸


·
3 7 3
5 2 1
m
Instrucţiunea corespunzătoare este
int m[2][3] = {{1, 2, 5},{3, 7, -3}};
La utilizarea într-o expresie, indicii elementelor tablourilor pot fi orice expresii
întregi. Presupunem următoarele declaraţii de tablouri
14
double a[10], b;
int i, j, y[3][4];
Următoarele expresii ce conţin elemente de tablouri :
) sin( ) cos(
*
b b
y b a
ij i

+
(a[i]+b*y[i][j])/(cos(b)-sin(b))
1 ,
) cos(
+
+
j i
b
y e
exp(cos(b)) + y[i][j + 1]
2
3 2 −
+
n jk
b a
a[j][k] + b[2 * n – 3] * b[2 * n – 3]
23 12
* y x
x[1][2] * y[2][3]
La utilizarea elementelor unui tablou, [] este un operator de selecţie, ce are doi
operanzi: un nume de tablou şi un indice. El se aplică asupra unui nume de
tablou şi selectează un element al acelui tablou. De exemplu, a[0] selectează
primul element al tabloului a, iar b[0] selectează prima linie a tabloului b.
Aplicând încă o dată operatorul de selecţie asupra lui b[0], de exemplu b[0][0],
selectează primul element din prima linie a lui b. Operatorul [] este asociativ la
stânga.
In acelaşi mod, operatorul de apelare a unei funcţii (), aplicat asupra unui nume
de funcţie, returnează valoarea calculată de funcţie. De exemplu cos(1.2)
reprezintă aplicarea operatorului () asupra numelui funcţiei cos.
Priorităţile şi asociativitatea operatorilor
Operator Asociativitate
[] () la stânga
+ unar, - unar, (tip) la dreapta
* / % la stânga
+ - la stânga
1.9 Instrucţiunea de atribuire
O operaţie definită pentru toate tipurile fundamentale este atribuirea. Operatorul
de atribuire = atribuie o valoare unei variabile. Forma instrucţiunii de atribuire este
variabilă = expresie;
De exemplu, următoarele instrucţiuni atribuie valori variabilelor x , y şi z .
float x, y, z;
x = -1.34;
y = sin(x) + cos(x * x);
z = (x + y) / (y + cos(x) * cos(x));
Menţionăm că, la atribuire, expresia din partea dreaptă este convertită în tipul
variabilei din stânga. Fie de exemplu instrucţiunile
int a;
double x = 2.7;
a = x;
Variabila întreagă a primeste valoarea 2.
Limbajele C şi C++ au operatori speciali pentru scrierea prescurtată a instrucţiunilor
de atribuire, +=, -=, *=, /= şi %=. Aceşti operatori se definesc astfel:
15
Considerăm o variabilă x şi o expresie e. Instrucţiunea
x op= e;
este echivalentă cu
x = x op e;
De exemplu, instrucţiunea
x = x + cos(y) ;
se scrie prescurtat
x += cos(y) ;
Tabelul următor prezintă aceşti operatori
Forma prescurtată a operatorilor de atribuire
Instrucţiune Forma prescurtată
x = x + e x += e
x = x – e x -= e
x = x * e x *= e
x = x / e x /= e
x = x % e x %= e
Alţi operatori de acest tip vor fi prezentaţi ulterior. Menţionăm în final că, un operator
de atribuire are doi operanzi şi ca rezultat valoarea operandului din stânga. Operanzii
de atribuire sunt asociativi la dreapta (se execută de la dreapta la stânga). Fie de
exemplu instrucţiunea
int x, y, z = 1;
Instrucţiunea
x = y = z;
atribuie variabilelor x şi y valoarea 1. Instrucţiunea
x += y += z;
atribuie variabilei y valoarea 2 şi variabilei z valoarea 3. De ce?
1.10 Prototipuri de funcţii. Biblioteci de prototipuri
Atunci când compilatorul întâlneşte un apel la o funcţie, el trebuie să poată verifica
concordanţa între parametrii actuali şi cei formali şi are nevoie de tipul rezultatului
funcţiei pentru a face conversiile necesare evaluării expresiei. Pentru aceasta este
nevoie de o definiţie a funcţiei în care apar tipurile parametrilor şi tipul rezultatului.
Această definiţie se numeşte şablon sau prototip şi are forma
tip numefuncţie(tip, tip, …, );
De exemplu, funcţia sin are un singur parametru de tip double,iar rezultatul este de tip
double; prototipul funcţiei sin este
double sin(double);
Limbajele C şi C++ au biblioteci standard cu prototipurile funcţiilor limbajului. De
asemenea, utilizatorul poate defini biblioteci cu prototipuri. Toate aceste biblioteci
sunt semnalate compilatorului cu directiva include cu forma
# include nume_bibliotecă
Aceste biblioteci sunt fişiere numite fişiere header sau antet. Directiva include este
diferită în cazul limbajului C de cea din limbajul C++.
In cazul limbajului C fişierele header au extensia h. De exemplu, biblioteca cu
prototipurile funcţiilor intrare/ieşire tip C este stdio.h, biblioteca cu prototipurile
funcţiilor matematice este math.h, biblioteca cu prototipurile funcţiilor de prelucrat
16
şiruri tip C este string.h, etc. In consecinţă, în cazul unui program în limbajul C vom
semnala compilatorului aceste biblioteci cu directivele
# include <stdio.h>
# include <math.h>
In limbajul C++ fişierele header nu au extensie. De exemplu, biblioteca cu
prototipurile funcţiilor intrare/ieşire tip C++ este iostream. La utilizarea în limbajul
C++, bibliotecile specifice limbajului C sunt redefinite ca <cstdio>, <cmath>,
<cstring>, etc. In plus, toate funcţiile standard ale limbajelor C şi C++ sunt grupate
într-un spaţiu de nume denumit std. In consecinţă, putem semnala compilatorului
bibliotecile standard iostream şi math astfel
# include <iostream>
# include <cmath>
using namespace std;
Putem defini propriile biblioteci cu prototipuri pe care să le semnalăm compilatorului
cu directiva include. Numele propriilor biblioteci cu prototipuri sunt scrise între
ghilimele.
1.11 Operaţii de intrare / ieşire
Orice aplicaţie ce rulează pe un calculator are un director curent asociat şi fişiere
standard de intrare şi ieşire. Fişierul standard de intrare este tastatura iar cel de ieşire
este ecranul. In program, orice fişier este asociat unui obiect numit stream. Există
două tipuri de fişiere: text şi binare. Ele vor fi prezentate detaliat într-un capitol
ulterior. Fişierele standard asociate unei aplicaţii sunt de tipul text. Fişierul text este
este compus dintr-un şir de caractere grupate în linii. Liniile constau din zero sau mai
multe caractere urmate de un caracter ‘\n’. Streamul de intrare asociat tastaturii are
denumirea cin, streamul de ieşire asociat ecranului se numeşte cout. Mai există alte
două streamuri cerr şi clog pentru scrierea mesajelor de eroare.
Operatorul de scriere a datelor
Operatorul de scriere a datelor este <<. El inserează date în streamul de ieşire. De
exemplu, secvenţa de instrucţiuni
int i = 123;
cout << “i = ” << i;
afişază pe ecran
i = 123
Operatorul << inserează în stream valoarea expresiei din dreapta sa. De exemplu,
expresia
<< “i= “
inserează în stream şirul de caractere i= , expresia
<< i
inserează în stream valoarea variabilei i, 123, etc.
Pentru a afişa valori pe mai multe linii, trebuie ca după fiecare linie să scriem
caracterul ‘\n’, care este predefinit ca endl. De exemplu, fie instrucţiunile
int j = -7;
double x = 1 + sin(0.2);
Pentru a afişa valorile variabilelor j si x pe două linii, vom scrie
cout << “j = “ << j << endl << “x = “ << x << endl;
sau
cout << “j = “ << j << ‘\n’ << “x = “ << x << ‘\n’;
sau
cout << “j =” << j << “\n” << “x = “ << x << “\n”;
17
sau, echivalent, cu doua instrucţiuni cout
cout << “j =” << j << endl;
cout << “x = “ << x << endl;
Operatorul << poate scrie orice tip predefinit de date: int, float, şiruri de caractere, etc.
Operatorul de citire a datelor
Operatorul de citire dintr-un stream este >>. El extrage date din streamul de intrare şi
le atribuie unor variabile. Operatorul >> este urmat de numele variabilei ce va
memora valoarea citită. De exemplu, secvenţa de instrucţiuni
int a;
cin >> a;
va citi o valoare introdusă de la tastatură şi o va atribui variabilei a (expresia >>a
citeşte o valoare de la tastatură şi o atribuie variabilei a). O instrucţiune cin poate citi
oricâte date din stream. Fie instrucţiunea
int a, b ;
Instrucţiunea
cin >> a >> b;
citeşte două valori de la tastatură şi le atribuie variabilelor a şi b. Ea poate fi scrisă ca
cin >> a;
cin >> b;
Valorile introduse de la tastatură sunt analizate şi atribuite variabilelor de către
instrucţiunea cin. Pentru fiecare variabilă se procedează astfel :
• se citesc şi se ignoră toate caracterele spaţiu, ‘\t’ sau ‘\n’ (acesta din urmă este
generat la apăsarea tastei Return),
• se citesc următoarele caractere corespunzând tipului variabilei sau până la
întâlnirea unui caracter spaţiu, ‘\t’ sau ‘\n’ şi valoarea citită se atribuie
variabilei.
Fie de exemplu valorile 25 şi 17 de citit pentru variabilele de tip întreg a şi b cu una
din instrucţiunile precedente. Ele pot fi introduse de la tastatură separate de un spaţiu
ca
Şirul introdus este analizat astfel :
• se citesc toate caracterele spaţiu, ‘\t’ sau ‘\n’ până la prima cifra întâlnită şi se
ignoră (în acest caz primul caracter este diferit de spaţiu, ‘\t’ sau ‘\n’),
• se citesc apoi toate caracterele până la primul caracter diferit de cifră, deoarece
trebuie citit un număr întreg, şi rezultă numărul 25.
Se procedează la fel pentru al doilea număr :
• se citeşte şi se ignoră spaţiul,
• se citesc cifrele până la primul caracter diferit de cifră şi rezultă numărul 17.
Aceleaşi valori pot fi introduce ca
Fie de iniţializat prin citire o variabilă x tip caracter la valoarea ‘A’. Variabila este
definită cu instrucţiunea
char x;
Instrucţiunea de citire este
cin >> x;
Putem introduce caracterul A astfel:
18
sau
Citirea caracterului se face după regula de mai sus : se citesc şi se ignoră toate
caracterele spaţiu, ‘\t’ sau ‘\n’ şi se citeşte un caracter care este atribuit variabilei x.
Valorile introduse de la tastatură sunt analizate şi atribuite variabilelor de către
instrucţiunea cin doar după introducerea unui caracter ‘\n’.
Reamintim că biblioteca de prototipuri pentru streamurile cin, cout, etc are numele
iostream. Pentru a modifica formatul de scriere sau citire a datelor putem utiliza
manipulatori definiţi în biblioteca iomanip. Pentru scrierea sau citirea unui număr
întreg în instrucţiunile cout sau cin în diferite baze se utilizează manipulatorii
• hex pentru baza 16
• dec pentru baza 10
• oct pentru baza 8
La începerea execuţiei unui program baza 10 este implicită.
Fie următorul exemplu
int a ;
cin >> hex >> a ;
cout << a << endl;
Presupunem introdus de la tastatură şirul de caractere de mai jos
Valoarea atribuită prin citire variabilei a este 27. De ce ?
In următorul exemplu vom citi numărul negativ -44 în baza 16.
int i ;
cin >> hex >> i ;
Numărul poate fi introdus ca în tabelul de mai jos.
Valoarea afişată este 27. Pentru a afişa valoarea variabilei a în baza 16 trebuie să
scriem
cout << hex << a << endl ;
Pentru afişarea bazei se utilizează manipulatorii:
• showbase pentru scrierea bazei
• noshowbase pentru a nu scrie baza
Exemplu. Fie instrucţiunea
int k = 20;
Tabloul următor prezintă exemple de utilizare a manipulatorilor pentru a afişa pe
ecran valoarea variabilei k.
cout << k; 20
cout << hex << k; 14
cout << showbase << hex << k; 0x14
cout << oct << k; 24
cout << showbase << oct << k; 024
In cazul numerelor reale avem manipulatorii:
19
• fixed numărul este scris fără exponent
• scientific numărul este scris cu exponent
Exemplu. Fie instrucţiunea
float x = 122.63;
Tabloul următor prezintă exemple de utilizare a manipulatorilor pentru a afişa pe
ecran valoarea variabilei x.
cout << x; 122.63
cout << fixed << x; 122.63
cout << scientific << x; 1.226300e+002
Funcţiile următoare sunt apelate de operatorul de scriere << :
• setbase(int)
• setw(int)
• setfill(char)
• setprecision(int)
Funcţia setbase(int) indică baza în care va fi afişat un număr întreg, 8, 10 sau 16.
Funcţia setw(int) dă dimensiunea câmpului în caractere pe care este scris un număr.
Valoarea implicită a acestui manipulator este 0. Dacă dimensiunea câmpului nu este
specificată, sau este prea mică, numărul este scris pe câte caractere este necesar.
Funcţia setfill(char) indică un caracter cu care se umplu spaţiile libere ale unui câmp.
Valoarea implicită a acestui caracter este spaţiul. Funcţia setprecision(int) dă numărul
de cifre cu care este scris un număr real.
Exemplu. Fie instrucţiunile
int x = 23;
double pi(3.1459);
Tabloul următor prezintă exemple de utilizare a funcţiilor operatorului <<.
cout << setw(5) << x; 23
cout << x; 23
cout << setbase(16) << x: 17
cout << dec << setw(3) << setfill(‘*’) << x; *23
cout << pi << endl; 3.1459
cout << setprecision(3) << pi << endl; 3.15
cout << setprecision(4) << pi << endl; 3.146
cout << setprecision(6) << pi << endl; 3.1459
Exemplu. Fie instrucţiunea
float z = 12.64;
Tabelul următor prezintă exemple de utilizare a funcţiilor anterioare la afişarea
variabilei z cu instrucţiunea cout.
cout << setprecision(3) << z; 12.6
cout << setw(8) << z; 12.64
cout << setw(8) << setfill(‘*’) << z; ***12.64
cout << setw(15) << scientific << z; 1.264000e+001
Manipulatorii următori specifică modul de cadrare al valorilor scrise :
• left - cadrare la stânga
20
• right – cadrare la dreapta
Menţionăm că dimensiunea câmpului prescrisă de funcţia setw(int) se aplică
doar următorului număr de scris. Ceilalţi manipulatori rămân la valoarea
prescrisă până sunt modificaţi.
Exemplu. Fie instrucţiunile
int x ;
cin >> x ;
cout << hex << x << endl ;
cout << x << endl ;
Valoarea variabilei x este afişată în baza 16 cu ambele instrucţiuni, deoarece
manipulatorul de scriere a bazei are valoarea hex. Pentru a afişa din nou variabilele
întregi în baza 10 trebuie să scriem o instrucţiune
cout << dec << x << endl ;
1.12 Funcţia main
Orice program scris în limbajele C sau C++ se compune din funcţii care se apelează
unele pe altele. Definiţia unei funcţii este
tip numefunctie (lista de parametri)
{
instrucţiuni
}
Una din funcţii are numele main iar execuţia programului începe cu această funcţie.
Prototipul acestei funcţii este
int main();
Semnificaţia acestui prototip este următoarea. Funcţia main are ca rezultat o valoare
întreagă şi nu are parametri. Corpul funcţiei este o instrucţiune compusă, adică o
secvenţă de instrucţiuni scrise între acolade, { şi }. Corpul funcţiei conţine şi o
instrucţiune return ce termină execuţia funcţiei şi transmite în programul appelant
valoarea calculată de funcţie.
Programul poate conţine comentarii. Comentariile plasate între delimitatorii /* şi */ se
pot întinde pe mai multe linii. Comentariile ce încep cu caracterele // se întind pe o
singură linie.
Putem scrie acum primul program care citeşte o valoare întreagă de la tastatură şi
afişează pătratul ei.
// program care se citeste o valoare intreaga si calculeaza patratul ei
# include <iostream>
using namespace std;
int main()
{
int i, j;
cout << “introduceti o valoare intreaga” << endl;
/* se citeste o valoare intreaga */
cin >> i;
cout << “valoarea introdusa este “ << i << endl;
/* se calculeaza patratul valorii citite */
j = i * i;
/* se afisaza valoarea calculata */
cout << “patratul valorii este “ << j << endl;
return 0;
21
}
Menţionăm că funcţia main() returnează valoarea 0 cu instrucţiunea
return 0;
Limbajul C++ defineşte constanta EXIT_SUCCESS ce are valoarea zero. In
consecinţă, putem scrie
return EXIT_SUCCESS;
Rezultatul rulării programului este cel de mai jos.
Pentru claritatea programelor, limbaajul defineşte şi constanta EXIT_FAILURE ce
are valoarea unu.
1.13 Execuţia unui program
Reamintim etapele de execuţie a unui program.
• Prima etapă este compilarea programului. In această etapă programul este
verificat pentru erori sintactice. Dacă programul nu conţine erori sintactice
compilatorul generează un program obiect traducând fiecare instrucţiune a
programului într-o serie de instrucţiuni elementare ale calculatorului. Fiecare
fişier sursă este compilat separat. Din fiecare fişier sursă rezultă un fişier
obiect.
• Etapa a doua este editarea legăturilor. In această etapă sunt ataşate
programului funcţiile din biblioteci. Atunci când compilatorul întâlneşte un
apel de funcţie (de exemplu sin, cos, etc.), sau o operaţie intrare/ieşire, el
generează doar o secvenţă de apel la funcţie. Funcţiile respective sunt
precompilate în biblioteci speciale şi programul editor de legături ataşează
aceste funcţii programului obiect. Editorul de legături generează un program
executabil din toate fişierele obiect. El este un fişier cu extensia exe.
• In etapa a treia programul excutabil este încărcat în memorie şi executat.
1.14 Operatorul sizeof
Operatorul sizeof se aplică asupra unei expresii sau asupra unui tip şi are ca rezultat
numărul de octeţi de memorie utilizaţi. Există două forme ale acestui operator
sizeof (tip)
sizeof (expresie)
De exemplu, expresia
sizeof(int)
are ca rezultat valoarea 4, expresia
sizeof (char)
are valoarea 1, etc.
In cazul unui tablou rezultatul este numărul total de octeţi ocupat de tablou. Fie
instrucţiunea
double a[5];
Expresia
22
sizeof(a)
are valoarea 40 iar expresia
sizeof(a) / sizeof(a[0])
dă numărul elementelor tabloului a.
In cazul cand operandul este o expresie, operatorul sizeof dă numărul de octeţi ocupat
de rezultatul expresiei. Fie de exemplu instrucţiunile
int x;
double m;
Expresia
sizeof(x + m)
are valoarea 8, deoarece tipul expresiei este double.
Exemplu. Vom scrie un program care să afişeze numărul de octeţi utilizaţi pentru
memorarea tipurilor fundamentale. Vrem ca rezultatele să fie afişate pe ecran astfel:
Numarul de octeti utilizati
int : ....
char : ....
float : ....
double : ….
vectorul float a[10] : ….
expresia a[0]+b : ....
Pentru a afişa datele deplasate la stânga cu un număr de spaţii vom scrie un caracter
tab, ‘\t’. Programul este următorul :
// dimensiunea tipurilor standard
# include <iostream>
using namespace std;
int main()
{
float a[10], b;
cout << “Numarul de octeti utilizati:” << endl;
// scrie dimensiunea tipurilor standard
cout << ”\tint: “ << sizeof(int) << endl;
cout << “\tchar: “ << sizeof(char) << endl;
cout << “\tfloat: “ << sizeof(float) << endl;
cout << “\tdouble: “ << sizeof(double) << endl;
// scrie dimensiunea unui vector
cout << “\tvectorul float a[10]: “ << sizeof(a) << endl;
// scrie dimensiunea rezultatului unei expresii
cout << “\texpresia a[0]+b: ” << sizeof(a[0]+ b) << ‘\n’;
}
Rezultatul rulării programului este prezentat mai jos.
23
Menţionăm că este echivalent dacă în instrucţiunea cout utilizăm caracterul ‘\n’, sau
endl, sau şirul de caractere “\n”. Şirului de caractere “\tint: “ duce la scrierea
caracterului ‘\t’ şi a şirului ”int: “. Instrucţiunea
cout << ”\tint: “ << sizeof(int) << endl;
putea fi scrisă ca
cout << ‘\t’ << ”int: “ << sizeof(int) << endl;
1.15 Operatorii ++ şi - -
Operatorul ++ incrementează o variabilă întreagă cu valoarea unu, operatorul - -
decrementează o variabilă întreagă cu unu. Aceşti operatori pot fi :
• prefix
++x, --x
• postfix
x++, x- -
Expresiile ++x şi x++ reprezintă instrucţiunea
x = x + 1
iar expresiile - -x şi x- - reprezintă instrucţiunea
x = x – 1
Cazul operatorilor prefix, ++x, --x
O expresie de forma
f(++x);
este scrierea prescurtată a expresiilor:
x = x + 1;
f(x);
In acelaşi fel, expresia
f(--x);
reprezintă scrierea prescurtată a expresiilor:
x = x – 1;
f(x);
Modul de execuţie a expresiei este următorul:
1. se incrementează sau decrementează valoarea variabilei x cu unu,
2. valoarea incrementată sau decrementată se utilizează mai departe în calcule.
Exemplu. Fie declaraţia de variabile
int i = 1, x;
Instrucţiunea
x = ++i;
reprezintă scrierea prescurtată a secvenţei de instrucţiuni
i = i + 1;
x = i;
După execuţia instrucţiunii x = ++i variabilele au valorile i = 2 şi x = 2.
Exemplu. Fie declaraţia de variabile
int j = 3, k;
Instrucţiunea
k = --j;
reprezintă scrierea prescurtată a secvenţei de instrucţiuni
j = j – 1;
k = j;
După execuţia instrucţiunii variabilele au valorile j = 2 şi k = 2.
Exemplu. Fie secventa de instrucţiuni
24
int i = 1;
cout << “i = “ << i << endl;
cout << “i = “ << ++i << endl;
cout << “i = “ << i << endl;
Valorile afişate vor fi
i = 1
i = 2
i = 2
deoarece a doua instructiune cout este echivalentă cu instrucţiunile
cout << “i = “;
i = i + 1;
cout << i << endl;
Exemplu. Fie următoarele declaraţii.
double a[4] = {1.2, -5, 8, 3};
double r;
int i = 1, k = 1;
Secvenţa de instrucţiuni
i = i +1;
r = a[i];
se poate scrie
r = a[++i];
Expresia ++i este evaluată la 2, iar r primeşte valoarea a[2] = 8.
Secvenţa de instrucţiuni
k = k – 1;
r = a[k];
se poate scrie
r = a[--k];
Expresia --k este evaluată la 0 iar r primeşte valoarea a[0] = 1.2
Cazul operatorilor postfix, x++, x--
O expresia de forma
f(x++);
reprezintă scrierea prescurtată a expresiilor
f(x);
x = x + 1;
In acelaşi mod, expresia
f(x--);
reprezintă scrierea prescurtată a expresiilor:
f(x);
x = x – 1;
Modul de execuţie a expresiei este următorul :
1. se utilizează în calcule valoarea variabilei x (neincrementate sau
nedecrementate),
2. se incrementează / decrementează apoi variabila x.
Exemplu. Fie cele două secvenţe de instrucţiuni de mai jos :
int a, b;
b = 3;
a = b++;
Expresia
a = b++;
corespunde secvenţei de instrucţiuni :
25
a = b;
b = b + 1;
In consecinţă, expresia b++ are valoarea 3 (valoarea neincrementată a variabilei) şi
apoi se incrementează b. Avem rezultatul
a = 3
b = 4
Exemplu. Fie instructiunile :
int j = 1;
cout << “j = “ << j << endl;
cout << “j = “ << j++ << endl;
cout << “j = “ << j << endl;
Valorile afişate vor fi :
j = 1
j = 1
j = 2
deoarece a doua instrucţiune cout este echivalentă cu instrucţiunile :
cout << “j = “ << j;
j = j + 1;
cout << endl;
Exemplu. Fie secvenţa de instrucţiuni de mai jos :
int i;
double x[4] = {2.4e1, 14.4, -3.1 0};
double d;
i = 1;
d = x[i++];
Instrucţiunea
d = x[i++];
corespunde secvenţei de instrucţiuni :
d = x[i];
i = i + 1;
Expresia i++ are valoarea 1 şi apoi se incrementează i. Avem deci
d = 14.4
şi
i = 2
Fie acum secvenţa de instrucţiuni următoare :
int j;
double x[4] = {2.4e1, 14.4, -3.1 0};
double d;
j = 1;
d = x[++j];
Instrucţiunea
d = x[++j];
corespunde secvenţei de instrucţiuni :
j = j + 1;
d = x[j];
In consecinţă expresia ++j are valoarea 2 şi avem
d = -3.1
şi
j = 2
26
1.16 Operaţii cu numere întregi la nivel de bit
1.16.1 Operatori de deplasare
Deplasarea la stânga a unui întreg cu un bit reprezintă înmulţirea acelui număr cu doi.
Deplasarea la dreapta a unui întreg cu un bit reprezintă câtul împărţirii acelui număr
cu doi. De exemplu, prin deplasarea numărului 7 la dreapta cu un bit se obţine
rezultatul 3. Operatorii de deplasare a unui număr întreg cu un număr de biţi sunt <<
pentru deplasare la stânga şi >> pentru deplasare la dreapta. Expresia de deplasare a
unui număr întreg are forma
Rezultatul expresiei din stânga este numărul întreg ce va fi deplasat. Expresia din
dreapta dă numărul de biţi cu care se face deplasarea. Deplasarea se face după regulile
deplasării numerelor binare cu semn, vezi anexa. (La deplasarea la dreapta se propagă
bitul de semn, la deplasarea la stânga se adaugă zerouri). Dacă expresia de deplasat
este de tipul unsigned biţii adăugaţi sunt zerouri. Operatorii de deplasare, << şi >>
sunt operatori aritmetici.
Exemple. Fie instrucţiunea
int x = 7 >> 1;
Variabila x primeşte valoarea 3 (câtul înpărţirii lui 7 la 2 este 3).
Fie instrucţiunile de mai jos
int a = 0xff; int y = 0xff;
int b; int c;
b = a << 4; c = y >> 4;
Numărul a, reprezentat pe patru octeţi, are valoarea hexazecimală
a = 000000ff
Numărul a deplasat la stânga cu 4 biţi este
00000ff0
Numărul y deplasat la dreapta cu 4 biţi este
0000000f
Numărul 0xff convertit în zecimal este 255. Care este valoarea numerelor 0xf şi 0xff0
în zecimal?
Fie două variabile întregi, i si j şi secvenţa de instrucţiuni :
i = 7;
j = i >> 1;
Variabila j va primi valoarea 3. Reamintim că 7 / 2 = 3.
Ca un alt exemplu, să definim constantele X, Y, Z şi R care să aibe valorile 1, 2, 4, 8,
folosind instrucţiunea enum.
enum {X = 1, Y = X << 1, Z = Y << 1, R = X << 3};
Limbajele C şi C++ au operatorii de atribuire <<= şi >>= pentru scrierea prescurtată a
instrucţ iunilor ce conţin operatorii << şi >>. Instrucţiunea
x = x << n ;
se scrie prescurtat
x <<= n ;
iar instrucţiunea
x = x >> n ;
se scrie prescurtat
27
x >>= n ;
Vom încheia acest paragraf cu un program care deplasează numere întregi şi afişază
valoarea lor în zecimal şi hexazecimal. Reamintim că, pentru scrierea sau citirea unui
număr întreg în instrucţiunile cout sau cin în diferite baze, se utilizează manipulatorii :
• hex pentru baza 16
• dec pentru baza 10
• oct pentru baza 8
La începerea execuţiei unui program baza 10 este implicită.
Vom exemplifica utilizarea operatorilor de deplasare deplasând două variabile întregi
la stânga şi la dreapta cu un bit şi apoi cu trei biţi şi vom afişa rezultatele în bazele 10
şi 16.
# include <iostream>
using namespace std;
/* uitlizarea operatorilor de deplasare */
int main()
{
int x = 10;
cout << “x = “ << dec << x << “ “ << hex << showbase << x << endl;
// deplasează variabila a la stanga cu un bit
x = x << 1;
cout << “x deplasat la stanga cu 1 bit = “
<< dec << x << “ “ << hex << showbase << x << endl;
int b = 50;
cout << “b = “ << dec << b << “ “ << hex << showbase << b << endl;
// deplasează variabila b la dreapta cu 3 pozitii
b = b >> 3;
cout << “b deplasat la dreapta cu 3 biti = “
<< dec << b << “ “ << hex << showbase << b << endl;
return 0;
}
Rezultatele rulării programului sunt prezentate în figura de mai jos.
1.16.2 Operaţii logice la nivel de bit
Limbajele C şi C++ au următorii operatori pentru operaţii logice la nivel de bit
şi logic (and) notat &
sau logic (or) notat |
sau excusiv (xor) notat ^
complement faţă de unu notat ~
Aceşti operatori se definesc cu tabelele de adevăr următoare
28
a b a&b a|b a^b
0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0
a ~a
0 1
1 0

Operatorii &, | ^ sunt operatori binari. Expresiile cu aceşti operatori logici au forma
următoare
Operaţiile sunt aplicate asupra fiecărei perechi de biţi din cei doi operanzi. Operatorul
~ este operator unar. Expresiile cu acest operator au forma următoare
Operatorul ~ complementează fiecare bit din expresia întreagă. Operatorii &, | şi ~
sunt operatori aritmetici.
Exemple. Fie următoarea secvenţă de program :
int a, b, c, d, e;
a = 0xf000;
b = 0xabcd;
c = a & b;
d = a | b;
e = a ^ b;
Rezultatele sunt
c = 0xa000
d = 0xfbcd
e = 0x5bcd
In primul caz, la calculul valorii c = a & b, vom exemplifica doar calculul expresiei
0xf & 0xa = 0xa
Rezultatul se obţine conform calculului de mai jos.
0 1 0 1
0 1 0 1
& 1 1 1 1
Pentru calculul valorii e = a ^ b vom calcula expresia
0xf ^ 0xa = 0x5
Rezultatul se obţine conform calculului de mai jos
1 0 1 0
0 1 0 1
^ 1 1 1 1
Pentru calculul valorii d = a | b vom calcula expresia
0xf | 0xa = 0xf
29
Rezultatul se obţine conform calculului de mai jos
1 1 1 1 |
1 0 1 0
------------------
1 1 1 1
Expresia ~a are valoarea 0x0fff. De ce?
Limbajele C şi C++ au operatorii &=, |= şi ^= pentru scrierea prescurtată a
instrucţiunilor de atribuire ce conţin operatori &, | şi ^. De exemplu expresia
x = x & a ;
Se scrie prescurtat
x &= a ;
Pentru a selecta sau modifica anumiţi biţi dintr-un număr întreg se defineşte un şir de
biţi numit mască ce conţine 1 pe poziţiile biţilor ce trebuie selectaţi sau modificaţi.
• Pentru selectarea biţilor se efectuează operaţia & între numărul iniţial şi
mască. De exemplu, pentru a selecta biţii unei cifre hexazecimale vom utiliza
ca mască valorile 0x1, 0x2, 0x4 şi 0x8. Pentru a selecta cifrele octale dintr-un
număr întreg vom utiliza ca mască valorile 07, 070, 0700, etc. Pentru a selecta
cifrele hexazecimale dintr-un număr întreg vom utiliza ca mască valorile 0xf,
0xf0, 0xf00, etc.
• Pentru inversarea biţilor se efectuează operaţia ^ între număr şi mască.
• Pentru a pune biţii din număr la valoarea 1 se execută operaţia | între număr şi
mască.
• Pentru a pune biţii din număr la valoarea 0 se execuţă operaţia & între număr
şi ~mască.
Exemplu. Fie variabila întreagă a = 0x6dbc. Vrem ca în variabila întreagă b să
selectăm ultimul octet, apoi ultimii 10 biţi şi în final primii 4 biţi ai variabilei a.
Inversarea unor biţi din variabila a este prezentată în finalul exemplului.
// operatii logice la nivel de bit
# include <iostream>
using namespace std;
int main()
{
int a = 0x6dbc, b;
cout << “a = “ << hex << showbase << a << endl;
// selecteaza ultimul octet din a
b = a & 0xff;
cout << “ultimul octet din a = “ << hex << showbase << b << endl;
b = a & 0x3ff;
// selecteaza ultimii 10 biti din a
cout << “ultimii 10 biti din a = “ << hex << showbase << b << endl;
// selectează primii patru biti din a
b = a & 0xf000;
cout << “primii 4 biti din a = “ << hex << showbase << b << endl;
// pastram primii opt biti si inversam ultimii opt biti din a
b = a ^ 0x00ff;
cout << “ultimii 8 biti inversati din a = “ << hex << showbase << b << endl;
return 0;
}
30
Rezultatul rulării programului este cel de mai jos.
Să se explice de ce, în cazul al doilea, masca este 0x3ff;
31
2 Structuri de control fundamentale
2.1 Algoritme
Programele reprezintă formulări concrete ale unor algoritme ce prelucrează structuri
de date. Un algoritm este format dintr-un şir finit de acţiuni bine definite şi neambigue
pentru rezolvarea unei probleme. Fiecare acţiune trebuie să poată fi executată într-un
interval finit de timp.
Orice algoritm se poate descompune într-un număr finit de etape. El poate fi
reprezentat printr-un şir de acţiuni astfel încât efectul general al acestor acţiuni să
conducă la rezultatul dorit al calculului.
In cazul cel mai simplu un algoritm poate fi descompus într-un număr de acţiuni
secvenţiale care se reprezintă în felul următor
s1; s2; … sn;
Pentru executarea unei acţiuni în funcţie de îndeplinirea unei condiţii avem operatori
condiţionali
• Operatorul if cu formele
if (condiţie)
s1;
sau
if (condiţie)
s1;
else
s2;
condiţie este o expresie booleană care are valoarea advărat sau fals. Acţiunea
s1 se execută când condiţie are valoarea adevărat.
• Operatorul switch este generalizarea operatorului if
switch(i)
i = 1: s1;
i = 2: s2;
………..
i = n: sn;
In funcţie de valoarea lui i se execută una dintre acţiunile s1, s2, …, sn.
Aceşti operatori se caracterizează prin faptul că au o singură intrare şi o singură ieşire.
Fiecare operator este interpretat în şirul de calcule ca o singură acţiune, indiferent de
conţinutul său. Operatorii anteriori sunt suficienţi pentru a descrie clasele de calcule
ce se pot descompune într-un număr cunoscut de acţiuni.
Pentru a descrie calculele repetate când numărul de acţiuni nu este cunoscut există
operatorii while şi do.
• Operatorul while are forma
while (condiţie)
s;
Acţiunea s se execută atâta timp cât expresia booleană condiţie are valoarea
adevărat.
• Operatorul do are forma
do
s;
while (condiţie)
In acest caz acţiunea s se execută atâta timp cât condiţie are valoarea adevărat.
32
Menţionăm că în cazul operatorului do acţiunea s se execută cel puţin o dată,
în timp ce pentru operatorul while este posibil ca acţiunea s să nu se execute
niciodată.
• Operatorul for se utilizează atunci când numărul de execuţii ale unei acţiuni
este dinainte cunoscut. Operatorul are o variabilă de control ce se poate
modifica la fiecare iteraţie. Forma acestui operator este
for (i = instrucţiune1; condiţie; instrucţiune2)
s;
Operatorul include o instrucţiune1 ce specifică valoarea iniţială a variabilei
de control, şi o instrucţiune2 ce poate modifica valoarea variabilei de control
după fiecare iteraţie. Acţiunea s se execută atât timp cât expresia booleană
condiţie are valoarea adevărat. Operatorul for este echivalent cu următoarele
instrucţiuni
instrucţiune1;
while(condiţie)
s;
instrucţiune2;
Menţionăm că testul condiţiei se face înainte de execuţia acţiunii s.
In toţi operatorii de mai sus, acţiunea este o instrucţiune simplă sau o
instrucţiune compusă (un bloc). In program, o instrucţiune compusă (un bloc),
este un grup de instrucţiuni inclus între acolade, { şi }. Intr-un bloc putem defini
variabile locale, al căror domeniu de existenţă este limitat la acel bloc.
Variabilele locale sunt create la intrarea în bloc şi sunt şterse la ieşirea din bloc.
Operatorii prezentaţi se numesc structuri de control fundamentale. In construcţia unui
program structurile de control şi structurile de date sunt inseparabile. Structurile
fundamentale de date sunt
• structuri simple: numere întregi, reale, caractere.
• structuri complexe: tablouri, fişiere.
Tablourile au un număr cunoscut de elemente. Ele se prelucrează de regulă cu
operatorul for. Fişierele secvenţiale au un număr de elemente necunoscut în avans. Ele
se prelucrează de regulă cu operatorul while.
Exemplu. Algoritmul de calcul pentru n! care este definit ca

·
·
n
i
i n
1
!
s = 1;
for i = 1; i <= n; i = i + 1
s = s * i;
2.2 Expresii relaţionale
O operaţie definită pentru tipurile de date fundamentale este compararea.
Operatorii relaţionali ai limbajelor C şi C++ sunt, în ordinea priorităţilor
<, <=, >, >=
= =, !=
O expresie relaţională are următoarea formă
33
Rezultatul evaluării unei expresii relaţionale este fals sau adevărat (false sau true).
Priorităţile operatorilor aritmetici sunt mai mari decât ale operatorilor relaţionali.
Exemple. Fie următoarele instrucţiuni de atribuire
int a = 2, b = 3, c = 6;
In tabela de mai jos sunt prezentate exemple de expresii relaţionale şi rezultatul
evaluării lor.
Expresie relaţională Valoare
a*b >= c true
b+2 > a *c false
a+b = = 3 false
a != b true
b/a = = 1 true
Reamintim că în limbajul C++ valoarea true este reprezentată prin 1 iar valoarea false
prin 0. La scrierea valorii unei variabile booleene valoarea afişată este 0 sau 1, după
cum variabila are valoarea false, respectiv true. Pentru afişarea valorii booleene ca
true, respectiv false se utilizează manipulatorul boolalpha.
Fie următoarea instrucţiune de declarare a unei variabile de tip bool
bool r;
Putem avea următoarele instrucţiuni de atribuire
r = a + b = = c;
r = a – b >= 2;
r = a * a != -b;
Variabila r poate avea valorile true sau false.
Un exemplu de program este cel de mai jos.
#include <iostream>
using namespace std;
int main()
{
int a = 3, b = -2, c = 5;
bool r;
cout << " a = " << a << " b = " << b << " c = " << c << endl;
r = a + b == c;
cout << "a + b == c " << boolalpha << r << endl;
r = a - b >= 2;
cout << "a - b >= 2 " << boolalpha << r << endl;
r = a * a != - b;
cout << "a * a != - b " << boolalpha << r << endl;
return EXIT_SUCCESS;
}
34
Rezultatul rulării programului este prezentat în continuare.
In final reamintim că operatorul = este operator de atribuire, iar operatorul = = este
operator de comparare. Instrucţiunea
a = b
atribuie variabilei a valoarea variabilei b, iar expresia
a = = b
este o expresia relaţională care are ca rezultat valoarea true sau valoarea false.
2.3 Expresii booleene
Operatorii booleeni ai limbajelor C şi C++ sunt, în ordinea priorităţilor
!
&&
| |
care reprezintă operatorii nu (not), şi (and), respectiv sau (or). Operatorul nu este
operator unar. Aceşti operatori se definesc folosind tabelele de adevăr.
x y x && y x | | y
false false false false
false true false true
true false false true
true true false true
x !x
false true
true false
Rezultatul evaluării unei expresii booleene este true sau false. In cursurile de logică
operatorii booleeni se notează astfel:
nu (not) ¬
şi (and) ˄
sau (or) ˅
Exemple de expresii booleene şi scrierea lor sunt prezentate mai jos :
) || &( & ) (
||! !
|| & &
) || ( ! ) (
& &
c b a c b a
b a b a
c b a c b a
b a b a
b a b a
∨ ∧
¬ ∨ ¬
∨ ∧
∨ ¬

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

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

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

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

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

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

·


·
n
i
i
m x
n
d
1
2
) (
1
1
Tipul funcţiei va fi void. Media şi dispersia vor fi asociate unor parametri de ieşire ai
funcţiei. Prototipul funcţiei va fi
void md(float x[], int n, float& m, float& d);
unde x este vectorul de numere de dimensiune n pentru care calculăm media m şi
dispersia d. Deoarece valorile calculate, m şi d sunt asociate unor parametri, funcţia
are tipul void.
#include <iostream>
using namespace std;
/*
Calculul mediei si dispersiei componentelor unui vector
void md(float x[], int n, float& m, float& d);
Parametri de intrare
x – vector
n – dimensiunea vectorului x
Parametri de iesire
m – media componentelor lui x
d – dispersia componentelor lui x
Preconditii
Parametrii de intrare sunt initializati
*/
void md(float x[], int n, float& m, float& d)
{
float s = 0;
int i;
// calculeaza media
for(i = 0; i < n; i++)
s = s + x[i];
m = s / n;
// calculeaza dispersia
d = 0;
for(i = 0; i < n; i++)
d = d + (x[i] – m) * (x[i] – m);
d = d / (n – 1);
return;
}
Un program ce testează funcţia de mai sus este următorul.
int main()
{
float a[4] = {1.2e-1, -2.34, 1.5, 3.33};
70
float media, dispersia;
md(a, 4, media, dispersia);
cout << “ media = “ << media << “ dispersia = “ << dispersia << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
3.8 Recursivitatea
Funcţiile limbajelor C şi C++ sunt recursive, adică o funcţie se poate apela pe ea
însăşi. Vom exemplifica acest lucru cu o funcţie care să calculeze valoarea n! în
variantă recursivă şi nerecursivă.
Varianta nerecursivă Varianta recursivă

·
·
n
i
i n
1
!
¹
'
¹
> −
·
·
1 )! 1 ( *
1 1
!
n n n
n
n
// calculul factorialului
int fact(int n)
{
int s = 1;
for(int i = 1; i <= n; ++n)
s = s * i;
return s;
}
// calculul factorialului
int fact(int n)
{
int s;
if(n = = 1)
s = 1;
else
s = n * fact(n – 1);
return s;
}
Vom prezenta stiva la apelarea variantei recursive pentru n = 3. Reamintim că
parametrul n şi variabila locală s sunt memoraţi în stivă. După apelul
fact(3)
stiva este
n=3 s=?
Deoarece n este diferit de unu, se execută instrucţiunea
s = n * fact(n – 1)
adică se apelează încă o dată funcţia, fact(2). Stiva de
n=3 s=? n=2 s=?
Se apelează încă o dată funcţia, fact(1), şi stiva devine
n=3 s=? n=2 s=? n=1 s=1
71
După această ultimă apelare se ajunge la instrucţiunea return, după care variabilele
corespunzând ultimei apelări a funcţiei se sterg din stivă. Stiva devine
n=3 s=? n=2 s=2
Din nou se ajunge la instrucţiunea return, variabilele corespunzând acestei apelări se
strerg din stivă
n=3 s=6

După care se obţine rezultatul final, valoarea 6.
3.9 Funcţii generice
In limbajul C++ putem să definim funcţii generice în care putem defini tipuri generale
pentru parametri şi valoarea returnată de funcţie. Definirea unei funcţii generice se
face cu instrucţiunea
template <typename identificator, typename identificator … >
declaraţie de funcţie
Să definim de exemplu o funcţie generică pentru calculul maximului a două variabile
/* functie generica ce calculeaza maximul a doua variabile */
template <typename T>
T maxval(T a, T b)
{
return (a > b ? a : b);
}
Apelarea unei funcţii generice se face astfel
nume_funcţie <tip, tip, …>(parametri);
De exemplu, putem calcula maximul a două numere întregi sau reale astfel.
int main()
{
// maximul a doua numere intregi
int a = 15, x = 20, c;
c = maxval<int>(a, x);
cout << “ maximul dintre “ << a << “ si “ << x << “ este “ << c <<
endl;
// maximul a doua numere reale
float fa = 3.43, fb = -9.3;
cout << “maximul dintre “ << fa << “ si “ << fb << “ este “
<< maxval<float>(fa, fb) << endl;
return;
}
Rezultatul rulării programului este cel de mai jos.
72
Exerciţiu. Să se definească o clasă generică ce permută două variabile de acelaşi tip.

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

z = y;

y[0] = x[0];
y[1] = x[1];
int i, j;
// afisaza adresele liniilor matricei x
cout << "adresele liniilor matricei" << endl;
for(i = 0; i < 2; i++)
{
cout << "adresa lui x[" << i << "] : " << z[i] << endl;
}
// afisaza adresele liniilor matricei x
cout << "adresele liniilor matricei" << endl;
for(i = 0; i < 2; i++)
{
cout << "adresa lui x[" << i << "] : " << *(z + i) << endl;
}
// afisaza adresele elementelor matricei x
cout << "adresele elementelor matricei" << endl;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
cout << "adresa lui x[" << i << "][" << j << "] : "
<< z[i] + j << endl;
}
// afisaza adresele elementelor matricei x
cout << "adresele elementelor matricei" << endl;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
cout << "adresa lui x[" << i << "][" << j << "] : "
<< *(z + i) + j << endl;
}
// afisaza elementele matricei x pe linii
cout << "elementele matricei" << endl;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
cout << setw(3) << z[i][j] << " ";
cout << endl;
}
// afisaza elementele matricei x pe linii
cout << "elementele matricei" << endl;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
101
cout << setw(3) << *(*(z + i) + j) << " ";
cout << endl;
}
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Exemplu. Vom aloca memorie pentru o matrice cu elemente întregi cu nlin linii şi
ncol coloane, unde nlin şi ncol se vor citi de la tastatură. Alocarea se face astfel. După
cum am spus mai sus, o matrice este compusă din vectori corespunzători liniilor.
Fiecare linie a matricei este un vector cu componente de tip int, deci tipul vectorului
liniei este int*. Se alocă mai întâi un vector de dimensiune nlin ce va conţine elemente
de tip int*. Variabila z ce conţine adresa acestui vector are tipul int **. Apoi, fiecare
element al acestui vector, primeşte ca valoare adresa unui vector de ncol întregi. In
final se atribuie valori componentelor matricei
z[i][j] = i * j ;
Programul este următorul
# include <iostream>
# include <cstdlib>
using namespace std;
int main()
{
int i, j;
int nlin, ncol;
cout << "introduceti # de coloane "; cin >> ncol;
cout << "introduceti # de linii "; cin >> nlin;
// aloca un vector de dimensiune nlin de pointeri de tip int *
102
int ** z = (int **) malloc(nlin * sizeof(int *));
// aloca pentru fiecare linie un vector de ncol intregi
for(i = 0; i < nlin; i++)
z[i] = (int*) malloc(ncol * sizeof(int));
// atribuie valori componentelor matricei
for(i = 0; i < nlin; i++)
for(j = 0; j < ncol; j++)
z[i][j] = i * j;
for(i = 0; i < nlin; i++)
{
for(j = 0; j < ncol; j++)
cout << z[i][j] << '\t';
cout << endl;
}
return 0;
}
Prima funcţie malloc() alocă memorie pentru un vector cu nlin elemente de tipul int*.
A doua funcţie malloc() alocă memorie pentru un vector cu ncol elemente de tipul int.
Rezultatul rulării programului este arătat mai jos.
Alocarea dinamică a memoriei cu operatorul new
Limbajul C++ permite alocarea memoriei necesară unei variabile cu operatorul new
cu formele:
• pentru alocarea de memorie pentru un scalar
new tip
• pentru alocarea de memorie pentru un vector cu un număr de componente dat
de expresie întreagă
new tip[expresie întreagă]
De exemplu, următoarele instrucţiuni alocă memorie pentru o variabilă scalară
double * ptr;
ptr = new double;
*ptr = 1.7;
Operatorul new alocă memoria corespunzătoare tipului de variabilă şi are ca rezultat
adresa zonei de memorie alocată. Primele două instrucţiuni puteau fi scrise
double *ptr = new double;
Memoria alocată cu operatorul new poate fi eliberată cu operatorul delete cu formele
• pentru eliberarea memoriei allocate unei variabile scalare
delete prt;
• pentru eliberarea memoriei alocate unui vector
delete [] ptr;
Variabila ptr conţine adresa memoriei alocată mai înainte cu new.
Operatorii new şi delete sunt utili la operaţii cu vectori.
Exemplu. Instrucţiunea
103
float *pf = new float[10];
alocă un vector de 10 componente de tip float, iat variabila pf conţine adresa
vectorului. Un vector alocat cu new poate fi şters cu operatorul delete astfel
delete [] pf;
Exemplu. Vom aloca un vector cu 10 componente întregi, îl vom iniţializa şi vom
afişa pe ecran componentele lui.
int main()
{
int * v = new int[10];
int j;
for(j = 0; j < 10; j++)
{
*(v + j) = j;
// v[j] = j;
}
for(j = 0; j < 10; j++)
{
cout << v[j] << “ “;
// cout << *(v + j) << “ “;
}
cout << endl;
delete [] v;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Instrucţiunea
int * v = new int[10];
are acelaşi efect cu instrucţiunea
int x[10];
dar vectorul v[10] alocat cu new poate fi şters, vectorul v[10] alocat de compilator nu
poate fi şters.
Exemplu. Vom aloca o matrice cu nlin linii şi ncol coloane, cu elemente întregi, unde
nlin şi ncol se vor citi de la tastatură. Pentru tipul variabilelor utilizate în program vezi
exemplu anterior. Elementele matricei vor primi aceleaşi valori ca în exemplul
anterior.
# include <iostream>
# include <cstdlib>
using namespace std;
int main()
{
104
int i, j;
int ** v;
int nlin, ncol;
cout << "introduceti # de coloane "; cin >> ncol;
cout << "introduceti # de linii "; cin >> nlin;
v = new int * [nlin];
for(i = 0; i < nlin; i++)
v[i] = new int[ncol];
for(i = 0; i < nlin; i++)
for(j = 0; j < ncol; j++)
v[i][j] = i * j;
for(i = 0; i < nlin; i++)
{
for(j = 0; j < ncol; j++)
cout << v[i][j] << '\t';
cout << endl;
}
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Instrucţiunea
v = new int * [nlin];
alocă memorie pentru un vector cu nlin elemente de tipul int*. La fel ca mai înainte
variabila tip pointer v are tipul int**.
105
5 Fişiere tip C
In limbajele C şi C++ fişierele sunt considerate ca un şir ordonat de octeţi. Există
două tipuri de fişiere, text şi binare.
• un fişier tip text este un şir ordonat de caractere grupate în linii. Fiecare linie
constă din zero sau mai multe caractere, urmate de un caracter ‘\n’.
• un fişier binar este un şir ordonat de octeţi.
Menţionăm că, fiecare fişier pe disc are o etichetă ce conţine numele fişierului, adresa
de început şi adresa de sfârşit a fişierului.
In program, fiecare fişier este asociat unui obiect numit stream. Un stream este
asociat unui fişier prin operaţia de deschidere a fişierului.
Pentru prelucrare, un fişier se deschide în citire. La deschiderea unui fişier în citire,
sistemul de operare caută pe disc fişierul cu numele specificat şi memorează adresele
de început şi de sfârşit ale fişierului în obiectul stream asociat.
Când se crează un fişier el se deschide în scriere. La deschiderea unui fişier în scriere,
se crează o etichetă corespunzătoare acestui fişier şi se completeză numele fişierului şi
adresa de început în obiectul stream asociat.
Un stream este disociat de un fişier prin operaţia de închidere a fişierului. La
închiderea unui fişier în creare se completează eticheta de pe disc a fişierului cu
adresa ultimului octet al fişierului.
Fişierele au un indicator de poziţie ce dă adresa următorului octet de citit sau scris.
Acest indicator este iniţial pus la zero şi este actualizat de operaţiile de citire sau
scriere.
Un fişier poate avea o zonă de memorie asociată (buffer) în care se citesc date din
fişier sau din care se scriu date în fişier.
Limbajele C şi C++ au tipul de structură FILE ce poate înregistra toate informaţiile
necesare pentru controlul unul stream, inclusiv:
• numele fişierului,
• indicatorul de poziţie al fişierului,
• indicator de eroare, poziţionat dacă a apărut o eroare intrare/ieşire,
• indicatorul de sfârşit de fişier, eof, (end of file), poziţionat la valoare true dacă
în timpul operaţiei de citire s-a ajuns la sfârşitul fişierului.
Prototipurile funcţiilor intrare / ieşire pentru fişierele tip C sunt memorate în
fişiere header şi trebuie semnalate compilatorului cu directive include.
Prototipurile funcţiilor ce efectuează operaţii intrare/ieşire în limbajul C se află
în biblioteca <stdio.h>. Pentru limbajul C++ numele fişierului header este
redefinit ca <cstdio>. Reamintim cele două forme ale directivei include:
• în cazul programelor C, directiva include conţine numele fişierului antet
(header), inclusiv extensia h. Forma directivei include este
#include <stdio.h>
• în cazul programelor C++, directiva include conţine doar numele
fişierului antet (header). Forma directivei include este
#include <cstdio>
In exemplele următoare vom utiliza stilul C.
Funcţiile importante pentru prelucrarea fişierelor sunt următoarele :
• FILE* fopen(const char * filename, const char * mode);
Această funcţie deschide un fişier. Ea crează o structură tip FILE cu informaţii despre
fişier şi are ca rezultat adresa ei. Parametrii acestei funcţii sunt
106
1. filename - un pointer la şirul de caractere ce dă numele fişierului.
2. mode - un pointer la un şir de caractere ce dă modul de deschidere.
Parametrul mode poate avea următoarele valori:
“r” – fişier text deschis în citire
“w” - fişier text deschis în scriere
“rb” – fişier binar deschis în citire
“wb” - fişier binar deschis în scriere
• int fclose(FILE * stream); Funcţia închide un fişier. Ea returnează valoarea
zero dacă operaţia a avut succes.
• int feof(FILE * stream); Funcţia returnează o valoare diferită de zero dacă s-a
detectat sfârşitul unui fişier în timpul operaţiei de citire precedente.
• int remove(char * filename); şterge fişierul cu numele filename. Funcţia
returnează o valoare diferită de zero dacă operaţia avut succes.
La lansarea în execuţie a unui program există trei streamuri standard tip text
deschise:
• stdin – fişierul standard de intrare asociat tastaturii,
• stdout - fişierul standard de ieşire asociat ecranului.
• stderr - fişierul standard de ieşire pentru scrierea mesajelor de eroare.
Exemplu. Schema prelucrării unui fişier text în citire este următoarea
FILE * fp;
fp = fopen(“numefisier”, “r”);
// prelucreaza fisierul
fclose(fp);
Menţionăm că la citirea secvenţială a unui fişier, după fiecare operaţie de citire
trebuie să testăm dacă s-a ajuns la sfârşitul fişierului. La întâlnirea sfârşitului de
fişier indicatorul de sfârşit de fişier este poziţionat la o valoare diferită de zero
iar funcţiile de citire returnează o valoare specifică (constanta EOF, predefinită
în biblioteca <stdio.h>). Citirea secvenţială a unui fişier se face cu instrucţiunea
while ce va testa indicatorul de sfârşit de fişier cu funcţia feof(), sau dacă funcţia
de citire a returnat valoarea EOF.
In cazul citirii datelor de la tastatură, (din fişierul stdin), sfârşitul de fişier este
indicat prin Ctrl+Z. După aceasta se apasă tasta return.
Fiecare aplicaţie are un director curent asociat. Atunci când parametrul
filename din instrucţiunea fopen() conţine doar numele fişierului, fişierul se află
în directorul curent. Pentru a prelucra fişiere în alte directoare, trebuie ca
parametrul filename să conţină şi calea, absolută sau relativă spre fişier.
5.1 Fişiere tip text
In cazul fişierelor tip text avem două tipuri de funcţii pentru operaţiile de intrare /
ieşire:
• funcţii pentru intrări / ieşiri cu format, la care operaţiile de citire / scriere se
fac sub controlul unui format,
• funcţii care citesc / scriu caractere.
107
5.1.1 Funcţii intrare / ieşire cu format
Aceste funcţii scriu / citesc valori în / din fişiere pe disc, fişierele standard sau fişiere
tip şir de caractere (fişiere memorate în vectori tip char). Pentru scriere, aceste funcţii
sunt:
int fprintf(FILE * stream, const char * format, argumente);
int printf( const char * format, argumente);
int sprintf(char * s, const char * format, argumente);
Funcţiile au ca rezultat numărul de octeţi scrişi. In cazul unei erori intrare / ieşire
funcţiile au ca rezultat un număr negativ.
Pentru citire, funcţiile sunt
int fscanf(FILE * stream, const char * format, argumente);
int scanf( const char * format, argumente);
int sscanf(char * s, const char * format, argumente);
Funcţiile au ca rezultat numărul de valori citite. In cazul unei erori intrare / ieşire
funcţiile au ca rezultat un număr negativ.
• funcţiile de scriere / citire pentru fişiere pe disc sunt fprintf şi fscanf,
• funcţiile de scriere / citire pentru fişierele standard stdout / stdin sunt printf şi
scanf,
• funcţiile de scriere / citire pentru fişierele tip şir de caractere sunt sprintf şi
sscanf. In cazul fişierelor şir de caractere, parametrul s este un şir tip C în care
se scriu sau din care se citesc valorile variabilelor.
Menţionăm că, în cazul instrucţiunilor scanf, argumentele sunt parametri de
ieşire, deci de tip pointer.
Parametrul format este un şir de caractere compus din specificatori de conversie
definiţi de % şi alte caractere. Specificatorii de conversie sunt :
%d – întreg cu semn în baza 10
%i – întreg cu semn
%o – întreg în baza 8
%u – întreg în baza 10 fară semn
%x – întreg în baza 16 cu semn
%c – caracter
%s – şir de caractere
%f – număr real tip float
%lf – număr real tip double
%e – număr real cu exponent
%le- număr real tip double cu exponent
%p – pointer
După % poate urma un număr întreg care dă lungimea minimă a câmpului în cazul
instrucţiunii printf şi lungimea maximă a câmpului în cazul instrucţiunii scanf. La
scrierea numerelor cu specificatorul %f putem specifica şi numărul de cifre ale părţii
subunitare. De exemplu specificatorul %7f descrie un număr real ce ocupă 7 poziţii,
iar specificatorul %7.3f descrie un număr real ce ocupă 7 poziţii din care 3 sunt pentru
partea subunitară.
108
Exemplu. Să citim un număr hexazecimal de la tastatură şi să-l scriem în format
zecimal şi hexazecimal pe ecran.
#include<stdio.h>
int main()
{
int i;
printf(“introduceti un numar hexazecimal\n”);
scanf(“%x”, &i);
printf(“\n numarul zecimal : %d\n numarul hexazecimal : %x\n”, i, i);
}
Rezultatul programului este cel de mai jos.
Reamintim că, parametrii funcţiei scanf sunt de tip pointer (sunt parametri de ieşire).
In consecinţă, la apelarea funcţiei scanf în program utilizăm adresa variabilei i
scanf(“%x”, &i);
Exemplu. Să citim două numere reale de tip float de la tastatură şi să afişăm suma lor.
Programul este următorul
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
float a, b, c;
printf("Dati doua numere reale\n");
scanf("%f %f", &a, &b);
c = a + b;
printf("a = %f, b = %f, a + b = %f\n", a, b, c);
return 0;
}
Rezultatul programului este cel de mai jos.
109
In cazul instrucţiunilor printf specificatorii de conversie dau formatul de scriere a
variabilelor, în timp ce restul caracterelor dn format sunt scrise în fişier. De exemplu,
instrucţiunea
printf(“\n numarul zecimal : %d\n numarul hexazecimal : %x\n”, i, i);
are ca rezultat scrierea caracterului ‘\n’, (trecerea la un nouă linie), a şirului “numarul
zecimal” şi a valorii variabilei i, etc.
Exemplu. Să definim o funcţie ce calculează suma unui şir de numere reale tip double
citite de la tastatură. Numărul termenilor din şir va fi citit tot de la tastatură.
Programul este dat în continuare. Numerele reale tip double se citesc şi se afişază cu
specificatorul de conversie tip %lf.
#include <stdio.h>
#include <stdlib.h>
double sumnum()
{
int i, n;
double s, x;
printf("cate numere ?\n");
scanf("%d", &n);
s = 0;
for(i = 1; i <=n; i++)
{
scanf("%lf", &x);
s = s + x;
}
return s;
}

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

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

};

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

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

+ ss.str());
}
else
return x / y;
}
193
int main()
{
int a = 10, b = 0, c;
try
{
// functie ce poate lansa o exceptie
c = fun(a, b);
cout << a << “/” << b << “=” << c << endl;
}
catch(string mesaj)
{
cout << mesaj << endl;
}
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Menţionăm că funcţia ce lansează o excepţie este apelată într-un bloc try, iar excepţia
este prinsă în blocul catch corespunzător.
Exerciţiu. Să se modifice funcţia fun() astfel ca şirul ss să fie de tip C.
Indicaţie. Se va defini un şir ss de tip C şi se vor înscrie în el constantele __LINE__ şi
__FILE__ cu funcţia sprintf() :
char ss[50];
sprintf(ss, "linia : %i fisierul : %s", __LINE__, __FILE__);
throw string(“impartire prin zero ”

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

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

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

return 0;
}
Rezultatul rulării programului este cel de mai jos.
Reamintim că, sfârşitul streamului de intrare cin, este indicat prin Ctrl+Z, după care se
apasă tasta Enter, vezi figura cu rezultatul programului.
Exemplu. Vom utiliza funcţia copy la copierea unui vector cu elemente de tip string în
altul. Vom iniţializa vectorul str cu elemente tip string şi vom copia elementele pe
ecran cu un iterator pentru streamul cout. Vom copia vectorul str în vectorul strcp cu
funcţia copy() şi vom copia elementele vectorului strcp pe ecran, tot cu un iterator
pentru streamul cout. Programul este următorul.
#include <iostream>
#include <vector>
#include <string>
#include <iterator>
using namespace std;
int main(int argc, char *argv[])
{
vector<string> str;
str.push_back("a+b");
str.push_back("3*2");
str.push_back("m*n+p");
cout << "vectorul initial" << endl;
copy(str.begin(), str.end(), ostream_iterator<string>(cout, " "));
cout << endl;
// se defineste un vector strcp de aceeasi lungime cu str
vector<string> strcp(str.size());
// se copiaza str in strcp
copy(str.begin(), str.end(), strcp.begin());
// se afisaza strcp
cout << "vectorul copiat" << endl;
copy(strcp.begin(), strcp.end(), ostream_iterator<string>(cout, " "));
cout << endl;
return 0;
}
218
Rezultatul rulaării programului este cel de mai jos.
14.4 Liste
Clasa generică list implementează o listă dublu înlănţuită, adică o listă ce poate fi
parcursă în ambele sensuri. Lista are elemente de acelaşi tip. Prototipul clasei este
definit în biblioteca <list>.
Clasa defineşte următorii constructori :
• constructorul implicit
list();
crează o listă vidă,
• constructorul copiere
list(const list& p);
crează o listă în care copiază elementele listei p.
Clasa defineşte destructorul
~list() ;
Fie T tipul elementelor listei. Funcţiile definite de clasa list sunt următoarele :
• void push_back(T&); adaugă un element la sfârşitul listei,
• void push_front(T&); adaugă un element la începutul listei,
• void pop_front(); şterge primul element din listă,
• void pop_back();şterge ultimul element din listă,
• int size(); dă numărul de elemente din listă,
• bool empty() ; are valoarea true dacă lista este vidă,
• T& front(); are ca rezultat primul element din listă,
• T& back();are ca rezultat ultimul element din listă,
• void clear(); ştrerge toate elementele din listă,
• operatorul = este operatorul de atribuire,
• operatori de comparare = =, !=, <, <=, >, >=, compară două liste, element cu
element.
14.4.1 Parcurgerea listelor
Pentru parcurgerea componetelor unei liste se utilizează iteratori. Un iterator este
asemenea unui pointer la un element al listei. O listă poate fi parcursă în sens direct
sau în sens invers.
Parcurgerea listelor în ordine directă

Pentru parcurgerea unei liste în ordine directă se utilizează clasa iterator care este o
clasă internă a clasei list. Un obiect de tipul acestei clase se defineşte astfel
list<tip>::iterator nume_obiect;
Operaţiile implementate de clasa iterator sunt cele prezentate în paragraful despre
vectori. Pentru parcurgerea directă a listelor clasa list defineşte funcţiile :
begin();
end();
219
ce au ca rezultat un iterator ce indică primul element al listei şi respectiv ultimul
element al listei.
Parcurgerea listelor în ordine inversă
Pentru parcurgerea inversă a listelor se utilizează clasa reverse_iterator care este tot o
clasă internă a clasei list. Un obiect de tipul acestei clase se defineşte ca
list<tip>::reverse_iterator nume_obiect;
Operaţiile implementate de clasa iterator sunt cele prezentate în paragraful despre
vectori.
Clasa list defineşte funcţiile :
rbegin();
rend();
ce au ca rezultat un iterator pentru parcurgerea în sens invers a listei, ce indică ultimul
element al listei şi respectiv primul element al listei.
14.4.2 Sortarea listelor
Pentru sortarea în ordine directă a listelor, clasa list defineşte funcţia
void sort();
ce sortează elementele listei în ordine crescătoare.
Exemplu. Vom crea o listă cu elemente întregi, o vom sorta ascendent şi vom afişa
elementele listei sortate parcurgând-o în sens direct şi invers.
# include <iostream>
# include <list>
using namespace std;
int main()
{
list <int> ls;
// adauga elemente la sfarsitul si inceputul listei
ls.push_back(11);
ls.push_back(7);
ls.push_front(4);
ls.push_front(12);
// parcurgerea listei initiale in sens direct
cout << "-- lista initiala --\n";
list<int>::iterator iter;
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << " ";
cout << endl;
// sortarea elementelor listei in ordine crescatoare
ls.sort();
cout << "-- sorteaza lista in ordine creascatoare --\n";
// parcurgerea listei sortate in sens direct
cout << "-- lista sortata parcursa in sens direct --\n";
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << " ";
cout << endl;
// parcurgerea listei sortate in sens invers
220
cout << "-- lista sortata parcursa in sens invers --\n";
list<int>::reverse_iterator iter1;
for(iter1 = ls.rbegin(); iter1 != ls.rend(); iter1++)
cout << *iter1 << " ";
cout << endl;
return 0;
}
Rezultatul rulării programului este cel de mai jos.
14.4.3 Inversarea ordinii elementelor listelor
Pentru inversarea ordinii elementelor unei liste clasa list defineşte funcţia
void reverse();
Vom exemplifica utilizarea acestei funcţii, inversând elementele unei liste şi
parcurgând-o în sens direct şi apoi invers.
# include <iostream>
# include <list>
using namespace std;
int main()
{
list <int> ls;
ls.push_back(11);
ls.push_back(7);
ls.push_front(4);
ls.push_front(12);
// parcurgerea listei in sens direct
cout << "-- lista initiala --\n";
list<int>::iterator iter;
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << endl;
// inversarea ordinii elementelor listei
cout << "-- inversarea ordinii elementelor listei --\n";
ls.reverse();
// parcurgerea listei in ordine inversa
cout << "lista parcursa in sens direct\n";
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << endl;
// parcurgerea listei in ordine inversa
cout << "lista parcursa in sens invers\n";
221
list<int>::reverse_iterator iter1;
for(iter1 = ls.rbegin(); iter1 != ls.rend(); iter1++)
cout << *iter1 << endl;
return 0;
}
Rezultatul programului este cel de mai jos.
14.4.4 Inserarea şi ştergerea elementelor din liste
Clasa list defineşte şi funcţiile :
erase(iterator pos);
erase(iterator first, iterator last);
Prima funcţie şterge elementul din poziţia specificată de iteratorul pos, a doua funcţie
şterge elementele între poziţiile first şi last.
Clasa list defineşte funcţia
insert(iterator pos, const T& val);
ce inserează elementul val înaintea poziţiei specificate de iteratorul pos.
După ştergerea sau inserarea unui element în vector, toţi iteratorii trebuie
iniţializaţi.
Exemplu. Vom crea o listă cu şiruri de caractere, vom şterge primul element din listă,
şi vom insera un element în prima poziţie. Definim lista cu şiruri de caractere astfel
list<string> lst;
Iniţializăm apoi lista ca în exemplele anterioare. Definim un ierator ce va fi utilizat în
funcţiile erase() şi insert()
list<string>::iterator it;
Stergerea primului element din listă se face astfel
it = lst.begin();
lst.erase(it);
Inserarea unui element în prima poziţie din listă se face astfel
it = lst.begin();
lst.insert(it, "xns");
Reamintim că, după fiecare ştergere sau inserare de elemente, iteratorii trebuie
iniţializaţi.
Vom afişa lista iniţială şi după fiecare modificare. Pentru aceasta, definim o funcţie
afisarelista(), ce are ca parametri o listă cu şiruri de caractere şi doi iteratori, ce dau
prima poziţie şi ultima poziţie din listă. Prototipul funcţiei este
void afisarelista(list<string>, list<string>::iterator, list<string>::iterator);
222
Programul este următorul

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

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

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

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


·




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



·

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


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


Dupǎ n astfel de operaţii vom avea:
1 1
2
− −
+ ·
n n n
r q q
unde:
1 1 − −
·
n n
b r
0 ·
n
q
După cum se observǎ, resturile obţinute reprezintǎ chiar cifrele numǎrului binar.
Exemplu. Sǎ convertim numarul 14 din baza 10 în baza 2.
0 2 * 7 2 14
0 1
+ · + · r q
229
Avem deci:
0
0 0
· · r b
1 2 * 3 2 7
1 2
+ · + · r q
Avem: 1
1 1
· · r b
1 2 * 1 2 3
2 3
+ · + · r q
de unde obţinem:
1
2 2
· · r b
In final:
1 2 * 0 2 1
3 4
+ · + · r q
de unde obţinem:
1
3 3
· · r b
deci reprezentarea numǎrului 14 în binar este:
( ) ( )
2 10
1110 14 ·
Algoritmul de conversie a unui numǎr din baza 10 în baza 2 este urmǎtorul:
1.
N q ·
0
2. 0 · i
3. while
0 ≠
i
q
{
2
1
i
i
q
q ·
+
2 %
i i
q r ·
1 + · i i
}
Resturile obţinute sunt cifrele numǎrului binar, primul rest fiind cifra cea mai puţin
semnificativǎ.
Conversia din baza 10 în baza 8 sau 16 se face prin împǎrţiri repetate cu 8 şi respectiv
16.
Unitatea de bază a informaţiei în calculator este un octet sau byte, ce cuprinde 8 cifre
binare (biţi). Numerele întregi se reprezintǎ în calculator pe 8, 16, 32 sau 64 de biţi.
Având un număr în baza 2, pentru reprezentarea sa în baza 16 se grupează câte 4 cifre
binare.
Exemplu.
( ) ( ) ( )
16 2 10
1100 12 C · ·
( ) ( ) ( ) ( )
10 10 16 2
158 14 16 * 9 9 10011110 · + · · E
Reprezentarea în baza 16 este importantǎ deoarece un octet poate fi reprezentat prin
două cifre hexazecimale.
Având un numǎr în baza 2, pentru reprezentarea în baza 8 se grupează câte 3 cifre
binare.
Exemplu.
( ) ( ) ( )
8 2 10
34 11100 28 · ·
Pentru verificare
( ) ( ) ( )
10 10
1
8
28 4 8 * 3 34 · + ·
Conversia unui număr din baza 16 în baza 2 se face reprezentând fiecare cifrǎ
hexazecimalǎ prin 4 cifre binare.
Conversia unui numǎr din baza 8 în baza 2 se face convertind fiecare cifrǎ octalǎ prin
3 cifre binare. Pentru conversii de numere între bazele 2, 8, 10 şi 16 şi operaţii cu
230
numere în aceste baze se poate folosi aplicaţia Calculator a sistemului de operare
Windows.
In final prezentăm în tabela de mai jos numerele de la 0 la 15 reprezentate în bazele
10, 2, 8 şi 16.
Baza 10 Baza 2 Baza 8 Baza 16
0 0000 0 0
1 0001 1 1
2 0010 2 2
3 0011 3 3
4 0100 4 4
5 0101 5 5
6 0110 6 6
7 0111 7 7
8 1000 10 8
9 1001 11 9
10 1010 12 a
11 1011 13 b
12 1100 14 c
13 1101 15 d
14 1110 16 e
15 1111 17 f
Reprezentarea numerelor binare cu semn
In cazul numerelor binare cu semn, bitul cel mai semnificativ este bitul de semn. El
este 0 pentru numere pozitive şi 1 pentru numere negative. Există trei reprezentǎri ale
numerelor binare cu semn.
Reprezentarea în mǎrime şi semn
Numǎrul pozitiv X se reprezintǎ ca:


·

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


·

+ − ·
2
0
1
2 2 * 1
n
i
i
i
n
a X
Exemple. Vom considera numere întregi reprezentate pe 8 biţi, un bit de semn şi 7 biţi
ai numǎrului:
Număr zecimal Reprezentare binarǎ Reprezentare hexazecimalǎ
13 0000 1101 0D
-13 1000 1101 8D
25 0001 1001 19
-7 1000 0111 87
127 0111 1111 7F
-127 1111 1111 FF
231
Gama numerelor întregi reprezentabile pe un octet în mǎrime şi semn este [-127, 127].
Putem scrie formula de reprezentare a numerelor binare în mǎrime şi semn ca:


·


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


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


·

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


·

+ − ·
2
0
1
2 2 * 1
n
i
i
i
n
a X
unde:
i i
a a − ·1 . Pentru a reprezenta un numǎr negativ în complement faţǎ de 1
complementǎm toate cifrele lui.
Exemple de numere reprezentate în complement faţǎ de 1 pe un octet.
Număr zecimal Reprezentare binarǎ Reprezentare hexazecimalǎ
15 0000 1111 0F
-15 1111 0000 F0
-19 1000 1100 8C
19 0001 0011 13
Reprezentarea numerelor binare în complement faţǎ de 2
Numerele pozitive se reprezintǎ în complement faţǎ de 2 ca:


·

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


·

+ + − ·
2
0
1
1 2 2 * 1
n
i
i
i
n
a X
unde:
i
i a a − ·1 .
Exemple de numere reprezentate în complement faţǎ de 2 pe un octet.
Număr zecimal Reprezentare binarǎ Reprezentare hexazecimalǎ
13 0000 1101 0D
-13 1111 0011 F3
-7 1111 1001 F9
127 0111 1111 7F
-127 1000 0001 81
Menţionǎm cǎ în calculatoare numerele întregi se reprezintǎ în general în complement
faţǎ de 2. Considerăm formula ce reprezintǎ un număr negativ în complement faţǎ de
2. Avem relaţia:
232
1
2
0
2 1 2


·
· +

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

·

·
− −

·

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

este echivalentǎ cu deplasarea
numǎrului binar la stânga sau la dreapta cu o cifra. La deplasarea numărului binar
bitul de semn rǎmane neschimbat.
Cazul numerelor pozitive.
Se deplaseazǎ toate cifrele numărului, iar cifrele adaugate sunt zerouri. Regula este
evidentǎ din formula de reprezentare a numerelor pozitive. Fie numărul pozitiv


·

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


·

+ · ·
2
0
1
2 2 * 0 * 2
n
i
i
i
n
b X Y
de unde se deduce:
i i
a b ·
+1
0
0
· b
Am presupus cǎ prin înmulţirea cu 2 a numǎrului X nu apare depǎsire. In acelaşi fel se
aratǎ cǎ regula este valabilǎ pentru împǎrţirea cu 2.
Exemple. Să deplasǎm numărul 28 cu o cifrǎ binarǎ la dreapta şi la stânga.
Număr zecimal Număr binar Număr hexazecimalǎ
28 0001 1100 1C
14 0000 1110 0E
56 0011 1000 38
Numere negative reprezentate în complement faţǎ de 2
Regula de înmulţire a acestor numere cu
1
2
sau
1
2

este urmǎtoarea:
• la deplasarea la stânga cifrele adǎugate sunt zerouri,
• la deplasarea la dreapta cifrele adaugate sunt unuri,
• reamintim ca bitul de semn rǎmâne neschimbat.
Exemplu.
Numǎr zecimal Numǎr binar Numǎr hexazecimal
-28 1110 0100 E4
-14 1111 0010 F2
-56 1100 1000 C8
233
A.3 Reprezentarea numerelor reale
Numerele reale se reprezintǎ în calculator în virgulǎ mobilǎ. Numarul real R este pus
sub forma
e
b f R * ·
unde: f este un număr subunitar (mantisa), b este baza iar e este exponent sau
caracteristicǎ. Pentru exemplificare vom considera baza zece.
Exemple de reprezentare a numerelor reale.
2
10 * 3245 . 0 45 . 32 ·
1
10 * 35 . 0 035 . 0

− · −
Mantisa f are totdeauna un număr finit de cifre şi în plus:
1 < f
In continuare, pentru exemplificare vom considera cǎ mantisa are şapte cifre
semnificative. Deoarece mantisa are un numǎr finit de cifre, în unele cazuri se pierd
cifrele mai puţin semnificative din numǎr.
Exemplu. Numǎrul
... 3333333333 . 0 ) 3 .( 0
3
1
· ·
se reprezintǎ ca:
0
10 * 3333333 . 0
Pentru mǎrirea preciziei calculelor se cautǎ ca prima cifră din mantisǎ sǎ fie diferitǎ
de zero (vezi exemplul de mai sus). Numerele în această formă se numesc
normalizate.
Efectuarea operaţiilor cu numere în virgulǎ mobilǎ
Pentru adunare numerele se aduc la acelaşi exponent (numǎrul mai mic la exponentul
celui mai mare) şi apoi se adunǎ mantisele. Aducerea numǎrului mai mic la
exponentul celui mai mare poate duce la pierderea cifrelor cel mai puţin semnificative
din mantisǎ.
Exemplu. Sǎ efectuăm adunarea:
12.48+0.35
Primul numǎr este reprezentat ca:
2
10 * 1248 . 0
iar al doilea
0
10 * 35 . 0
Pentru adunare se aduc numerele la acelaşi exponent
0.1248 2
0.035 2
şi se adunǎ mantisele.
0.1283 2
Vom prezenta un exemplu în care se pierd cifrele cele mai puţin semnificative din
rezultat. Sǎ adunǎm numerele:
237.55 + 0.000425
Numerele sunt reprezentate astfel:
3
10 * 23755 . 0
0.3245 2
-0.35 -1
0.1248 2
0.35 0
0.23755 3
234
3
10 * 425 . 0

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

iar mantisa corespunde la opt cifre zecimale.
Exemple. Fie numărul
0
2 * 0 . 1 0 . 1 · · R . Bitul de semn este zero iar exponentul
0+127. In consecinţă, primii nouă biti din număr sunt
001111111
Numărul R va fi reprezentat în hexazecimal ca 3F800000.
Fir numărul
1
2 * 0 . 1 5 . 0

· · R . Bitul de semn este zero iar exponentul este
-1+127=126. Primii nouă biţi din număr sunt
001111110
Numărul se reprezintă în hexazecimal ca 3F000000.
Menţionǎm cǎ dacǎ exponentul este 255 iar mantisa este 0, numărul este ∞ t Dacǎ
exponentul este 255 şi mantisa este diferitǎ de zero numărul este NaN (not a number).
Valoarea NaN apare atunci când efectuǎm urmatoarele operaţii
∞ ∞

∞ − ∞
/
0 / 0
* 0
sau când un operand este NaN.
In reprezentarea în virgulǎ mobilă lungǎ (dublǎ precizie) numǎrul se reprezintă pe 64
biţi. Domeniul exponentului este [-1023, 1023]. La exponent se adaugǎ cantitatea
1023, deci exponentul are domeniul [0, 2046]. Bitul de semn este bitul 63, exponentul
ocupǎ biţii 52-62 iar mantisa ocupă biţii 0-51. Gama numerelor reale ce se pot
reprezenta este
) 10 , 10 (
307 307

A.4 Reprezentarea caracterelor
Unul dintre sistemele de reprezentare a caracterelor este codul ASCII în care fiecare
caracter este reprezentat pe un octet. De exemplu caracterul A are codul hexazecimal
41, caracterul a are codul hexazecimal 61, cifra 0 are codul hexazecimal 30, etc.
Codurile ASCII ale caracterelor sunt prezentate în tabela de mai jos.
y\x 0 1 2 3 4 5 6 7
0 Nul Dle 0 P p
1 Soh Dc1 ! 1 A Q a q
2 Stx Dc2 “ 2 B R b r
3 Etx Dc3 # 3 C S c s
4 Eot Dc4 $ 4 D T d t
5 Enq Nak % 5 E U e u
6 Ack Syn & 6 F V f v
7 Bel Elb ‘ 7 G W g w
8 Bs Can ( 8 H X h x
9 Ht Em ) 9 I Y i y
10 Lf Sub * : J Z j z
236
11 Vt Esc + : K [ k {
12 Ff Fs , < L \ l |
13 Cr Gs - = M ] m }
14 So Rs . > N ^ n ~
15 Si Us / ? O _ o del
Tabelul 1.1 Codul ASCII
Codul zecimal unui caracter este 16*x+y. De exemplu caracterul A are codul zecimal
65 sau, echivalent, codul hexazecimal 41.
Un alt sistem de reprezentare a caracterelor este UNICODE. în varianta UNICODE-
16 fiecare caracter este reprezentat pe 2 octeţi.
237
Anexa 2. Priorităţile şi asociativitatea operatorilor
Operator Asociativitate
1 :: stânga
2 [ ] ( ) -> . stânga
3 ++ -- + - ! ~ sizeof (tip) new
delete * &
dreapta
4 (tip) dreapta
5 * / % stânga
6 + - stânga
7 << >> stânga
8 < <= > >= stânga
9 = = != stânga
10 & stânga
11 ^ stânga
12 | stânga
13 && stânga
14 | | stânga
15 ?: dreapta
16 = += -= *= /= %= <<= >>= &= |
= ^=
dreapta
17 , stânga
238
Anexa 3. Tipul complex în limbajul C
Limbajul C defineşte tipul complex pentru lucrul cu numere complexe. Un număr
complex este o pereche ordonată de două numere reale. In limbajul C vom avea
numere complexe constituite din două numere reale tip float şi separat, numere
complexe constituite din două numere reale tip double.
Un număr complex (o constantă complexă) are forma
a + b * I
unde a şi b sunt numere reale sau întregi ce definesc numărul complex.
Forma instrucţiunilor de declarare a variabilelor de tip complex este
complex double listă de variabile;
complex float listă de variabile;
Lista de variabile este formată din nume de variabile separate de virgule. Putem
defini, de exemplu, variabilele de tip complex următoare
complex float x, y, z ;
complex double m, n, p ;
Variabilele de tip complex pot fi iniţializate la definirea lor. La iniţializare se
utilizează constante complexe cu forma de mai sus sau cu forma
{a + b * I}
unde a şi b sunt cele două numere reale ce definesc constanta complexă.
Exemplu. Să definim variabilele complexe x = 2.35 – 7.4i şi y = -12.5 + 4.3i.
Instrucţiunea de definire a acestor variabile este
complex double x = 2.35 – 7.4 * I, y = -12.5 + 4.3 * I ;
sau
complex double x = {2.35 – 7.4 * I}, y = {-12.5 + 4.3 * I} ;
Operaţiile cu numere complexe sunt cele cunoscute, +, -, * şi /. Priorităţile şi
asociativitatea operatorilor sunt tot cele cunoscute. Expresiile se scriu după regulile
cunoscute. In scrierea expresiilor se pot utiliza ca termeni variabile şi numere întregi,
reale şi complexe.
Scrierea numerelor complexe se face cu instrucţiunea printf, în care vom defini doi
specificatori de conversie pentru fiecare număr complex, primul pentru partea reală, al
doilea pentru partea imaginară.
Pentru lucrul cu numere complexe trebuie să includem biblioteca antet <complex.h>.
Ca prim exemplu, definim două variabile complexe, d1 şi d2, şi calculăm expresiile
d1 * d2 şi d1 / d2 + 1. Variabila d1 este iniţializată la definirea di, în instrucţiunea
complex, la valoarea 2.35 – 3.45i, variabila d2 este iniţilizată în program la valoarea
4.3 - 2.5i -3.57 + 2.95i. Programul este următorul.
#include <stdio.h>
#include <stdlib.h>
#include <complex.h>
int main(int argc, char *argv[])
{
// definirea de variabile complexe
complex double d1 = 2.35 - 3.45 * I, d2, d3;
d2 = 4.3 - 2.5 * I -3.57 + 2.95 * I;
d3 = d1 * d2;
239
printf(" d1 = %6.2f %6.2f\n d2 = %6.2f %6.2f\n d1 + d2 = %6.2f %6.2f\n",
d1, d2, d3);
d3 = d1 / d2 + 1;
printf(" d1 = %6.2f %6.2f\n d2 = %6.2f %6.2f\n d1 / d2 + 1 = %6.2f %6.2f\n",
d1, d2, d3);
return 0;
}
Rezultatul rulării programului este cel de mai jos.
Ca un alt exemplu, definim două variabile complexe, a1 şi a2, calculăm produsul lor,
a3=a1*a2 şi afişăm rezultatul pe ecran. Programul este următorul.
#include <stdio.h>
#include <stdlib.h>
#include <complex.h>
int main(int argc, char *argv[])
{

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

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

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

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

2

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

3

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

4

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

x >>= n . # include <iostream> using namespace std. /* uitlizarea operatorilor de deplasare */ int main() { int x = 10. return 0. cout << “x = “ << dec << x << “ “ << hex << showbase << x << endl. Vom exemplifica utilizarea operatorilor de deplasare deplasând două variabile întregi la stânga şi la dreapta cu un bit şi apoi cu trei biţi şi vom afişa rezultatele în bazele 10 şi 16. se utilizează manipulatorii : • hex pentru baza 16 • dec pentru baza 10 • oct pentru baza 8 La începerea execuţiei unui program baza 10 este implicită. // deplasează variabila b la dreapta cu 3 pozitii b = b >> 3. cout << “b = “ << dec << b << “ “ << hex << showbase << b << endl. Reamintim că. cout << “x deplasat la stanga cu 1 bit = “ << dec << x << “ “ << hex << showbase << x << endl. Vom încheia acest paragraf cu un program care deplasează numere întregi şi afişază valoarea lor în zecimal şi hexazecimal.16. cout << “b deplasat la dreapta cu 3 biti = “ << dec << b << “ “ << hex << showbase << b << endl.2 Operaţii logice la nivel de bit Limbajele C şi C++ au următorii operatori pentru operaţii logice la nivel de bit şi logic (and) notat & sau logic (or) notat | sau excusiv (xor) notat ^ complement faţă de unu notat ~ Aceşti operatori se definesc cu tabelele de adevăr următoare 28 . // deplasează variabila a la stanga cu un bit x = x << 1. pentru scrierea sau citirea unui număr întreg în instrucţiunile cout sau cin în diferite baze. int b = 50. } Rezultatele rulării programului sunt prezentate în figura de mai jos. 1.

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

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

Rezultatul rulării programului este cel de mai jos.

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

31

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

32

Menţionăm că în cazul operatorului do acţiunea s se execută cel puţin o dată, în timp ce pentru operatorul while este posibil ca acţiunea s să nu se execute niciodată. • Operatorul for se utilizează atunci când numărul de execuţii ale unei acţiuni este dinainte cunoscut. Operatorul are o variabilă de control ce se poate modifica la fiecare iteraţie. Forma acestui operator este for (i = instrucţiune1; condiţie; instrucţiune2) s; Operatorul include o instrucţiune1 ce specifică valoarea iniţială a variabilei de control, şi o instrucţiune2 ce poate modifica valoarea variabilei de control după fiecare iteraţie. Acţiunea s se execută atât timp cât expresia booleană condiţie are valoarea adevărat. Operatorul for este echivalent cu următoarele instrucţiuni instrucţiune1; while(condiţie) s; instrucţiune2; Menţionăm că testul condiţiei se face înainte de execuţia acţiunii s. In toţi operatorii de mai sus, acţiunea este o instrucţiune simplă sau o instrucţiune compusă (un bloc). In program, o instrucţiune compusă (un bloc), este un grup de instrucţiuni inclus între acolade, { şi }. Intr-un bloc putem defini variabile locale, al căror domeniu de existenţă este limitat la acel bloc. Variabilele locale sunt create la intrarea în bloc şi sunt şterse la ieşirea din bloc. Operatorii prezentaţi se numesc structuri de control fundamentale. In construcţia unui program structurile de control şi structurile de date sunt inseparabile. Structurile fundamentale de date sunt • structuri simple: numere întregi, reale, caractere. • structuri complexe: tablouri, fişiere. Tablourile au un număr cunoscut de elemente. Ele se prelucrează de regulă cu operatorul for. Fişierele secvenţiale au un număr de elemente necunoscut în avans. Ele se prelucrează de regulă cu operatorul while. Exemplu. Algoritmul de calcul pentru n! care este definit ca n!= ∏ i
i =1 n

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

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

33

Rezultatul evaluării unei expresii relaţionale este fals sau adevărat (false sau true). Priorităţile operatorilor aritmetici sunt mai mari decât ale operatorilor relaţionali. Exemple. Fie următoarele instrucţiuni de atribuire int a = 2, b = 3, c = 6; In tabela de mai jos sunt prezentate exemple de expresii relaţionale şi rezultatul evaluării lor. Expresie relaţională a*b >= c b+2 > a *c a+b = = 3 a != b b/a = = 1 Valoare true false false true true

Reamintim că în limbajul C++ valoarea true este reprezentată prin 1 iar valoarea false prin 0. La scrierea valorii unei variabile booleene valoarea afişată este 0 sau 1, după cum variabila are valoarea false, respectiv true. Pentru afişarea valorii booleene ca true, respectiv false se utilizează manipulatorul boolalpha. Fie următoarea instrucţiune de declarare a unei variabile de tip bool bool r; Putem avea următoarele instrucţiuni de atribuire r = a + b = = c; r = a – b >= 2; r = a * a != -b; Variabila r poate avea valorile true sau false. Un exemplu de program este cel de mai jos. #include <iostream> using namespace std; int main() { int a = 3, b = -2, c = 5; bool r; cout << " a = " << a << " b = " << b << " c = " << c << endl; r = a + b == c; cout << "a + b == c " << boolalpha << r << endl; r = a - b >= 2; cout << "a - b >= 2 " << boolalpha << r << endl; r = a * a != - b; cout << "a * a != - b " << boolalpha << r << endl; return EXIT_SUCCESS; }

34

în ordinea priorităţilor ! && | | care reprezintă operatorii nu (not). şi (and). Instrucţiunea a=b atribuie variabilei a valoarea variabilei b. respectiv sau (or). In cursurile de logică operatorii booleeni se notează astfel: nu (not) şi (and) sau (or) ¬ ˄ ˅ Exemple de expresii booleene şi scrierea lor sunt prezentate mai jos : a∧b a & &b ¬(a ∨ b) !(a || b) a ∧b∨c a & &b || c ¬ a ∨ ¬b !a ||!b a ∧ (b ∨ c ) a & &(b || c) Pentru scrierea simplă a expresiilor booleene sunt importante două teoreme numite legile lui DeMorgan 35 .3 Expresii booleene Operatorii booleeni ai limbajelor C şi C++ sunt. Operatorul nu este operator unar. 2.Rezultatul rulării programului este prezentat în continuare. 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. In final reamintim că operatorul = este operator de atribuire. iar operatorul = = este operator de comparare. Aceşti operatori se definesc folosind tabelele de adevăr. iar expresia a==b este o expresia relaţională care are ca rezultat valoarea true sau valoarea false.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

O altă soluţie este următoarea. Pentru ca o funcţie externă să aibe acces la membri privaţi ai clasei, ea trebuie declarată în definiţia clasei ca funcţie prietenă, friend. Diagrama sintactică a definiţiei unei funcţii prietene este friend tip nume_funcţie ( parametri); Noua definiţie a clasei Line este următoarea. class Line { public: Line (double, double); Line (const Line & x); Line (); void assign(double, double); double value(double); double intgrl(double, double); void print(); void invert(); friend Line sum(const Line x, const Line y); private: double m; double n; }; In această definiţie funcţia sum a fost declarată prietenă, deci poate utiliza variabilele private ale clasei. Implementarea funcţiei sum este următoarea Line sum(const Line x, const Line y) { Line temp(x.m + y.m, x.n + y.n); return temp; } Un program care testează funcţia sum este cel de mai jos. int main() { Line a(1.5, 2.3); a.print(); Line b(2.2, -1.5); b.print(); Line c; c = sum(a, b); c.print(); return 0; } Rezultatul rulării programului este cel de mai jos.

141

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

142

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

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

143

8 Siruri tip C++
8.1 Clasa string
Limbajul C++ defineşte clasa string pentru lucrul cu şiruri. Spre deosebire de şirurile tip C care sunt terminate printr-un octet cu valoarea zero, obiectele acestei clase au un câmp ce conţine lungimea şirului. Clasa string este definită în fişierul header <string>. Clasa are următorii constructori: • constructorul implicit care crează un şir vid string(); • constructor copiere cu argument şir tip C sau şir tip string string(const string&); string(const char *); Operatorul = are ca argument drept un şir tip C sau şir tip string. Exemple de definiri de obiecte tip string string x = "abcd"; string y(x); string z = x; string x1("abc"); Clasa implementează operatorii + şi += (concatenarea şirurilor). Exemple. string a = x + “xyz”; a += “abc”; Clasa defineşte operatori de comparare ai şirurilor, <, <=, >, >= , = = şi != ce au ca rezultat valorile true sau false. Operatorii << şi >> scriu şi citesc şiruri tip string. Exemple. if(x < x1) cout << x1; else cout << x; Funcţii membre importante ale clasei sunt : • int length(); – dă lungimea şirului tip string, • int size(); – dă lungimea şirului tip string, • const char * c_str(); - converteşte şirul într-un şir tip C, • bool empty(); - are valoarea true dacă şirul este vid. Exemplu. Să copiem un şir C++ într-un şir tip C. char c[100]; string str(“abcd”); strcpy(c, str.c_str()); Clasa are operatorul de selecţie [] ce poate selecta un caracter din şir sau poate atribui o valoare unui caracter din şir. Indicele primului caracter din şir este 0. Exemplu. Să scriem un şir, caracter cu caracter. string s = “abcd”; for(j = 0; j < s.length(); j++) cout << s[j]; Vom prezenta acum funcţii de căutare de subşiruri şi de modificare a şirurilor. Fie un obiect tip string. • funcţia find() cu prototipul 144

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

207

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

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

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

208

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

228 .

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

Numerele întregi se reprezintǎ în calculator pe 8. Unitatea de bază a informaţiei în calculator este un octet sau byte. Având un numǎr în baza 2. pentru reprezentarea sa în baza 16 se grupează câte 4 cifre binare. Având un număr în baza 2.Avem deci: b0 = r0 = 0 7 = q 2 2 + r1 = 3 * 2 + 1 Avem: b1 = r1 = 1 3 = q3 2 + r2 = 1 * 2 + 1 de unde obţinem: b2 = r2 = 1 In final: 1 = q 4 2 + r3 = 0 * 2 + 1 de unde obţinem: b3 = r3 = 1 deci reprezentarea numǎrului 14 în binar este: (14) 10 = (1110) 2 Algoritmul de conversie a unui numǎr din baza 10 în baza 2 este urmǎtorul: 1. while qi ≠ 0 { q qi +1 = i 2 ri = qi %2 i = i +1 } Resturile obţinute sunt cifrele numǎrului binar. pentru reprezentarea în baza 8 se grupează câte 3 cifre binare. Conversia din baza 10 în baza 8 sau 16 se face prin împǎrţiri repetate cu 8 şi respectiv 16. 8. ( 28) 10 = (11100) 2 = ( 34) 8 Pentru verificare ( 34) 8 = (3 * 81 + 4)10 = ( 28) 10 Conversia unui număr din baza 16 în baza 2 se face reprezentând fiecare cifrǎ hexazecimalǎ prin 4 cifre binare. Exemplu. 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. 16. i = 0 3. 10 şi 16 şi operaţii cu 230 . q 0 = N 2. Exemplu. (12) 10 = (1100) 2 = ( C ) 16 (10011110) 2 = ( 9 E ) 16 = ( 9 *16 + 14) 10 = (158) 10 Reprezentarea în baza 16 este importantǎ deoarece un octet poate fi reprezentat prin două cifre hexazecimale. ce cuprinde 8 cifre binare (biţi). 32 sau 64 de biţi. primul rest fiind cifra cea mai puţin semnificativǎ.

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

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

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

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

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

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

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

+ .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 238 .! ~ sizeof (tip) new delete * & (tip) */% +<< >> < <= > >= = = != & ^ | && || ?: = += -= *= /= %= <<= >>= &= | = ^= .

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

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

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

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

creal cimag carg cabs conj

csin ccos casin cacos catan

cexp clog csqrt cpow

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

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

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

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

242

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

244 .

You're Reading a Free Preview

Download
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->