Documente Academic
Documente Profesional
Documente Cultură
Cuprins
Cap. 1 Reprezentarea informaţiilor
1.1 Reprezentarea numerelor întregi în sistemul binar
1.2 Deplasarea numeral binare cu semn
1.3 Reprezentarea numerelor reale
1.4 Reprezentarea caracterelor
Cap. 2 Constante, variabile şi expresii
2.1 Tipuri fundamentale
2.2 Variabile
2.3 Modificatori de tip
2.4 Operatorul typedef
2.5 Constante
2.6 Constante cu nume
2.7 Expresii aritmetice
2.8 Tablouri
2.9 Instrucţiunea de atribuire
2.10 Prototipuri de funcţii
2.11 Operaţii de intrare / ieşire
2.12 Funcţia main
2.13 Execuţia unui program
2.14 Operatorii ++ şi - -
2.15 Operaţii cu numere întregi la nivel de bit
2.15.1 Operatori de depasare
2.15.2 Operaţii logice la nivel de bit
Cap. 3 Structuri de control fundamentale
3.1 Algoritme
3.2 Expresii relaţionale
3.3 Expresii booleene
3.4 Operatorul do-while
3.5 Operatorul while
3.6 Operatorul for
3.7 Operatorul if
3.8 Operatorul ?
3.9 Operatorul switch
3.10 Operatorul ,
Cap. 4 Funcţii
4.1 Definirea funcţiilor
4.2 Pasarea parametrilor funcţiilor
4.3 Recursivitatea
4.4 Sabloane de funcţii
Cap. 5 Pointeri şi referinţe
5.1 Pointeri
5.2 Referinţe
5.3 Parametri funcţiilor
5.4 Pointeri la funcţii
5.5 Declalararea variabilelor tip pointer la funcţie
5.6 Pointeri şi tablouri unidimensionale
5.7 Siruri tip C
5.8 Pointeri şi tablouri multidimensionale
5.9 Parametrii funcţiei main
Cap. 6 Fişiere tip C
6.1 Fişiere text
6.1.1 Funcţii intrare / ieşire cu format
6.1.2 Funcţii intrare / ieşire tip character
6.2 Fişiere binare
Cap. 7 Structuri tip C
Cap. 8 Clase
8.1 Definirea unei clase
8.1.1 Definirea unei clase
8.1.2 Pointerul this
8.1.3 Spaţii de nume
8.2 Constructori şi destructori
8.3 Funcţii prietene
8.4 Fişiere standard în C++
Cap. 9 Supraîncărcarea operatorilor
9.1 Supraîncărcarea operatorului de atribuire
9.2 Supraîncărcarea operatorilor aritmetici
9.3 Supraîncărcarea operatorilor << şi >>
Cap. 10 Moştenirea
10.1 Pointeri la obiecte
10.2 Moştenirea
10.3 Funcţii virtuale. Polimorfism
10.4 Date şi funcţii statice
Cap. 11 Fişiere tip CPP
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
Cap. 12 Siruri tip C++
Cap. 13 Tratarea excepţiilor
13.1 Excepţii
13.2 Excepţii lansate de funcţii
13.3 Excepţii standard
Cap. 14 Aplicaţii
14.1 Funcţii de timp
14.2 Fire de execuţie
Cap. 15 Biblioteca de şabloane standard
15.1 Funcţii generice
15.2 Vectori
15.3 Liste
Cap 1. Reprezentarea informaţiilor
1.1 Reprezentarea numerelor întregi în sistemul binar
unde: r>1 este baza, n este numărul de cifre, iar d i este cifra de pe poziţia i. Avem
totdeauna 0 ≤ d i < r . 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ǎ
In partea dreaptă avem un polinom de puteri ale lui 2. Coeficientul bi este 0 sau 1. Din
expresia de mai sus a numǎrului se observǎ cǎ cifrele bo , b1 , etc se pot obţine ca
resturile impǎrţirilor repetate ale numǎrului N cu 2. Vom nota:
N = q0
şi vom scrie:
q 0 = q1 2 + r0
de unde deducem:
r0 = b0
q1 = bn −1 2 n −2 + ... + b1
Vom scrie
q1 = q 2 2 + r1
de unde deducem:
r1 = b1
q 2 = bn −1 2 n −3 + ... + 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ǎ, resturile obţinute reprezintǎ chiar cifrele numǎrului binar.
Exemplu. Sǎ convertim numarul 14 din baza 10 în baza 2.
14 = q1 2 + r0 = 7 * 2 + 0
Avem deci: b0 = r0 = 0
7 = q 2 2 + r1 = 3 * 2 + 1
Avem: b1 = r1 = 1
3 = q 3 2 + r2 = 1 * 2 +1
de unde obţinem:
b2 = r2 = 1
In final:
1 = q 4 2 + r3 = 0 * 2 + 1
de unde obţinem:
b3 = r3 = 1
deci reprezentarea numǎrului 14 în binar este:
(14 ) 10 = (1110 ) 2
In acelaşi fel obţinem:
(10 ) 10 = (1010 ) 2
(11) 10 = (1011 ) 2
(12 ) 10 = (1100 ) 2
(13) 10 = (1101) 2
(15 ) 10 = (1111 ) 2
Algoritmul de conversie a unui numǎr din baza 10 în baza 2 este urmǎtorul:
1. q 0 = N
2. i = 0
3. cât timp qi ≠ 0
{
qi
q i +1 =
2
ri = qi %2
i = i +1
}
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.
(12 ) 10 = (1100 ) 2 = ( C ) 16
(10011110 ) 2 = ( 9 E ) 16 = ( 9 *16 + 14 ) 10 = (158 ) 10
Reprezentarea în baza 16 este importantǎ deoarece un octet poate fi reprezentat prin două
cifre hexazecimale.
Având un numǎr în baza 2, pentru reprezentarea în baza 8 se grupează câte 3 cifre binare.
Exemplu.
( 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.
Conversia unui numǎr din baza 8 în baza 2 se face convertind fiecare cifrǎ octalǎ prin 3
cifre binare. Pentru conversii de numere între bazele 2, 8, 10 şi 16 şi operaţii cu numere
în aceste baze se poate folosi aplicaţia Calculator a sistemului de operare Windows.
In 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.
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:
n −2
X = ( −1) a n −1 2 n −1
+ ∑a i 2 i
i =0
unde: a i =1 − a i .
Exemple de numere reprezentate în complement faţǎ de 2 pe un octet.
∑2
i =0
i
+ 1 = 2 n −1
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
n−2
X = 0 * 2 n −1 + ∑ a i 2 i
i =0
şi fie numărul înmulţit cu 2
n−2
Y = 2 * X = 0 * 2 n −1 + ∑ bi 2 i
i =0
de unde se deduce:
bi +1 = a i
b0 = 0
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.
Numerele reale se reprezintǎ în calculator în virgulǎ mobilǎ. Numarul real R este pus sub
forma
R = f * be
unde: f este un număr subunitar (mantisa), b este baza iar e este exponent sau
caracteristicǎ. Pentru exemplificare vom considera baza zece.
Exemple de reprezentare a numerelor reale.
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:
0.1248 * 10 2 0.1248 2
iar al doilea
0.35 * 10 0 0.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:
1237.55 + 0.000425
Numerele sunt reprezentate astfel:
0.123755 * 10 4 0.123755 4
0.425 * 10 −3
0.425 -3
La adunare mantisa celui de-al doilea numǎr este deplasatǎ la dreapta cu 7 cifre.
Considerând lungimea mantisei de 8 cifre, se pierd doua cifre semnificative din al doilea
numǎr la aducerea la acelaşi exponent.
0.123755 4
0.00000004 4
Rezultatul adunǎrii este:
0.12375504 4
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 opt cifre din rezultat.
Exemplu. Fie de efectuat operaţia de scădere:
103.45678 – 23.456789
Numerele se reprezintǎ astfel:
0.10345678 3
0.23456789 2
Dacǎ mantisa are doar opt cifre semnificative, când deplasǎm la dreapta cu o cifrǎ,
mantisa celui de-al doilea număr, pierdem o cifră semnificativǎ.
0.02345678 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 nouǎ cifre semnificative. In acest caz cele douǎ numere sunt
0.103456780 3
0.023456789 3
După scǎderea celor două numere obţinem:
0.079999991 3
iar dupǎ normalizare obţinem rezultatul corect:
0.79999991 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 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 37 ,10 37 )
iar mantisa corespunde la opt cifre zecimale.
Exemple. Fie numărul R = 1.0 = 1.0 * 2 0 . 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 R = 0.5 = 1.0 * 2 −1 . 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 ± ∞ 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 307 ,10 307 )
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
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
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.
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.
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++
Pentru fiecare tip există constante predefinite ce dau limitele minima ş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.
2.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.
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.
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).
Menţionăm că tipul bool nu este definit în limbajul C.
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. Tipul unsigned int are domeniul de valori
[0 2 32 −1] , iar tipul unsigned char are domeniul de valori [0 2 8 −1]
• signed. Valorile variabilelor sunt numere cu semn. Primul bit este bit de semn.
• short. Se aplică tipului int. De exemplu, tipul short int are domeniul de valori
[−215 215 −1] , iar tipul unsigned short int are domeniul de valori
[0 216 −1] . Tipul wchar_t este tipul unsigned short int predefinit.
• long
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
typedef char bool
2.5 Constante
• constante tip caracter UNICODE corespund tipului wchar_t. Ele se definesc sub
forma L’\xhhhh’ unde h reprezintă o cifră hexazecimală.
• constante booleene sunt true şi false şi se reprezintă prin valorile unu şi
respective zero.
De exemplu,
enum CLRX {aaa, bbb, ccc};
defineşte trei constante aaa, bbb, ccc, prima are valoarea zero şi fiecare constantă
următoare are o valoare mărită cu unu.
aaa = 0
bbb = 1
ccc = 2
In program constantele sunt înlocuite cu valorile lor. Valoarea fiecărei constante
poate fi specificată printr-o expresie întreagă constantă. De exemplu
enum cstval {ppp = 2, dd, cc = -1, nn = 2 + 3 * 4};
defineşte constantele
ppp = 2
dd = 3
cc = -1
nn = 14
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.
• utilizarea cuvantului cheie const. O asemenea instrucţiune are forma
const tip nume = valoare;
unde tip este un tip al limbajului iar valoare este o expresie constantă. De
exemplu, instrucţiunile
const int a = 7;
const char b = ‘a’;
const float c = 2.5e-2 * 4.14;
const float x = sin(0.2) + cos(1.5);
const float z = log(12.5) / 2.3;
Expresiile aritmetice sunt formate din constante, variabile şi funcţii. Operatorii sunt +, - *
şi /, şi î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,
15 % 4 = 3
11 % 5 = 2
Pentru gruparea termenilor se folosesc paranteze rotunde ( şi ). De exemplu, 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)
Funcţiile matematice standard uzuale ale limbajelor C şi C++ sunt cele de mai jos
Funcţia floor(x) calculează valoarea x (cel mai mare număr întreg cuprins în x), iar
funcţia ceil(x) calculează valoarea 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.
a * cos 2 ( x ) + b * sin( x )
2.75 + x
(a*cos(x)*cos(x)+b*sin(x))/(2.75+fabs(x))
e x + e −2 x
(exp(x)+exp(-2*x))/5
5
log( x +2) +ln 1 +cos( x ) log10(fabs(x)+2)+log(fabs(1+cos(x)))
Menţionăm că în expresii putem folosi orice fel de constante întregi, reale sau character.
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 character sunt convertite în numărul întreg corespunzător.
Evaluarea expresiilor aritmetice se face ţinand cont de priorităţile operatorilor şi de
asociativitatea lor.
Prioritatea operatorilor Asociativitate
+ unar, - unar
*/% Asociativ la stânga
+- Asociativ la stânga
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.
Diagrama sintactică 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, expresia
(i + f) % 2
nu este corectă deoarece 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 diagrama sintactică
tip(expresie)
De exemplu, conversia expresiei anterioare la tipul int se poate scrie
int(i + f)
Menţionăm că se pot face conversii în toate tipurile standard existente în limbaje.
2.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 [ întreg] ;
unde întreg reprezintă numărul de elemente ale tabloului. Elementele tabloului sunt
nume[0], nume[1], …, nume[î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]. Un tablou poate fi iniţializat la declararea sa scriind valorile elementelor între
accolade, {}, şi separate de virgule. Exemple de instrucţiuni ce declară tablouri şi le
atribuie valori
float x[3] = {1.32, -2.15, 4.45};
char c[4] = {‘a’, ‘b’, ‘c’, ‘x’};
In cazul în care lista de valori este mai scurtă decat 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. Instrucţiunea precedentă este echivalentă cu
char s[4] = “abc”;
sau cu instrucţiunea
char s[4] = {‘a’, ‘b’, ‘c’ , ‘\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]
Un tablou cu mai multe dimensiuni poate fi de asemenea iniţializat la declararea sa. Fie
de definit matricea cu elemente întregi
1 2 5
m =
3 7 − 3
Instrucţiunea corespunzătoare este
int m[2][3] = {{1, 2, 5},{3, 7, -3}};
La utilizarea într-o expresie, indicii elementelor tablourilor pot fi orice expresii întregi.
Presupunem următoarele declaraţii de tablouri
double a[10], b;
int i, j, y[3][4];
Exemple de expresii ce conţin elemente de tablouri.
ai + b * y ij
(a[i]+b*y[i][j])/(cos(b)-sin(b))
cos( b) − sin( b)
e cos( b ) + y i , j +1 exp(cos(b)) + y[i][j + 1]
Operator Asociativitate
[] () Asociativi la stânga
+ unar, - unar
*/% Asociativi la stânga
+- Asociativi la stânga
Operatori 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
declaraţia
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?
Orice aplicaţie 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.
Fişierul text este este compus dintr-un şir de caractere grupate în linii. Liniile constau din
zero sau mai multe caractere plus un caracter ‘\n’. Fişierele standard sunt de tipul text.
Streamul de intrare asociat tastaturii are denumirea cin, streamul de ieşire asociat
ecranului se numeşte cout. Mai există alte doua streamuri cerr şi clog pentru scrierea
mesajelor de eroare.
Operatorul de scriere este <<. El inserează date în streamul cout. De exemplu, secvenţa
de instrucţiuni
int i = 123;
cout << “i = ” << i;
afişază pe ecran
i = 123
Pentru a despărţi textul afişat în linii, trebuie să scriem caracterul ‘\n’, care este predefinit
ca endl. De exemplu, pentru a afişa cele de mai sus pe două linii, vom scrie
cout << “i = “ << endl << i << endl;
sau
cout << “i = “ << ‘\n’ << i << ‘\n’;
sau, echivalent
cout << “i =” << endl
cout << i << endl;
Operatorul << poate scrie orice tip predefinit de date: int, float, şiruri de caractere, etc.
Operatorul de citire dintr-un stream este >>. El extrage date din stream. Operatorul >>
este urmat de numele variabilei ce va memora valoarea citită. De exemplu, secvenţa de
instrucţiuni
int a;
cin >> a;
va citi valoarea introdusă de la tastatură şi o va atribui variabilei a. Se pot citi oricâte date
din streamul cin. Instrucţiunea
cin >> a >> b;
este echivalent cu
cin >> a;
cin >> b;
Cele două valori introduce pentru exemplul de mai sus trebuie separate de spaţii , \t sau \n
(acesta din urmă este generat la apăsarea tastei Return).
Biblioteca de prototipuri pentru streamurile cin, cout, etc are numele <iostream.h>.
Pentru a modifica formatul de scriere sai citire a datelor putem utilize manipulatori.
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 implicită este baza 10. Pentru scrierea bazei se
uitlizează 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.
cout << k; 20
cout << hex << k; 14
cout << oct << k; 24
cout << showbase << hex << k; 0x14
cout << showbase << oct << k; 024
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 nume (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 prototipului este următoarea. Funcţia main are ca rezultat o valoare întreagă
care este codul de terminare a programului şi nu are parametri. Corpul funcţiei este o
instrucţiune compusă, adică o secvenţă de instrucţiuni scrise între accolade, { şi }.
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.
# include <iostream.h>
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 scrie valoarea calculata */
cout << “patratul valorii este “ << j << endl;
return 0;
}
Operatorul sizeof se aplică asupra unei expresii sau asupra unui tip şi are ca rezultat
numărul de octeţi de memorie utilizaţi. De exemplu, expresia
sizeof(int)
are ca rezultat valoarea 4. In cazul unui tablou rezultatul este numărul total de octeţi
ocupat de tablou.
Exemplu. Vom scrie un program care să afişeze numărul de octeţi utilizaţi pentru
memorarea tipurilor fundamentale. Pentru a afişa datele deplasate la stânga cu un număr
de spaţii vom scrie un caracter tab, ‘\t’.
# include <iostream.h>
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 << ‘\t expresie’ << sizeof(a[0]+ b) << ‘\n’;
}
Menţionăm că este echivalent dacă în instrucţiunea cout utilizăm caracterul ‘\n’, sau endl,
sau şirul de caractere “\n”.
2.14 Operatorii ++ şi - -
Rezultatul expresiei din stânga este configuraţia de biţi 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. (La deplasarea la dreapta se propagă bitul de semn,
la deplasarea la stânga se adaugă zerouri). Dacă expresia de deplasat este de tipul
unsigned biţii adăugaţi sunt zerouri.
Exemple. Fie instrucţiunile de mai jos
int a = 0xff; int a = 0xff;
int b; int b;
b = a << 4; b = a >> 4;
Numărul a reprezentat pe patru octeţi are valoarea hexazecimală
a = 000000ff
Numărul deplasat la stânga cu 4 biţi este
00000ff0
iar 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?.
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 >>= care realizează operaţiile
următoare. Instrucţiunile
x = x << n
şi
x = x >> n
se scriu
x <<= n
şi respectiv
x >> n
Vom încheia acest paragraf cu un program care deplasează numere întregi şi le afişază
valoarea in zecimal şi hexazecimal. 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 implicită este baza 10.
Vom exemplifica utilizarea operatorilor de deplasare şi vom afişa rezultatele în baza 16.
# include <iostream.h>
/* uitlizarea operatori de deplasare */
int main()
{
int a = 10;
cout << “a = “ << dec << a << “ “ << hex << a << endl;
// deplasează variabila a la stanga cu un bit
a = a << 1;
cout << “a deplasat la stanga cu 1 bit = “
<< dec << a << “ “ << hex << a << endl;
int b = 50;
cout << “b = “ << dec << b << “ “ << hex << b << endl;
// deplasează variabila b la dreapta cu 3 pozitii
b = b >> 3;
cout << “b deplasat la dreapta cu 3 biti = “
<< dec << b << “ “ << hex << b << endl;
return 0;
}
a ~a
0 1
1 0
Operatorii &, | ^ sunt operatori binary, ~ este operator unar. Expresiile cu operatori logici
la nivel de bit au formele următoare
# include <iostream.h>
int main()
{
int a = 0x6dbc, b;
cout << “a = “ << hex << a << endl;
// selecteaza ultimul octet
b = a & 0xff;
cout << “b = “ << hex << b << endl;
b = a & 0x3ff;
// selecteaza ultimii 10 biti
cout << “b = “ << hex << b << endl;
return 0;
}
3.1 Algoritme
Rezultatul evaluării unei expresii relaţionale este fals sau adevărat. 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.
Reamintim că în limbajul C++ valoarea true este reprezentată prin 1 iar valoarea false
prin 0. 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 valori booleene, true sau false.
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
not ¬
and ∧
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
¬( a ∨ b) = ¬a ∧ ¬b
¬( a ∧ b) = ¬a ∨ ¬b
care se demonstrează cu ajutorul tabelelor de adevăr.
In final menţionăm că operatorii booleeni and şi or sunt
• Comutativi
a ∨b = b ∨a
a ∧b = b ∧a
• asociativi la stânga
a ∨b ∨c = ( a ∨ b) ∨ c
a ∧b ∧ c = ( a ∧ b) ∧ c
• distributivi
a ∨ (b ∧ c) = ( a ∨ b) ∧ ( a ∨ c )
a ∧ (b ∨ c) = ( a ∧ b) ∨ ( a ∧ c )
In final prezentăm prioritatea (precedenţa) şi asociativitatea operatorilor
Operator asociativitate
[]() stânga
++ -- + - ! ~ sizeof dreapta
*/% stânga
+- stânga
<< >> stânga
< <= > >= stânga
= = != stânga
& stânga
^ stânga
| stânga
&& stânga
|| stânga
= += -= *= /= %= stânga
do
instrucţiune
while(expresie);
Instrucţiunea se execută atâta timp cât expresia este diferită de zero. Instrucţiunea din
diagrama sintactică este o secvenţă de instrucţiuni.
Ca exemplu vom calcula valoarea 5!.
5
5!= ∏i
i =1
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)
# include <iostream.h>
/* calculul valorii 5! */
int main()
{
int i, n;
i = 1;
n = 1;
do
n = n * i;
i = i + 1;
while(i < 6);
cout << “5! are valoarea “ << n << endl;
return 0;
}
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ă.
#include <iostream.h>
/* 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;
}
s = 0;
i = 0;
while(i < 4)
{
s += a[i];
i++;
}
sau
s = 0;
i = 0;
while(i < 4)
{
s += a[i++];
}
for i = 0; i <6; i = i + 1
x = 1 + 0.2 * i
y = ex + 2* x
#include <iostream.h>
# include <math.h>
int main()
{
int i;
float x, y
cout << “x” << ‘\t’ << “y” << endl;
for(i = 0; i < 6; i = i + 1)
{
x = 1 + 0.2 * i;
y = exp(x) + 2 * x + sqrt(x);
cout << x << ‘\t’ << y << endl;
}
return 0;
}
Vrem ca rezultatul să fie afişat în două coloane cu antetul x şi y. Pentru aceasta utilizăm
instrucţiunea
<< “x” << ‘\t’ << “y” << endl;
care scrie caracterele x şi y separate de caracterul ‘\t’ (tab). In program rezultatele sunt
scrise de asemenea separate de tab.
Menţionăm că instrucţiunea for se putea scrie astfel
for(i = 0; i < 6; i++)
sau
for(i = 0; i < 6; ++i)
Un alt mod de a calcula valorile expresiei de mai sus este următorul.
# include <iostream>
# include <math.h>
int main()
{
float x, y
cout << “x” << ‘\t’ << “y” << endl;
for(x = 1.0; x <= 2.0; x = x + 0.2)
{
y = exp(x) + 2 * x + sqrt(x);
cout << x << ‘\t’ << y << endl;
}
return 0;
}
Vom încheia acest paragraf cu un program care să calculeze suma a doi vectori.
# include <iostream.h>
/* calculul sumei a doi vectori */
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;
}
3.7 Operatorul if
Acest operator execută o anumită instrucţiune în funcţie dacă o anumită condiţie este
îndeplinită. Forma operatorului if este
if(expresie)
instrucţiune1;
else
instrucţiune2;
Dacă expresie este diferită de zero se execută instrucţiune1 altfel instrucţiune2.
Instrucţiunile din diagrama sintactică pot fi instrucţiuni compuse.
Vom exemplifica utilizarea acestui operator calculând suma componentelor pare şi
impare ale unui vector x cu şase elemente. Programul pseudocod este următorul
s1 = 0;
s2 = 0;
for i = 0; i < 6; i=i+1
if i % 2 = = 0
s1 = s1 + xi
else
s2 = s2 + xi
Programul este următorul
#include <iostream.h>
/* calculul sumei componentelor pare si impare ale unui vector */
int main()
{
float s1 = 0, s2 = 0;
int i;
float x[10] = {1, -12, 23.2, 0.44, 3.4, -2.3};
for(i = 0; i < 6; i = i + 1)
if(i % 2 = = 0)
s1 = s1 + x[i];
else
s2 = s2 + x[i];
cout << “suma elementelor pare este “ << s1 << endl
<< “suma elementelor impare este “ << s2 << endl;
return 0;
}
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).
3.8 Operatorul ?
Acest operator este o formă simplificată a operatorului if. Forma lui este
expresie ? instrucţiune1 : instrucţiune2;
Dacă expresie are o valoare diferită de zero se execută instrucţiune1 altfel instrucţiune2.
Vom rescrie programul anterior astfel
#include <iostream.h>
/* calculul sumei componentelor pare si impare ale unui vector */
int main()
{
float s1 = 0, s2 = 0;
int i;
float x[10] = {1, -12, 23.2, 0.44, 3.4, -2.3};
for(i = 0; i < 6; i = i + 1)
i % 2 = = 0 ? s1 = s1 + x[i] : s2 = s2 + x[i];
cout << “suma elementelor pare este “ << s1 << endl
<< “suma elementelor impare este “ << s2 << endl;
return 0;
}
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ă.
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.
Instrucţiunea break este utilizată pentru ieşirea din instrucţiunea switch.
Exemplu. Vom citi două numere întregi şi un operator +, -, *, / sau % şi vom calcula
rezultatul operaţiei corespunzătoare.
#include <iostream.h>
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 + y;
break;
case ‘-‘ :
cout << x – y;
break;
case ‘*’ :
cout << x * y;
break;
case ‘/’ :
cout << x / y;
break;
case ‘%’ :
cout << x % y;
break;
default :
cout << “eroare”;
break;
}
return 0;
}
3.10 Operatorul ,
#include <iostream.h>
/* funcţie ce calculeaza suma componentelor unui vector
Parametri de intrare
a – vector
n – dimensiunea vectorului a
*/
float suma(float a[], int n)
{
int i;
float s = 0;
// calculeaza suma componentelor
for (i = 0; i < n; i = i++)
s = s + a[i];
return s;
}
int main()
{
float x[5] = {11.3, -2.67, 0.34, -2.5, 14};
float z;
// calculeaza suma componentelor
z = suma(x, 5);
// scrie componentele vectorului
cout << “suma componentelor vectorului “ << endl;
for(i = 0, i < 5; i++)
cout << x[i] << “\t”;
cout << endl;
// scrie rezultatul
cout << “este “ << z << endl;
}
Componentele vectorului sunt scrise 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. Menţionăm că este posibil de a scrie definiţia unei funcţii după ce ea este
utilizată. In acest caz trebuie să scriem înainte de prima utilizare un şablon al funcţiei.
Şablonul unei funcţii are următoarea definiţie
tip nume(lista de parametri);
Pentru exemplul anterior şablonul poate fi
float suma(float a[], int n);
sau
float suma(float [], int);
In şablon putem să identificăm doar tipurile parametrilor.
Intr-un program putem avea mai multe funcţii cu acelaşi nume dar cu parametri
diferiţi sau cu un număr de parametri diferit. Acest lucru se numeşte
supraîncărcarea funcţiilor.
La apelarea unei funcţii, parametrii actuali şi variabilele locale ale funcţiei sunt memorate
într-o stivă. Există două moduri de a pasa paremetrii unei funcţii: prin valoare şi prin
referinţă (adresă). In cazul pasării unui parametru prin valoare, în stivă se pune chiar
valoarea parametrului, în cazul pasării prin adresă în stivă se pune adresa parametrului.
• Cazul parametrilor pasaţ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 pasaţi prin valoare nu pot fi parametri de ieşire ai
funcţiei.
• Cazul parametrilor pasaţi prin referinţă. Dacă în corpul funcţiei modificăm aceşti
parametri, valoarea lor se modifică în programul apelant (în stivă este adresa
acestor parametri). In consecinţă, pentru ca un parametru al unei funcţii să fie
parametru de ieşire, el trebuie pasat prin adresă
Pentru a vedea care este diferenţa între cele două moduri considerăm o funcţiei care să
permute valoarea a două variabile de tip întreg. O variantă necorectă este următoarea
int main()
{
int x = 7, y = 12;
cout << “valoarile initiale x = “ << x << “ y = “ << y << endl;
perm(x, y);
cout << “valoarile permutate x = “ << x << “ y = “ << y << endl;
}
int main()
{
int x = 7, y = 12;
cout << “valoarile initiale x = “ << x << “ y = “ << y << endl;
perm(x, y);
cout << “valoarile permutate x = “ << x << “ y = “ << y << endl;
}
In stivă se pun de această dată adresele variabilor x şi y deoarece parametri funcţiei sunt
de tip referinţă. Menţionăm că apelarea funcţiei se face utilizând pentru parametrul
referinţă o variabilă din programul apelant (nu o constantă sau expresie), î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 pasat 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 pasat este o
variabilă din programul apelant.
Menţionăm că în cazul în care un parametru este un tablou, în stivă se pune adresa
primului element al tabloului. In consecinţă, parametri formali tablouri pot fi
parametri de ieşire.
Exemplu. Vo defini o funcţie ce calculează suma a doi vectori x şi y de numere reale de
dimensiune n. Tipul funcţiei este void. Vectorul sumă va fi z.
/*
Calculul sumei a doi vectori
Parametri de intrare
x – vector
y – vector
n – dimensiunea vectorilor x, y, z
Parametri de iesire
z – vector, z = x + y
Preconditii
Parametri 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];
}
Vom încheia acest paragraf definind o funcţie care să calculeze media şi dispersia unui şir
de numere x1 , x2 , xn . Valoarea medie a elementelor şirului este
1 n
m = ∑ xi
n i =1
iar dispersia
1 n
d= ∑ ( xi − m) 2
n − 1 i =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 şi dispersia.
#include <iostream.h>
/*
Calculul mediei si dispersiei componentelor unui vector
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
float d = 0;
for(i = 0; i < n; i++)
d = d + (x[i] – m) * (x[i] – m);
d = d / (n – 1);
return;
}
int main()
{
float a[4] = {1.2e-1, -2.34, 1.5, 3.33};
float media, dispersia;
md(a, 4, media, dispersia);
cout << “ media = “ << media << “ dispersia = “ << dispersia << endl;
}
4.3 Recursivitatea
Funcţiile limbajelor C şi C++ sunt recursive. 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ă.
n
1 n =1
n! = ∏i n! =
i =1 n * (n −1)! n >1
N=3 S=?
După această ultimă apelare se ajunge la înstrucţiunea return, după care variabilele
corespunzând ultimei apelări a funcţiei se sterg din stivă. Stiva devine
N=3 S=6
Şabloanele ne permit să definim funcţii generice în care putem defini tipuri generale ale
parametrilor şi a valorii returnate de funcţie. Definirea unui şablon de funcţie se face cu
instrucţiunea
template <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 unui şablon de funcţie se face astfel
nume_funcţie <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 “ << b << “ 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;
}
5.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
int main()
{
int x = 3, z = -4;
// scrie variabilele inainte de permutare
cout << “x = “ << x << “ y = “ << y << endl;
perm(&x, &y);
// scrie variabilele dupa permutare
cout << “x = “ << x << “ y = “ << y << endl;
}
#include <iostream.h>
int main()
{
int a, b;
// definim variabile tip pointer
int *pa, *pb;
pa = &a;
pb = &b;
// se scrie adresa variabilei a
cout << “ adresa variabilei a “ << &a << “ “ << pa << endl;
// se scrie valoarea variabilei b
b = 15;
cout << “ valoarea variabilei b “ << b << “ “ << *pb << endl;
}
Prima instrucţiune cout va afişa de două ori acceaşi adresă. A doua instrucţiune cout va
afişa de două ori aceeaşi valoare, 15.
Menţionăm că numele unui tablou este, prin definiţie, un pointer la primul element
al tabloului. Fie de exemplu instrucţiunile
float x[7], * pf;
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. 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 întregi.
# include <iostream.h>
int main()
{
int x[5];
for(int i = 0; i < 5; i++)
cout << “indice “ << i << “ adresa “ << &x[i] << endl;
return 0;
}
In cazul instrucţiunii
y = x;
la execuţia programului valoarea de la adresa variabilei x se depune la adresa variabilei y.
Ea este echivalentă cu instrucţiunile
a = &x;
y = * a;
Valoarea de la adresa a indică adresa unei alte variabile (x în cazul nostru, deoarece s-a
executat instrucţiunea a = &x). Se aplică operatorul * asupra variabilei a şi se obţine
conţinutul acestei variabile care este adresa lui x. Conţinutul de la această adresă se pune
la adresa lui y. Reamintim că operatorul * este asociativ la dreapta.
Instrucţiunea anterioară este echivalentă şi cu instrucţiunile
a = &x;
b = &a;
y = **b;
După execuţia instrucţiunii
a = &x;
variabila a conţine adresa lui x după cum s-a explicat mai înainte.
După excuţia instrucţiunii
b = &a;
variabila b conţine adresa lui a. Instrucţiunea y = **b se execută aplicând operatorul *
asupra variabilei b, rezultatul fiind valoarea variabilei b, adică adresa variabilei a. Se
aplică operatorul * asupra acestei valori (ceea ce este achivalent cu *a) şi se obţine
conţinutul lui a care este adresa variabilei x. Valoarea de la adresa lui x se depune în y.
Ca un ultim exemplu considerăm următoarele instrucţiuni
int x, y;
int *a;
int **aa;
int ***aaa;
int ****aaaa;
Aceste instrucţiuni declară că a poate conţine o adresă de variabilă tip int, aa poate
conţine o adresă de tip int *, aaa poate conţine o adresă de tip int **, etc. Vom iniţializa
pe x şi variabilele tip pointer astfel:
x = 10;
a = &x;
aa = &a;
aaa = &aa;
aaaa = &aaa;
Instrucţiunile
y = x;
y = *a;
y = **aa;
y = ***aaa;
y = ****aaa;
produc acelaşi rezultat, adică
y = 10;
Instrucţiunile
cout << x
cout << *a
cout << **aa
cout << ***aaa
cout << ****aaaa;
produc afişarea aceleiaşi valori
x = 10
La tipărirea valorilor variabilelor de tip pointer, se tipăresc adresele conţinute de acestea
în hexazecimal. Instrucţiunile
cout << &x << ‘\t’ << a << endl;
cout << &a << ‘\t’ << aa << endl;
cout << &aa << ‘\t’ << aaa << endl;
cout << &aaa << ‘\t’ << aaaa << endl;
afişază respectiv următoarele valori:
adresa lui x
adresa lui a
adresa lui aa
adresa lui aaa
Considerăm următoarele instrucţiuni
int x, y = 4;
x = y;
Operatorul = aplicat asupra celor doi operanzi, în cazul nostru x şi y, ia valoarea de la
adresa corespunzând variabilei din stânga şi o memorează şi o memorează la adresa
corespunzătoare variabilei din dreapta. In consecinţă, în partea stângă a semnului egal
putem utiliza valorile unor variabile tip pointer.
Considerăm următoarele instrucţiuni
int x, y;
int * a;
a = &y;
x = 10;
*a = x;
Instrucţiunea
cout << “x = “ << x << “ y = “ << y << endl;
va afişa valorile
x = 10 y = 10
Modul de excuţie a acestor instrucţiuni este următorul: instrucţiunea
a = &y;
memorează în variabila a adresa lui y. Instrucţiunea
*a = x;
se execută în felul următor: operatorul * are o prioritate mai mare decât operatorul =, în
consecinţă se aplică operatorul * asupra variabilei a şi se obţine adresa lui y. Se aplică
apoi operatorul = asupra adresi lui y (şi asupra adresei lui x) şi y primeşte valoarea 10. In
consecinţă, instrucţiunile
a = &y;
*a = x;
sunt echivalente cu instrucţiunea
y = x;
Fie de exemplu următoarele instrucţiuni:
float x, y;
float * a;
float ** aa;
float *** aaa;
float **** aaaa;
Aceste instrucţiuni declară că a poate conţine adresa unei variabile tip float, aa poate
conţine adresa unei variabile de tip float *, etc. Fie instrucţiunile
a = &x;
aa = &a;
aaa = &aa;
aaaa = &aaa;
Fie acum instrucţiunea
y = 3.45e-1;
Instrucţiunile
*a = y;
**aa = y;
***aaa = y;
****aaaa = y;
au acelaşi effect, variabila x primeşte valoarea variabilei y. De exemplu
**aa = y;
aplică de două ori operatorul *. Ea se execută astfel:
se aplică operatorul * asupra variabilei aa şi se obţine conţinutul acesteia, adică adresa
variabilei a. Se aplică încă o dată operatorul * asupra rezultatului şi se obţine adresa
variabilei x. In final operatorul = atribuie variabilei x valoarea lui y.
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ă. 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.
5.2 Referinţe
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;
}
După fiecare instrucţiune de scriere, valorile f şi fr vor fi aceleaşi. După cum vom vedea
în paragraful următor, 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ă. Fie următorul program în care scriem adresa
unei variabile şi a referinţei la variabilă.
int main()
{
double dbl = 23.44.
double& dbref = dbl;
// tipareste adresele lui dbl si dblref
cout << “adresa lui dbl = “ << &dbl
<< “ adresa lui dblref = “ << &dblref << endl;
double& db2 = dbl;
double& db3 = dbref;
cout << “&dbl = “ << &dbl << “ &db2 = “ << db2
<< “ &db3 = “ << db3 << “ & dbref = “ << dbref << endl;
}
int main()
{
float a[3] = {1.22, -3.76, 16.02};
float med, disp;
md(a, 3, med, disp);
cout << “media = “ << med << endl
<< “dispersia = “ << disp << endl;
}
La apelarea unei funcţii, parametrii acesteia se pun într-o stivă, împreună cu variabilele
locale ale funcţiei. După tipul parametrului, în stivă se pune valoarea sau adresa
parametrului. Considerăm de exemplu următoarea funcţie care adună două numere reale:
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);
Tipul unei variabile pointer la această funcţie este
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.
Exemplu. Fie o funcţie f ce calculează suma a două numere întregi x, y.
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;
// prt este o variabila pointer la functia f
int (*ptr)(int, int);
// se initializeaza ptr
prt = f;
// se apeleaza functia f prin pointer
sm = (*ptr)(a, b);
cout << “suma numerelor “ << a << “ si “ << b << “ este “ sm << endl;
return 0;
}
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. Interpretarea
şabloanelor se face considerând operatorii în ordinea priorităţii lor, [] reprezintă tablouri,
() reprezintă funcţii, * reprezintă un pointer, etc.
Exemplu. Fie şablonul
float *f();
Interpretarea lui se face astfel:
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:
Prin definiţie, o variabilă tip tablou conţine adresa primului element al tabloului
(elementul cu indicele zero al tabloului). Variabila 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. Dacă scriem
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.
Exemplu. Fie un vector cu 5 componente tip float şi un vector z cu 3 componente întregi.
Se cere să scriem componentele vectorilor. Un program posibil este următorul
#include <iostream.h>
int main ()
{
float x[5] = {1.23, 6.89, -8.5e2, 0, 9.01};
int z[3] = {-11, 2, 4};
int i;
// 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;
}
Programul poate fi rescris astfel. Definim o variabilă tip pointer la tipul float ce va
conţine pe rand adresele componentelor x vectorului şi o variabilă tip pointer la tipul int
pentru vectorul z. Variabilele vor fi initializate cu adresele vectorilor.
# include <iostream.h>
int main()
{
float x[5] = {1.23, 6.89, -8.5e2, 0, 9.01};
int z[3] = {-11, 2, 4};
int i;
float * px;
int * pz;
px = x;
pz = z;
// scrie componentele vectorului x
for(i = 0; i < 5; i++)
cout << ‘\t’ << *(px + i)
cout << endl;
// scrie componentele vectorului z
for(i = 0; i < 5; i++)
cout << ‘\t’ << pz[i];
cout << endl;
return 0;
}
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
conţine adresa unui alt element de tablou. Fie din nou instrucţiunile
char buffer[20];
char * px;
px = buffer;
Expresia px + 1 este adresa variabilei buffer[1], iar *(px + 1) este chiar valoarea
componentei buffer[1], px + i este adresa variabilei buffer[i] iar *(px + i) este chiar
valoarea componentei buffer[i]. Putem deci să rescriem programul precedent astfel
# include <iostream.h>
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;
}
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 paragraph cu un program care să scrie adresele
componentelor unui vector utilizand o variabilă tip pointer
# include <iostream.h>
int main()
{
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 << i << ‘\t’ << px << endl;
px = px + 1;
}
return 0;
Funcţia
size_t strlen(const char * s);
calculează lungimea şirului s (numărul de caractere ce preced ‘\0’).
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.
Următoarele funcţii manipulează caractere tip ASCII. Prototipurile lor se află în
biblioteca <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 isspace (int c); Test dacă un caracter este spaţiu (‘ ‘, ‘\n’, ‘\t’)
int isupper (int c); Test dacă un caracter este literă mare
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
#include <stdlib.h>
#include <iostream.h>
void prtcars(const char * nume, int (*fn)(int))
{
char c;
// scrie numele functiei
cout << nume << “ : “;
/* genereaza toate caracterele si scrie pe cele pentru
care functia are valoarea adevarat */
for(c = 0; c < UCHR_MAX; ++c)
if((*fn)(c))
cout << c;
cout << endl;
}
int main()
{
ptrcars(“isdigit”, &isdigit);
ptrcars(“islower”, &islower);
ptrcars(“isupper”, &isupper);
ptrcars(“isalpha”, &isalpha);
ptrcars(“isalnum”, &isalnum);
ptrcars(“isxdigit”, isxdigit);
}
Exerciţiu. Să se scrie un program care să convertească literele mici ale unui şir în litere
mari.
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
# include <stdlib.h>
# include <iostream.h>
int main()
{
char * s1 = “-123”;
char * s2 = “1.22e-1”;
int x;
double y;
// scrie sirurile
cout << “sirul s1 : ” << s1 << endl
<< “sirul s2 : ” << s2 << endl;
// converteste sirurile in numere
x = atoi(s1);
y = atof(s2);
// scrie numerele
cout << “x = “ << x << “ y = “ << y << endl;
cout << “x * y = “ << x * y << endl;
}
Numele unui tablou este prin definiţie un pointer la primul element al tabloului.
Elementele tabloului pot fi selectate folosind operatorul *. Fie de exemplu tabloul
double x[3];
După cum am spus anterior, prin definiţie, expresiile
x[i]
şi
*(x+i)
sunt echivalente.
Considerăm tabloul
float a[2][3];
Expresia
a[i][j]
este echivalentă cu
(a[i])[j]
care, conform celor de mai sus, este echivalentă cu
*((a[i]) + j)
care este echivalentă cu expresia
*(*(a + i) +j)
Ca exemplu de utilizare a pointerilor la tablouri bidimensionale să construim o funcţie
care să scrie elementele unei matrice cu două linii şi două coloane. Funcţia poate fi
Reamintim că
a[i]
selectează un element al tabloului a, aici o linie, deci
*(a + i)
selectează o linie a tabloului a.
argv reprezină un vector de pointeri la character, 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ă.
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 cutie de dialog a mediul de
programare.
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 plus un caracter ‘\n’ (return).
• 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 sfarşit a fişierului.
In program fiecare fişier este asociat unui 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. 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. La deschiderea în citire sistemul de operare caută pe disc fişierul cu numele
specificat şi memorează adresele de început şi de sfarşit ale fişierului. 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 fişierului cu adresa ultimului octet al fişierului.
Fişierele binare 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)
Prototipurile funcţiilor ce efectuează operaţii intrare/ieşire se află în biblioteca
<stdio.h>. 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. Parametrii acestei funcţii sunt
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
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 indicatorul de sfarşit al fişierului. La întalnirea sfarşitului de fişier
funcţiile de citire returnează o valoare specifică. Citirea secvenţială a unui fişier se
face cu instrucţiunea while ce va testa acest indicator.
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
Aceste funcţii scriu sau citesc valori în sau din fişiere pe disc, fişierele standard sau şiruri
de caractere. 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);
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);
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
%e – număr real cu exponent
După % poate urma un număr întreg care dă lungimea minimă a câmpului în cazul
instrucţiunii printf şi lungimea maximă a campului î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ă.
Exemplu. Vom scrie un program care calculează valoarea unei expresii
1 +cos( 2 x ) + 2 sin( x )
x +ln x
pentru valori ale lui x cuprinse între 1 şi 2 cu pasul 0.2. Valorile expresiei vor fi scrise pe
ecran şi într-un fişier cu numele rez.txt. Specificatorii de format vor fi %4f pentru
variabila x şi %6f pentru expresie. Valoarea lui x va ocupa 4 caractere iar cea a lui z 6
caractere. Valorile vor fi separate de un caracter tab, ‘\t’. Formatul se încheie cu un
caracter ‘\n’.
#include <stdio.h>
#include <math.h>
int main void()
{
FILE * fp;
fp = fopen(“rez.txt”, “w”);
int i;
float x, z;
// scrie antetul
printf(“x\tf(x) \n”);
fprintf(fp, “x\tf(x) \n”);
// 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(“ %4f \t %6f \n”, x, z);
fprintf(fp, “ %4.1f \t %6.2f \n”, x, z);
x = x + 0.2;
}
fclose(fp);
return 0;
}
#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);
}
Menţionăm 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. Consideram un vector cu 5 componente numere reale. Vom afişa componentele
vectorului sub forma
Element Valoare
x[0] …..
x[1] …..
# include <stdio.h>
int main()
{
double x[5] = {1.23, -23, 0.98, 4.12, 5.1};
int i;
printf(“Element\tValoare”);
// scrie componentele vectorului
for(i = 0; i < 5; i++)
printf(“x[%d] \t\t%d\t”, i, x[i]);
return 0;
}
Funcţia
int fgetc(FILE * stream);
citeşte un character 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 character din streamul stdin. Ea este echivalentă cu funcţia fgetc(stdin);
Funcţia
int fputc(int c, FILE * stream);
scrie caracterul c în streamul specificat de stream. Funcţia returnează caracterul scris sau
constanta EOF în caz de eroare.
Funcţia
int putchar(int c);
scrie caracterul c in streamul stdout.
Funcţia
int ungetc(int c, FILE * stream);
pune caracterul c în streamul de intrare.
Următoarele funcţii citesc sau scriu linii din fişiere tip text.
Funcţia
char * fgets(char * s, int n, FILE * stream);
citeşte cel mult n – 1 caractere în vectorul s. Ea se opreşte dacă întâlneşte caracterul ‘\n’
sau sfârşitul de fişier. 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 retuurnează valoarea NULL.
Funcţia
int fputs(const char * s, FILE * stream);
scrie şirul de caractere s (terminat cu ‘\0’) în fluxul stream. Caracterul ‘\0’ nu este scris.
Funcţia
int puts(char * s);
scrie şirul de caractere s (terminat cu ‘\0’) în fluxul stdout. Caracterul ‘\0’ nu este scris.
Funcţia scrie şi caracterul ‘\n’ după ce a scris şirul s.
Reamintim că funcţia fputs nu scrie caracterul ‘\n’ în fişier, în timp ce funcţia puts scrie
caracterul ‘\n’ în fişierul stdout după şir. In consecinţă, pentru a afişa câte un şir de
caractere pe rand, la scrierea în fişierul stdout cu funcţia fputs vom scrie
fputs(x, stdout);
fputs(“\n”, stdout);
read(file)
while(! eof(file))
{
prelucreaza blocul citit
read(file)
}
In cazul citirii caracterelor de la tastatură, sfarşitul de fişier este indicat prin CTRL+Z.
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. Apoi se va citi câte un caracter pană la întalnirea sfarş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.
Exemplu. Vom face un program care să copieze un fişier existent în altul. Fie un fişier cu
numele file1.txt în directorul curent. Vom copia acest fişier în fişierul cu numele file2.txt
tot în directorul curent. Vom deschide cele două fişiere şi vom testa dacă operaţia a avut
succes. Funcţia fopen are ca rezultat valoarea NULL dacă operaţia nu a avut succes. Apoi
se citeşte câte un caracter din primul fişier şi se scrie în al doilea.
# include <stdio.h>
int main ()
{
FILE * fin, * fout;
char nume1[64], nume2[64];
// citeste numele primului fisier
printf(“introduceti numele fisierului ce va fi copiat\n”);
scanf(“%s”, nume1);
fin = fopen(nume1, "r");
if(fin == NULL)
{
printf("fisierul %s nu exista\n", nume1);
return 0;
}
// citeste numele noului fisier
printf(“introduceti numele noului fisier\n”);
scanf(“%s”, nume2);
fout = fopen(nume2, "w");
if(fout == NULL)
{
printf("fisierul %s nu se poate crea", nume2);
return 0;
}
// copiaza fisierul
int c;
c = fgetc(fin);
while(c != EOF)
{
fputc(c, fout);
c = fgetc(fin);
}
fclose(fin);
fclose(fout);
return 0;
}
int c;
while((c = fgetc(fin)) != EOF)
fputc(c, fout);
Pentru scrierea şi citirea de date din fişiere binare există funcţiile fread şi fwrite. Aceste
funcţii pot citi sau scrie unul sau mai multe blocuri de date.
Funcţia
size_t fread(void * ptr, size_t size, size_t nmb, FILE * stream);
citeşte în vectorul ptr cel mult nmb blocuri de dimensiune size din fişierul stream. Funcţia
returnează numărul efectiv de blocuri citite. Indicatorul de poziţie al fişierului este
avansat cu numărul de octeţi citiţi. In cazul întâlnirii sfarşitului de fişier, funcţia
returnează valoarea 0.
Funcţia
size_t fwrite(const void * ptr, size_t size, size_t nmb, FILE * stream);
scrie în vectorul ptr cel mult nmb blocuri de dimensiune size în fişierul stream. Funcţia
returnează numărul efectiv de blocuri scrise. Indicatorul de poziţie al fişierului este
avansat cu numărul de octeţi scrişi.
Pentru citirea elementelor în ordine aleatoare există posibilitatea de a modifica
indicatorul de poziţie al fişierului cu funcţia
int fseek (FILE * stream, long int offset, int whence);
Noua valoare a indicatorului fişierului este obţinută adăugând valoarea offset la poziţia
specificată de whence care poate fi
SEEK_SET începutul fişierului
SEEK_CUR poziţia curentă a indicatorului
SEEK_END sfârşitul fişierului
După o instrucţiune fseek, următoarea instrucţiune poate fi o operaţie de citire sau scriere.
Funcţia
long ftell(FILE* stream);
are ca rezultat valoarea indicatorului de poziţie al fişierului.
Exemplu. Vom 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,
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., o expresie de forma
‘0’ + i
unde i ia valorile 0, 1, 2, …
#include <stdio.h>
int main()
{
FILE * fil;
int i, j;
char x[15];
// deschide fisierul in creare
fil = fopen(“fil.txt”, “wb”);
if(fil == NULL)
{
printf(“fisierul nu se poate crea\n”);
return 0;
}
for(i = 0; i < 10; i++)
{
// creaza un bloc
for(j = 0; j < 14; ++j)
x[j] = ‘0’ + i ;
x[14] = 0;
// scrie blocul in fisier
fwrite(x, 15, 1, fil);
}
// inchide fisierul
fclose(fil);
// deschide fisierul in citire
fil = fopen(“fil.txt”, “rb”);
if(fil == NULL)
{
printf(“fisierul nu se poate deschide\n”);
return 0;
}
int xct;
// citeste un sir
xct = fread(x, 15, 1, fil);
while(xct != 0)
{
// scrie sirul pe ecran (in streamul stdout)
printf(“%s\n”, x);
// citeste un sir
xct = fread(x, 15, 1, fil);
}
// inchide fisierul
fclose(fil);
}
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 0;
}
for(i = 0; i < 15; i++)
{
// creaza un bloc
for(j = 0; j < 9; ++j)
x[j] = ‘a’ + i + j;
x[9] = 0;
// scrie blocul in fisier
fwrite(x, 10, 1, fil);
// scrie sirul pe ecran (in fluxul stdout)
puts(x);
}
// inchide fisierul
fclose(fil);
// deschide fisierul in citire
fil = fopen(“fil”, “rb”);
if(fil == NULL)
{
printf("fisierul nu se poate citi\n");
return 0;
}
printf("fisierul citit\n");
// citeste sirurile pare
for(i = 0; i < 15; i += 2)
{
// pozitioneaza indicatorul fisierului
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);
}
O structură este un tip de date definit de utilizator. O structură este o colecţie de date de
tipuri diferite. Datele dintr-o structură vor fi numite componente sau câmpuri. Definirea
unei structuri se face cu instrucţiunea
struct nume {liste de declaraţii de tip };
unde nume este noul tip de date.
De exemplu, o 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
struct nume listă de variabile;
De exemplu, într-un program putem defini numere complexe a şi b
struct complex a, b;
O structură poate fi iniţializată la declarare. Putem scrie
complex n = {1.1, -2.34};
Adresarea unui element al unei structuri se face cu operatorul .
nume.membru
Putem da valori variabilelor definite anterior
a.real = 1.0;
a.imag = -1.2 + sin(0.3);
Putem utilize valorile componentelor unei structuri în expresii
float x;
x = a.real * a.imag;
Pentru a defini funcţii ce au ca argumente sau rezultat structuri în limbajul C,
definim un nou tip de date corespunzand structurii cu instrucţiunea typedef. 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
{
float compa;
float compb;
} 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.
int main()
{
vector cx, cy, cz;
cx.compa = 1;
cx.compb = 1;
cy.compa = 0;
cy.compb = 1;
cz = addcmp(cx, cy);
printf(“suma este (%f , %f) \n”, cz.compa ,cz.compb);
}
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ă este
struct nume * identificator;
sau
numetip * identificator;
dacă am definit un tip de structură cu instrucţiunea typedef.
unde nume este numele structurii iar 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 camp al structurii este adresat astfel
(*p).membru
Prin definiţie, această scriere se prescurtează
p->membru
Operatorii . şi -> au aceeaşi prioritate ca operatorii () şi [].
Exemplu. Fie instrucţiunile
complex y;
struct complex * py;
py = &y;
Putem iniţializa structura y, utilizand pointerul py
py->real = -23.4;
py->imag = 1.2;
Putem iniţializa direct componentele structurii
y.imag = 1.24;
y.real = 0.23;
Putem utiliza valorile variabileleor definite de structura y.
float fm = pc->imag;
Sunt corecte şi scrierile
(*pc).real = cos(1.23);
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ă.
# 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);
}
Cap. 8 Clase
In programe creăm variabile de tipul unor clase care se numesc obiecte. Vom spune că
obiectele sunt instanţe 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
• Primul principiu al programării orientate obiect este încapsularea. Acesta cere
combinarea datelor şi metodelor într-o singură structură de date. Acest principiu
cere ascunderea structurii de date şi asigurarea 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 mult încapsulăm detaliile şi înmulţim două numere complexe fără să ne
intereseze cum se face operaţia în detaliu.
• Moştenirea. Acest principiu cere definirea unei clase generale ce conţine
caracteristicile comune ale mai multor elemente. Apoi această clasă este
moştenită de alte clase particulare, fiecare adaugă doar elementele proprii. Clasa
care este moştenită se numeşte clasă de bază, iar clasele care moştenesc se
numesc clase derivate.
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.
Clasele sunt definite utilizand cuvantul cheie class. Specificatorii de acces sunt public,
protected şi private, identificator este numele clasei, iar membru este o declaraţie de
funcţie membru sau dată a clasei. Implicit toţi membri clasei sunt privaţi. Ca exemplu
vom defini o clasă care reprezintă numere raţionale ca un cât a două numere întregi.
class Ratio
{
private:
int num, den;
public:
void assign(int, int);
double convert();
void invert();
void print();
};
Numele clasei este Ratio. Definiţia clasei este inclusă între accolade { şi }, urmate de ;.
Date membre ale clasei sunt num şi den şi ele vor memora numărătorul şi numitorul
numărului raţional descris de clasă. Ele sunt de tip int ş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 ascunderea
informaţiilor. Funcţiile membre ale clasei sunt assign(), ce atribuie valori numărului
raţional, convert(), ce dă valoarea numărului, invert(), ce inversează numărul şi print(), ce
scrie numărul ca un cât de două numere întregi. Ele sunt declarate publice şi vor putea fi
utilizate de obiecte în afara clasei. Funcţiile membre pot fi definite în interiorul clasei sau
în afara ei. Funcţiile definite în interiorul clasei sunt funcţii inline. Compilatorul
generează direct textul lor în program, şi nu un apel la funcţie.
In limbajul C++ avem posibilitatea de a grupa anumite nume în spaţii de nume.
Prin definiţie, datele şi funcţiile unei clase constituie un spaţiu de nume ce are
numele clasei. Operatorul de rezoluţie :: arată că un nume de dată sau de funcţie
aparţine unui spaţiu de nume. Operatorul de rezoluţie :: se utilizează pentru a
defini o funcţie membru a clasei în afara clasei.
Implementarea clasei este următoarea
double Ratio::convert()
{
return double(num) / den;
}
void Ratio::invert()
{
int temp;
temp = num ;
num = den;
den = temp;
}
void Ratio::print()
{
cout << num << “/” << den;
}
Apelul unei funcţii a clasei de către un obiect se face conform următoarei diagrame
sintactice
Utilizarea unui câmp al un obiect se face conform următoarei diagrame sintactice
Vom exemplifica utilizarea clasei definind un număr rational şi calculând inversul lui.
int main()
{
// creaza un obiect x de tipul Ratio
Ratio x;
x.assign(2, 7);
// scrie x
cout << “x = “ ;
x.print();
cout << “ = “ << x.convert() << endl;
// inverseaza numarul
x.invert();
cout << “1/x = “ <<;
x.print();
cout << endl;
}
Declararea unui obiect se face în acelaşi fel cu declararea tipurilor standard. Vom
spune că un obiect este o instanţă a unei clase. Orice obiect are propriile variabile.
In funcţia main(), x este declarat ca un obiect de tipul Ratio (x este o instanţă a clasei
Ratio). El are cele două data membre num şi den şi poate apela funcţiile membre assign(),
invert() şi print() care au fost declarate publice în definiţia clasei.
void Ratio::invert()
{
int temp;
temp = this->num;
this->num = this->den;
this->den = temp;
}
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
}
De exemplu, putem defini următorul spaţiu
namespace general
{
int a, b;
}
8.2.1 Constructori
#include <iostream>
using namespace std;
class Ratio
{
private:
int num, den;
public:
void assign(int, int);
double convert();
void invert();
void print();
Ratio(int, int);
};
Vom prezenta în continuare doar definiţia constructorului, restul definiţiilor fiind cele
anterioare.
Ratio::Ratio(int a, int b)
{
num = a;
den = b;
}
Vom exemplifica utilizarea acestei clase definind două obiecte şi afişând valorile lor.
int main()
{
Ratio x(-2, 12), y(4, 5);
cout << “x = “;
x.print();
cout << endl;
cout << “y = “;
y.print();
cout << endl;
return 0;
}
Orice clasă are cel puţin doi constructori. Ei sunt identificaţi de declaraţ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, constructorul implicit este definit de
compilator.
Constructorul copiere este apelat atunci când un obiect este copiat. De exemplu
instrucţiunea
X a(b);
declară un obiect a de tipul X şi îi atribuie ca valoare obiectul b. Dacă nu definim un
constructor copiere, el este definit de compilator. Menţionăm cuvântul cheie const
din definiţia constructorului copiere. El interzice modificarea argumentului în
timpul copierii.
Orice clasă include un operator implicit de atribuire = care are ca parametru un
obiect de tipul clasei.
Exemplu. Fie din nou clasa Ratio în care definim un constructor copiere, un constructor
fără parametri, şi un constructor cu parametri.
# include <iostream>
using namespace std;
class Ratio
{
public:
Ratio(int, int);
Ratio(const Ratio& x);
Ratio();
void print();
void invert();
private:
int num;
int den;
};
Vom prezenta doar definiţiile constructorilor, definiţiile funcţiilor print() şi invert() sunt
cele anterioare.
Ratio::Ratio(const Ratio& x)
{
num = x.num;
den = x.den;
}
Ratio:: Ratio()
{
num = 0;
den = 1;
}
Ratio::Ratio(int x, int y)
{
num = x;
den = y;
}
int main()
{
Ratio x(12, 25);
Ratio z;
Ratio y(x); // se apeleaza constructorul copiere
x.print();
y.print();
z = x;
z.print();
}
Exemplu. Presupunem clasa Ratio definită anterior. Fie o funcţie globală f care are ca
parametru un obiect de tip Ratio (un număr raţional) şi ca rezultat un obiect de tip Ratio
care este inversul parametrului funcţiei. Definitia funcţiei este următoarea
8.2.2 Destructori
Destructorul unei clase este apelat automat când un obiect este distrus. Fiecare clasă are
un singur destructor. Destructorul nu are tip şi nici parametri. Diagrama sintactică a
destructorului este următoarea
~ nume_clasa(){/* corpul destructorului*/}
Dacă el nu este definit explicit, compilatorul generează unul. Pentru a vedea cum sunt
apelaţi constructorii şi destructorul fie următoarea clasă în care constructorul şi
destructorul scriu un mesaj.
class Ratio
{
public:
Ratio() {cout << “obiectul este creat” << endl;}
~Ratio() {cout << “obiectul este distrus” << endl;}
private:
int num;
int den;
};
Fie două blocuri de instrucţiuni în funcţia main. In fiecare bloc creăm câte un obiect.
int main()
{
{
cout << “incepe blocul 1” << endl;
Ratio x;
cout << “iesire blocul 1” << endl;
}
{
cout << “incepe blocul 2” << endl;
Ratio y;
cout << “sfarşit blocul 2” << endl;
}
}
incepe blocul 1
obiectul este creat
iesire blocul 1
obiectul este distrus
incepe blocul 2
obiectul este creat
iesire blocul 2
obiectul este distrus
Menţionăm că toate variabilele locale unei funcţii sau unui bloc sunt create intr-o stivă la
intrarea în funcţie sau bloc şi sunt şterse din stivă la ieşirea din funcţie sau bloc. Stiva se
numeşte heap.
In general avem nevoie să utilizăm şi să modificăm datele unui obiect în timpul execuţiei
programului. Dacă datele sunt declarate private, nu putem face acest lucru direct. De
aceea se definesc funcţii care să modifice şi să furnizeze valorile datelor unui obiect. In
cazul clasei Ratio putem defini funcţii membre ale clasei
int getnum();
int getden();
void setnum(int);
void setden(int);
Aceste funcţii se numesc funcţii de acces şi de regulă sunt publice.
Definiţiile acestor funcţii pot fi
int Ratio::getnum()
{
return num;
}
void Ratio::setnum(int x)
{
num = x;
}
Exemplu. Vom defini o funcţie globală care să adune două numere raţionale. Prototipul
funcţiei va fi
Ratio sum(const Ratio x, const Ratio y);
Parametrii x şi y sunt cele două numere raţionale ce sunt adunate. Deoarece obiectele nu
vor fi modificate, ele au fost declarate const.
a c ad + bc
Considerăm două numere rationale, r = şi s = . Suma lor va fi t = . In
b d bd
implementarea funcţiei nu putem folosi direct variabilele num şi den, deoarece ele sunt
declarate private în definiţia funcţiei. Vom utiliza funcţiile getnum() şi getden() pentru a
obţine valoarea lor. Implementarea funcţiei este următoarea
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 Ratio este
class Ratio
{
public:
Ratio(int, int);
Ratio(const Ratio& x);
Ratio();
void print();
void invert();
friend Ratio sum(Ratio x, Ratio y)
private:
int num;
int den;
};
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
Limbajul C++ defineşte clasele istream şi ostream pentru operaţii intrare şi ieşire cu
fişierele standard, tastatura şi ecranul. Streamul cin este un obiect de tipul istream, iar
streamurile cout, cerr şi clog sunt obiecte de tipul ostream. Aceste obiecte sunt definite în
bibliotecile <iostream.h> şi <iostream>.
Clasa istream defineşte operatorul de citire >> şi funcţii ce citesc caractere ASCII şi
şiruri tip C. Funcţiile
int get();
istream& get(char&);
citesc un character. Funcţiile
istream& get(char* s, int size, char ch);
istream& getline(char* s, int size, char ch);
citeşte cel mult size - 1 caractere în vectorul s. Citirea se opreşte după citirea a size - 1
caractere sau la întalnirea caracterului ch. Valoarea implicită a caracterului ch este ‘\n’.
Clasa ostream defineşte operatorul de scriere << şi funcţia
ostream& put(char);
ce scrie un character.
Exemplu. Fie instrucţiunea
char c;
Instrucţiunile
cin.get(c);
şi
c = cin.get();
citesc un caracter de la tastatură în variabila c.
Menţionăm că operatorul >> este un operator binar. Operandul stâng este un obiect tip
istream, oparandul drept este o variabilă, rezultatul este o referinţă la un obiect tip
istream. Ţinând cont de prototipul funcţiei get
istream& get(char&);
este posibil să scriem
char c;
int x;
cin.get(c) >> x;
deoarece operandul stâng este o referinţă tip istream, rezultatul evaluării funcţiei
get(char&).
Toate operaţiile de scriere de până acum în streamul cout nu au utilizat un format. Este
posibil de a preciza formatul informaţiei scrise în felul următor
• putem insera spaţii sau caracterul ‘\t’
cout << s << “ “ << ‘\t’ << x << endl;
• putem insera manipulatori definiţi în biblioteca <iomanip> sau <iomanip.h>.
Implicit, orice valoare este scrisă pe un număr de caractere specificat sau pe
numărul de caractere necesare. De exemplu, numărul întreg 123 este totdeauna
scris pe cel puţin 3 caractere. Funcţia
setw(int w);
dă dimensiunea câmpului în care este scrisă valoarea. Dimensiunea implicită a
câmpului este 0. Funcţia
setfill(char c);
dă caracterul c cu care sunt umplute spaţiile libere. Valoarea implicită este spaţiu.
Exemplu. Fie variabila
int k = 192;
scrisă cu diverse dimensiuni ale campului şi caractere de umplere.
Funcţia setw() care dă dimensiunea câmpului este valabilă doar pentru câmpul
următor.
Funcţia
setprecision(int p);
dă numărul p de caractere pe care este scris un număr real, tip float sau double.
Exemple. Fie variabila
float x = 3.1459;
scrisă cu diferite precizii. De remarcat că valoarea scrisă este rotunjită.
Operatorii limbajului C++ sunt automat definiţi pentru tipurile fundamentale: int, double,
char, etc. Când definim o clasă nouă, creăm un nou tip. Operatorii limbajului pot fi
definiţi şi pentru tipurile nou create. Această operaţie se numeşte supraîncărcarea
operatorilor (operator overloading). Pentru a supraîncarca un operator definim o
functie de forma
tip operator semn (parametri) {/* corpul funcţiei */}
unde semn este operatorul dorit +,-, *, / , [], (), <<, >>, =, etc. Operatorii supraîncărcaţi
au aceeaşi prioritate ca cei originali şi acelaşi număr de parametri. Menţionăm că orice
operator supraîncărcat poate fi definit ca funcţie membră a clasei sau ca funcţie globală.
Exemplu. Să supraîncărcăm operatorul + pentru clasa Ratio, care să adune două numere
raţionale. Operatorul va fi definit ca o funcţie globală.
Se va compara această funcţie cu funcţia sum definită anterior. Funcţia are doi parametri,
numerele de adunat deoarece este o funcţie globală. Putem utiliza funcţia definită astfel
int main()
{
Ratio x(2, 3), y(3, -5), z;
z = x + y;
z.print();
return 0;
}
Putem defini operatorul ca funcţie prietenă a clasei. In definiţia clasei vom scrie
instrucţiunea
friend Ratio operator+ (Ratio, Ratio);
In acest caz definiţia funcţiei este
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 Ratio. 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 Ratio cu operatorul + este
class Ratio
{
public:
Ratio(int, int);
Ratio(const Ratio& x);
Ratio();
void print();
void invert();
Ratio operator+(Ratio); // functia are un singur parametru
private:
int num;
int den;
};
Ratio Ratio::operator+(Ratio x)
{
Ratio temp(num * x.den + den * x.num, num * x.den);
return temp;
}
# include <iostream>
using namespace std;
class Ratio
{
private:
int num, den;
public:
Ratio();
Ratio(const Ratio&);
void operator = (const Ratio&);
};
class Ratio
{
private:
int num, den;
public:
Ratio();
Ratio(const Ratio&);
Ratio& operator = (const Ratio&);
}
class Complex
{
private:
float real;
float imag;
public:
Complex();
Complex (float, float);
};
Problema scrierii funcţiei este aceea că datele clasei Complex sunt declarate private şi nu
pot fi utilizate direct de funcţie, care este globală. O soluţie este de a scrie două funcţii
membre ale clasei, de exemplu
float getreal();
float getimag();
care au ca rezultat variabilele real şi imag ale clasei. Această soluţie rămane ca exerciţiu.
O altă soluţie este următoarea. Pentru ca o funcţie externă să aibe acces la membrii
privaţi ai clasei, ea trebuie declarată în definiţia clasei ca funcţie prietenă, friend.
Diagrama sintactică a definiţiei unei funcţii prietene este
friend tip nume_funcţie ( parametri);
In consecinţă, definiţia clasei Complex va fi
class Complex
{
private:
float real;
float imag;
public:
Complex();
Complex (float, float);
friend Complex operator+(Complex, Complex);
};
class Complex
{
private:
float real;
float imag;
public:
Complex();
Complex (float, float);
Complex operator+ (Complex);
};
Complex::Complex(float x, float y)
{
real = x;
imag = y;
}
Complex::Complex()
{
real = 0;
imag = 0;
}
Complex Complex::operator+(Complex p)
{
Complex temp;
temp.real = real + p.real;
temp.imag = imag + p.imag;
return temp;
}
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 functiile de inserţie au forma
Primul parametru este o referinţă la streamul de ieşire. Al doilea este obiectul ce trebuie
inserat. Ultima instrucţiune este
return stream;
Exemplu. Operatorul de inserţie << pentru clasa Complex. In definiţia clasei definim
funcţia
friend ostream& operator << (ostream& stream, Complex x);
Implementarea funcţiei este
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 este un membru al 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. Diagrama sintactică a definiţiei unei funcţii prietene
este
friend tip nume_funcţie ( parametri);
Definiţia clasei este
class Complex
{
private:
float real;
float imag;
public:
Complex (float, float);
Complex operator+ (Complex);
friend ostream& operator << (ostream&, Complex&);
friend istream& operator >> (istream&, Complex&);
}
int main()
{
Complex a(1.0, 1.0);
Complex b(0.0, 1.0);
Complex c;
c = a + b;
// echivalent putem scrie
c = a.operator+(b);
cout << c << endl;
return 0;
}
O clasă, odată definită, este un tip valid. Putem defini variabile tip pointer la clase la fel
cu pointeri la orice tip. O variabilă tip pointer la o clasă poate fi iniţializată cu adresa unui
obiect iar datele şi funcţiile acelui obiect pot apelate utilizând pointerul.
Instrucţiunea de definire a unui pointer la o clasă este
tip * identificator;
unde tip este numele clasei iar identificator este numele variabilei tip pointer.
Variabila tip pointer poate fi iniţializată ca orice variabilă tip pointer, folosind operatorul
de calcul al adresei &.
Fie p un pointer la un obiect. Un câmp al obiectului este adresat astfel
(*p).membru
Prin definiţie, această scriere se prescurtează
p->membru
O funcţie membră a clasei se apelează astfel
(*p).funcţie(parametri);
Prin definiţie, această scriere se prescurtează astfel
p->funcţie(parametri);
Operatorii . şi -> au aceeaşi prioritate ca operatorii () şi [].
Exemplu. Fie o clasă ce defineşte un dreptunghi. Clasa are ca variabile lungimea şi
lăţimea dreptunghiului, funcţii de acces la variabilele clasei, şi o funcţie care calculează
aria.
class Rectangle
{
private:
float h;
float b;
public:
float aria();
Rectangle(float, float);
float geth();
float getb();
void seth(float);
void setb(float);
};
Rectangle::Rectangle(float x, float y)
{
h = x;
b = y;
}
float Rectangle::aria()
{
return h * b;
}
float Rectangle::geth()
{
return h;
}
void Rectangle::seth(float x)
{
h = x;
}
int main()
{
Rectangle d1(1.2, 4.5);
// defineste un pointer la obiect
Rectangle * pd1;
pd1 = &d1;
// apeleaza functiile direct
cout << “inaltimea = “ << d1.geth() << endl
<< “latimea = “ << d1.getb() <<
<< “aria = “<< d1.aria() << endl;
// apeleaza functiile prin pointer
cout << “inaltimea = “ << pd1->geth() << endl
<< “latimea = “ << pd1->getb() <<
<< “aria = “<< pd1->aria() << endl;
}
int main()
{
Rectangle * ptr;
// creaza obiectul cu operatorul new
ptr = new Rectangle(2.5, 17.1);
// functii apelate prin pointer
cout << “inaltimea = “ << ptr->geth() << endl
<< “latimea = “ << ptr->getb() <<
<< “aria = “<< ptr->aria() << endl;
// distruge obiectul
delete ptr;
return 0;
}
Putem crea vectori de obiecte cu operatorul new. Pentru aceasta clasa trebuie să aibă un
constructor fără parametri. De exemplu, putem crea un vector cu zece obiecte de tip
Complex
Complex * vect;
vect = new Complex[10];
Acest vector se şterge cu operatorul delete cu forma
delete [] vect;
10.2 Moştenirea
In această definiţie cuvântul cheie acces poate fi public, protected sau private.
Semnificaţia acestui termen va fi prezentată ulterior.
Exemplu. Vrem să definim două clase Rectangle şi Triangle care să descrie dreptunghiuri
şi respective triunghiuri. Clasele au în comun două variabile ce conţin baza şi înalţimea şi
o funcţie ce dă valori acestor variabile. Vom defini deci o clasă de bază numită Polygon
ce are două variabile h şi b, înălţimea şi lăţimea, declarate aici cu acces protejat,
(protected), un constructor fără parametri, o funcţie setval() ce dă valori acestor variabile
şi o funcţie print() ce afişază valorile variabilelor.
class Polygon
{
protected:
float h, b;
public:
void setval(float x, float y){h = x; b = y;}
Polygon() {h = 0; b = 0;}
void print() {cout << “b = “ << b << “,” << “ h = “, << h << endl;}
};
Vom defini cele două clase care descriu triunghiuri şi dreptunghiuri derivate din clasa
Polygon. Clasele vor avea o funcţie ce calculează aria. O reprezentare grafică a acestei
dependenţe este cea de mai jos.
Un exemplu de utilizare a acestor clase în care calculăm aria unui triunghi şi a unui
dreptunghi este următorul.
int main()
{
Rectangle drpt;
Triangle trgh;
drpt.setval(1.3, 4.4);
trgh.setval(2.3, 12);
cout << “triunghi” << endl;
cout << trgh.area() << endl;
cout << “dreptunghi” << endl;
cout << drpt.area() << endl;
}
Am definit variabilele clasei de bază h şi b de tipul protected. In acest caz ele pot fi
utilizate în clasele derivate, dar nu pot fi utilizate de obiecte. De exemplu, nu putem scrie
instrucţiunea
cout << trgh.h << endl;
Pentru a afişa valorile variabilelor b şi h am definit funcţia print().
Vom prezenta acum semnificaţia cuvântului cheie acces din definiţia clasei derivate. Fie
definiţia unei clase
class Baza
{
public:
int x;
protected:
int y;
}
In acest caz variabilele x şi y au în clasa Deriv specificatorul de acces protected. Ele pot
fi utilizate de funcţiile definite în clasa Deriv, dar nu pot fi utilizată de obiecte de tipul
Deriv.
Considerăm clasa derivată definită cu specificatorul de acces private
In acest caz toate variabilele x si y din clasa Baza au în clasa Deriv specificatorul de
acces private.
Menţionăm că datele şi funcţiile declarate private în clasa de bază nu pot fi utilizate
în clasele derivate sau de obiecte de tipul claselor derivate.
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 accesul la variabilele unui obiect în funcţie de specificatorii lor
Obiectul a are cele trei câmpuri din declaraţia clasei, x, y, z cu specificatorii respectiv
public, protected, private. Obiectul b are câmpurile x şi w cu specificatorul de acces
public şi y cu specificatorul protected. Câmpul z nu este moştenit de obiectul b.
O clasă derivată moşteneşte toţi membri clasei de bază exceptând constructorii,
destructorul şi operatorul =. Deşi constructorii nu sunt moşteniţi, constructorul implicit şi
destructorul sunt totdeauna apelaţi când un obiect de tipul clasei derivate este creat sau
distrus. In constructorul clasei derivate se apelează la început constructorul implicit al
clasei de bază. Dacă este nevoie, putem apela explicit un constructor al clasei de bază
pentru iniţializarea variabilelor. Apelarea sa se face astfel
constructor_clasa_derivata(lista de parametri)
: constructor_clasa_de_baza (lista de parametri)
{
/* definitia constructorului clasei derivate */
}
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 şi o funcţie clear() ce pune la valoarea
zero cele două coordinate.
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;
}
Când un obiect dintr-o clasă derivată este creat sau distrus, constructorul implicit
(fără parametri) şi destructorul clasei de bază sunt totdeauna apelaţi. De aceea am
definit şi constructorul implicit în definiţia clasei Point.
Vrem să 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. Funcţia clear() va pune la
valoarea zero coordonatele şi culoarea obiectului tip Pixel.
class Pixel : public Point
{
public:
int color;
void clear();
Pixel(int, int, int);
void print();
};
void Pixel::clear()
{
Point::clear();
color = 0;
}
void Pixel::print()
{
Point::print();
cout << “culoarea : “ << color << endl;
}
Menţionăm că nu puteam defini funcţia clear() a clasei Pixel astfel
void Pixel::clear()
{
clear();
color = 0;
}
int main()
{
Pixel p(1, 2, 15);
p.print();
return 0;
}
Considerăm exemplul anterior în care, pentru a apela funcţia area() prin pointer pentru
cele două obiecte am definit două variabile pointer de tipuri diferite. Este posibil ca un
pointer la clasa de bază să conţină adresa unui obiect al unei clase derivate.
Exemplu. Fie clasele Point şi Pixel definite anterior. Vom crea obiecte de tip Point şi
Pixel şi vom scrie valorile lor utilizând o variabilă pointer de tip Point.
int main()
{
// defineste un pointer de tip Point
Point * prt;
// creaza un obiect de tip Point
Point p(12, 240);
// atribuie variabilei prt adresa obiectului p de tip Point
prt = &p;
// afisaza coordonatele utilizand variabila pointer
prt->print();
/* creaza un obiect de tip Pixel */
Pixel px(15, 124, 5);
/* atribuie variabilei prt adresa obiectului px de tip Pixel */
prt = &px;
/* afisaza coordonatele utilizand variabila pointer */
prt->print();
return 0;
}
Menţionăm că ambele instrucţiuni
prt->print();
apelează funcţia print() a clasei Point, chiar dacă ne aşteptăm ca în cazul al doilea să
se apeleze funcţia print() a clasei Pixel.
Funcţia apelată este determinată de tipul variabilei pointer, nu de tipul obiectului.
Putem face ca funcţia apelată să fie determinată de tipul obiectului şi nu de tipul
variabilei pointer definind funcţii virtuale.
Exemplu. Vom redefini clasele Point şi Pixel astfel. Funcţie clear(), ce pune la valoarea
zero variabilele, şi funcţia print() ce afişază valoarea variabilelor vor fi declarate virtuale.
Definiţia clasei Point este următoarea
class Point
{
private:
int x, y;
virtual void clear();
virtual void print();
Point(int, int);
Point(){x = 0; y = 0;}
};
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;
}
void clear()
{
Point::clear();
color = 0;
}
void Pixel::print()
{
Point::print();
cout “ culoare = “ << p.color << endl;
}
In funcţia main() vom crea obiecte de tip Point şi apoi 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
ptr->print();
// creaza un obiect de tipul Pixel
Pixel px(1, 2, 15);
// atribuie variabilei ptr adresa obiectului px de tip Pixel
ptr = &px;
// afisaza coordonatele si culoarea pixelului
prt->print();
return 0;
}
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.
Vom spune că apelul de funcţie
ptr->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ă.
Exemplu. Vrem să definim clase care să conţină informaţii despre angajaţii unei
intreprinderi, numele şi departamentul, în cazul tuturor angajaţilor, iar în cazul
managerilor şi poziţia. Aceste informaţii vor fi şiruri de caractere. Va trebui să avem
două tipuri de obiecte, unul ce va conţine numele şi departamentul, şi altul ce va conţine
numele, departamentul şi poziţia. Pentru aceasta vom defini o clasă de bază numită
Person ce conţine numele unei persoane (un şir de caractere) şi o funcţie ce afişază
numele.
class Person
{
protected:
char * name;
public:
Person(char * s)
{ name = new char[strlen(s) + 1]; strcpy(name, s);}
virtual void print()
{cout << “name : “ << name << “\n”;}
};
Vom defini o clasă ce moşteneşte clasa Person, clasa Hired cu date despre angajaţi şi o
clasă numită Manager cu date despre manageri ce moşteneşte din clasa Hired conform
diagramei de mai jos.
Ca exemplu de utilizare a claselor create vom defini obiecte de cele două tipuri.
int main()
{
Hired x1(“Alex”, “proiectare”);
Manager m1(“Bob”, “proiectare”, “sef”);
Person * ps;
// scrie datele pentru x1
ps = &x1;
ps->print();
// scrie datele despre m1
ps = & m1;
ps->print();
return 0;
}
10.4 Date şi funcţii 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 iniţializate în afara clasei. Datele statice sunt la fel ca şi variabilele globale,
dar în spaţiul de nume al clasei.
class A
{
static int x;
};
int A::x = 0;
Datele statice există chiar dacă nu există nici un obiect de tipul clasei. Pentru exemplul de
mai sus putem utiliza variabila statică x ca A::x sau A.x sau, dacă există obiecte de tipul
clasei, ca nume_obiect.x
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ă.
# include <iostream>
using namespace std;
class X
{
public:
static int nb;
X(){nb ++;}
~X(){nb--;}
};
int X::nb = 0;
int main()
{
X a, b;
cout << “exista “ << X.nb << “ obiecte” << endl;
{
X c, d;
cout << “exista “ << X.nb << “ obiecte” << endl;
}
cout << “exista “ << X::nb << “ obiecte” << endl;
return 0;
}
Funcţiile statice sunt la fel ca şi funcţiile globale. Ele pot prelucra doar datele statice ale
clasei. Ele pot fi apelate utilizând numele clasei sau al unui obiect de tipul clasei.
Definirea unei funcţii statice se face scriind cuvantul cheie static înaintea definiţiei
funcţiei. Un exemplu de utilizare a funcţiilor statice este tehnica de programare
ClassFactory. Ea constă în următoarele. Fie o clasă de bază ş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.
Exemplu. Vom defini o clasă cu o metodă ce calculează suma a două numere şi o clasă cu
o metodă ce calculează produsul a două numere. In ambele cazuri metoda se va numi
oper(). Ele vor moşteni o clasă ce memorează cele două numere. Clasa de bază are
următoarea definiţie
# include <iostream>
using namespace std;
class Base
{
protected:
float a, b;
public:
Base() {a = 0; b = 0;}
Base(float, float);
virtual float oper(){return 0;}
};
Base::Base(float x, float y)
{
a = x;
b = y;
}
Clasele derivate definesc funcţia oper() ce efectuează adunarea sau produsul a două
numere. Ele au următoarele definiţii
Vom defini o clasă cu o metodă statică 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).
class clf
{
public:
static Base * operfactory(float, float, char);
};
Funcţia main() crează un obiect de tipul Add şi memorează adresa lui într-un pointer de
tipul clasei de bază. Apoi utilizează obiectul la calculul sumei a două numere.
int main()
{
float z;
Base * base;
clf cf;
// creaza un obiect care sa calculeze suma a doua numere
base = cf.operfactory(1.2, -2.35, '+');
// calculeaza suma numerelor
z = base->oper();
cout << z << endl;
return 0;
}
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
cout o instanţă a clasei ostream. 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 din clasele istream şi ostream. Definiţiile acestor clase se găsesc
în biblioteca <fstream>. O serie de constante utilizate de aceste clase sunt definite în
clasa ios. Pentru prelucrarea unui fişier se crează un obiect instanţă a uneia din clasele de
mai sus care este denumit stream. Diagrama moştenirii acestor clase este cea de mai jos.
Funcţiile membre importante ale claselor sunt
• funcţia membră open() cu prototipul
open(char * filename, int mode);
asociază obiectul cu fişierul. 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.
• funcţia
close();
închide un fişier.
• funcţia
eof()
are valoarea adevărat dacă s-a detectat sfârşitul fişierului
• funcţia
is_open()
are valoarea adevărat dacă fişierul este deschis
Există două tipuri de operaţii intrare/ ieşire pentru fişiere tip text
• funcţii pentru intrări / ieşiri cu format
• funcţii ce scriu / citesc caractere
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
cos 2 ( x) + x
e= + sin( x)
1+ x
pentru x cuprins în intervalul [0, 2] cu pasul 0.2. Vom scrie valorile calculate într-un
fişier cu numele rez.txt.
# include <fstream>
# include <iostream>
# include <cmath>
using namespace std;
int main()
{
char * filename = “rez.txt”;
ofstream fil1;
fil1.open(filename);
if(!fil1.is_open)
{
cout << “ Nu se poate crea fisierul “ << filename << endl;
return 0;
}
// creaza fisierul
int i;
double x, e;
for(i = 0; i < 11; i++)
{
// calculeaza expresia si scrie in fisier
x = i * 0.2;
e = (cos(x) * cos(x) + x) / (1.0 + fabs(x)) + sin(x);
fil1 << x << ‘\t’ << e << endl;
}
fil1.close();
// citeste fisierul creat si afisaza rezulatele
ifstream fil2;
fil2.open(filename);
if(!fil2.is_open())
{
cout << “ nu se poate deschide fisierul “ << filename << endl;
return 0;
}
// scrie antetul
cout << "x" << '\t' << "e" << endl;
fil2 >> x >> e;
while(!fil2.eof())
{
cout << x << ‘\t ‘ << e << endl;
fil2 >> x >> e;
}
fil2.close();
return 0;
}
# include <iostream>
# include <fstream>
using namespace std;
int main()
{
char filename [24];
cout << “Introduceti numele fisierului de copiat” << endl;
cin >> filename;
ifstream fila(filename);
if(!fila.is_open())
{
cout << endl << “Fisierul “ << filename << “ nu exista “ << endl;
return 0;
}
cout << endl << “Introduceti numele noului fisier” << endl;
cin >> filename;
ofstream filb(filename);
if(!filb.is_open())
{
cout << “Fisierul “ << filename << “ nu se poate crea” << endl;
return 0;
}
char car;
int nl = 0;
fila.get(car);
while(!fila.eof())
{
filb.put(car);
nl++;
fila.get(car);
}
fila.close();
filb.close();
cout << "fisierul " << filename << “ are ” << nl << " caractere" << endl;
return 0;
}
Menţionăm că expresia
fila.get(car)
are valoarea fals la întâlnirea sfarşitului de fişier. In consecinţă, puteam copia fişierul
astfel
while(fila.get(car))
{
filb.put(car);
nl++;
}
# include <iostream>
# include <fstream>
using namespace std;
int main()
{
char filename[] = “fil.txt”;
char x[11];
ofstream filx;
// creaza fisierul
filx.open(filename, ios::out | ios::binary);
if(!filx.is_open())
{
cout << “ Nu se poate crea fisierul “ << filename << endl;
return 0;
}
int i, j;
for(i = 0; i < 26; i++)
{
// creaza un bloc
for(j = 0; j < 10; j++)
x[j] = ‘a’ + i;
x[10] = 0;
// scrie blocul in fisier
filx.write(x, 11);
}
filx.close();
ifstream fily;
// citeste si afisaza fisierul
fily.open(filename, ios::binary | ios::in);
if(!fily.is_open())
{
cout << “Nu se poate citi fisierul “ << filename << endl;
return 0;
}
// citeste si afisaza fisierul
for(i = 0; i < 26; i = i + 4)
{
// modifica indicatorul fisierului
fily.seekg(i * 11, ios::beg);
fily.read(x, 11);
cout << x << endl;
}
fily.close();
return 0;
}
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, <, <=, >, >= , = = şi != ce au ca rezultat valorile
true sau false.
Exemple.
if(x < x1)
cout << x1;
else
cout << x;
Funcţii membre importante ale clasei sunt
int length(); – dă lungimea şirului
int size(); – dă lungimea şirului
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 s-au atribui o
valoare unui caracter din şir.
Exemplu. Să scriem un şir, caracter cu caracter.
string s = “abcd”;
for(j = 0; j < s.length(); j++)
cout << s[j];
Operatorii << şi >> scriu şi citesc şiruri tip string.
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
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 şterge un subşir din şir. Ea are prototipul
erase(int index, int size)
Funcţia ş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”
Exemplu. Vom rescrie un exemplu anterior în care am utilizat şiruri tip C şi vom utiliza
acum şiruri tip string. Vom defini o clasă de bază numită Person ce conţine numele unei
persoane (un şir de caractere) şi o funcţie ce afişază acest şir (numele persoanei).
class Person
{
protected:
string name;
public:
Person(string s)
{ name = s;}
virtual void print()
{cout << “name : “ << name << “\n”;}
};
Vom defini o clasă ce moşteneşte clasa Person, clasa Hired cu date despre angajaţi şi o
clasă numită Manager cu date despre manageri ce moşteneşte din clasa Hired.
int main()
{
Hired x1(“Alex”, “proiectare”);
Manager m1(“Bob”, “proiectare”, “sef”);
Person * ps;
// scrie datele pentru x1
ps = &x1;
ps->print();
// scrie datele despre m1
ps = & m1;
ps->print();
return 0;
}
13.1. Excepţii
In timpul execuţiei programului pot apare diverse erori. Un tip de erori sunt cele asociate
operaţiilor intrare/ ieşire : încercăm să citim un fişier inexistent, vrem să creăm un fişier
pe disc dar discul este plin, etc. Al tip de erori sunt cele legate de operaţiile aritmetice :
împărţirea prin zero, indici eronaţi, depăşirea gamei de reprezentare a numerelor, etc.
Limbajul C++ are un mecanism de tratare a excepţiilor ce permite ca atunci când apare o
eroare (o excepţie) programul să apeleze automat o funcţie de tratare a erorii. Această
funcţie are ca parametru un obiect cu informaţii despre eroare. Tratarea excepţiilor se
face cu instrucţiunile try, catch şi throw. Instrucţiunea pe care o urmărim pentru apariţia
unei excepţii trebuie inclusă într-un bloc try. Dacă apare o excepţie (o eroare), ea este
lansată cu instrucţiunea throw care generează un obiect cu informaţii despre eroare şi
este prinsă cu 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
}
Tipurile argumentelor din instrucţiunile catch trebuie să fie diferite. Atunci când
apare o excepţie trebuie să executăm instrucţiunea throw cu forma
throw obiect;
Excepţia este un obiect cu informaţii despre eroare generat de instrucţiunea throw. Ea
poate fi un obiect simplu de tip int, char, double, etc., sau un obiect instanţă a unei clase
definită de programator. Intr-un bloc pot apare mai multe tipuri de excepţii. Fiecare tip de
excepţie este tratată de o anumită instrucţiune catch. Ea se determină după argumentului,
care este tipul obiectului generat de instrucţiunea throw.
Exemplu. Tratarea unei excepţii împărţire prin zero.
# include <iostream>
using namespace std;
int main()
{
int x = 2, y = 0, z;
try
{
if(y == 0)
throw y;
z = x / y;
cout << "x = " << x << " y = " << y << " x / y = " << z << endl;
}
catch(int a)
{
cout << "divide by zero" << endl;
cout << "line : " << __LINE__ << endl;
}
return 0;
}
Dacă dorim ca un singur bloc catch să preia toate excepţiile unui bloc try vom scrie
blocul catch astfel
catch(…)
{
// trateaza exceptiile
}
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
# include <iostream>
# include <string>
using namespace std;
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;
}
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.
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, un constructor cu
parametru şir de caractere şi unul copiere, operatorul = şi funcţia virtuală what() cu
prototipul
virtual char * what();
# include <iostream>
# include <exception>
using namespace std;
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(exception exm)
{
cout << exm.what() << endl;
}
return 0;
}
Cap. 14 Aplicaţii
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. Fiecare fir execută o
anumită funcţie cu prototipul
void f(void *);
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);
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 pasată firului.
• funcţia
_sleep(int ms);
suspendă execuţia firului de aşteptare pentru un număr de milisecunde dat de parametrul
ms.
Exemplu. Vom face un program care să lanseze în execuţie două fire. Funcţiile executate
de fire vor executa 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>
int main()
{
_beginthread(&fthr1, 0, NULL);
_beginthread(&fthr2, 0, NULL);
return 0;
}
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 pentru crearea şi manipularea acestor structuri. Aceste clase sunt clase
generice care au ca parametru tipul obiectelor manipulate. Biblioteca de şabloane
standard are trei componente
• Containere. Un container este o structură de date ce conţine alte obiecte. Aceste
obiecte se numesc elementele containerului. Exemple de containere sunt vectorii
şi listele.
• 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.
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
In această definiţie T1, T2, …, Tn sunt tipuri ce se pot utiliza la declararea de obiecte de
tipul clasei generice. Un obiect de tipul unei clase generice se declară cu următoarea
diagramă sintactică
Exemplu. Să definim o clasă generică X ce poate calcula pătratul unui număr întreg sau
real.
# include <iostream>
using namespace std;
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;
}
15.2 Vectori
# include <iostream>
# include <vector>
using namespace std;
int main()
{
// definim un vector cu componente intregi
vector <int> v;
// adauga 4 elemente
v.push_back(12);
v.push_back(-5);
v.push_back(7);
v.push_back(13);
// afisaza componentele vectorului
int k;
for(k = 0; k < v.size(); k++)
cout << v[k] << endl;
return 0;
}
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 / modificarea valorii de la
poziţia curentă a containerului
Un iterator poate fi iniţializat la orice poziţie iniţială din container. Vom menţiona că
există două tipuri de iteratori ce permit parcurgerea vectorilor în sens direct şi respectiv în
sens invers.
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();
ce au ca rezultat un iterator ce indică primul element al vectorului şi respectiv elementul
fictiv primul ce urmează după ultimul element al vectorului. Pentru vectorul anterior
avem situaţia din figura de mai jos
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
# 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 utilizand iteratori
vector <int>::iterator it;
for(it = v.begin(); it != v.end(); it++)
cout << *it << endl;
// afisaza conponentele vectorului in ordine inverse utilizand iteratori
vector<int>::reverse_iterator iter1;
for(iter1 = v.rbegin(); iter1 != v.rend(); iter1++)
cout << *iter1 << endl;
return 0;
}
# 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
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
cout << "vectorul sortat" << endl;
for(i = 0; i < vst.size(); i++)
cout << vst.at(i) << endl;
return 0;
}
# 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
*/
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
cout << “Vectorul initial” << endl;
int i;
for(i = 0; i < v.size(); i++)
// cout << v[i] << endl;
cout << v.at(i) << endl;
// afisaza vectorul cu iterator
vector<int>::iterator iter;
cout << "Vectorul initial\n";
for(iter = v.begin(); iter != v.end(); iter++)
cout << *iter << endl;
// sorteaza vectorul in ordine crescatoare
sort(v.begin(), v.end(), comp);
// afisaza vectorul sortat
cout << "Vectorul sortat in ordine crescatoare\n";
for(i = 0; i < v.size(); i++)
cout << v[i] << endl;
// sorteaza vectorul in ordine descrescatoare
sort(v.begin(), v.end(), comp2);
// afisaza vectorul sortat
cout << "Vectorul sortat in ordine descrescatoare\n";
for(i = 0; i < v.size(); i++)
cout << v[i] << endl;
return 0;
}
15.3 Liste
Clasa generică list implementează o listă dublu înlănţuită, adică o listă ce poate fi
parcursă în ambele sensuri. Lista poate avea oricâte elemente de acelaşi tip. Prototipul
clasei este definit în biblioteca <list>. Clasa defineşte următorii constructori
• constructorul implicit
list();
defineşte o listă vidă
• constructorul
list(list p);
crează o listă în care copiază elementele listei p
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ă
• T& front(); are ca rezultat primul element din listă
• T& back();are ca rezultat ultimul element din listă
15.3.1 Parcurgerea listelor
Pentru parcurgerea componetelor unei liste se pot utiliza iteratori. Un iterator este un
pointer la un element al listei.
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 anterior.
Pentru parcurgerea directă a listelor clasa list defineşte funcţiile
begin();
end();
ce au ca rezultat un iterator ce indică primul element al listei şi respectiv ultimul element
al listei.
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 anterior.
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.
Pentru sortarea în ordine directă şi inversă a listelor clasa list defineşte funcţiile
void sort();
void reverse();
ce sorteză elementele listei în ordine directă şi inversă.
Exemplu. Vom crea o listă cu elemente întregi, o vom sorta ascendant şi descendent şi
vom afişa elementele listei.
# 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
cout << "lista initiala\n";
list<int>::iterator iter;
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << endl;
// sortarea elementelor
ls.sort();
// parcurgerea listei sortate
cout << "lista sortata\n";
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << endl;
// sortarea elementelor in ordine inversa
ls.reverse();
// parcurgerea listei sortate
cout << "lista sortata in ordine inversa\n";
for(iter = ls.begin(); iter != ls.end(); iter++)
cout << *iter << endl;
// parcurgerea listei
cout << "parcurgerea listei in ordine inversa\n";
list<int>::reverse_iterator iter1;
for(iter1 = ls.rbegin(); iter1 != ls.rend(); iter1++)
cout << *iter1 << endl;
return 0;
}