Sunteți pe pagina 1din 160

UNIVERSITATEA TEHNICĂ A MOLDOVEI

LIMBAJUL DE PROGRAMARE C++

Îndrumar de laborator
Chişinău
2002

2
UNIVERSITATEA TEHNICĂ A MOLDOVEI

CATEDRA CALCULATOARE

LIMBAJUL DE PROGRAMARE C++

Îndrumar de laborator

Chişinău
U.T.M.
2002
3
PREFAŢĂ

Îndrumarul este adresat studenţilor cu specializările


Calculatoare, Tehnologii informaţionale, Microelectronica,
Automatica şi Informatica de la Facultatea de Calculatoare,
Informatică şi Microelectronică pentru a fi utilizat la executarea
lucrărilor de laborator ce ţine de limbajul de programare C++.
Fiecare lucrare de laborator conţine materialul teoretic
necesar executării lucrării respective, tema pentru acasă şi
problemele pentru executarea lucrării.
Prin problematica pe care o tratează îndrumarul poate fi
utilizat şi de studenţii altor specializări care studiază şi practică
limbajul de programare C++.

Elaborare: dr. şt. f.-m. Elena Boian


Redactor responsabil: dr.şt. tehn. Tamara Sibirschi
Redactor:
Culegere computerizată: dr. şt.f.-m. Elena Boian

Bun de tipar Formatul hîrtiei 60x84 1/16


Hîrtie de ofset Tipar ofset.
Coli de tipar
Tiparul 100 ex. Comanda nr.

U.T.M., Chişinău, bd. Ştefan cel Mare, 168

Secţia de redactare, editare şi multiplicare a U.T.M.


Chişinău, str. Studenţilor, 11

U.T.M., 2002

4
CUPRINSUL

Introducere 5
Lucrarea de laborator nr. 1. Reprezentarea tipurilor de 7
date ale limbajului C++ în memoria calculatorului.
Operatorii limbajului C++. Construcţiile elementare ale
limbajului C++ (instrucţiunile for, while, do-while, if-else,
switch-break, goto). Tipuri de date recursive, operaţii
asupra listelor, arborilor. Construirea şi elaborarea
programelor recursive. Fişierele.
Lucrarea de laborator nr. 2. Clase (constructori, 59
destructori).Funcţii şi clase prietene.
Lucrare a de laborator nr. 3. Clase derivate, funcţii 79
virtuale, supraîncărcarea funcţiilor şi operatorilor.
Lucrarea de laborator nr. 4. Clase derivate cu moştenire 104
multiplă.
Lucrarea de laborator nr. 5. Fluxurile Input şi Output 111
standard şi definite de utilizator. Formatarea fluxurilor
numerice şi textuale. Fluxurile stringuri şi de memorie.
Lucrarea de laborator nr. 6. Templates: template pentru 128
clase şi funcţii.
Lucrarea de laborator nr. 7. Prelucrarea excepţiilor. 142
Blocul try{…}catch()…
Bibliografie 147

5
INTRODUCERE

Limbajul C++ se consideră atît un limbaj de programare


intermediar – de nivel scăzut datorită apropierii sale de limbajele de
asamblare cu posibilităţi de manipulare a biţilor, apel de sistem de
operare, operaţii cu memoria, etc., cît şi un limbaj de nivel înalt cu
proprietăţi caracteristice limbajelor din această categorie ce permite
de a crea aplicaţii în baza principiilor programării orientate pe obiect.
Îndrumarul prezentă lucrările de laborator pe care studenţii
trebuie să le execute la disciplina “Programarea în limbajul de
programare C++”. Pentru fiecare lucrare de laborator este indicată
tema şi scopul lucrării, consideraţiile teoretice necesare, întrebări
pentru verificarea cunoştinţelor, temele pentru acasă şi de laborator.
Aceste compartimente împreună cu bibliografia vor fi utilizate de
studenţi pentru executarea lucrărilor de laborator.
Pentru a fi admis la îndeplinirea lucrării de laborator fiecare
student trebuie să îndeplinească următoarele condiţii:
– să execute tema pentru acasă, care constă din descrierea
algoritmului de rezolvare a problemelor lucrărilor de laborator,
– să răspundă la întrebările de control puse de către profesor.
În perioada efectuării lucrărilor de laborator studentul
compilează şi execută programele corespunzătoare problemelor
indicate de către profesor în conformitate cu îndrumarul de laborator,
efectuează analiza datelor de intrare şi a rezultantelor obţinute,
colectează datele pentru darea de seamă a lucrării.
Lucrarea de laborator se consideră executată după ce
studentul demonstrează profesorului funcţionarea corectă a
programelor la calculator.
Pentru fiecare lucrare studentul pregăteşte dare de seamă pe
care o susţine în faţa profesorului.

6
Darea de seamă pentru fiecare lucrare de laborator include:
foaia de titlu, tema, scopul lucrării, conţinutul problemei propuse
spre rezolvare, descrierea algoritmului de rezolvare a problemei
propuse, listingul programului, datele obţinute în urma executării
lucrării respective, bibliografia utilizată.
Temele de laborator pentru studenţi se indică în conformitate
cu următoarea tabelă:

1 2 3 4 5 6 7
1 1 1 1 1 1 1 1
2 2 2 2 2 2 2 2
3 3 3 3 3 3 3 3
4 4 4 4 4 4 4 4
5 5 5 5 5 5 5 5
6 6 6 6 6 6 6 6
7 7 7 7 7 7 7 7
7 1 2 3 4 5 6 7
8 1 2 3 4 5 6 7
9 1 2 3 4 5 6 7
10 1 1 1 1 1 1 10
0 0 0 0 0 0
11 1 1 1 1 1 1 11
1 1 1 1 1 1
12 1 1 1 1 1 1 12
2 2 2 2 2 2
13 1 1 1 1 1 1 13
3 3 3 3 3 3
14 1 1 1 1 1 1 14
4 4 4 4 4 4
15 1 1 1 1 1 1 15
5 5 5 5 5 5

7
unde pe verticală este numărul de ordine al studentului în registru, iar
pe orizontală este numărul de ordine al lucrării de laborator propuse
pentru executare.

8
Lucrarea de laborator nr. 1
Reprezentarea tipurilor de date ale limbajului C++ în
memoria calculatorului. Operatorii limbajului C++. Construcţiile
elementare ale limbajului C++ (instrucţiunile for, while, do-while,
if-else, switch-break, goto). Tipuri de date recursive, operaţii asupra
listelor, arborilor. Construirea şi elaborarea programelor recursive.
Fişierele.

Scopul lucrării: familiarizarea studenţilor cu reprezentarea tipurilor


de date ale limbajului C++ în memoria calculatorului, operatorii
limbajului C++, construcţiile elementare ale limbajului C++
(instrucţiunile for, while, do-while, if-else, switch-break, goto),
tipuri de date recursive, operaţii asupra listelor, arborilor, construirea
şi elaborarea programelor recursive, lucrul cu fişierele.

Consideraţiile teoretice necesare:


Tipurile simple şi structurate ale limbajului C++ .
Identificatorii limbajului C++ sînt formaţi cu ajutorul
caracterelor alfanumerice şi caracterul de subliniere “_”. Primul
caracter al unui identificator nu poate fi o cifră.
Pi //legal
mesaj //legal
maxx //legal
x3 //legal
3x //ILEGAL
În cadrul mulţimii identificatorilor posibili, remarcăm o clasă aparte,
reprezentînd cuvintele cheie ale limbajelor indicate.
Cele mai frecvente cuvintele cheie ale limbajul C++sînt
auto delete float interrupt register template
break do for long return this
case double friend near short typedef

9
char else goto new signed union
class enum huge operator sizeof unsigned
const export if private static virtual
continue extern inline protected struct void
default far int public switch while
Declararea variabilelor poate fi efectuată înainte de a fi
folosite, cu toate că anumite declaraţii pot fi făcute în funcţie de
context. 0 declaraţie specifică un tip şi este urmată de o listă de una
sau mai multe variabile de acel tip, ca în exemplul de mai jos:
int i,n;
char c, linie[80];
Domeniu de acţiune a variabilelor. Variabilele pot fi
iniţializate în momentul declaraţiei lor. Dacă numele este urmat de
semnul egal şi de o constantă, aceasta serveşte la iniţializare, ca în
următoarele exemple:
char backslash = '\\';
int i = 0;
float eps = 1.0e-5;
Dacă variabila este externă sau statică, iniţializarea are loc o
singură dată, înainte ca programul să-şi înceapă execuţia. Variabilele
automate, iniţializate explicit, sînt iniţializate la fiecare apel al
funcţiei în care sînt conţinute. Variabilele automate pentru care nu
există o iniţializare explicită au valoare nedefinită. Variabilele
externe şi statice se iniţializează implicit cu zero, dar este un bun stil
de programare acela de a efectua iniţializarea lor în orice caz.
Fiecare variabilă şi constantă posedă un tip, care determină
dimensiunea spaţiului necesar memorării lor. Tipurile datelor se pot
divide în două categorii: tipuri fundamentale şi tipuri derivate sau
structurate.
Tipurile fundamentale ale limbajului C++ sînt
char reprezentînd tipul caracter pe 1 octet,
int întreg pe 2 octeţi
10
long întreg pe 4 octeţi
float, însemnînd un număr real pe 4 octeţi
double, ataşat unui număr real pe 8 octeţi
Aceste tipuri admit diferite variante, numite tipuri de bază de
date.
Tipurile enumerabile sînt introduse prin sintaxa
nume {membrul,membru2, . . . ) varl,var2, . . . ;
De exemplu,
enum CULORI {ROŞU , VERDE, ALBASTRU }
culoarea_punct, culoare_linie;
CULORI culoare_cerc, culoare_fond;
defineşte tipul de dată CULORI şi declară variabilele culoarea_punct
şi culoare_linie urmate de declarările a încă două variabile
culoare_cerc şi culoare_fond de tipul enumerabil.
Membrii unui tip enumerabil trebuie să fie numai de tip
întreg. Valoarea fiecăruia este obţinută prin incrementarea cu 1 a
valorii membrului anterior, primul membru avînd, implicit, valoarea
0. Iniţializarea unui membru cu o valoare oarecare, avîndu-se în
vedere că doi membri ai aceluiaşi tip nu pot avea aceeaşi valoare.
Valorile membrilor următori se stabilesc conform regulilor
menţionate. Exemple de tipuri de date enumerabile:
enum ANOTIMP (IARNA=1, PRIMĂVARA, VARA,
TOAMNA) ;
enum BOOLEAN {fals, adevărat) condiţie;
enum DIRECŢIE {SUS, JOS, DREAPTA, STÎNGA=5};
Putem defini tipuri enumerabile fără a specifica numele acestora.
Procedînd astfel, putem grupa un set de constante fără a denumi acea
mulţime, de exemplu,
enum {bine, foarte_bine, cel_mai_bine};
Utilizarea variabilelor de tip enumerabil. Limbajul C++
permite atribuiri de tipul
condiţie=0;
11
while (! condiţie)
{ cout <<”Utilizarea variabilei enumerabile”;
condiţie=true; }
Este bine ca astfel de atribuiri să fie însoţite de conversia de tip
corespunzătoare.
condiţie=false;
condiţie=(enum BOOLEAN) 0;
Enumerările, definite în interiorul structurilor limbajului C++, nu sînt
vizibile în afara acestora.
Tipurile structurate sînt obţinute de la tipurile de bază.
Tipurile derivate acceptate de limbajul C++ sînt: pointeri, referinţe,
tablouri, structuri, uniuni şi clase.
Pointerul este o variabilă care conţine adresa unei alte
variabile de orice tip. Pentru a defini un pointer vom specifica tipul
datei a cărei adresă urmează să o memoreze.
int *ip; // pointer către un întreg
char **s; // pointer la un pointer pe caractere.
Să considerăm o variabilă de tip int i şi un pointer pi către un întreg.
int i=15, j;
int *pi=NULL;
pi=&i;
*pi=20; // i=20;
Deoarece operatorul adresă & furnizează adresa unei variabile,
instrucţiunea pi=&i asignează variabilei pi adresa lui i.(de exemplu,
adresa 1000). Un alt operator unar ce însoţeşte clasa pointerilor este
* care furnizează conţinutul locaţiei de memorie de pe adresa
indicată de către pointer, de exemplu,
*pi=i; // adică 15;
Dacă j este un alt int, atunci j=*pi asignează lui j conţinutul locaţiei
indicate de pi. Are loc următoarea echivalenţă: j=*pi; adică j=i;

12
Pointerii pot apărea în expresii. De exemplu, dacă pi conţine
adresa lui i, atunci *pi poate apărea în orice context în care ar putea
apărea i, cum ar fi
j=*pi+l; // adică j=i+l;
printf("%d\n",*pi);
d=sqrt((double)*pi);
În expresii ca j=*pi+1; operatorii unari * şi & sînt prioritari faţă de
cei aritmetici, astfel, această expresie adună 1 şi asignează valoarea
obţinută lui j ori de cîte ori pointerul pi avansează.
Referiri prin pointeri pot apărea şi în membrul stîng al
atribuirilor. Dacă pi conţine adresa lui i, atunci *pi=0 îl pune pe i ca
0, iar *pi+=1 îl incrementează pe i, ca şi (*pi)++. În ultimul
exemplu parantezele sînt necesare, fără ele se incrementează pi în loc
să incrementeze ceea ce indică pi, deoarece operatorii unari * şi ++
sînt evaluaţi de la dreapta spre stînga. De exemplu,
void main()
{ int *pi, i=10;
float *pf, f=12.5;
double *pd, d=0.001;
char *pc, c=’a’;
*pi=i; *pf=f; *pd=d; *pc=c;
printf(“pi=%p, pf=%p, pd=%p, pc=%p”, pi, pf, pd, pc);
printf(“*pi=%i, *pf=%f, *pd=%e, *pc=%c”, *pi, *pf, *pd,
*pc);
printf(“pi++ =%p, pf++ =%p, pd++ =%p, pc++=%p”,
pi++, pf++, pd++, pc++);
printf(“(*pi)++ =%p, (*pf)++ =%p, (*pd)++ =%p, (*pc)
++ = %p”, (*pi)++ , (*pf)++, (*pd)++, (*pc)++);
}
Deoarece pointerii sînt variabile, ei pot fi manevraţi ca orice
altă variabilă. Dacă pj este un alt pointer la int, atunci
pj=pi;
13
copiază conţinutul lui pi în pj, adică pj va pointa la variabila adresa
căreia este indicată în pi, astfel că pj se va modifica odată cu pi.
Pointerii pot fi indicaţi către elemente fără tip, cu void. Putem atribui
unui pointer void valoarea unui pointer non-void fără a fi necesară o
operaţie de conversie de tip typecast.
char *cp; // pointer către un caracter
void *vp; // pointer către void
vp=cp; // legal - pointerul la caracter depus în
//pointerul către void
cp=vp; // ILEGAL - lipseşte conversia de tip
cp=(char*) vp; // legal - pointerul către void depus în
pointerul către caracter cu conversie de tip.
Referinţa prezintă o legătură cu o variabilă, conţinînd o
adresă. Spre deosebire de pointeri, în a căror declarare se utilizează
simbolul “*”, pentru a defini o referinţă vom folosi simbolul “&”.
int i; // declararea unui întreg
int *p=&i; // definirea unui pointer la i
int &r=i; // definirea unei referinţe la i
Atît p, cît şi r acţionează asupra lui i.
i=55; // acţiune asupra lui i
*p=13; // acţiune asupra lui i
r=20; // acţiune asupra lui i.
Există însă o diferenţă majoră între p şi r, nu numai în modul de
apelare, ci şi datorită faptului că p poate, la un moment dat, să fie în
legătură cu o altă variabilă, a cărei locaţie de memorie o va conţine,
diferită de cea a lui i, în timp ce r nu-şi poate schimba referinţa,
acesta nefiind altceva decît o redenumire a variabilei i. În ceea ce
priveşte utilizarea referinţelor, va trebui să ţinem cont de următoarele
restricţii:
– referinţele trebuie iniţializate chiar în momentul declarării lor,

14
– odată fiind iniţializate, referinţelor nu li se pot schimba locaţiile
la care se referă,
– nu sînt permise referinţe la referinţe şi pointeri către referinţe,
dar putem avea o referinţă la un pointer.
Referinţele pot fi utilizate drept constante, pot fi iniţializate
cu constante, funcţii sau chiar structuri.
Tablourile, din rîndul cărora fac parte vectorii şi matricele,
sînt tipuri de date foarte apropiate pointerilor şi referinţelor. Vom
vedea că orice operaţie care poate fi rezolvată prin indexarea
tablourilor poate fi rezolvată şi cu ajutorul pointerilor. Astfel,
declaraţia
char linie[80];
defineşte linie ca fiind un şir de 80 de caractere şi, în acelaşi timp,
linie va constitui un pointer la caracter. Dacă pc este un pointer la un
caracter, declarat prin
char *pc;
atunci atribuirea pc=&1inie[0]; face ca pc să refere primul element
al tabloului linie (de indice zero). Aceasta înseamnă că pc conţine
adresa lui linie[0]. Acum atribuirea
c=*pc;
va copia conţinutul lui linie[0] în c. Dacă pc indică un element al lui
linie, atunci, prin definiţie, pc+1 indică elementul următor şi, în
general, pc-i indică cu i elemente înaintea elementului indicat de pc,
iar pc+i cu i elemente după acelaşi element. Dacă pc indică
elementul linie[0], *(pc+1) referă conţinutul lui linie[1], pc+i este
adresa lui linie[i], iar *(pc+i) este conţinutul lui linie[i]. Observăm
că operatorul de indexare [], de forma E1[E2], este identic
cu*((E1)+(E2)). Aceste remarci sînt adevărate indiferent de tipul
variabilelor din tabloul linie. Definiţia adunării unităţii la un pointer
şi, prin extensie, toată aritmetica pointerilor constă, de fapt, în
calcularea dimensiunii memoriei ocupate de obiectul indicat. Astfel,

15
în pc+i i este înmulţit cu lungimea obiectelor pe care le referă pc,
înainte de a fi adunat la pc.
Corespondenţa între indexare şi aritmetica pointerilor este
foarte strînsă. Referinţa la un tablou este convertită de către
compilator într-un pointer spre începutul tabloului. Numele acestui
tablou este o expresie de tip pointer.
Evaluînd elementul linie[i] limbajul C++ îl converteşte în
*(linie+i), cele două forme fiind echivalente. Aplicînd operatorul &
ambilor termeni ai acestei echivalenţe, rezultă că linie[i] este identic
cu linie+i, unde linie+i fiind adresa elementului i din tabloul linie.
Dacă pc este un pointer el poate fi utilizat în expresii cu un indice
pc[i] fiind identic cu *(pc+i). Un pointer este o variabilă. Deci,
pc=linie; // şi
pc++;
sînt operaţii permise. Numele unui tablou este o constantă şi nu o
variabilă, construcţii de tipul linie++ fiind interzise. Singurele
operaţii permise a fi efectuate asupra numelor tablourilor, în afara
celor de indexare, sînt cele care pot acţiona asupra constantelor
Aritmetica adreselor pentru pointeri, tablouri constituie unul
din punctele forte ale limbajului C++, se garantează că nici un
pointer care conţine adresa unei date nu va conţine valoarea zero,
valoare rezervată semnalelor de eveniment anormal. Această valoare
este atribuită constantei simbolice NULL pentru a indica mai clar că
aceasta este o valoare specială pentru un pointer. În general, întregii
nu pot fi asignaţi pointerilor, zero fiind un caz special.
Există situaţii în care pointerii pot fi separaţi. Dacă p şi q
indică elemente ale aceluiaşi tablou, operatorii <, >, =, etc. lucrează
conform regulilor cunoscute. p<q este adevărată, de exemplu, în
cazul în care p indică un element anterior elementului pe care îl
indică q. Relaţiile == şi != sînt şi ele permise. Orice pointer poate fi
testat cu NULL, dar nu există nici o şansă în a compara pointeri
situaţi în tablouri diferite.
16
Este valabilă şi operaţia de scădere a pointerilor. Astfel, dacă
p şi q indică elementele aceluiaşi tablou, p-q este numărul de
elemente dintre p şi q. O funcţie, deosebit de utilă în lucrul cu şiruri
de caractere, este strlen(), care returnează lungimea şirului de
caractere transmis ca parametru.
int strlen(char *s)
{ char *p=s;
while (*p!='\0') p++;
return p-s; }
Prin declarare p este iniţializat cu s şi indică primul caracter
din s. În cadrul ciclului while este examinat conţinutul şirului de
caractere, indirect, prin intermediul pointerului p, caracter după
caracter, pînă cînd se întîlneşte '\0'', acesta din urmă semnificînd
sfîrşitul şirului. Dacă while ar testa doar dacă expresia este zero, este
posibilă omiterea testului explicit, astfel de cicluri fiind deseori scrise
sub forma
while (*p) p++;
Deoarece p indică şirul de caractere, p++ face ca p să avanseze de
fiecare dată la caracterul următor, iar p-s dă numărul de caractere
parcurse (lungimea şirului). Aritmetica pointerilor este consistentă:
dacă am fi lucrat cu float, care ocupă mai multă memorie decît char,
şi dacă p ar fi fost un pointer la float, p++ ar fi avansat la următorul
float. Toate manipulările de pointeri iau automat în considerare
lungimea obiectului referit.
Să implementăm, de exemplu, o funcţie de comparare a două
şiruri de caractere. Funcţia strcmp(s, t) compară şirurile de caractere
s şi t şi returnează valoare negativă, nulă sau pozitivă, în funcţie de
relaţia dintre s şi t (care poate fi s<t, s=t sau s>t). Valoarea returnată
este obţinută prin scăderea caracterului de pe prima poziţie unde s
diferă de t. Pentru claritatea problemei, vom prezenta două variante,
una utilizînd tablourile, iar cea de a doua utilizînd pointerii.
Varianta cu tablourile:
17
strcmp(char s[], char t[])
{ int i=0;
while (s[i]==t[i])
if (s[i++]=='\0') return 0;
return s[i]-t[i];}
Varianta cu pointerii:
strcmp(char *s, char *t)
{ for(;*s==*t;s++,t++)
if(*s=='\0') return(0);
return (*s-*t);}
Dacă ++ şi - - sînt folosiţi ca operatori prefixaţi, pot apărea alte
combinaţii de *, ++ şi - -, deşi mai puţin frecvente. De exemplu: *+
+p incrementează pe p înainte de a aduce caracterul spre care indică
p. *- - p decrementează pe p în aceleaşi condiţii.
Alte operaţii, în afara celor menţionate deja (adunarea sau
scăderea unui pointer cu întreg, scăderea sau compararea a doi
pointeri), sînt ilegale. Nu este permisă adunarea, împărţirea,
deplasarea logică sau adunarea unui float sau double la pointer.
Tablourile multidimensionale pot fi definite cu ajutorul
tablourilor de tablouri, de exemplu.:
char ecran [25][80];
excepţie făcînd tablourile de referinţe, acestea din urmă nefiind
permise datorită faptului că nu sînt permişi pointerii la referinţe.
Dacă E este un tablou n-dimensional de dimensiuni i, j, ..., k,
atunci apariţiile lui E în expresii sînt convertite în pointer la un
tablou n-1-dimensional de dimensiuni j, ..., k. Dacă la acesta se
aplică explicit sau implicit (prin indexare) operatorul *, rezultatul
este tabloul n-1-dimensional indicat de pointer, care, la rîndul său,
este convertit imediat în pointer.
Tablourile sînt memorate pe linii şi, deci, ultimii, de la stînga
la dreapta, indici variază mai repede decît primii. Prima dimensiune a
unui tablou se foloseşte numai pentru a determina spaţiul ocupat de
18
acesta, ea nefiind luată în consideraţie decît la determinarea unui
element de indici daţi. Este permisă omiterea primei dimensiuni al
unui tablou, dacă tabloul este extern, alocarea făcîndu-se în cadrul
altui modul sau cînd se efectuează iniţializarea tabloului în
declaraţie, în acest caz fiind determinată dimensiunea din numărul de
elemente iniţializate.
Iniţializarea tablourilor poate avea loc chiar în cadrul
declarării acestora
int point[2]={10,19};
char mesaj1[6]={'S1,'a','l','u','t','\0'};
char mesaj2[6]="Salut";
Observăm că şirurile de caractere se comportă oarecum ciudat. Atît
mesaj1, cît şi mesaj2 sînt şiruri de 6 caractere avînd drept terminator
de şir caracterul nul. Diferenţa între cele două şiruri nu se află în
conţinutul lor, ci în cadrul iniţializării lor. În cazul iniţializării prin
acolade, { }, caracterul nul nu este subînţeles, prezenţa acestuia
rămînînd la latitudinea noastră, în schimb, adoptînd o iniţializare prin
ghilimele, “ “, va trebui să dimensionăm corespunzător şirul de
caractere, ţinînd cont de prezenţa terminatorului de şir. În exemplul
mesaj2 avem 5 litere plus caracterul nul, fiind necesare 6 locaţii în
vederea memorării cuvîntului “Salut”.
Dimensionarea tablourilor se realizează ţinîndu-se în
concordanţă cu necesităţile aplicaţiei. Există posibilitatea
iniţializărilor parţiale, care nu utilizează întreg spaţiu rezervat. În
cazul şirurilor de caractere, restul spaţiului rămas neutilizat va
conţine numai caracterul nul. În restul situaţiilor, conţinutul tabloului
fiind aleator, se recomandă iniţializarea acestuia în cadrul unui ciclu.
Tablouri de pointeri. Pointerii sînt ei înşişi variabile, de
aceea ei sînt utilizaţi în tablouri de pointeri. Pentru exemplificare,
vom considera un program care sortează un set de linii de text în
ordine alfabetică. Cu toate că algoritmul de sortare este unul comun,
deosebirea dintre sortarea unui tablou de numere şi a unuia de şiruri
19
de caractere constă în aceea că liniile de text de lungimi diferite nu
pot fi comparate sau deplasate printr-o singură operaţie. Avem
nevoie de o reprezentare a datelor care să se poată face eficient şi
potrivit regulilor de gestionare a liniilor de text de lungimi diferite.
Introducem noţiunea de tablou de pointeri. Dacă liniile de
sortare sînt memorate cap la cap într-un şir de caractere, atunci
fiecare linie poate fi accesată printr-un pointer la primul său caracter.
Pointerii înşişi pot fi memoraţi într-un tablou. Două linii pot fi
comparate prin transmiterea pointerilor respectivi lui strcmp(). Cînd
două linii neordonate trebuiesc inversate, se inversează pointerii lor
în tabelul de pointeri, nu liniile însele. Acest mod de lucru elimină
cuplul de probleme legate de gestionarea memoriei şi poate deplasa
liniile
Procesul de sortare constă din trei etape:
- citirea tuturor liniilor la intrare,
- sortarea liniilor,
- tipărirea liniilor în ordine.
Împărţim programul în funcţii care efectuează aceste trei etape.
Funcţia de intrare trebuie să colecteze şi să salveze caracterele din
fiecare linie şi să construiască un tablou de pointeri pe linii. Va trebui
să numere liniile la intrare. Această informaţie este necesară pentru
sortare şi tipărire. Deoarece funcţia de intrare poate opera doar cu un
număr finit de linii, ea va returna o valoare, cum ar fi -1, în cazul în
care se vor prezenta mai multe linii. Funcţia de ieşire trebuie doar să
tipărească liniile în ordinea în care apar în tabloul de pointeri.
#include <stdio.h>
#include <string.h>
#include <conio.h>
#define LINII 100
#define MAXLEN 1000
int citeste_linii(char *s[])

20
{ printf("Introdu un text (maxlen=1000)\n");
printf("Pentru introducerea unei linii noi se utilizează tasta
ENTER.\n");
printf("Sfîrşitul textului se va marca prin '*'. \n\n");
char c;
int i=0, j=0;
while((c=getchar())!='*')
{ if (c=='\n')
{ s[i][j]='\0'; i++;j=0; }
else s[i][j++]=c;
}
return i+1; }
void scrie_linii (char *linieptr[],int maxlinii)
{ for( int i=0; i<maxlinii;i++)
printf("%s\n", linieptr[i]); }
void sortare_linii(char *v[],int n)
{ char *temp;
for(int k=n/2;k>0;k/=2)
for(int i=k;i<n;i++)
for(int j=i-k;j>=0;j-=k)
{ if (strcmp(v[j],v[j+k])<=0) break;
temp=v[j];
v[j]=v[j+k];
v[j+k]=temp; } }
void main()
{ clrscr();
char *linieptr[LINII];
int nlinii;
if ((nlinii=citeste_linii(linieptr))>=0)
{ printf("\n Textul pînă la sortare:\n");
scrie_linii(linieptr,nlinii);
sortare_linii(linieptr,nlinii);
21
printf("\n Textul după sortare \n");
scrie_linii(linieptr,nlinii);
}
else printf("Input prea mare pentru sortare \n"); }
Declararea variabilei linieptr:
char *linieptr[LINII];
arată că linieptr este un tablou de LINII elemente, fiecare element
fiind un pointer la char. linieptr[i] este un pointer la caractere, iar
*linieptr[i] accesează un caracter. Dacă linieptr este el însuşi un
tablou care este transmis lui scrie_linii(), el poate fi tratat ca un
pointer, iar funcţia poate fi scrisă.
void scrie_linii(char *linieptr[],int nlinii)
{ while (- - nlinii>=0)
printf("%s\n",*linieptr++); }
*linieptr adresează iniţial prima linie, iar, cu fiecare incrementare, el
avansează linia următoare pînă cînd nlinii se epuizează.
Sortarea are loc în cadrul funcţiei sortare_linii(). Dacă orice
element individual din v este un pointer la caractere, temp va fi un
astfel de pointer, încît cei doi pot fi copiaţi unul în altul.
Fiind date declaraţiile
int a[10][10]; int *b[10];
utilizările lui a şi b pot fi similare, în sensul că a[5][5] şi b[5][5] sînt
referinţe legale ale aceluiaşi int. Toate cele 100 celule de memorie
ale tabloului a trebuiesc alocate, iar găsirea fiecărui element se face
prin calculul obişnuit al indicelui. Pentru tabloul b, prin declararea
sa, se alocă 10 pointeri, fiecare dintre aceştia urmînd să indice un
tablou de întregi. Presupunînd că fiecare indică la 10 elemente din
tablou, vom obţine 100 celule de memorie rezervate, plus cele 10
celule pentru pointeri. Astfel, tabloul de pointeri utilizează mai mult
spaţiu şi poate cere un mod explicit de iniţializare. Dar există două
avantaje: accesarea unui element se face indirect prin intermediul
unui pointer, în loc să se facă prin înmulţire şi adunare (cum este în
22
cazul tabloului multidimensional), iar liniile tabloului pot fi de
lungimi diferite. Aceasta înseamnă că nu orice element al lui b este
constrîns să indice la un vector de 10 elemente, unii pot indica la cîte
2 elemente, alţii la cîte 20 de elemente sau chiar la nici unul.
Structura. este o colecţie de elemente de tipuri diferite şi care
pot fi referiţi atît separat, cît şi împreună. Definirea unei structuri se
realizează cu ajutorul cuvîntului cheie struct. Ea are următoarea
sintaxă:
struct [tip structură] { tip1 element1;
………
tip n element n
} obiect_ de_ tip_ structură;
unde tip structură descrie organizarea structurii,
tip1,..., tipn indică tipul elementelor structurii,
element1, ..., element sunt numele elementelor structurii,
obiect_ de_ tip_ structură este una sau o lista de variabile
pentru care se alocă memorie. De exemplu,
struct punct { float x,y;} p;
S-a definit o structură p ca fiind de tip punct, punctul fiind compus
din două elemente x şi y reale. Asupra componentelor unei structuri
putem acţiona prin intermediul operatorului de apartenenţă, “.”, de
exemplu:
p.x=10; p.y=30;
Există posibilitatea efectuării operaţiilor cu întreaga structură,
atribuirea fiind, de exemplu :
p={10,30);
0 declaraţie de structură care nu este urmată de o listă de variabile nu
produce alocarea memoriei, ci descrie organizarea structurii., de
exemplu:
typedef struct { char name[25]; int id, age;
char prp; } student;

23
Prin definirea unei structuri se va constitui un nou tip de date,
definind în continuare pointeri la acea structură, masive ale căror
elemente sînt de tipul acestei structuri şi, elemente de acest tip pot
interveni în definirea altor structuri.
Un alt aspect al utilităţii structurilor îl constituie tratarea
tablourilor de structuri, de exemplu:
punct hexagon[6]; punct octogon[8];
Accesul către membrii componenţi ai fiecărui element al vectorului
se realizează prin combinarea accesului indexat, caracteristic
tablourilor, cu cel utilizat în cazul structurilor:
hexagon[i].x=10;
În cazul definirii unui pointer la o structură, accesul la componentele
acelei structuri se va efectua prin expresii de forma
punct *pptr;
pptr->x=10; // Echivalent cu p.x=10;
(*pptr) .y=30; // Echivalent cu p.y=30;
Parantezele au rolul de a indica ordinea în care acţionează cei doi
operatori “*” şi “.”, prioritar fiind “*”.
Unele elemente ale unei structuri pot fi cîmpuri de biţi. Un
cîmp de biţi este o configuraţie de biţi adiacenţi, ce apar într-un
element de tip int. Cîmpurile sînt declarate de tip unsigned, iar
numele cîmpului este urmat de două puncte “:” şi un număr ce
reprezintă numărul de biţi ocupaţi de cîmpul respectiv:
unsigned nume_cîmp:nr_biţi;
Cîmpurile pot fi accesate ca orice alt element de structură. Orice
cîmp trebuie să aibă toţi biţii în interiorul unei zone de tip int (nu
poate avea biţi în două cuvinte diferite). Ordinea de alocare a
memoriei pentru cîmpuri este dependentă de sistem. Unele sisteme
fac alocarea de la stînga la dreapta, iar altele invers. Nu se pot utiliza
tablouri de cîmpuri. Cîmpurile nu au adresă şi nu li se poate aplica
operatorul de adresare &.

24
Un caz special de structuri îl constituie union. Acestea sînt
structuri alternative pentru care dimensiunea spaţiului necesar
memorării lor este egală cu cea mai mare dimensiune necesară
memorării unei componente a acelei structuri. De exemplu, variabila
este de tipul
union un_tip
{ int uint; float ufloat; char uchar;
punct upunct; // upunct este de tipul structurii punct
} variabila;
Toate componentele uniunii ocupă aceeaşi zonă în cadrul memoriei.
Spre deosebire de structuri, în uniune este accesibilă o singură
componentă a unei uniuni. Uniunea se defineşte în aceeaşi manieră
ca şi structurile, cuvîntul cheie utilizat fiind union.
Operatorii
Operatori şi expresii. Acţiunile desfăşurate în cadrul oricărui
program, în marea lor majoritate, se efectuează prin expresiile
formate prin combinaţii de date şi operatori. Limbajul C++ posedă
toţi operatorii limbajului C şi completează această listă cu operatori
proprii. Din lista operatorilor disponibili ai limbajului C++ indicăm
operatorii caracteristici lui new – pentru alocarea memoriei, delete –
pentru eliberarea memoriei alocate cu operatorul new, :: – operatorul
de scop sau de rezoliţie.
În funcţie de numărul de operanzi, operatorii se pot clasifica
în trei categorii: operatori unari, binari şi ternari.
Operatori unari. Formarea expresiilor în care intervin
operatorii unari se produce de la dreapta la stînga.
Operatorul de indirectare: * se poate aplica unei expresii de
tip pointer (*expresie) şi are drept rezultat o valoare (lvalue sau
adresă) care se referă la obiectul indicat de pointer.
Operatorul de adresare: & poate fi aplicat unei valori
(&lvalue) şi are ca rezultat un pointer la obiectul definit de lvalue şi
avînd acelaşi tip ca şi tipul lvalue.
25
Operatorul unar minus: - se aplică unei expresii (-expresie)
în vederea inversării semnului acesteia.
Operatorul negaţie logică: ! se poate aplica unei expresii
aritmetice sau unui pointer (! expresie) şi are ca rezultat 1, dacă
valoarea operandului este 0, şi 0, în caz contrar, tipul rezultatului
fiind int.
Operatorul negaţie pe biţi: ~ se aplică unei expresii de tip
întreg (~expresie) şi transformă 0 în 1 şi 1 în 0 în toţi biţii rezultaţi
după conversiile uzuale.
Operatorul de incrementare: ++ incrementează cu 1 valoarea
operandului.
Operatorul de decrementare: - - decrementează cu 1
valoarea operandului.
Operatorul ++, ca şi operatorul - -, poate fi utilizat atît ca
prefix, cît şi ca sufix. În cazul utilizării lor ca prefix, întîi se
acţionează cu operatorul asupra valorii operandului şi apoi se
utilizează noua valoare a acestuia. În cazul utilizării lor ca sufix, întîi
se utilizează valoarea acestuia, apoi se acţionează cu operatorul
asupra valorii operandului.
Conversia unei expresii (typecast): este de tipul (tip)
expresie sau (expresia) şi produce conversia valorii expresiei la tipul
specificat.
Operatorul dimensiune: sizeof este de tipul sizeof (expresie)
sau sizeof (tip) şi ne indică dimensiunea în octeţi a operandului,
determinată din declaraţiile elementelor ce apar în expresie.
Operatorul de alocare a memoriei: new apare sub forma
pointer_la_nume = new nume [ iniţializator]
şi încearcă să creeze un obiect nume prin alocarea unui număr egal
cu sizeof(nume) de octeţi în memoria heap, adresa acestuia fiind
returnată. În cazul în care alocarea nu este efectuată cu succes, se
returnează valoarea NULL

26
Operatorul de eliberare a memoriei: delete are sintaxă de
forma
delete pointer_la_nume
şi eliberează memoria alocată începînd de la adresa conţinută de
pointer_la_nume.
Operatorul virgulă: , produce expresii de forma
expresie, expresie;
El efectuează evaluarea expresiilor de la stînga la dreapta şi are ca
rezultat şi tip valoarea şi tipul ultimei expresii. Gruparea cu
paranteze este permisă şi produce o singură valoare. De exemplu:
void main()
{int s;
for(int i=0,s=0;i<10,i++) s+=I;
cout<< “Suma este de ”<<s<<endl;
}
Operatori binari
Operatorii aritmetici: +, -, *, / acţionează respectînd regulile
binecunoscute de calculare a expresiilor. Trebuie făcută o observaţie
asupra operatorului de împărţire /. În cazul în care ambii operanzi
sînt întregi, rezultatul este întreg (prin trunchierea rezultatului real).
Operatorul modulo: % furnizează restul împărţirii primului
operand la cel de al doilea. De exemplu, un număr este par dacă este
divizibil cu 2. Deci
if (x%2==0) cout « "x este par";
else cout « "x este impar";
Operatorul de deplasare la stînga: « are ca rezultat
deplasarea către stînga a valorii operandului stîng cu un număr de
biţi egal cu valoarea operandului drept, biţii eliberaţi astfel fiind
completaţi cu valoarea 0.
Operatorul de deplasare la dreapta: » acţionează în mod
similar cu precedentul, singurul element care diferă faţă de
operatorul anterior fiind sensul deplasării. De exemplu, funcţia
27

26
definită mai jos Biti (x,4,3) returnează 3 biţi din poziţiile 4,3 şi 2,
aliniaţi la dreapta.
Biti (unsigned x,unsigned p,unsigned n)
{return (x»(p+1-n))&~ (~0«n) ; }
Operatorii de comparaţie: <, <=, >, >=, = =(egal), !
=(neegal) au ca rezultat o valoare de tip int care este 0 în cazul în
care condiţia nu este îndeplinită şi 1– în caz contrar. Pointerii pot fi
comparaţi numai pe aceeaşi structură de date, iar ultimii doi operatori
permit compararea pointerului cu NULL, care corespunde adresei
vide.
Operatorii logici binari pe biţi: & (şi), | (sau),^ (sau exclusiv)
furnizează un rezultat de tip int (0 pentru valoarea false şi 1 pentru
valoarea true). De exemplu, funcţia numar_biti() controlează
numărul de biţi pe 1 dintr-un argument întreg
numar_biti (unsigned n)
{ int b;
for (b=0; n!=0; n»=1)
if (n&O1) b++;
return b; }
Operatorii logici binari: && (şi), || (or). Pentru ambii
operatori se efectuează evaluări de la stînga spre dreapta pînă la
prima expresie de valoare 0 (pentru &&) sau, respectiv, nenulă
(pentru || ), cînd valoarea întregii expresii devine 0 şi, respectiv, 1.
Operatorii de atribuire: op= unde op face parte din mulţimea
{ +, -, *, /, %, «, », &, ^, |} se grupează de la dreapta la stînga, tipul
expresiei de atribuire este tipul operandului stîng, iar rezultatul
acţiunii operatorului se regăseşte tot în acest operand. Orice expresie
de tipul x op= y este echivalentă cu x =x op y.
Operatori ternari
Operatorul condiţional: (condiţie) ? : produce expresii de
forma (expr1) ? expr2 : expr3, în care se evaluează exp1. În cazul în
care aceasta este nenulă, se returnează valoarea expresiei expr2, în
28
caz contrar, se returnează valoarea lui expr3. De exemplu, ciclul
următor tipăreşte N elemente ale unui tablou, 10 pe linie, cu fiecare
coloană separată printr-un blanc şi cu fiecare linie (inclusiv ultima)
terminată cu un singur caracter ‘\n’– linie nouă:
for (i=0;i<N;i++)
printf("%6d %c",a[i],(i%10==9||i==N-1)?’-\n':' ‘);
Acest exemplu poate fi scris prin intermediul instrucţiunii if în felul
următor:
for (i=0;i<N;i++)
if,(i%10==9||i==N-1)
printf("%6d %c",a[i],’-\n');
else printf("%6d %c",a[i],' ‘);
Instrucţiuni
Expresiile sînt utilizate în scrierea instrucţiunilor. O
instrucţiune este o expresie care se încheie cu punct şi virgulă “;".
Instrucţiunile pot fi scrise pe mai multe linii program, spaţiile
nesemnificative fiind ignorate. Pe o linie de program putem scrie
multe instrucţiuni. Instrucţiunile pot apărea în diferite forme: de
atribuiri, de declaraţii, instrucţiuni condiţionale, de ciclare, de salt,
instrucţiuni compuse.
Instrucţiunea compusă (blocul de instrucţiuni) grupează
declaraţii şi instrucţiuni în vederea utilizării unui bloc de instrucţiuni
echivalent cu o instrucţiune compusă. Forma generală este:
{lista_declaraţii lista_instrucţiuni}
Instrucţiunea condiţională if, if-else are una din formele:
if (expr) instrucţiune;
if (expr) instrucţiune_l; else instrucţiune_2;
Instrucţiunea if evaluează expresia expr. În cazul în care se obţine o
valoare nenulă, se trece la executarea instrucţiune_1, iar dacă această
valoare este nulă şi există instrucţiune_2, se va executa
instrucţiune_2. În cazul absenţei variantei else se va trece la execuţia
instrucţiunii imediat următoare instrucţiunii if.
29
Instrucţiunea de ciclu condiţionată anterior while este
while (expr) instrucţiune;
Atît timp cît valoarea expresiei expr este nenulă, se execută
instrucţiune. De exemplu,
i=0;
while (i<n) a[i++]=0.0;
Evaluarea expresiei are loc înaintea execuţiei instrucţiunii, fapt
pentru care această instrucţiune este din clasa ciclurilor cu
precondiţie. Din acest motiv este posibil ca corpul ciclului să nu se
execute nici măcar o dată, dacă condiţia ciclului este falsă. Execuţia
programului va trece la instrucţiunea imediat următoare instrucţiunii
de ciclu.
Instrucţiunea de ciclu condiţionată posterior do-while are
forma do instrucţiune while (expr);
instrucţiune se va executa pînă ce valoarea expresiei expr devine
falsă. Spre deosebire de instrucţiunea while, în ciclul do-while
evaluarea expresiei are loc după fiecare executare a corpului ciclului.
Datorită acestui fapt instrucţiune se va executa cel puţin o singură
dată, iar instrucţiunea do se încadrează în categoria ciclurilor cu
postcondiţie. De exemplu:
i=0;
do
{ a[i++]=0.0; }
while (i<n);
Ciclul do-while este folosit mai puţin decît ciclul for. Cu toate
acestea, prezenţa sa se impune în cazurile în care este necesară
executarea corpului unui ciclu cel puţin o dată, urmînd ca ulterior să
se execute în funcţie de îndeplinirea condiţiei finale, indicate în
contextul ciclului while.
Instrucţiunea de ciclu aritmetic for are următoarea formă
generală for (expr_1; expr_2; expr_3) instrucţiune;
şi este echivalentă cu următoarea succesiune de instrucţiuni:
30
expr_1;
while (expr_2)
( instrucţiune;
expr_3; )
Oricare dintre cele trei expresii poate lipsi, absenţa expresiei expr_2
fiind înlocuită, implicit, cu valoarea 1. De exemplu,
for (int i=0; i<=n; i++) a[i]=0.0;
for ( int k=0, number_of_nums=0, number_of_chars=0;
k<strlen(text); k++)
{ cout « text[k] «‘\n’;
if (is_num(text[k])) number_of_nums++;
if (is_alpha(text[k])) number_of_chars++;
}
Ciclul for este util de folosit atunci cînd există o simplă iniţializare şi
reiniţializare, deoarece se păstrează instrucţiunile de control al
ciclului împreună.
Instrucţiunea switch face parte din categoria instrucţiunilor
de selectare. Transferul controlului se va efectua la una din variantele
posibile, în funcţie de valoarea unei expresii de control. Sintaxa
instrucţiunii este switch (expr) instrucţiune;
unde instrucţiune este o instrucţiune compusă, în care fiecare
instrucţiune individuală trebuie etichetată cu o etichetă de forma
case expresie_constanta:
unde expresie_constanta trebuie să fie de tip int şi nu pot fi două
etichete egale în aceeaşi instrucţiune switch. Cel mult o instrucţiune
poate fi etichetată cu default:
La execuţia unei instrucţiuni switch se evaluează expresia
expr şi se compară valoarea obţinută cu fiecare constantă ce apare în
etichetele asociate instrucţiunii. Dacă se găseşte o astfel de constantă,
controlul este dat instrucţiunii ce urmează ei, în caz contrar, controlul
fiind transferat la instrucţiunea de după eticheta default, dacă aceasta
există, sau instrucţiunii imediat următoare instrucţiunii switch.
31
Pentru a se menţiona sfîrşitul unei instrucţiuni ataşate unui
caz se va utiliza una dintre instrucţiunile goto, break sau return. La
începutul unei instrucţiuni switch pot apărea declaraţii, dar nu se vor
efectua iniţializări ale variabilelor de tip auto sau register. De
exemplu,
switch (text[k])
{ case ‘A’ : numar_caractere++; break;
case ‘B’: numar_caractere++; break;
… // se vor completa toate cazurile posibile
case 'Z' : numar_caractere++; break;
case ' a’ : numar_caractere++; break;
… // se vor completa toate cazurile posibile
case ‘z’: numar_caractere++; break;
case ‘0’: :numar cifre++;
… // se vor completa toate cazurile posibile
case '9':numar cifre++; }
Instrucţiunea break are forma break;
Are ca efect terminarea execuţiei unui ciclu de tip while, do-while,
for sau switch, controlul fiind transferat primei instrucţiuni din corpul
blocului cel mai interior.
Instrucţiunea continue are forma continue;
Are drept efect trecerea controlului următorului ciclu într-o
instrucţiune de tip while sau for în care apare şi nu are nici un efect
dacă nu apare în corpul unor astfel de instrucţiuni. Cînd este întîlnită,
ea se trece la următoarea iteraţie a ciclului (while, for, do-while). În
cazul lui while şi do-while aceasta înseamnă că partea de control se
execută imediat. În cazul ciclului for, controlul va trece la faza de
reiniţializare. De obicei, instrucţiunea continue nu se va aplica
instrucţiunii switch. Ca exemplu, fragmentul următor sumează numai
elementele pozitive dintr-un tablou a, în care valorile negative sînt
omise.
int s;
32
for (int i=0,s=0; i<N; i++)
{ if (a[i]<0) continue; //sare indicii elementelor negative
s+=a[I]; }
Instrucţiunea return admite următoarele două forme
return; sau return (expr);
cea din urmă fiind echivalentă cu următoarea return expr;
Efectul instrucţiunii return este trecerea controlului la funcţia care a
apelat funcţia respectivă fără transmiterea unei valori în prima
variantă sau cu transmiterea unei valori în ultimele -două variante.
Instrucţiunea goto şi etichete. Limbajul C++ oferă
instrucţiunea goto pentru ramificare. Formal, instrucţiunea goto nu
este necesară şi uşor se poate scrie programe fără ea. Cu toate
acestea, există cîteva situaţii în care goto îşi poate găsi locul. Cea mai
obişnuită folosire este aceea de a abandona prelucrarea în anumite
structuri puternic imbricate, de exemplu, de a ieşi afară din două
cicluri deodată, instrucţiunea break nu poate fi folosită, deoarece ea
părăseşte numai ciclul cel mai din interior. Astfel:
for(...)
for(...)
( ...
if(dezastru) goto error;}
…error:;
Sau căutarea primului element negativ într-un tablou bidimensional.
O posibilitate este:
for (i=0; i<N; i++)
for(j=0; j<M; j++)
if (v[i][j]<0) goto found;
found: // s-a găsit în poziţia i, j
Programul cu un goto poate fi scris întotdeauna fără goto, chiar dacă
preţul pentru aceasta este o variabilă suplimentară sau nişte
controluri repetate. De exemplu, căutarea în tablou devine:
found=0;
33

32
for (i=0; i<N && found; i++)
for (j=0; j<M && found; j++) found = v[i][j]<0;
if (found) { …….. } // a fost găsit la i-1, j-1
else {…………..} // nu s-a găsit
Instrucţiunea vidă are forma “;” şi este utilizată pentru a
evita existenţa unei etichete chiar în faţa unei acolade de închidere a
unui bloc sau în cazul în care corpul unui ciclu nu conţine nici o
instrucţiune.
Tipuri de date recursive, operaţii asupra listelor, arborilor.
Listele simplu şi dublu lănţuite, arborii sînt formate din elemente
definite de structuri cu autoreferire. Ele sînt consecutivităţi de
elemente de acelaşi tip, numărul cărora se schimbă dinamic în
procesul de executare a programului. Lista liniară F, care constă din
elemente D1, D2,...,Dn, grafic se poate reprezenta în modul următor:
D1  D2  D3  ...  Dn
Asupra elementelor listelor se pot efectua următoarele operaţii:
- de căutare a elementului după criteriul dat;
- de determinare a primului element în lista liniară;
- insertarea unui element nou înainte sau după o componentă
indicată a listei liniare;
- eliminarea unui element din listă;
- sortarea componentelor listei.
Metodele de stocare a listelor liniare se divid în metode
consecutive şi stocare lănţuită.
Elementele listei liniare, utilizate de metodele consecutive, se
alocă într-un tablou d de dimensiune fixă, de exemplu, 100, şi
lungimea listei este indicată de variabila l, adică se declară
float d[100]; int l;
Dimensiunea 100 mărgineşte dimensiunea maximală a listei liniare.
Lista F în tabloul d se formează în modul următor:
d[0]=7; d[1]=10; l=2;
34
Lista obţinută se păstrează în memorie în conformitate cu
schema:

l: 2
d: 7 10 ...
[0] [1] [2] [3] [98] [99]
Pentru organizarea elementelor în formă de listă simplu
lănţuită se utilizează structurile care sînt legate cîte o componentă în
lanţ, începutul căreia (prima structură) este indicat de pointerul dl.
Structura care defineşte elementul listei conţine în afară de
componenta informaţională şi un pointer la următorul element din
listă. Descrierea acestui tip de structură cu autoreferire şi pointerul în
cauză se face în modul următor:
typedef struct nod // structura cu autoreferire
{float val; // valoarea componentei informaţionale
struct nod *urm ; // pointerul la următorul element din lanţ
} DL;
DL *p; // pointerul la elementul curent
DL *prim; // pointerul la începutul listei
Pentru alocarea memoriei elementelor listei în C++ se utilizează
operatorul de alocare: new care apare sub forma
pointer_la_nume = new nume [ iniţializator];
care încearcă să creeze un obiect nume prin alocarea unui număr egal
cu sizeof(nume) de octeţi în memoria heap, adresa acestuia este
returnată şi asignată variabilei pointer_la_nume. În cazul în care
alocarea nu este efectuată cu succes, se returnează valoarea NULL.
Operatorul de eliberare delete este apelat printr-o
instrucţiune de forma
delete pointer_la_nume ;
eliberează memoria alocată începînd cu adresa conţinută de
pointer_la_nume. De exemplu,
p=new(DL);
35
p->val=10;
p->n=NULL;
dl=new(DL));
dl->val=7;
dl->n=p;
În ultimul element al listei pointerul la elementul vecin are valoarea
NULL. Lista are următoarea formă:

Operaţii asupra listelor simplu lănţuite


Fiecare element al listei simplu lănţuite reprezintă o structură
alcătuită din două componente: val – folosit pentru componenta
informaţională şi p pentru pointer la următorul element din lista
lănţuită. Pointerul dl indică adresa de alocare pentru primul element
al listei. Pentru toate operaţiile asupra listei se va utiliza următoarea
descriere a structurii elementelor liste:
typedef struct nod
{ float val;
struct nod * urm;
} NOD;
int i,j;
NOD * prim, * r, * p;
Pentru executarea operaţiilor pot fi utilizate următoarele fragmente
de program:
1) formarea listei simplu lănţuite:
float x=5; int n=1;
p=new(nod);
r=p;
p->val=x;

36
p->urm=NULL;
prim=p;
while (p->val !=0)
{ p=new(nod); n++;
p->val=x-1.0*n;
r->urm=p;
p->urm=NULL;
r=p; }
2) tiparul elementului j:
r=prim;j=2;
while(r!=NULL && j<n-1)
{ if (r==NULL) printf("\n nu este elementul %d ",j);
else printf("\n elementul %d este egal cu %f ",j++,r->val);
r=r->urm; }
3) tiparul ambilor vecini ai elementului determinat de pointerul p :

p=prim;
if((r=p->urm)==NULL) printf("\n nu are vecin din
dreapta");
else printf("\n vecinul din dreapta este %f", r->val);
if(prim==p) printf("\n nu are vecin din stînga" );
else { r=prim;
while( r->urm!=p ) r=r->urm;
printf("\n vecinul de stînga este %f", r->val); }
4) eliminarea elementului care este succesorul elementului în cauză,
la care indică pointerul р

37
p=prim;
if ((r=p->urm)==NULL) printf("\n nu este succesorul ");
p->urm=r->urm; delete(r->urm);
5) insertarea noului element cu valoarea newval=100 după elementul
determinat de pointerul p:

r=new(NOD);
r->urm=p->urm; r->val=100; p->urm=r;
Organizarea listelor dublu lănţuite
Lista dublu lănţuită este o listă în care fiecare element conţine
doi pointeri: unul la precedentul element, altul – la succesorul
element din listă. Lista dublu lănţuită în program se poate determina
cu ajutorul următoarelor descrieri:
typedef struct ndd
{ float val; // valoarea informaţională a componentei
struct ndd * succesor; // pointer la succesorul element al
//listei n
struct ndd *precedent; // pointer la precedentul element
al //listei m
} NDD;
NDD * prim, * p, * r;
Interpretarea grafică a listei F=< 2,5,7,1 > ca listă dublu lănţuită
este următoarea:

Insertarea noului element cu valoarea newval după elementul


determinat de pointerul p, se efectuează de operatorii
r=new(NDD);
38
r->val=newval;
r->succesor=p->succesor;
(p->succesor)->precedent=r;
p->=r;
Eliminarea elementului urmat de elementul la care indică pointerul p
se efectuează în modul următor:
p->succesor=r;
p->succesor=(p->succesor)->succesor;
( (p->succesor)->succesor )->precedent=p;
delete r;
Lista liniară este ciclică dacă ultimul element al listei indică la
primul element, iar pointerul dl indică la ultimul element al listei.
Schema listei ciclice pentru lista F=< 2,5,7,1 > este următoarea:

La rezolvarea problemelor pot apărea diferite tipuri de liste lănţuite.


Stivă şi coadă
În funcţie de metoda de acces la elementele listei liniare pot fi
cercetate următoarele tipuri de liste liniare: stive, cozi şi cozi de tip
vagon.
Stiva este o consecutivitate de elemente de acelaşi tip –
variabile scalare, tablouri, structuri sau uniuni. Stiva reprezintă o
structură dinamică, numărul de elemente a căreia variază. Dacă stiva
n-are elemente, ea este vidă.
Asupra elementelor stivei pot fi efectuate următoarele
operaţii:
- verificarea dacă stiva este vidă,
- includerea unui element nou în vîrful stivei;
- eliminarea elementului din vîrful stivei;
- accesarea elementului din vîrful stivei, dacă stiva nu este vidă.

39
Astfel, operaţia de includere şi eliminare a elementului, de
asemenea, accesarea elementului are loc numai asupra elementului
din vîrful stivei.
Coada este o listă liniară în care elementele listei se elimină
din capul listei şi elementele noi se includ prin coada listei.
Coadă de tip vagon este o listă liniară în care includerea şi
eliminarea elementelor din listă se efectuează din ambele capete
(vîrful şi sfîrşitul) ale listei.
Stiva şi coada se organizează atît static prin intermediul
tabloului, cît şi dinamic – prin listă (simplu sau dublu lănţuită).
Vom cerceta cum se utilizează lista în formă de stivă pentru
implementarea calculării expresiei aritmetice în formă inversă
poloneză. În astfel de mod de prezentare a expresiei operaţiile se
înregistrează în ordinea executării lor, iar operanzii se află nemijlocit
în faţa operaţiei. De exemplu, expresia (6+8)*5-6/2 în forma inversă
poloneză are forma: 6 8 + 5 * 6 2 / -
Utilizînd noţiunea de stivă expresia aritmetică în formă
inversă poloneză se execută print-o singură trecere de examinare a
expresiei. Fiecare număr se introduce în stivă, iar operaţia se execută
asupra următoarelor două elemente din vîrful stivei, înlocuindu-le cu
rezultatul operaţiei efectuate. Dinamica schimbărilor din stivă va fi
următoarea:
S = < >; <6>; <6,8>; <14>; <14,5>; <70>;
<70,6>; <70,6,2>; <70,3>; <67>.
Mai jos este descrisă funcţia eval, care calculează valoarea expresiei
indicate în tabloul m în formă de expresie inversă poloneză, m[i]>0
indică numărul nenegativ, iar valoarea m[i]<0 - operaţia. În calitate
de coduri pentru operaţiile de adunare, scădere, înmulţire şi împărţire
se aleg numerele: -1, -2, -3, -4. Pentru organizarea stivei se utilizează
tabloul interior stack. Parametrii funcţiei sînt tabloul de intrare m şi
lungimea sa l.
float eval (float *m, int l)
40
{ int p,n;
float stack[50],c;
for(int i=0; i < l ;i++)
if ((n=m[i])<0)
{ c=st[p--];
switch(n)
{ case -1: stack[p]+=c; break;
case -2: stack[p]-=c; break;
case -3: stack[p]*=c; break;
case -4: stack[p]/=c; } }
else stack[++p]=n;
return(stack[p]); }
Arbori
Arborii sînt structuri de date dinamice, cu autoreferire. Prin
arbore se înţelege o mulţime finită şi nevidă de elemente (noduri):
A={A1, A2,..., An}, n>0 cu următoarele proprietăţi:
–există un nod şi numai unul care se numeşte rădăcina arborelui,
–celelalte noduri formează submulţimi ale lui A, care formează
fiecare cîte un arbore, arborii respectivi se numesc subarbori ai
rădăcinii.
Într-un arbore există noduri cărora nu le corespund subarbori.
Astfel de noduri se numesc terminale.
În multe aplicaţii se utilizează noţiunea de arbori binari.
Dacă mulţimea de elemente a arborelui binar este vidă, se consideră
că arborele constă numai din rădăcină. Dacă mulţimea de elemente
este nevidă, arborele binar se divide în două submulţimi:
subarborele drept şi cel de stînga. Arborele binar este ordonat,
deoarece în fiecare nod subarborele stîng se consideră că precede
subarborele drept. Un nod al unui arbore binar poate să aibă numai
un descendent: subarborele drept sau subarborele stîng. De exemplu,

41
un nod al unui arbore binar poate fi o structură care poate fi definită
în felul următor:
typedef struct tnod
{ int nr, int f; //declaraţii
struct tnod *st; // este pointerul spre subarborele stîng al
//nodului curent
struct tnod *dr; // este pointerul spre subarborele drept al
//nodului curent
} TNOD;
Asupra arborilor binari pot fi definite următoarele operaţii:
– afişarea componentelor informaţionale ale nodului,
– specificarea criteriului de determinare a poziţiei în care să se
inserteze în arbore nodul curent;
– determinarea echivalenţei a doi arbori;
– insertarea unui nod terminal într-un arbore binar;
–accesarea unui nod al arborelui,
–parcurgerea unui arbore;
–ştergerea unui arbore.
Afişarea componentelor informaţionale ale nodului se poate
de efectuat prin funcţia:
void prelucrare (TNOD *p)
{printf(“numarul = %d apariţii= %d \n”, p->nr, p->f);}
Criteriul de determinare a poziţiei în care să se inserteze în
arbore nodul curent se defineşte de funcţia:
int criteriu(TNOD *p, *q)
{ if (q->nr < p -> nr )
return –1; // insertarea nodului curent
//în subarborele stîng al nodului spre care indică
//pointerul p
if (q->nr > p-> nr )
return 1; // insertarea nodului curent

42
//în subarborele drept al nodului spre care indică
//pointerul p
}
Insertarea unui nod terminal într-un arbore binar poate fi
efectuată prin următoarea funcţie:
TNOD* insertnod()
{ TNOD *parb, *p, *q;
int n=sizeof(TNOD);
if (parb ==0)
{ parb=p; return p; }
int i;
q=parb;
for(;;)
if ((i=criteriu(q,p)) <0) {q->st=p; return p; }
else { q=q->st; continue; }
if (i>0)
if (q->dr ==0)
{q->dr=p; return p;}
else {q=q->dr; continue; }
return eq(q,p); }
}
if(p==0)
{ printf(“eroare: memorie insuficientă\n”); exit(1);}
elibnod(p); return 0; }
Accesarea unui nod al unui arbore poate fi realizată prin
următoarea funcţie:
TNOD * cauta (TNOD *p)
{TNOD *parb, *q;
if (parb==0) return 0;
int i;
for (q=parb;q;)
if ((i=criteriu(q,parb))==0) return q;
43
else if(I<0) q=q->st;
else q=q->dr;
return 0; }
Parcurgerea unui arbore poate fi efectuată în trei modalităţi:
în preordine; în inordine; în postordine.
Parcurgerea în preordine presupune accesul la rădăcină şi
apoi parcurgerea celor doi subarbori ai săi: mai întîi subarborele
stîng, apoi cel drept.
void preord (TNOD *p)
{ if (p!=0)
{ prelucrare(p); preord(p->st); preord(p->dr); }
}
Parcurgerea în inordine presupune parcurgerea mai întîi a
subarborelui stîng, apoi accesul la rădăcină şi în continuare se
parcurge subarborele drept.
void inord (TNOD *p)
{ if (p!=0)
{inord(p->st); prelucrare(p); inord(p->dr);}
}
Parcurgerea în postordine presupune parcurgerea mai întîi a
subarborelui stîng, apoi a arborelui drept şi, în final, accesul la
rădăcina arborelui.
void postord (TNOD *p)
{ if (p!=0)
{ postord(p->st); postord(p->dr); prelucrare(p); }
}
Ştergerea unui arbore poate fi efectuată de următoarea
funcţie:
void elibnod(TNOD *p)
{ delete(p); }
void stergearbore (TNOD *p)
{ if (p!=0)
44
{ postord(p->st); postord(p->dr); elibnod(p); }
}
Recursivitatea ca metodă de programare
Recursivitatea presupune o repetare. Ea constă în apelarea
unei funcţii de către ea însăşi.
Funcţia se numeşte recursivă dacă în momentul executării
sale funcţia se apelează pe ea însăşi, sau indirect, printr-o
succesivitate de apeluri ale altor funcţii.
Funcţie este nemijlocit recursivă dacă ea se apelează din
corpul aceleiaşi funcţii. De exemplu:
int a()
{.....a().....}
Funcţia este indirect recursivă dacă se efectuează apel
recursiv prin intermediul unei succesivităţi de apeluri ale altor
funcţii. Toate funcţiile componente ale acestei succesivităţi de
apeluri se socot recursive. De exemplu,
a(){.....b().....}
b(){.....c().....}
c(){.....a().....} .
Funcţiile a,b,c sînt recursive, deoarece la apelul unei din
funcţii are loc apelul altor funcţii inclusiv şi pe ea însăşi.
Execuţia algoritmului recursiv presupune crearea unui număr
(finit) de copii ale algoritmului, care corespund diferitelor valori ale
unei variabile. În construirea algoritmului recursiv este inclusă o
condiţie de terminare a apelării recursive de o expresie; care prin
apelări succesive valoarea ei creşte pînă la o valoare ce satisface
condiţia de finalizare a recursivităţii. La executarea programului cu
funcţii recursive se creează copii ale acestora, fiecare din ele
corespunzînd unei valori a expresiei de recursie. Atît timp cît
expresia recursiei se calculează pînă cînd creşte pînă la o valoare ce
satisface condiţia de finalizare a recursivităţii, se spune că are loc
recursia înainte. Cînd expresia atinge valoarea soluţiei recursiei, se
45
execută copiile create, astfel încît se obţine soluţia problemei. În
acest caz are loc recursia înapoi. Executarea programelor cu funcţii
recursive necesită multă memorie şi mult timp de calcul, cu o
complexitate mai mare decît cele nerecursive.
Recursivitatea ca metodă de programare este mai eficientă,
codul programelor cu funcţii recursive este mai compact şi mai uşor
de înţeles.
În limbajul C++ funcţiile pot să se autoapeleze. Exemplul
clasic de funcţie recursivă este calcularea factorialului numărului N!
= 1*2*3*...*N.
Vom numi această funcţie factorial().
long factorial(int n) {return((n==1)?1: n*factorial(n-1) ); }
Apelul funcţiei recursive creează noi copii ale variabilelor locale şi
ale parametrilor pentru clasa de memorie auto şi register, valorile lor
din apelurile precedente se păstrează. Pentru fiecare moment sînt
accesibile numai valorile ale apelului curent. Variabilele declarate cu
clasa de memorie static nu necesită crearea noilor copii. Valorile lor
sînt accesibile în orice moment de executare a programului. În corpul
funcţiei recursive este necesar de indicat condiţia de ieşire din
procesul recursiv, în caz contrar sistemul de calcul poate intra în
impas. De exemplu, funcţia de tipărire unui număr (ca un şir de
caractere): poate fi apelată de ea însăşi, adică să fie recursivă
void print_cifre(int n)
{ int i;
if (n<0)
{ putchar(‘-’); n=-n; }
if ((i=n/10)!=0) print_cifre(i);
putchar(n%10+’0’); }
Programul de mai jos calculează funcţia Akkerman cu
utilizarea funcţiei recursive ackr şi funcţiei auxiliare smacc:
// calculul recursiv al funcţiei Аkkerman
# include <stdio.h>
46
void main () // funcţia în care se apelează funcţia
{ int x,y,n,t;
int ackr(int, int, int);
scanf("%d %d %d",&n,&x,&y);
t=ackr(n,x,y);
printf("%d",t); }
int smacc( int n,int x ) // funcţie auxiliară
{ switch (n )
{ case 0: return(x+1);
case 1: return (x);
case 2: return (0);
case 3: return (1);
default: return (2); }
}
int ackr( int n, int x, int y) // funcţie recursivă
{ int z;
int smacc( int,int);
if(n==0 || y==0) z=smacc(n,x);
else { z=ackr(n,x,y-1); // apeluri recursive ackr(...)
z=ackr(n-1,z,x); }
return z; }
Fişierele input/output ale limbajul C++. Deschiderea şi
închiderea fişierelor. Citirea şi scrierea în fişiere.
Limbajul C++ include în sine funcţiile standard input/output
ale limbajului C de prelucrare a fişierelor la nivelul jos, inferior şi
superior.
Funcţiile de prelucrare a fişierelor de nivel inferior pot fi
utilizate prin includerea fişierelor io.h, fcntl.h şi stat.h.
Pentru deschiderea unui fişier se utilizează funcţia open care
are următoarea formă sintactică:
df = open(…);
unde df este variabilă de tip int (descriptorul de fişier).
47
Funcţia open are următorii parametri: se indică calea de
acces la fişier şi modalitatea de accesare a componentelor fişierului.
Modalitatea de accesare a componentelor se indică prin una din
următoarele constante:
O_RDONLY fişierul se deschide numai pentru citirea
componentelor lui
O_WRONLY fişierul se deschide numai pentru
înregistrarea componentelor lui
O_RDWR fişierul se deschide pentru citirea şi
înregistrarea componentelor lui
O_APPEND fişierul se deschide pentru adăugarea
componentelor noi la sfîrşitul lui
O_BINARY fişierul se prelucrează binar
O_TEXT fişierul se prelucrează textual
Pentru a crea un fişier se utilizează funcţia creat cu următorii
parametri: calea de acces la fişierul creat şi modalitatea de utilizare
a fişierului. Al doilea parametru se indică de una din următoarele
constante:
S_IREAD fişierul va fi creat numai pentru citire
S_IWRITE fişierul va fi creat numai pentru înregistrare
S_IEXE fişierul va fi creat numai pentru executare
Citirea dintr-un fişier se efectuează prin funcţia read
indicîndu-se următorii parametri:
read(df, buf, lung)
unde df este descriptor de fişier; buf – pointer spre zona de memorie
în care se va păstra înregistrarea citită din fişier; lung – lungimea în
octeţi a înregistrării citite.
Înregistrarea în fişier se efectuează prin funcţia write. Această
funcţie are aceiaşi parametri ca şi funcţia read.
Poziţionarea într-un fişier se efectuează prin funcţia fseek(df,
deplasare, origine). Această funcţie are următorii parametric: df –
descriptorul fişierului, deplasarea indică numărul de octeţi pentru a
48
deplasa capul de citire sau scriere al discului, origine are următoarele
valori pentru efectuarea deplasării: 0 – faţă de începutul fişierului, 1-
faţă de poziţia curentă a capului de citire sau înregistrare, 2 – faţă de
sfîrşitul fişierului.
Închiderea fişierului se efectuează de funcţia close (df).
Exemplu de utilizare ale acestor funcţii:
char nfis[]=”fisier1.dat”;
int df; char *p;
df=open(nfis,O_RDONLY);
read(df,p,80);
close(df);
Prototipurile funcţiilor de prelucrare a fişierelor de nivel
superior pot fi utilizate prin includerea fişierului stdio.h.
Fişierele input/output standard se efectuează prin intermediul
funcţiilor scanf şi printf, gets, getc, getch şi puts, putc, respective.
Funcţiile getc(), getch() citesc cîte un caracter din fişierul
standard input .
Funcţiile scanf şi fscanf, printf şi fprintf permite citirea,
respectiv, afişarea uneia sau a mai multor valori la intrarea standard
sau dintr-un fişier, respectiv, ieşirea standard sau înregistrare într-un
fişier. Prototipurile acestor funcţii se află în biblioteca stdio.h.
Principiul de utilizare al funcţiei printf constă în asocierea
unei liste, care conţine indicaţii de formatare, dată sub forma unui şir
de caractere, o listă de variabile. Ambele funcţii utilizează
specificaţiile de scriere sau citire plasate într-o constantă de tip şir de
caractere, urmată de o listă de argumente.
Funcţia de afişare printf utilizează ca argumente nume de
variabile, iar funcţia de citire scanf utilizează drept argumente adrese
de variabile. De exemplu,
#include<«stdio.h>
void main()
{printf("întreg:%6i \n real: %9.3f",316,144.82) ;
49
int z;
scanf(“%d”,&z); }
Lista este parcursă de la stînga la dreapta. Fiecare semn este asociat
cu caracterul care îl urmează şi este interpretat drept caracter de
control. Caracterele de control utilizate sînt:
\n avans la început de linie nouă;
\r poziţionare la începutul liniei curente;
\t tabulator;
\a emite un semnal sonor .
Fiecare semn % este interpretat ca începutul descrierii
caracteristicilor de tipărire a unei valori. Cele mau utilizate semne
sînt următoarele:
Semnul Descrierea
%d,% i un întreg zecimal este aşteptat la intrare; argumentul
corespunzător trebuie să fie un pointer la întreg;
%o un întreg octal este aşteptat la intrare; argumentul
corespunzător trebuie să fie un pointer la întreg;
%x un întreg hexazecimal este aşteptat la intrare;argumentul
corespunzător trebuie să fie un pointer la întreg;
%h un întreg short este aşteptat la intrare; argumentul
trebuie să fie un pointer la un întreg short;
%u un întreg fără semn zecimal este aşteptat la intrare;
argumentul să fie pointer la întreg;
%f un număr în virgulă flotantă este aşteptat; argumentul
corespunzător trebuie să fie un pointer la un cîmp float.
Caracterul de conversie e este*f. Formatul prezentat la
intrare pentru un float este alcătuit dintr-un semn
opţional
%e un număr în virgulă flotantă este aşteptat; argumentul
corespunzător trebuie să fie un pointer la un cîmp
double. Caracterul de conversie e este*e. Formatul
prezentat la intrare pentru un double este alcătuit dintr-
50
un semn opţional, un şir de numere care pot să conţină
şi un punct zecimal şi un cîmp de exponent care este
format din E sau e, urmat de un întreg cu semn.
%c un singur caracter este aşteptat la intrare; argumentul
corespunzător trebuie să fie un pointer la caracter. În
acest caz, ignorarea caracterelor albe este suprimată;
pentru a citi următorul caracter altul decît caracterele albe
se va utiliza %1s;
%s un şir de caractere este aşteptat; argumentul
corespunzător trebuie să fie un pointer al unui tablou de
caractere, destul de mare pentru a încăpea şirul şi un
terminator ‘\0’, care va fi adăugat;

Caracterele de conversie d, u, i ,o, şi x pot fi precedate de


litera l, pentru a indica un pointer la long, mai degrabă decît la int,
care apare în lista de argumente. Similar, litera l înaintea lui e sau f
indică un pointer la double în lista de argumente. De exemplu:
int i;
float x;
char nume[50];
scanf (“%d%f%s”,&i,&x,nume) ;
cu linia de intrare
25 244.32E-1 Mircea
va asigna lui i valoarea 25, lui x valoarea 244.32E-1, iar lui nume
valoarea “Mircea”. Cele trei cîmpuri de la intrare pot fi separate de
oricîte spaţii, taburi şi caractere de linie nouă. Apelarea
int i;
float x;
char nume[50];
scanf (“%2d%4.2f%2s”,&i,&x,nume) ;
cu linia de intrare
25 244.32E-1 Mircea
51
va asigna 25 lui i, 44.32 lui x, iar nume va obţine valoarea “Mi”.
Cele mai utilizate secvenţe asociate valorilor de tip întreg sînt
%ssNX sau %ssNU, unde s este semnul + dacă se doreşte afişarea
explicită a semnului, - arată că se va face o aliniere la stînga. N este
un număr care arată pe cîte poziţii se va face afişarea. De exemplu,
#include<stdio.h>
void main()
{ printf("% -+5i",3); }
programul indicat va afişa valoarea +3 prin aliniere la stînga în
conformitate cu specificaţiile date, astfel semnul - cere alinierea la
stînga, în cîmpul afectat valorii, semnul + cere afişarea explicită a
semnului, cifra 5 arată că afişarea se va face pe 5 poziţii, simbolul i
arată că va fi afişată o valoare de tip întreg.
Valorile de tip real pot fi tipărite utilizînd secvenţe asociate
de forma %ssNMf sau %sSN.tte, în care simbolul M semnifică
precizia cu care vor fi reprezentate numerele (numărul de cifre după
punctul zecimal).
Toate caracterele care nu aparţin secvenţelor de control sînt
afişate şi sînt tratate ca şiruri de caractere.
De exemplu,
#include<stdio.h>
void main ()
{ char * p="abracadabra";
char ch=’B’;
printf("%s %c ",p, ch); }
programul afişează şirul de caractere adresat prin intermediul
pointerului p şi valoarea variabilei ch.
Funcţia scanf se utilizează la iniţializarea unor variabile.
Funcţiile scanf şi printf sînt utilizate împreună. Prin funcţia printf se
afişează un mesaj care adesea este un comentariu legat de valoarea
care urmează să fie introdusă. De exemplu, în programul de mai jos

52
am afişat mesajul “numar real”, după care urmează apelul funcţiei
scanf care aşteaptă introducerea unei valori de tip real sau întreg:
#include<stdio.h>
void main()
{ float a;
printf ("Introdu un numar real: ");//utilizatorul va
introduce, //la
scanf("%f", &a); //tastatură, un număr urmat de Enter.
printf(“\n Introdu un numar întreg: "); //la fel se
procedează //în cazul variabilei
i
scanf ("%i,", &i); // afişarea textului “număr întreg” va
printf("\n Număr întreg: %6i \n Număr real: % 9. 3f", i, a); }
// fi urmată de introducerea, la tastatură, a numărului dorit.
Funcţiile de scriere şi citire anterioare pot fi folosite şi în
cazul fişierelor. Biblioteca stdio.h, specifică limbajului C, conţine
definiţia unui tip de date FILE. Accesul la un fişier se face printr-un
pointer de tip FILE. Etapele care trebuie să fie parcurse sînt definirea
unui pointer de tip FILE şi asocierea unui fişier fizic. Pointerul va
primi drept valoare adresa unei variabile de tip FILE, obţinută prin
intermediul funcţiei fopen(). Această funcţie are două argumente: un
şir de caractere care conţine numele fişierului fizic recunoscut de
sistemul de operare şi un alt şir de caractere care conţine indicaţii
relativ la modul de utilizare al fişierului. Ultimul parametru poate
conţine caracterul
r pentru fişiere deschise pentru citire,
w pentru fişiere deschise pentru creare sau scriere,
t pentru fişiere de tip text sau
b pentru fişiere binare
În exemplul de mai jos am creat un fişier prin intermediul
unui pointer de tipul FILE şi o iniţializare prin funcţia fopen().
Pointerul p conţine o valoare de tip FILE fumizată de funcţia fopen(),
53
care deschide pentru înregistrare fişierul disk.dat. Am scris în acest
fişier două valori în acelaşi mod cum am făcut afişarea la ieşirea
standard.
#include<stdio .h>
void main()
{ FILE *p;
p = fopen(“disk.dat”,”wt”);
fprintf(p,"%6i\n%9.3f", 29, 2.71);
fclose (p) ; }

Întrebări pentru verificarea cunoştinţelor:


1. Enumeraţi tipurile de date fundamentale în limbajul C++?
2. Pentru ce se utilizează variabilele în limbajul C++?
3. Prin ce se deosebesc variabilele descrise ca extern de cele descrise
ca static?
4. Prin ce se deosebeşte o referinţă de un pointer?
5. De ce nu este permisă definirea unui vector de referinţe?
6. Prin ce se deosebeşte noţiunile union şi struct?
7. Care sînt operaţiile aritmetice admise asupra pointerilor?
8. Definiţi un tablou tridimensional elementele cărora sînt de tip
point.
9. Daţi exemple de utilizare a operatorilor unari, binari şi ternari.
10. Prin ce se deosebeşte operatorii de incrementare şi decrementare
prefixate de cele sufixate? Exemple de expresii.
11. Explicaţi acţiunea instrucţiunilor de ciclu. Exemple de utilizare.
12. De ce fiecare variantă a instrucţiunii switch se poate termina cu
instrucţiunea break? Exemple de utilizare a instrucţiunii switch
cu şi fără instrucţiunea break.
13. Corectaţi programul de mai jos care efectuează sortarea parţială a
listei simplu lănţuite, după sortare pointerul v indică la elementul
k1.

54
NOD *v;
float k1;
k1=prim->val;
r=prim;
while( r->urm!=NULL )
{ v=r->urm;
if (v->val; v=v->urm;
v->n=prim;
prim=v; }
else r=v; }
14. Alcătuiţi un program care organizează o listă ciclică Fi (1<I)
dintr-o consecutivitate de numere întregi B1, B2,..., Bn din
intervalul de la 1 pînă la 9999, sortate crescător.
Indicaţie: La rezolvarea acestei probleme avem o listă sortată
crescător Fi. La introducerea unui nou element Bi+1 acest
element se insertează la locul său în lista Fi. Pentru insertarea
elementului nou în lista Fi să se cerceteze trei cazuri:
- lista Fi este vidă,
- elementul se insertează la începutul listei,
- elementul se insertează la sfîrşitul listei.
15. Cercetaţi programul de mai jos
typedef struct str1
{ float val;
struct str1 *n;
} NOD;
void main()
{ NOD *arrange(void); NOD *p;
p=arrange();
55
while(p!=NULL)
{ cout<< p->val<<endl;
p=p->n; }
}
NOD *arrange() // formarea listei sortate
{ NOD *dl, *r, *p, *v; // dl - începutul listei, p,v – pointeri la
//două elemente vecine,
float in=1; // r – fixează pointerul la elementul
curent //care conţine valoarea în
char *is;
dl=new(NOD);
dl->val=0; // primul element
dl->n=r=new(NOD);
r->val=10000; r->n=NULL; // ultimul element
while(1)
{ cin>> is;
if(* is=='q') break;
in=atof(is);
r=new(NOD);
r->val=in;
p=dl;
v=p->n;
while(v->valn);
}
r->n=v;
p->n=r; }
return(dl); }
16. Inversaţi consecutivitatea de simboluri introduse. De exemplu,
s-a introdus consecutivitatea ABcEr-1, consecutivitatea inversată
va fi 1-rEcBA. Utilizaţi noţiunea de listă simplu lănţuită.
17. Cercetaţi acţiunea operaţiilor efectuate asupra stivei sînt
efectuate în programul de mai jos:
56
typedef struct st // declararea tipului STACK
{ char ch;
struct st *ps;
} STACK;
main()
{ STACK *p,*q;
char a;
p=NULL;
do // completarea stivei
{ a=getch();
q=new(STR1);
q->ps=p; p=q;
q->ch=a;
} while(a!='.');
do // tiparul stivei
{ p=q->ps; delete (q); q=p;
cout<< p->ch;
} while(p->ps!=NULL);
}
18. Scrieţi un program în care numerele din baza 10 din lista dublu
lănţuită dată se înlocuiesc cu cele din baza 2.
19. Scrieţi un program care ar efectua următoarele operaţii. Se
creează o listă dintr-un şir de numere întregi, care se termină cu
zero. Din listă se şterg mai întîi elementele negative, apoi
numerele pare.
20. Scrieţi un program care din trei cozi se selectează o coadă nouă
mai întîi numerele negative, zerourile, apoi numerele pozitive.
21. Analizaţi ce efectuează programul de mai jos:
typedef struct nod
{ float val;
struct nod *n;
} NOD;
57
int index (NOD *x[100])
{ NOD *p;
int i,j=0;
float inp;
for (i=0; i<100; i++) x[i]=NULL;
cin>> inp;
while (inp!=0)
{ j++;
p=new(NOD);
i=inp%100+1;
p->val=inp;
p->n=x[i];
x[i]=p;
cin>>inp; }
return j; }
Valoarea returnată de funcţia index va fi numărul de elemente
cercetate din listă.
22. Scrieţi un program care din lista L1 ce conţine numere întregi să
se extragă în lista L2 elementele cu numere impare din lista L1.
23. Cum se utilizează funcţiile standard de citire a valorilor
variabilelor de la tastatură şi din fişier?
24. Cum se utilizează funcţiile standard de tipar la ecran şi
înregistrare într-un fişier a valorilor variabilelor?
25. Care sînt caracterele de control şi specificare a tiparului
variabilelor la ecran?

Temele pentru acasă:


1. Care dintre exemplele de declaraţie şi/sau iniţializare sînt corecte:
int r;
int 25;
int p=25;
int i;
58

64
int r=i;
r=25;
const int j=25;
int r=j;
2. Scrieţi o versiune de funcţie cu pointeri pentru strcat() concatenare
a două şiruri de caractere.
3. Scrieţi variante de programe cu pointeri care ar efectua funcţiile
citeşte_linie(), reverse_linie().
4. Care dintre exemplele de declaraţie şi/sau iniţializare sînt corecte:
int &r;
int &r=25;
int *p=25;
int i;
int &r=i;
r=25; |
const int j=25;
int &r=j;
5. Scrieţi un program pentru a număra biţii de la dreapta spre stînga
pentru un număr dat.
6. Scrieţi un program care roteşte întregul n la dreapta cu b poziţii.
7. Scrieţi un program care inversează cei n biţi ai săi care încep de la
poziţia p, lăsîndu-i pe ceilalţi neschimbaţi.
8. Scrieţi un program care converteşte literele mari în litere mici
utilizînd o expresie condiţională.
9. Scrieţi un program care converteşte numărul întreg n în baza
zecimală, într-un şir de caractere.
10. Scrieţi un program care converteşte întregii fără semn n, într-o
reprezentare binară în s.
11. Scrieţi un program care converteşte un întreg într-un număr
hexazecimal.
12. Scrieţi un program de convertire a unui şir de caractere într-un
întreg.
59
13. Scrieţi un program care inversează un şir de caractere s.
14. Scrieţi un program pentru a număra biţii de la dreapta spre stînga
pentru fiecare număr citit dintr-un fişier.
15. Scrieţi un program care roteşte un întreg n (citit dintr-un fişier) la
dreapta cu b poziţii. b este un număr aleator de la 1 la 16.
16. Scrieţi un program care în fiecare număr citit dintr-un fişier
inversează cei n biţi ai săi care încep de la poziţia p, lăsîndu-i pe
ceilalţi neschimbaţi, p ia valoare de la 1 la 10.
17. Scrieţi un program care converteşte literele mari în litere mici
dintr-un fişier textual.
18. Scrieţi un program care converteşte numărul întreg n în baza
zecimală, citit dintr-un fişier, într-un şir de caractere.
19. Scrieţi un program de convertire a fiecărui element dintr-o stivă
într-un şir de caractere.

Temele pentru lucrări de laborator:


I.
1. Scrieţi un program care compară două stive date.
2. Scrieţi un program care generează o mie de seturi de cinci numere
aeratoare cuprinse între 1 şi 40, în final afişînd frecvenţa cu care a
fost generat fiecare număr.
3. Scrieţi un program pentru a număra biţii de la dreapta spre
stînga pentru un număr introdus de la tastatură.
II.
1. Scrieţi un program care calculează numărul de elemente dintr-o
listă simplu lănţuită care sînt mai mici ca valoarea medie
aritmetică a tuturor elementelor acestei liste.
2. Scrieţi un program care efectuează înmulţirea cifrelor unui număr
dat.
3. Scrieţi un program care converteşte numărul întreg n în baza
zecimală într-un şir de caractere.
III.
60
1. Scrieţi un program care determină un număr obişnuit din inversul
cifrelor numărului dat.
2. Scrieţi un program care permite crearea unui arbore binar şi
traversarea lui în inordine, preordine, postordine.
3. Scrieţi un program care converteşte întregii fără semn dintr-o listă
dublu lănţuită în reprezentare binară.
IV.
1. Scrieţi un program care din 100 de numere aleatoare se determină
numărul maximal şi cel minimal. Să se determine diferenţa dintre
numărul maximal şi cel minimal determinat
2. Scrieţi un program care converteşte întregii fără semn n selectaţi
dintr-un fişier, într-o reprezentare binară în s.
3. Scrieţi un program care converteşte un întreg într-un număr
hexazecimal.
V.
1. Scrieţi un program care determină numărul de ordine a numărului
minimal dintr-o consecutivitate de numere aleatoare. Cantitatea de
numere aleatoare ale consecutivităţii este aleatoare (N = 1,
…,100). Valorile numerelor aleatoare ale consecutivităţii sînt din
intervalul 0 …100000.
2. Scrieţi un program de convertire a unui întreg într-un şir de
caractere.
3. Scrieţi un program cu o funcţie recursivă care calculează cel mai
mare divizor comun al elementelor dintr-o consecutivitate.
VI.
1. Scrieţi un program care dintr-o listă circulară de 100 de numere
aleatoare se determină numărul maximal şi cel minimal. Să se
determine consecutivitatea de elemente ce se află între numărul
maximal şi cel minimal determinat.
2. Scrieţi un program care calculează suma cifrelor pentru fiecare
număr din consecutivitatea de 100 de numere aleatoare.
3. Scrieţi un program care inversează un şir de caractere s.
61
VII.
1. Scrieţi un program care din trei liste simplu lănţuite se selectează
într-o listă nouă mai întîi numerele divizibile la 3, 5 şi 7, apoi
numerele pozitive pare.
2. Scrieţi un program care generează un fişier al căror valori ale
elementelor sînt cuprinse între 1 şi 100. Să se determine frecvenţa
cu care a fost generat fiecare element în fişierul creat.
3. Scrieţi un program care inversează cei n biţi ai elementelor unei
liste simplu lănţuită care încep de pe poziţia p, lăsîndu-i pe ceilalţi
neschimbaţi.
VIII.
1. Scrieţi un program care atribuie unei liste simplu lănţuite
elementele altei liste în ordine inversă.
2. Scrieţi un program cu funcţie recursivă care calculează cel mai
mare divizor comun dintr-un şir de numere date.
3. Scrieţi un program care din două fişiere ordonate descrescător se
unesc în unul nou păstrîndu-i-se ordinea descrescătoare de sortare.
IX.
1. Scrieţi un program care determină cîte numere din consecutivitatea
de 100 de numere aleatoare sînt mai mari ca “vecinii” săi.
2. Scrieţi un program care înlocuiesc numerele din baza 10 din
consecutivitatea dată cu cele din baza 2.
3. Scrieţi un program care decide dacă o valoare x aparţine unei liste
dublu lănţuite v. Elementele lui v trebuie să fie în ordine
crescătoare. Se tipăreşte numărul elementului din listă (un număr
între 0 şi n-1), dacă x apare în v, şi –1, dacă nu apare.
X.
1. Scrieţi un program care va tipări în ordine inversă
subconsecutivitatea de numere dintre valoarea minimă şi maximă
ale unei liste simplu lănţuită.
62
2. Scrieţi un program care determină un număr obişnuit din inversul
cifrelor numărului dat.
3. Utilizînd funcţia recursivă, scrieţi un program care converteşte un
întreg citit dintr-un fişier în numere hexazecimale.
XI.
1. Scrieţi un program care formează o listă dublu lănţuită nouă din
cea dată după următoarea legitate: elementele listei noi se obţine
din inversul cifrelor numărului din lista dată.
2. Să se scrie un program care ar conţine două funcţii: una -
recursivă, a doua - nerecursivă pentru numărarea elementelor unei
liste.
3. Scrieţi un program care inversează fiecare element de tip şir de
caractere dintr-o listă simplu lănţuită.
XII.
1. Scrieţi un program care converteşte literele mari în litere mici
utilizînd din elementele unei stive.
2. Să se scrie un program care din lista L1 ce conţine numere întregi
să se extragă în lista L2 elementele cu numere impare din lista L1.
3. Scrieţi un program care converteşte întregii fără semn dintr-o listă
simplu lănţuită n, într-o reprezentare binară.
XIII.
1. Scrieţi un program care calculează suma cifrelor pentru fiecare
număr din consecutivitatea de 100 de numere aleatoare.
2. Scrieţi un program care roteşte fiecare element al listei dublu
lănţuite n la dreapta cu b poziţii.
3. Scrieţi un program care atribuie unui fişier elementele altui fişier
în ordine inversă.
XIV.
1. Scrieţi un program care creează o listă circulară a căror valori ale
elementelor sînt cuprinse între 1 şi 100. Să se determine frecvenţa
cu care a fost generat fiecare element al listei create.

63
2. Scrieţi un program care calculează suma cifrelor pentru fiecare
număr din consecutivitatea de 100 de numere aleatoare.
3. Scrieţi un program care converteşte fiecare element al listei dublu
lănţuite într-un număr hexazecimal.
XV.
1. Scrieţi un program care determină cîte numere ale unei cozi de
100 de numere aleatoare sînt mai mari ca “vecinii” săi.
2. Scrieţi un program care formează un fişier nou din cel dat după
următoarea legitate: elementele fişierului nou se obţine din
inversul cifrelor numărului din fişierul dat.
3. Alcătuiţi un program care ar efectua următoarele operaţii asupra
listei dublu lănţuite:
– iniţializarea listei;
– cãutarea elementului dupã criteriul dat;
– insertarea unui element nou înainte sau dupã o componentã
indicatã a listei;
– eliminarea unui element din listã; sortarea componentelor
listei.

Lucrarea de laborator nr 2
Clase (constructori, destructori).Funcţii şi clase prieten

Scopul lucrării: familiarizarea studenţilor cu noţiunea de clase,


utilizarea constructorilor, destructorilor, cu funcţii şi clase prietene.

Consideraţiile teoretice necesare:


Clase
Sintaxa simplificată a declarării unei clase este următoarea:

64
class NumeClasă
{...
declaraţii variabile membre...
declaraţii funcţii membre...
}
Din definirea clasei se poate observa că clasa este asemănătoare cu o
structură. Ea are în componenţa sa membri atît de tip variabilă, cît şi
de tip funcţie. Pentru datele din interiorul clasei se utilizează, de
regulă, termenul de date membre, iar pentru funcţii -denumirea de
funcţii membre sau metode. O clasă permite incapsularea în
interiorul sau a datelor şi a codului.
Pentru a putea utiliza efectiv un tip de date (în cazul de faţă o
clasă), trebuie sa definim o variabilă de acel tip. Într-un mod similar
declaraţiei
int i;
putem scrie:
NumeClasă variabilă
Vom considera că variabilă este un obiect. Exprimarea uzuală este
că un obiect este instanţierea unei clase.
O clasă este compusă din două părţi: declaraţia şi
implementarea ei. Declaraţia clasei prezintă membrii clasei. Membrii
clasei sînt variabile de instanţiere şi funcţii membri indicate prin
prototipul lor (tipul returnat, numele funcţiei, lista de parametri).
Implementarea funcţiilor membri are loc prin implementarea clasei.
Gradul de accesibilitate la elementele componente ale clasei este
indicat prin cuvintele: private sau protected – elementele clasei sînt
accesate numai prin intermediul funcţiilor membri sau prietene
friend, public – toate elementele sînt disponibile în exteriorul clasei.
De exemplu:
#include<iostream.h>
class myclass // se declară un nou tip de date myclass

65
{private: int a; // componenta int a se declară
implicit în //zona private
public: // funcţiile membri declarate mai joc sînt
din //zona public
void set_a (int num); // prin intermediul acestor funcţii se
// accesează componenta a
int get_a ();
};
void myclass::set_a(int num) { a=num;}
// această funcţie setează valoarea componentei a
int myclass::get_a(){return a; }
// această funcţie furnizează valoarea componentei a
void main () // funcţia de bază a programului
{ myclass ob1, ob2; // se declară două obiecte ob1 şi ob2
//de tipul myclass
ob1.set_a (10); // pentru obiectul ob1 se setează //valoarea
//componentei a egală cu 10
ob2.set_a (99); // pentru obiectul ob2 se setează
//valoarea //componentei a egală cu 99
cout << ob1.get_a ()<<“ \n”; // pentru obiectul ob1 se
//furnizează valoarea componentei a
//care apoi se tipăreşte la ecran
cout << ob2.get_a () << “ \n”; // pentru obiectul ob2 se
//furnizează valoarea componentei a
//care apoi se tipăreşte la ecran
}
Funcţiile set_a şi get_a, pentru setarea şi furnizarea valorii pentru
componenta a, nu sînt necesare,dacă componenta a va fi inclusă în
zona public,. Componenta a va fi explicit accesată şi i se va iniţializa
sau atribui valoare. Exemplul de mai sus se va modifica în felul
următor:
#include<iostream.h>
66
class myclass // se declară un nou tip de date myclass
{public:
int a; // componenta int a se declară explicit în
//zona public
// funcţiile membri declarate mai sus nu
//sînt necesare
};
void main ()
{ myclass ob1, ob2; // se declară două obiecte ob1 şi
ob2 //de tipul myclass
ob1.a =10; // pentru obiectul ob1 se iniţializează
//valoarea componentei a în mod
//explicit cu valoarea 10
ob2.a = 99; // pentru obiectul ob2 se iniţializează
//valoarea componentei a în mod
// explicit cu valoarea 99
cout << ob1.a << “\n”; // pentru obiectul ob1 se //tipăreşte
valoarea componentei a
cout << ob2.a << “\n”; // pentru obiectul ob2 se //tipăreşte
componentei a
}
Constructorii sînt un tip special de funcţie membru, avînd
acelaşi nume ca şi numele clasei, nu returnează rezultat şi sînt apelaţi
automat la instanţierea unei clase, fie ea statică sau dinamică. Ei au
scopul de a atribui valori iniţiale elementelor membri, dar pot efectua
şi unele operaţii, cum ar fi, alocarea dinamică de memorie,
deschiderea unui fişier ş.a. De exemplu:
class persoana
{ private:
char nume[40];
long int telefon;
public:
67
persoana() {nume=’\0’; telefon =0;};
//constructorul iniţializează valori nule elementelor membri
persoana(char*p, long int t) {strcpy(nume,p); telefon=t;}
//constructor iniţializează valori concrete pentru
elementele //membri ale clasei
persoana(char* nume) {return nume; };
//aceste funcţii setează valori pentru elementele membri nume
persoana(long int telefon) {return telefon;}; //şi telefon
persoana persoana_ input (char *n, long int t=0)
{persoana p;
strcpy(p.nume,n); p.telefon=t;
return p; };
};
Apelul constructorului se efectuează în momentul declarării unui
obiect. Dacă declarăm o variabilă de tipul persoana, fie
persoana p = persoana (“Vasilina”, 743567);
sau
persoana p (“Vasilina”, 743567);
constructorul va iniţializa elementele membri nume şi telefon ale
clase persoana respectiv cu valorile “Vasilina” şi 743567. Dacă se
va declara un obiect de tipul persoana fără date iniţiale, constructorul
va completa elementele membri nume cu stringul vid ‘\0’ şi telefon
cu valoarea 0.
Destructorii dezactivează toate funcţiile unui obiect, îl
distruge şi sînt apelaţi automat la eliminarea unui obiect, la
încheierea timpului de viaţă în cazul static, sau la apelul unui delete
în cazul dinamic. De regulă, destructorii sînt utilizaţi în cazul, cînd
constructorii efectuează alocări dinamice de memorie. Destructorul
are acelaşi nume ca şi constructorul, fiind precedat de semnul “~”.
De exemplu:
#include<iostream.h>
#include<string.h>
68
#include<stdlib.h>
#define Size 255
class strtype
{ private:
char *p;
int len;
public:
strtype() // constructorul
{ p=new char;
if (!p){cout << “Eroare la alocarea memoriei \n”; exit(1);}
*p=’\0’; len=0; };
~strtype() {cout << “Eliberarea memoriei\n”; delete p; }
// destructorul
void set (char*ptr)
{ if (strlen(ptr)> Size )
cout<<”Stringul conţine mai mult de 255 de caractere \n”;
strcpy(p,ptr); len=strlen(p);};
void show()
{ cout << p << “- lungimea “<< len << “\n”;}
};
void main()
{ strtype s1,s2;
s1.set (“Test”); s2.set(“Program C++”);
s1.show(); s2.show(); }
Destructorii obiectelor membri sînt apelaţi, după ce destructorul
obiectului principal a fost executat. Dacă obiectul membru este
compus din alte obiecte, atunci se va proceda la executarea
destructorilor obiectelor incluse. Destructorii obiectelor membri sînt
apelaţi în ordine inversă, în care aceştea apar în declaraţia clasei.
Din punct de vedere cronologic, constructorul este apelat
după alocarea memoriei necesare, deci în faza finală a creării

69
obiectului, iar destructorul înaintea eliberării memoriei aferente, deci
în faza iniţială a distrugerii sale.
Constructorii şi destructorii se declară şi se definesc similar
cu celelalte funcţii membre, dar prezintă o serie de caracteristici
specifice:
– numele lor coincide cu numele clasei căreia ii aparţin;
destructorii se disting de constructori prin faptul că numele lor
este precedat de caracterul
– nu pot returna nici un rezultat
– nu se pot utiliza pointeri către constructori sau destructori
– constructorii pot avea parametri, destructorii insa nu. Un
constructor fără parametri poartă denumirea de constructor
implicit.
în care o clasa nu dispune de constructori sau destructori,
compilatorul de C++ generează automat un constructor, respectiv
destructor, implicit.
Membrii unei clase
Accesarea membrilor unei clase se face în felul următor:
obiect.VariabiăMembră = valoare;
pentru accesul la o variabilă membra, şi
obiect.FuncţieMembră();
pentru apelarea unei funcţii membre.
Pentru exemplificare să consideram o implementare a
noţiunii de punct. Ca variabile membre avem nevoie doar de
coordonatele x şi y care definesc poziţia în spaţiu a unui punct. Am
mai declarat o funcţie care calculează aria dreptunghiului avînd
punctele (0, 0) şi (x, y).
class Point
{unsigned x, y;
unsigned long Arie() {return x * y;};
unsigned GetX();

70
unsigned GetY();
void SetX(unsigned X);
void SetY(unsigned Y);
};
unsigned Point::GetX() {return x;}
unsigned Point::GetY(){return y; }
void Point::SetX(unsigned X){ x = X; }
void Point::SetY(unsigned Y) { y = Y; }
Am folosit un operator nou, specific C++, ::, numit operator de
rezoluţie, numit şi operator de acces sau de domeniu. El permite
accesul la un identificator dintr-un bloc în care acesta nu este vizibil
datorită unei alte declaraţii locale. Un exemplu de folosire este
următorul:
char *sir = "variabilă globală";
void funcţie()
{ char *sir = "variabilă locală";
printf("%s\n", ::sir); // afişează variabila globală
printf("%s\n", sir); // afişează variabila locală
}
Pentru definiţiile funcţiilor membre aflate în afara declaraţiei clasei
este necesară specificarea numelui clasei urmat de acest operator,
indicînd faptul că funcţia are acelaşi domeniu cu declaraţia clasei
respective şi este membră a ei, deşi este definită în afara declaraţiei.
Cuvîntul cheie this
Toate funcţiile membre ale unei clase primesc un parametru
ascuns, pointer-ul this, care reprezintă adresa obiectului în cauza.
Acesta poate fi utilizat în cadrul funcţiilor membre. De exemplu:
unsigned long Point::Arie()
{return this->x * this->y; }
Crearea şi distrugerea obiectelor

71
Să considerăm următorul program C++:
void main()
{Point p; }
În momentul definirii variabilei p, va fi alocat automat spaţiul
de memorie necesar, acesta fiind eliberat la terminarea programului.
În exemplul de mai sus, variabila p este de tip static. În continuare
vom modifica acest program pentru a folosi o variabilă dinamică
(pointer).
void main()
{ Point *p;
p = new Point;
p->x = 5; p->y = 10;
printf("Aria = %d\n", p->Aria());
delete p; }
Operatorul new este folosit pentru alocarea memoriei, iar
sintaxa acestuia este:
variabila = new tip;
variabila = new tip(valoare_iniţială);
variabila = new tip[n];
Prima variantă alocă spaţiu pentru variabilă dar nu o iniţializează, a
doua variantă ii alocă spaţiu şi o iniţializează cu valoarea specificată,
a treia alocă un tablou de dimensiune n. Acest operator furnizează ca
rezultat un pointer conţinînd adresa zonei de memorie alocate, în caz
de succes, sau un pointer cu valoarea NULL (practic 0) cînd alocarea
nu a reuşit.
Eliminarea unei variabile dinamice şi eliberarea zonei de
memorie aferente se realizează cu ajutorul operatorului delete.
Sintaxa acestuia este:
delete variabilă;
Deşi aceşti doi operatori oferă metode flexibile de gestionare a
obiectelor, există situaţii în care aceasta nu rezolva toate problemele.
De aceea pentru crearea şi distrugerea obiectelor în C++ se folosesc
72
nişte funcţii membre speciale, numite constructori şi destructori,
despre care s-a menţionat mai sus.
Sa completam în continuare clasa Point cu un constructor şi
un destructor:
Point::Point() // constructor implicit
{ x = 0; y = 0; }
Point::Point(unsigned X, unsigned Y)
{ x = X; y = Y; }
Point::~Point() { }
Aţi remarcat cu aceasta ocazie modul de marcare a comentariilor în
C++: tot ce se afla după caracterul // este considerat comentariu.
De notat este faptul ca definiţii de forma
Point p; //sau
Point *p = new Point();
duc la apelarea constructorului implicit.
O întrebare care poate apare este motivul pentru care am
realizat funcţiile GetX(), GetY(), SetX(), SetY(), cînd puteam utiliza
direct variabilele membru x şi y. Deoarece una din regulile
programării C++ este de a proteja variabilele membru, acestea pot fi
accesate numai prin intermediul unor funcţii, care au rolul de metode
de prelucrare a datelor incapsulate în interiorul clasei.
Funcţii şi Clase friend
Conceptul friend permite abateri controlate de la ideea
proiecţiei datelor prin incapsulare. Mecanismul de friend (sau
prietenie) a apărut datorita imposibilităţii ca o metoda sa fie membru
a mai multor clase.
Funcţiile prietene sînt funcţii care nu sînt metode ale unei
clase, dar care au totuşi acces la membrii privaţi ai acesteia. Orice
funcţie poate fi prietenă a unei clase, indiferent de natura acesteia.
Sintaxa declarării unei funcţii prietene în cadrul declaraţiei unei
clase este următoarea:
friend NumeFuncţie
73
De exemplu:
class Point {
friend unsigned long Calcul(unsigned X, unsigned Y);
public:
friend unsigned long AltăClasă::Calcul(unsigned X,
unsigned Y);
... };
unsigned long Calcul(unsigned X, unsigned Y)
{return X * Y / 2; }
unsigned long AltăClasă::Calcul(unsigned X, unsigned Y)
{ ... }
Funcţii membri ca prietene
Orice funcţie membru nu poate fi prietenă aceleiaşi clase, dar,
posibil, să fie prietena altei clase. Astfel, funcţiile friend constituie o
punte de legătură între clase. Există două moduri de a face, ca o
funcţie membru a unei clase, să fie prietena altei clase. Prima
variantă este specificarea funcţiei membru a unei clase, ca fiind
prietenă altei clase. Deci, în cea de-a doua clasă, vom declara funcţia
membru în prima clasă, ca fiind de tip friend.
class B;
class A
{…..
void Al (B &x) ;
…..
);
class B
{ friend void A::A1(B &x); );
A doua variantă este declararea unei clase prietenă, astfel, că toate
funcţiile sale membri, sînt, de fapt, prietene clasei, în care un obiect
de tipul primei clase este declarat friend.
class B;
class A
74
{ void Al (B &x) ; );
class B
{…
friend A;
…};
Indiferent de metodă, se impune predeclararea clasei, care va fi
prietenă sau va conţine funcţii, care sînt prietene unei alte clase.
Clasele prietene sînt clase care au acces la membrii privaţi ai
unei clase. Sintaxa declarării unei clase prietene este:
friend class NumeClasăPrietenă
De exemplu:
class PrimaClasă {
...
};
class ADouaClasă {
...
friend class PrimaClasă;
};
Clasa PrimaClasă are acces la membrii privaţi ai clasei
ADouaClasă.
Relaţia de prietenie nu este tranzitivă. Daca o clasa A este
prietena a clasei B, şi clasa B este prietena a unei clase C, aceasta nu
înseamnă ca A este prietena a clasei C. De asemenea, proprietatea
de prietenie nu se moşteneşte în clasele derivate. Clase prietene sînt
utile în situaţia, în care avem nevoie de clase, care să comunice între
ele deseori, acestea, aflîndu-se pe acelaşi nivel ierarhic. Pentru
exemplificare, presupunem, că vom implementa o stivă de caractere
ca o listă simplu înlănţuită. Vom utiliza două clase, una ataşată
nodurilor din listă şi una – stivei propriu-zise.
#include<conio.h>
#include<stdio.h>
class stiva;
75
class nod
{ private:
friend stiva;
nod(int d, nod *n);
int data;
nod *anterior;
};
class stiva
{ private:
nod *virf;
public:
stiva () { virf=NULL; }
~stiva() { delete virf; }
void push (int c);
int pop ();
};
nod::nod (int d, nod *n) {data=d; anterior=n;}
void stiva::push (int i) {nod *n=new nod(i, virf); virf=n; }
int stiva::pop ()
{ nod *t=virf;
if (virf)
{ virf=virf->anterior;
int c= t->data;
delete t;
return c; }
return -1;
}
void main()
{ int c;
stiva cs;
printf(“Introdu un sir de caractere, ce se termina în *”);
while ((c=getch ())!='*')
76
{ cs.push (c);}
putch(c);
while ((c=cs.pop ())!=-1)
{ putch (c); }
c='\n'; putch(c); }
}
}
Prototipul unei funcţii friend cu o clasă se află, de regulă, în cadrul
clasei respective. Funcţia friend nu este membru a acestei clase.
Indiferent de poziţia declaraţiei unei asemenea funcţii, în cadrul
declaraţiei clasei funcţia va fi publică. De exemplu:
class punct
{ private:
int x, y;
public:
punct (int xi, int yi) {x=xi; y=yi; };
friend int compara (punct &a, punct &b);
};
int compara (punct &a, punct &b)
{ //returnează <0, dacă a este mai aproape de origine
// >0, dacă b este mai aproape de origine
// =0, dacă a şi b sînt egal depărtate.
return a. x*a. x+a. y*a. y-b. x*b. x-b. y*b. y; }
void main()
{ punct p (14,17), q(57,30);
if(compara(p,q)<0) printf("p este mai apropiat de origine\n");
else printf (“q este mai apropiat de origine. \n") ; }
Orice funcţie friend unei clase poate fi transformată într-o funcţie
membru a acelei clase, renunţîndu-se, însă la gradul de “prietenie”.
Exemplul de mai sus se modifică în felul următor:
class punct
{ private:
77
int x,y;
public:
punct (int xi, int yi) { x=xi; y=yi; }
int compara (punct &b);
};
int punct:: compara (punct &b)
{ return x*x+y*y-b.x*b.x-b.y*b.y; }
void main ()
{ punct p(14,17), q(57,30);
if (p.compara (q)<0)printf ("p este mal apropiat de origine\n");
else printf ("q este mai apropiat de origine.\n");
}

Întrebări pentru verificarea cunoştinţelor:


1. Ce se va afişa la ecran?
#include <iostream.h>
int a=1,b=2, c=43;
class numere
{ public:
int a,b;
void Actiune(int b)
};
void numere:: Actiune(int b) { a=14; b=56; c=3; }
void main ()
{numere doua_numere;
doua_numere.Actiune(56); }
2. Care este deosebirea dintre constructor şi un destructor al unei
clase? Cum sînt activaţi constructorii şi destructorii?
Ce se numesc clase prietene?
3. Ce se numesc funcţii prietene?

Temele pentru acasă:


78

8
1. Introduceţi şi lansaţi în execuţie următorul program. Ce rezultat
furnizează?
#include <stdio.h>
class numar
{public:
int n;
numar(int i) { printf( "num() %d \n",n); };
-numar() { printf("-num() %d \n",n); };
};
class numere
{ public:
numar a,b,c;
numere(int x, int y, int z);
-numere();
};
numere::numere(int x, int y, int z) : c(z), a(x), b(y)
{ printf("A fost apelat un constructor \n"); }
numere::~numere {printf("A fost apelat un destructor\n"); }
void main ()
{numere bon(1, 2, 3);)
2. Introduceţi şi lansaţi în execuţie următorul program. Ce rezultat se
afişează?
#include. <stdio.h>
class 0_clasa
{public:
o_clasa ()
{ printf("Apel constructor\n"); }:
~o_clasa() { printf("Apel destructor \n"); }
};
class alta_clasa2
( public:
alta_clasa *ac;
79
};
void main()
{ alta_clasa acc;}
3. Scrieţi un program în C, echivalent cu următorul program C++.
Creaţi funcţii în limbajul C echivalente constructorului şi
destructorului şi verificaţi apelul lor la momentul oportun.
#include <stdio.h>
class calculator
{ public:
float memorie;
calculator () ;
~calculator ();
float aduna (float f);
};
calculator::calculator ()
{ printf("S-a pornit calculatorul\n") ;
memorie=0 .0; }
calculator::~calculator(){printf("S-a oprit calculatorul\n"); }
float calculator::aduna (float f)
(memorie+=f; return memorie; }
void main ()
{calculator c;
c.aduna(lO.O); c.aduna(30.0); c.aduna(2.0);
printf ("Memoria este %f\n" ,c.memorie) ; }
4. Se va compila corect următoarea clasă?
class num
{ public:
int data;
num(int i) { data=i; )
int set (int i) { return data=i; }
};

80
5. Descrieţi funcţiile de modificare a stringului (copiere, includere a
unui substring, concatenare şi eliminare a unui substring) pentru
clasa:

class sir
{ private:
char continut[80];
int lungime;
public:
sir ();
char *contine() (return continut;}
};

Temele pentru lucrări de laborator:


1. Să se scrie un program care să definească un nou tip de variabile -
tipul complex şi să construiască funcţii adecvate pentru operaţiile
de bază cu acest tip de date (adunare, scădere, înmulţire, calculul
modulului numărului complex).
2. Să se scrie un program care ar evalua o funcţie, a cărei expresie
analitică se introduce de la terminalul calculatorului ca un şir de
caractere.
3. Să se scrie un program în care se defineşte o clasă stivă elementele
căreia sînt de un tip abstract de date cu următoarele funcţii:
– empty(Q) care curăţă stiva Q,
– if_is_empty(Q) care verifică dacă stiva Q este vidă,
– in_query(Q,x) care adaugă elementul x în stiva Q,
– out_query(Q,x) care scoate elementul x din stiva Q,
– error (k) care indică eroarea cu numărul k (k=1 dacă stiva este
supraîncărcată, k=2 dacă stiva este vidă).
4. Scrieţi un program care determină pentru o clasă listă simplu
lănţuită cu următoarele funcţii:
81
– member(x,L) care determină apartenenţa elementului x listei L;
– equal(L1,L2) care determină echivalenţa a două liste L1 şi L2;
– print(L) care tipăreşte toate elementele listei L;
– readlist(L,fin) care citeşte elementele din fişierul fin în lista L.
5. Scrieţi un program care determină o clasă coadă în care sînt
determinate următoarele funcţii:
– empty(Q) care curăţă coada Q,
– if_is_empty(Q) care verifică dacă coada Q este vidă,
– in_query(Q,x) care adaugă elementul x în coada Q,
– out_query(Q,x) care scoate elementul x din coada Q,
– error (k) care indică eroarea cu numărul k (k=1 dacă coada este
supraîncărcată, k=2 dacă coada este vidă).
6. Scrieţi un program care la o singură trecere prin fişierul fin fără
utilizare a fişierelor suplimentare va tipări elementele fişierului fin
în următoarea ordine:
– toate elementele mai mici ca valoarea a,
– elementele din segmentul [a,b],
– restul elementelor păstrînd aceeaşi ordine din fişierul fin. (a<b, a
şi b sînt cunoscute şi sînt de acelaşi tip ca şi elementele fişierului
fin).
7. Scrieţi un program care determină o clasă stivă în care sînt
determinate următoarele funcţii:
– empty(S) care curăţă stiva S,
– if_is_empty(S) care verifică dacă stiva S este vidă,
– pop(S,x) care adaugă elementul x în stiva S,
– push(S,x) care scoate elementul x din stiva S,
– error (k) care indică eroarea cu numărul k (k=1 dacă stiva este
supraîncărcată, k=2 dacă stiva este vidă).
8. Scrieţi un program care efectuează asupra elementelor fin -
fişierului de tip text - următoarele operaţii:
82
– tipăreşte inversat conţinutul fiecărui cuvînt al liniei fişierului fin,
– sortează lexicografic atît cuvintele din fişierul fin, cît şi cuvintele
inversate,
– determină cuvintele “simetrice” din fişierul fin.
9. Scrieţi un program care determină clasa arbore. Asupra
elementelor arborelui să se determine următoarele operaţii:
– repeat (A,x) care determină numărul de repetări ale elementului x
în arborele A,
– media(A) care calculează media aritmetică a elementelor de tip
întreg sau real. Dacă elementele sînt de alt tip, media (A) =0,
– dacă elementele arborelui A sînt de tip întreg sau real, valorile
negative ale elementelor se înlocuiesc cu valorile lor absolute,
– dacă elementele arborelui A sînt de tip întreg sau real valorile
maximale şi minimale se schimb cu locurile,
– dacă elementele arborelui A sînt de tip string, elementele de
lungime minimală se schimb cu locurile cu elementele de lungime
maximală.
10. Scrieţi un program care determină pentru o clasă listă dublu
lănţuită următoarele funcţii:
– member(x, L) care determină apartenenţa elementului x listei L,
– equal(L1, L2) care determină echivalenţa a două liste L1 şi L2,
– min_max(L, x, y) care determină elementele cu valoare minimală
x şi cu valoare maximală y din lista L,
– sum(L) care determină suma elementelor din lista L,
– substract(L) care determină diferenţa elementelor din lista L,
– multiply(L) care determină produsul elementelor din lista L,
– sum(L,x,y) care determină suma elementelor din lista L în
intervalul elementelor cu valorile x şi y, în caz contrar
sum(L,x,z)=0,
– print(L) care tipăreşte toate elementele listei L,

83
– readlist(L,fin) care creează lista L din elementele fişierului fin.
11. Scrieţi un program care efectuează următoarelor operaţii asupra
unităţilor băneşti (de exemplu, lei, bani):
– adunarea,
– înmulţirea cu un coeficient,
– scăderea,
– împărţirea la un coeficient,
– valoarea numerică a unităţilor băneşti să se tipărească cu cuvinte.
12. Scrieţi un program care efectuează următoarele operaţii asupra
numerelor fracţionare:
– transformarea unui număr fracţionar compus într-o fracţie
supraunitară,
– adunarea numerelor fracţionare,
– înmulţirea numerelor fracţionare,
– scăderea numerelor fracţionare,
– împărţirea numerelor fracţionare.
13. Scrieţi un program care efectuează următoarele operaţii asupra
unităţilor de lungime (de exemplu, metri, centimetri, milimetri):
– adunarea,
– înmulţirea cu un coeficient,
– scăderea,
– împărţirea la un coeficient,
– valoarea numerică a unităţilor de lungime să se tipărească cu
cuvinte.
14. Scrieţi un program care efectuează următoarele operaţii asupra
unităţilor de greutate (de exemplu, tone, kilograme, grame):
– adunarea,
– înmulţirea cu un coeficient,
– scăderea,
– împărţirea la un coeficient,
84
– valoarea numerică a unităţilor de greutate să se tipărească cu
cuvinte.
15. Scrieţi un program care efectuează următoarele operaţii asupra
unităţilor de timp (de exemplu, anul, luna, ziua, ceasul, minuta,
secunde):
– adunarea,
– înmulţirea cu un coeficient,
– scăderea,
– împărţirea la un coeficient,
– valoarea numerică a unităţilor de timp să se tipărească cu cuvinte.
Lucrarea de laborator nr. 3
Clase derivate, funcţii virtuale, supraîncărcarea funcţiilor şi
operatorilor

Scopul lucrării: familiarizarea studenţilor cu noţiunile de clase


derivate, funcţii virtuale şi redefinite, operatori supraîncărcaţi,
obiecte

Consideraţiile teoretice necesare:


Clase derivate.
Moştenirea este o relaţie între clase, caracterizată prin
trecerea atributelor de la o clasă, de bază, la alta, derivată. Clasele
derivate posedă toate caracteristicile clasei de bază. Ele pot fi
îmbogăţite atît structural, cît şi funcţional. Totodată se observă o
ierarhizare datorită faptului că există posibilitatea ca o clasă derivată
să aibă mai multe clase de bază ordinea importanţei nivelelor
rămînînd aceeaşi.
Noţiunea de derivare este o abstractizare a noţiunii de
moştenire. O clasă care adaugă proprietăţi noi la o clasă deja
existentă vom spune ca este derivata clasei de bază. Clasa derivată
moşteneşte toate datele şi funcţiile membre ale clasei de bază; ea
85
poate adăuga noi date la cele existente şi poate suprascrie sau adăuga
funcţii membre. Clasa de bază nu este afectată în nici un fel în urma
acestui proces de derivare şi, ca urmare, nu trebuie recompilată.
Declaraţia şi codul obiect sînt suficiente pentru crearea clasei
derivate, ceea ce permite reutilizarea şi adaptarea uşoară a codului
deja existent, chiar dacă fişierul sursă nu este disponibil. Astfel, nu
este necesar ca programatorul unei clase derivate să cunoască modul
de implementare a funcţiilor membre din componenta clasei de bază.
În funcţie de necesităţi, derivarea claselor va fi un proces cu
durată variabilă. În acest sens, se preferă conceperea unor clase de
bază simple, în locul unora dezvoltate.
Dintr-o clasă de bază pot fi derivate mai multe clase şi fiecare
clasă derivată poate servi mai departe ca bază pentru alte clase
derivate. Se poate astfel realiza o ierarhie de clase, care să modeleze
adecvat sisteme complexe. Pornind de la clase simple şi generale,
fiecare nivel al ierarhiei acumulează caracteristicile claselor "părinte"
şi le adaugă un anumit grad de specializare. O clasă poate să
moştenească simultan proprietăţile mai multor clase, procedură
numită moştenire multiplă. Construirea ierarhiei de clase reprezintă
activitatea fundamentală de realizare a unei aplicaţii orientate obiect.
Sintaxa simplificată a derivării este:
class NumeClasăDerivată : NumeClasaDeBază
În continuare vom deriva din clasa Point o clasă specializată,
GraphicPoint, care va "şti" să deseneze punctul pe ecran:
class GraphicPoint : public Point
{unsigned color;
GraphicPoint(unsigned X, unsigned Y, unsigned Color);
~GraphicPoint();
void Draw();
void SetX(unsigned X);
void SetY(unsigned Y);
};
86
GraphicPoint::GraphicPoint(unsigned X, unsigned Y, unsigned
Color) : Point(X, Y) {color = Color; }
GraphicPoint::~GraphicPoint() {}
GraphicPoint::Draw()
{
// ...
// apelarea primitivelor grafice pentru desenarea punctului
}
GraphicPoint::SetX(unsignedX)
{Point::SetX(); // funcţia SetX() este membru a clasei de bază
Draw();
}
GraphicPoint::SetY(unsigned Y)
{Point::SetY();
Draw();
}
În exemplul de mai sus s-a adăugat o variabilă nouă faţă de
clasa Point, color, pentru a putea memora culoarea cu care se face
desenarea punctului. De asemenea, s-a suprascris constructorul şi
destructorul clasei părinte. În constructorul derivat s-a apelat
constructorul original folosind construcţia:
ClasaDerivată::ClasaDerivată() : ClasaDeBază()
În clasa GraphicPoint s-a adăugat o funcţie membră nouă,
Draw(), care desenează punctul pe ecran. Am suprascris funcţiile
SetX() şi SetY(), apelînd în ambele funcţiile originale, utilizînd
sintaxa:
ClasaDeBază::FuncţieMembră()
apelînd apoi funcţie de desenare, Draw().
Regulile de funcţionare ale constructorilor şi destructorilor,
descrise în lucrarea de laborator 2, rămîn valabile şi în cazul claselor
derivate, cu două observaţii privind ordinea de apelare a acestora:

87
– la instanţierea clasei derivate, se apelează mai întîi constructorul
clasei de bază, apoi se apelează propriul constructor.
– la distrugerea unui obiect al unei clase derivate, este apelat mai
întîi propriul destructor, şi apoi destructorul clasei de bază (în
ordine inversa creării obiectului).
Controlul accesului la clase
Limbajul C++ permite controlul accesului la membrii claselor.
În acest scop s-au creat trei specificatori de control al accesului:
– public, membrul poate fi accesat de orice funcţie din domeniul
declaraţiei clasei;
– private, membrul este accesibil numai funcţiilor membre şi
prietene ale clasei;
– protected, similar cu private, însă accesul se extinde şi la funcţiile
membre şi prietene ale claselor derivate.
O funcţie membră a unei clase are acces la toţi membrii clasei,
indiferent de specificatorul de acces.
Aşa dar, sintaxa declaraţiei unei clase derivate, incluzînd
controlul accesului, este:
class NumeClasăDerivată : SpecificatorAcces NumeClasaDeBază
unde SpecificatorAcces poate fi public sau private.
Accesul
Atributul din Modificator Accesul din
moştenit de
clasa de bază de acces exterior
clasa derivată
private private inaccesibil inaccesibil
protected private private inaccesibil
public private private inaccesibil
private public inaccesibil inaccesibil
protected public protected inaccesibil
public public public accesibil
Pentru a oferi clasei derivate acces la un membru al clasei de
bază, acesta trebuie declarat protected sau public. Elementele
88
declarate cu specificatorul public în clasa de bază sînt accesibile
elementelor în clasa derivată. Elementele declarate cu specificatorul
private sau protected în clasa de bază nu sînt accesibile în mod
direct elementelor din clasa derivată, ele pot fi accesate doar prin
unele funcţii declarate special (care returnează valorile elementelor
din clasa de baza descrise cu specificatorul private sau protected).
Pentru respectarea principiului incapsulării datelor, datele membre
pentru care se oferă acces claselor derivate se declară în clasa de
bază cu atributul protected. De asemenea, pentru a conserva dreptul
de acces în urma derivării, se utilizează derivarea public. Accesul
poate fi stopat pe orice nivel al ierarhiei de clase printr-o derivare
private.
Stabilirea atributelor de acces ale membrilor unei clase,
precum şi ale derivărilor, dezvoltarea ierarhiei de clase, trebuie să se
facă astfel ca să nu afecteze incapsularea datelor.
Să cercetăm exemplul următor, completat cu specificatori de
acces:
class Point
{ protected:
unsigned x, y;
public: Point();
Point(unsigned X, unsigned Y);
~Point();
unsigned long Arie();
unsigned GetX();
unsigned GetY();
void SetX(unsigned X);
void SetY(unsigned Y);
};
class GraphicPoint : public Point
{unsigned color;

89
public: GraphicPoint(unsigned X, unsigned Y, unsigned
Color);
~GraphicPoint();
void Draw();
void SetX(unsigned X);
void SetY(unsigned Y);
};
Variabilele membru x şi y sînt declarate protected, aşa încît vor
fi vizibile şi vor avea acelaşi atribut în clasa GraphicPoint (deşi nu
sînt utilizate). În mod normal, x şi y ar trebui sa fie declaraţi private,
întrucît nu sînt utilizaţi decît în interiorul clasei Point. Funcţiile din
GraphicPoint nu accesează aceşti doi membri direct, ci prin
intermediul metodelor publice de accesare a lor oferite de clasa
Point.
Implicit, dacă nu este utilizat nici un specificator de acces,
membrii sînt consideraţi private.
void main()
{Point *p;
p = new Point;
p->x = 5; // operaţie imposibilă: x este membru privat
p->y = 8; // operaţie imposibilă: y este membru privat
p->SetX(5); // corect: acces la variabila x prin intermediul
//funcţiei SetX()
p->SetY(8); // corect: acces la variabila x prin intermediul
//funcţiei SetY()
printf("Aria = %d\n", p->Aria());
delete p;
}
Deci din exteriorul unei clase nu pot fi accesate datele membre
private sau protected.
Atunci cînd funcţiile unei clase de bază sînt rescrise într-una
derivată, spunem că aceste clase sînt polimorfe, vom avea o singură
90
denumire şi mai multe acţiuni, adică, o singură interfaţă cu metode
multiple. Această noţiune legată de derivare este cea de
supraîncărcare sau suprascriere a funcţiilor membre. Ea se referă la
redefinirea unor funcţii a clasei de bază în clasa derivată. Funcţiile
din clasa părinte sînt în continuare accesibile în clasa derivată.
Să vedem cum arată obiectele polimorfe. Pentru aceasta,
determinăm clasele de bază Punct şi cea derivată Cerc şi funcţia
membru Aria(), ce va avea ca efect determinarea ariei obiectului
respectiv:
#include <stdio.h>
class Punct
{ float x,y;
public:
void Incarc_punct(float xi,float yi) {x=xi;y=yi;};
virtual float Aria() { return 0.0; };
};
const float pi=3.14159;
class Cerc : public Punct
{float raza;
public: void Incarc_raza (float r) { raza=r; }
float Aria(){ return pi*raza*raza; } ; // funcţie redefinită
};
void main()
{ Punct p;
float a=p.Aria();
printf("Aria unui punct este: %5.2f\n",a);
Cerc c;
c.Incarc_raza(3.65637);
a=c.Aria();
printf("Aria cercului este: %5.2f\n",a);
}
Clasa Cerc este derivată de la clasa Punct. Cerc şi Punct sînt
91
obiecte din aceeaşi categorie, sînt polimorfe. Pentru a obţine obiecte
polimorfe, va trebui să construim o ierarhie de clase şi apoi să
redefinim funcţiile, aparţinînd clasei de bază în clasele derivate.
Această operaţie poate fi realizată în două moduri:
– rescriind funcţiile respective, efectuînd, deci o nouă implementare
a acestora,
– utilizînd funcţiile virtuale.
Să vedem cum are loc rescrierea funcţiilor. Este evident, că
funcţia membru Aria() a clasei Punct este moştenită de clasa Cerc,
dar nu convine din punctul de vedere al valorii returnate. Astfel, s-a
impus reimplementarea acesteia.
Punct *p;
Cerc c;
p=&c;
float aria=p->Aria ();
În instrucţiunea, în care este apelată funcţia Aria(), este apelată
funcţia Cerc::Aria(), p indică spre obiectul c de tipul Cerc.
Ataşarea codului unei funcţii la numele său poate fi efectuată
static sau dinamic. Acest proces poartă denumirea de identificare a
funcţiilor, existînd, deci două metode de identificare: statică şi
dinamică.
Identificarea statică are loc la un apel normal de funcţie.
Compilatorul preia numele funcţiei, argumentele sale şi, în cazul, în
care aceasta este membru, numele clasei obiectului şi o identifică.
Identificarea dinamică se va realiza în momentul execuţiei,
funcţia nefiind identificată, decît la această etapă. Cum? O
posibilitate ar fi de a utiliza pointeri la funcţii, astfel, că doar în
momentul rulării vom şti funcţia, spre care a indicat acel pointer.
Vom defini un pointer în interiorul clasei Punct, ce va indica spre
funcţia Aria().
Reţinem, că acest pointer va fi moştenit de către Cerc. După

92
crearea unui obiect de tip Punct sau Cerc, vom iniţializa pointerul
spre funcţia corectă.
O a doua posibilitate este aceea de a utiliza funcţiile virtuale.
Funcţiile virtuale sînt utilizate pentru reimplementarea unor
funcţii membri ale unor clase de bază, astfel, încît această redefinire
de funcţii să funcţioneze în ambele situaţii.
const float pi=3.14159;
class Cilindru : public Cerc
{ float inaltime;
public:
void Incarca_ inaltime(int h) {inaltime =h;};
float Aria() ; };
float Cilindru::Aria()
{return 2*pi*raza*inaltime +2 *Cerc::Aria(); }
În implementarea noii versiuni a funcţiei membri Cilindru::Aria() se
utilizează versiunea anterioară, moştenită de la clasa de bază Punct.
Evident, că Punct::Aria() nu va modifica rezultatul furnizat, aportul
său la aceasta fiind nul, dar se sugerează, că este permisă apelarea
funcţiilor moştenite pentru oricare nivel al ierarhiei, ţinînd cont de
nivelurile de protecţie ( private, protected şi public).
În procesul de lucru cu clase derivate putem foarte uşor greşi,
transformînd o funcţie virtuală în funcţie redefinită datorită faptului,
că cele două categorii se aseamănă. Totuşi, există cîteva diferenţe
dintre aceste două categorii de funcţii:
– funcţiile membri redefinite sînt ataşate obiectului, urmînd
procedura statică (la compilare), în timp ce funcţiile virtuale fac
parte din cea de– a doua categorie, legăturile cu obiectele fiind
realizate dinamic (în timpul execuţiei);
– funcţiile membri redefinite pot avea liste diferite de parametri, în
timp ce funcţiile virtuale trebuie să posede aceeaşi listă de
parametri.

93
Dacă, în cadrul ierarhiei de clase, funcţiile nu se comportă
exact cum vrem noi, va trebui să verificăm toate funcţiile virtuale,
pentru a ne asigura, că, într-adevăr, sînt virtuale şi nu redefinite.
# include <stdio.h>
class scade
{public:
virtual int executa (unsigned char c) { return --c; };
};
class aduna : public scade
{ public:
int executa ( char c) { return ++c; };
};
void main()
{scade *p=new aduna;
int k=p->executa(43);
printf(“k = %d \n”,k);}
În acest exemplu programul va afişa răspunsul 42. Versiunea
clasei scade acceptă un argument unsigned char, iar versiunea clasei
aduna – un argument de tip char. Datorită acestei schimb de tip, a
doua versiune a funcţiei executa() nu este virtuală, ea este redefinită.
Chiar dacă prin intermediul lui p acţionăm asupra unui obiect aduna,
apelînd funcţia executa(), ne referim la versiunea scade. Deci
programul va afişa valoarea 42.
O funcţie operator are aceleaşi componente, pe care le are
orice funcţie, include un nume, un tip returnat, argumente, corp şi,
eventual, apartenenţa la o clasă. Există trei elemente, care trebuie
stabilite la declararea operatorului, şi anume, este operator unar sau
binar, este postfixat sau prefixat ca poziţie şi este funcţie membru sau
nu –domeniu de acţiune.

94
Funcţiile operator membri vor avea cu un argument mai
puţin decît cele non-membri, argumentul ascuns. Apelul
unui astfel de operator membru va fi
p+=5;
sau
p. operator+=(5);
Redefinirea operatorilor
Limbajul C++ permite programatorilor să definească operatori
pentru a lucra cu propriile clase. Sintaxa supraîncărcării unui
operator este:
operator Simbol
unde Simbol este simbolul oricărui operator C++, exceptînd: . *-
adresare la componenta prin pointer, ::- operatorul de rezoluţie, ()
?:- operatorul condiţional, operatorul sizeof, etc.. Această definire se
face în cadrul clasei, întocmai ca o funcţie membră (vezi Tabela 1.)
Există două variante de definire a operatorilor:
– ca funcţie membră a clasei;
– ca funcţie prietenă a clasei.
Tabela 1.
Tipul Simbolul
Asociativitate Observaţii
operatorului operatorului
Se definesc ca
Binar () [] -> ->
funcţii membre
Unar + - ~ * & (tip) <-
Nu se poate dis-
Unar ++ -- <- tinge între pre- şi
postfixare
Poate fi supra-
Unar new, delete <- definit şi pentru o
clasă
Binar -> * / % + - & | ->

95
&& ||
<< >> < <= >
Binar ->
>= == !=
= += -= *= /=
Se definesc ca
Binar %= &= ^= |= <-
funcţii membre
<<= >>=
Binar , ->
Pentru exemplificare, vom extinde clasa Point cu utilizarea
unor operatori.
class Point
{// ...
Point& operator += (Point p);
Point& operator -= (Point p);
Point operator + (Point p);
Point operator - (Point p);
Point& operator = (Point p);
int operator == (Point p);
int operator != (Point p);
int operator < (Point p);
int operator > (Point p);
int operator <= (Point p);
int operator >= (Point p);
};
Point& Point::operator += (Point p)
{x += p.x; y += p.y; return *this;}
Point& Point::operator -= (Point p)
{x -= p.x; y -= p.y; return *this;}
Point Point::operator + (Point p)
{return Point(x + p.x, y + p.y);}
Point Point::operator - (Point p)
{return Point(x -p.x, y -p.y);}
int Point::operator == (Point p)
96
{return x == p.x && y == p.y;}
int Point::operator != (Point p)
{return !(*this == p);}
int Point::operator < (Point p)
{return x < p.x && y < p.y;}
int Point::operator > (Point p)
{return x > p.x && y > p.y;}
int Point::operator <= (Point p)
{return x <= p.x && y <= p.y;}
int Point::operator >= (Point p)
{return x >=p.x && y >= p.y;}
Am utilizat mai sus varianta cu funcţii membre. Vom descrie
implementarea operatorului + folosind cea de-a doua variantă.
class Point {
// ...
friend Point operator + (Point p1, Point p2);
};
Point operator + (Point p1, Point p2)
{return Point(p1.x + p2.x, p1.y + p2.y);}
Definirea operatorilor ca funcţii membre a unei clase prezintă o
restricţie majoră: primul operand este obligatoriu să fie de tipul clasa
respectiv.
În limbajul C++ supradefinirea operatorilor este supusă unui set
de restricţii:
– nu este permis introducerea de noi simboluri de operatori;
– patru operatori nu pot fi redefiniţi (vezi mai sus);
– caracteristicile operatorilor nu pot fi schimbate: pluralitatea (nu
se poate supradefini un operator unar ca operator binar sau
invers), precedenţa şi asociativitatea, prioritatea lor;
– funcţia operator trebuie sa aibă cel puţin un parametru de tipul
clasa căruia îi este asociat operatorul supradefinit.

97
Programatorul are libertatea de a alege natura operaţiei
realizate de un operator, însă este recomandat ca noua operaţie să fie
apropiată de semnificaţia iniţială.
Redefinirea operatorului +=. Acţiunea acestuia este aceea de a
adăuga o valoare unui număr de tip char, int, float sau double. Putem
redefini acest operator, pentru a opera asupra obiectelor, de exemplu,
de tip persoana.
# include <stdio.h>
# include <string.h>
class persoana
{ private:
char nume[40];
long int telefon;
int virsta;
public:
persoana () {strcpy(nume,'\0');telefon=0; virsta=0;};
persoana(long int Telefon) { telefon= Telefon; };
persoana(char *Nume) { strcpy(nume,Nume); };
persoana(int Virsta) { virsta= Virsta; };
persoana(char *n, long l) { strcpy(nume,n);telefon=l; };
void Citeste(char *n,long t=0,int v=0)
{ strcpy(nume,n); telefon=t; virsta=v;};
void Tipar(persoana s)
{printf("\n nume %s, telefon %ld, virsta %d ",s.nume,
s.telefon, s.virsta);};
int Seteaza_Virsta(int v) { return(virsta=v); };
};
void main()
{ persoana p;
p.Citeste("Cristina",234567,p.Seteaza_Virsta(20));
p.Tipar(p);}
Pentru aceasta, vom defini o funcţie avînd numele operator
98
+=() cu doi parametri. Aceşti parametri sînt cei doi operanzi ai
operatorului +=.
void operator +=(persoana &p, int v)
{ p.Seteaza_Virsta(p.Virsta()+v); }
Utilizarea operatorului definit se realizează prin iniţializarea
persoana p('Mircea" , 0 , 9) ; şi apelul
p+=5; sau apelîndu-l ca funcţie
operator+=(p,5);
Bineînţeles, funcţia operator poate fi membru a clasei asupra căreia
acţionează
class persoana
{ public: …
void operator+=(int v);

};
void persoana::operator+=(int v)
{Seteaza_Varsta(varsta+v); }
Redefinirea operatorului =. Operatorul = este deja predefinit
în C++, pentru operanzi de tip clasă. Dacă nu este supradefinită,
atribuirea se face membru cu membru în mod similar cu iniţializarea
obiectului, efectuată de către compilator. În caz de o atribuire
specifică a clasei, operatorul = poate fi supradefinit.
Point& Point::operator = (Point p)
{x = p.x; y =p.y; return *this;}
Redefinirea operatorului [].Operatorul de indexare [] se
defineşte astfel:
int &operator[](int)
De exemplu
Point Array_Point::operator []= (Point *p, int j) {return p[j];}
Redefinirea operatorilor new şi delete poate fi efectuată pentru
a realiza operaţii specializate de alocare/eliberare dinamică a
memoriei. Funcţia operator new trebuie sa primească un argument de
99
tipul size_t care să precizeze dimensiunea în octeţi a obiectului alocat
şi să returneze un pointer de tip void conţinînd adresa zonei alocate:
void *operator new(size_t)
cu menţiunea că size_t este definit în stdlib.h. Chiar dacă parametrul
de tip size_t este obligatoriu, calculul dimensiunii obiectului în
cauză şi generarea sa se face de către compilator.
Funcţia operator delete trebuie sa primească ca prim parametru
un pointer de tipul clasei în cauză sau void, conţinînd adresa
obiectului de distrus, şi un al doilea parametru, opţional, de tip
size_t. Funcţia nu întoarce nici un rezultat.
void operator delete(void *, size_t)
Operatorii new şi delete supradefiniţi păstrează toate
proprietăţile operatorilor new şi delete standard.
Redefinirea operatorilor unari poate fi efectuată utilizînd o
funcţie membră fără parametri sau o funcţie prietenă cu un
parametru de tipul clasei respectiv. Pentru operatorii ++ şi -- dispare
distincţia între utilizarea ca prefix şi cea ca postfix, de exemplu,
intre x++ şi ++x, respectiv, x-- şi –x.
De exemplu:
Point Point::operator++ ()
{x++; y++; return *this;}
Conversii de tip definite de programator. În limbajul C++
este definit un set de reguli de conversie pentru tipurile de bază de
date. C++ permite definirea de reguli de conversie pentru clasele
create de programator. Regulile astfel definite sînt supuse unor
restricţii:
– într-un şir de conversii nu este admisă decît o singură conversie
definită de programator;
– se recurge la aceste conversii numai după ce se verifică existenţa
altor soluţii (de exemplu, pentru o atribuire, se verifica mai întîi

100
supraîncărcarea operatorului de atribuire şi în lipsa acestuia se
face conversia).
Exista două metode de a realiza conversii de tip.
Supraîncărcarea operatorului unar "cast". Sintaxa este:
operator TipData() respectiv:
operator (TipData)
Operatorul "cast" este unar, aşa dar are un singur parametru,
adresa obiectului în cauză, şi întoarce un rezultat de tipul
operatorului. Ca urmare, prin această metodă se pot defini numai
conversii dintr-un tip clasă într-un tip de bază sau un alt tip clasă.
În cazul conversiei dintr-un tip clasă într-un alt tip clasă,
funcţia operator trebuie să aibă acces la datele membre ale clasei de
la care se face conversia, deci trebuie declarată prietenă a clasei
respective.
Conversiile de tip folosind constructori constă în definirea
unui constructor ce primeşte ca parametru tipul de la care se face
conversia. Constructorul întoarce întotdeauna ca rezultat un obiect de
tipul clasei de care aparţine, ca urmare folosind această metodă, se
pot realiza numai conversii dintr-un tip de bază sau un tip clasă într-
un tip clasă.
În cazul conversiei dintr-un tip clasă într-un alt tip clasă,
constructorul trebuie să aibă acces la datele membre ale clasei de la
care se face conversia, deci trebuie declarată prietenă a clasei
respective.
Constructorul de copiere. O situaţie care poate apărea deseori
este iniţializarea unui obiect cu datele membre ale unui obiect de
acelaşi tip. Exista totuşi situaţii în care operatorul de atribuire nu
poate fi utilizat, de exemplu, la transferul unui obiect ca parametru
sau la crearea unei instanţe temporare a unei clase, cînd copierea
membru cu membru nu este adecvată. Pentru a rezolva aceste situaţii,
în limbajul C++ a fost introdus un constructor special, numit
constructorul de copiere. Sintaxa este:
101
NumeClasă::NumeClasă (NumeClasă &NumeObiectSursă)
În continuare vom completa clasa Point cu un constructor de
copiere:
Point::Point(Point &p)
{p.x = x;
p.y = y;}
În cazul în care clasa nu dispune de constructor de copiere,
compilatorul generează automat un constructor de copiere care
realizează copierea membru cu membru.
Clase abstracte. În limbajul C++ exista posibilitatea de a
defini clase generale, care sînt destinate creării de noi clase prin
derivare. Ele nu pot fi instanţiate şi utilizate ca atare. Acest gen de
clase se numesc clase abstracte. Ele se constituie ca bază în cadrul
elaborării de ierarhii de clase, putînd fi folosite, de exemplu, pentru a
impune anumite restricţii în realizarea claselor derivate.
În vederea construirii unor astfel de clase, s-a introdus
conceptul de funcţie virtuală pură. O astfel de funcţie este declarată
în cadrul clasei, dar nu este definită. O clasă care conţine o funcţie
virtuală pură este considerată abstractă. Sintaxa definirii acestor
funcţii este:
virtual TipData NumeFunctieMembră() = 0
Funcţiile virtuale pure trebuie definite în clasele derivate, altfel
şi acestea vor fi considerate abstracte.
Membri statici ai unei clase. În mod normal, datele membre
ale unei clase sînt alocate în cadrul fiecărui obiect. În C++, se pot
defini date membre cu o comportare specială, numite date statice.
Acestea sînt alocate o singură dată, existînd sub forma unei singuri
copii, comună tuturor obiectelor de tipul clasa respectiv, iar crearea,
iniţializarea şi accesul la aceste date sînt independente de obiectele
clasei. Sintaxa este:
static DeclarareMembru

102
Funcţiile membre statice efectuează de asemenea operaţii care nu sînt
asociate obiectelor individuale, ci întregii clase. Funcţiile exterioare
clasei pot accesa membrii statici ale acesteia astfel:
NumeClasă::NumeMembru
Obiect::NumeMembru
Funcţiile membre statice nu primesc ca parametru implicit
adresa unui obiect, aşa dar în cadrul lor cuvîntul cheie this nu poate fi
utilizat. De asemenea, membrii normali ai clasei nu pot fi referiţi
decît specificînd numele unui obiect.

Întrebări pentru verificarea cunoştinţelor:


1. Care dintre instrucţiunile de mai jos sînt legale?
a: class sir
{private:
static char eos;
char *continut;
int dimensiune;
public:
sir(int sz);
void copy(sir &s);
};
b: char sir::eos=Ox1A;
void sir::copy(sir 63)
delete continut;
continut = new char[3.dimensiune];
char *p=continut;
char *q=s.continut;
while <*qt=eos)
c: *p++=*q++;
*p=eos;
void main()
{sir s(80);}
103
d: sir::eos=0;
e: s.eos='$';
2. După cum ştim, atunci cînd redefinim funcţii, listele de
argumente trebuie să difere de la implementare la implementare.
De ce acest lucru nu se impune atunci cînd redefinim funcţia
Aria() din cadrul claselor Punct şi Cerc?
3. Dacă redefinim operatorul+ ataşat clasei persoana şi facem
această funcţie membră, cîte argumente explicite va avea
aceasta?
4. Scrieţi o funcţie operator membră a clasei persoana pentru a
rescrie operatorul ==. Există mai multe variante ale acestei
funcţii?

Temele pentru acasă:


1. Ce va afişa programul următor?
#include<iostream.h>
Class Bunic
{ public :
virtual void sfat()
{ cout<< “Distrează-te”; }
};
class Tata : public Bunic
{ public:
void sfat()
{ cout « "Fă-ţi lecţiile'\n"; }
}
class Fiu : public Tata
{ public:
void sfat()
{ Bunic::sfat(); }
};
void main()
104
( Fiu lon;
lon.sfat(); }
2. Ce se va afişa la ecran după executarea programului următor?
#include <iostream.h>
class Mesaje_bune
{ public:
virtual void act1()
{ cout«"Prinţul vede prinţesa\n"; act2(); }
void act2()
{ cout«"Prinţul o sărută\n"; act3(); }
virtual void act3()
{ cout«"Prinţesa se trezeşte\n"; act4(); }
virtual void act4 ()
{ cout«"Si au trăit fericiţi . . . \n" ; act5(); )
void act5()
{ cout « "Sfîrşit '\n"; }
};
class Mesaje rele : public Mesaje bune
{ public:
void act3()
{ cout«"Prinţesa rămîne teapănă\n" ;act4 () ; }
void act4()
{ cout«"Prinţul fuge îngrozit\n";act5(); }
void act5()
{ cout«"Un sfîrşit, nefericit' \n" ; }
};
void main ()
{ char c;
Mesaje bune *mes;
cout«"Care varianta vreţi sa o vedeţi (L/B) ?\n" ;
cin»c;
if (p=='L') | | (c=='l’)) mes=new Mesaje_bune;
105

108
else mes=new Mesaje rele;
mes->act1();
delete mes;}

Temele pentru lucrări de laborator:


1. Scrieţi un program care ar defini clasa de bază num. În această
clasă determinaţi un număr întreg şi funcţia shownum(). Creaţi
două clase derivate outhex şi outoct care moştenesc num. Funcţia
shownum() se va redefini în clasele derivate astfel ca ea să afişeze
la disply respectiv, valorile din sistemele de numeraţie
hexazecimală şi octală, să se efectueze toate operaţiile aritmetice
asupra numerelor din aceste sisteme de numeraţie.
2. Scrieţi un program care care ar defini clasa de bază num. În
această clasă determinaţi un număr întreg şi funcţia shownum().
Creaţi clasa derivată outbin care moşteneşte num. Funcţia
shownum() se va redefini în clasa derivată astfel ca ea să afişeze la
disply valorile din sistemul de numeraţie binar, să se efectueze
toate operaţiile aritmetice şi logice asupra numerelor binare.
3. Scrieţi un program care defineşte clasa de bază distance pentru a
păstra în variabila de tipul double distanţa dintre două puncte. În
clasa distance să se creeze funcţia virtuală trav_time(), care
afişează valoarea timpului necesar pentru a parcurge distanţa în
mile, viteza fiind de 60 mile/oră. În clasa derivată metric se va
redefini funcţia trav_time() astfel va ea să afişeze timpul necesar
pentru parcurgerea distanţei în kilometri, viteza fiind de 100
km/oră.
4. Scrieţi un program care ar defini clasa de bază vector. În această
clasă determinaţi un vector de numere întregi şi funcţia
showvector(). Creaţi clasa derivată array care moşteneşte vector.
Funcţia showvector() se va redefini în clasa derivată astfel ca ea să
afişeze la disply valorile tabloului pe linii şi pe coloane. Să se
calculeze suma elementelor tabloului, să se efectueze operaţiile
106
algebrice asupra a două tablouri (adunarea, scăderea, înmulţirea).
5. Scrieţi un program care ar defini clasa de bază punct. În această
clasă determinaţi un punct în plan şi funcţia showpoint(). Creaţi
clasa derivată poligon care moşteneşte punct. Funcţia showpoint()
se va redefini în clasa derivată astfel ca ea să afişeze la disply
punctele cu coordonatele date. Să se determine poziţia punctului
faţă de celelalte puncte din poligon, să se determine distanţa
minimă de la punctul dat pînă la primul punct ce aparţine
poligonului.
6. Scrieţi un program care ar defini clasa de bază string. În această
clasă determinaţi funcţia showstring(). Creaţi clasa derivată
newstring care moşteneşte string. Funcţia showstring() se va
redefini în clasa derivată astfel ca ea să afişeze la disply
simbolurile prin codurile lor interioare. Să se efectueze
următoarele operaţii asupra codurilor:
– să se determine spaţiul pentru stringul dat,
– să se compare două stringuri,
– să se extragă un substring din stringul dat,
– să se lichideze un substring din stringul dat din poziţia dată,
– să se inverseze un string dat.
– să se caute un substring din stringul dat.
7. Scrieţi un program care ar defini clasa de bază bit. În această clasă
determinaţi valorile logice true şi false şi funcţia showbit(). Creaţi
clasa derivată outbit care moşteneşte bit. Funcţia showbit() se va
redefini în clasa derivată astfel ca ea să afişeze la disply valorile
unui şir de biţi. Să se efectueze următoarele operaţii asupra şirului
de biţi:
– să se determine lungimea şirului de biţi, să se determine spaţiul
pentru şirului de biţi dat,
– să se compare două şiruri de biţi,
– să se extragă un subşir de biţi din şirul de biţi dat,
107
– să se lichideze un subşir de biţi din şirul de biţi dat din poziţia
dată,
– să se inverseze un şir de biţi dat,
– să se caute un subşir de biţi din şirul de biţi dat.
8. Scrieţi un program care ar defini clasa de bază set_bit. În această
clasă determinaţi funcţia showset_bit(). Creaţi clasa derivată
mulţime care moşteneşte set_bit. Funcţia showset_bit() se va
redefini în clasa derivată astfel ca ea să afişeze la disply valorile
unei mulţimi. Să se efectueze următoarele operaţii asupra
mulţimii:
– să se determine numărul de elemente a mulţimii
– să se determine spaţiul pentru mulţimea dată,
– să se compare două mulţimi,
– să se extragă o submulţime din mulţimea dată,
– să se adauge un nou element la mulţime,
– să se şteargă un element din mulţime,
– să se caute o submulţime din mulţimea dată.
9. Scrieţi un program care ar defini clasa de bază int. În această clasă
determinaţi valorile întregi şi funcţia showint(). Creaţi clasa
derivată longint care moşteneşte int. Funcţia showint() se va
redefini în clasa derivată astfel ca ea să afişeze la disply valorile
unui număr de tip longint..Să se efectueze următoarele operaţii
asupra tipului longint:
– să se determine numărul de cifre din numărul dat,
– să se compare două numere longint,
– să se extragă numerele de tip int din numărul longint (partea
inferioară şi partea superioară),
– să se efectueze operaţiile algebrice asupra numerelor date,
– să se inverseze un număr de tip longint (partea inferioară cu cea
superioară).

108
10. Scrieţi un program care ar defini clasa de bază real. În această
clasă determinaţi valorile reale şi funcţia showreal(). Creaţi clasa
derivată double care moşteneşte real. Funcţia showreal() se va
redefini în clasa derivată astfel ca ea să afişeze la disply valorile
unui număr de tip double. Să se efectueze următoarele operaţii
asupra tipului double:
– să se determine numărul de cifre din partea întreagă a numărul
dat,
– să se determine numărul de cifre din partea fracţionară a numărul
dat,
– să se compare două numere double,
– să se transforme numărul dat într-un şir de caractere,
– să se efectueze operaţiile algebrice asupra numerelor date.
11. Scrieţi un program care ar defini clasa de bază int în sistemul de
numeraţie p. În această clasă determinaţi valorile întregi şi
funcţia showint(). Creaţi clasa derivată longint în sistemul de
numeraţie p care moşteneşte int. Funcţia showint() se va redefini
în clasa derivată astfel ca ea să afişeze la disply valorile unui
număr de tip int. în sistemul de numeraţie p. Să se efectueze
următoarele operaţii asupra tipului longint în sistemul de
numeraţie p:
– să se determine numărul de cifre din numărul dat,
– să se compare două numere longint,
– să se extragă numerele de tip int din numărul longint (partea
inferioară şi partea superioară),
– să se efectueze operaţiile algebrice asupra numerelor date,
– să se inverseze un număr de tip longint (partea inferioară cu cea
superioară).
12. Scrieţi un program care ar defini clasa de bază real în sistemul de
numeraţie p. În această clasă determinaţi valorile reale în
sistemul de numeraţie p şi funcţia showreal(). Creaţi clasa
109
derivată double în sistemul de numeraţie p care moşteneşte real
în sistemul de numeraţie p. Funcţia showdouble() se va redefini
în clasa derivată astfel ca ea să afişeze la disply valorile unui
număr de tip double în sistemul de numeraţie. Să se efectueze
următoarele operaţii asupra tipului double în sistemul de
numeraţie p:
– să se determine numărul de cifre din partea întreagă a numărul
dat,
– să se determine numărul de cifre din partea fracţionară a numărul
dat,
– să se compare două numere double,
– să se transforme numărul dat într-un şir de caractere,
– să se efectueze operaţiile algebrice asupra numerelor date.
13. Scrieţi un program care ar defini clasa de bază int. În această
clasă determinaţi valorile întregi şi funcţia showint(). Creaţi
clasa derivată fraction care moşteneşte int. Funcţia
showfraction() se va redefini în clasa derivată astfel ca ea să
afişeze la disply valorile unui număr fracţionar. Să se efectueze
următoarele operaţii asupra numerelor fracţionare:
– transformarea unui număr fracţionar mixt într-o fracţie
supraunitară,
– adunarea numerelor fracţionare,
– înmulţirea numerelor fracţionare,
– scăderea numerelor fracţionare,
– împărţirea numerelor fracţionare.
14. Scrieţi un program care ar defini clasa de bază list. În această
clasă determinaţi valorile întregi ale listei şi funcţia showlist().
Creaţi clasa derivată stiva care moşteneşte list. Funcţia showlist()
se va redefini în clasa derivată astfel ca ea să afişeze la disply
valorile unei stive. Să se efectueze următoarele operaţii asupra
stivei:
110
– empty(S) care curăţă stiva S,
– if_is_empty(S) care verifică dacă stiva S este vidă,
– pop(S,x) care adaugă elementul x în stiva S,
– push(S,x) care scoate elementul x din stiva S,
– error (k) care indică eroarea cu numărul k (k=1 dacă stiva este
supraîncărcată, k=2 dacă stiva este vidă).
15. Scrieţi un program care ar defini clasa de bază int. În această
clasă determinaţi valorile întregi şi funcţia showdate(). Creaţi
clasa derivată date care moşteneşte int. Funcţia showdate() se va
redefini în clasa derivată astfel ca ea să afişeze la disply valorile
unei date. Să se efectueze următoarele operaţii asupra datei:
– adunarea,
– înmulţirea cu un coeficient,
– scăderea,
– împărţirea la un coeficient,
– valoarea lunii datei să se tipărească cu cuvinte.
16. Scrieţi un program care ar defini clasa de bază data. În această
clasă determinaţi valorile întregi şi funcţia showdate(). Creaţi
clasa derivată date care moşteneşte int. Funcţia showdate() se va
redefini în clasa derivată astfel ca ea să afişeze la disply valorile
unei date. Să se efectueze următoarele operaţii asupra datei:
– adunarea,
– înmulţirea cu un coeficient,
– scăderea,
– împărţirea la un coeficient,
– valoarea lunii datei să se tipărească cu cuvinte.

Lucrarea de laborator nr. 4.


Clase derivate cu moştenire multiplă.
111
Scopul lucrării: familiarizarea studenţilor cu clase derivate cu
moştenire multiplă.

Consideraţiile teoretice necesare:


Moştenirea multipla
Limbajul C++ permite crearea de clase care moştenesc
proprietăţile mai multor clase de bază. Dintr-o clasă de bază pot fi
derivate mai multe clase şi fiecare clasă derivată poate servi mai
departe ca bază pentru alte clase derivate. Se poate astfel realiza o
ierarhie de clase, care să modeleze adecvat sisteme complexe.
Pornind de la clase simple şi generale, fiecare nivel al ierarhiei
acumulează caracteristicile claselor "părinte" şi le adaugă un anumit
grad de specializare. O clasă poate să moştenească simultan
proprietăţile mai multor clase, procedură numită moştenire multiplă.
Construirea ierarhiei de clase reprezintă activitatea fundamentală de
realizare a unei aplicaţii orientate obiect.
Moştenirea multiplă creşte astfel flexibilitatea dezvoltării
ierarhiei de clase. Dacă derivarea normală duce la construirea unei
ierarhii de tip arbore, derivarea multiplă va genera ierarhii de tip
graf. Sintaxa completă pentru operaţia de derivare este următoarea:
class NumeClasăDerivată : ListaClaseDeBază
unde ListaClaseDeBază este:
SpecificatorAcces NumeClasaDeBază, ...
Clase virtuale. Utilizarea moştenirii multiple se poate
complica odată cu creşterea dimensiunii ierarhiei de clase. O situaţie
care poate apare este derivarea din două clase de bază, Clasa1 şi
Clasa2, care la rîndul lor sînt derivate dintr-o clasă comună,
ClasaDeBază. În acest caz, noua clasă, ClasaNouă, va conţine datele
membre ale clasei ClasaDeBază duplicate. Dacă prezenţa acestor
date duplicate este utilă, ele pot fi distinse evident cu ajutorul
operatorului de rezoluţie, ::. Totuşi, în cele mai multe cazuri, această
112
duplicare nu este necesară şi duce la consum inutil de memorie. De
aceea, în C++ a fost creat un mecanism care sa evite aceasta situaţie,
prin intermediul conceptului de clasă virtuală. Sintaxa este:
class NumeClasaDerivată : SpecificatorAcces virtual
NumeClasaDeBază
Această declaraţie nu afectează clasa în cauză, ci numai
clasele derivate din aceasta. Astfel, clasele Clasa1 şi Clasa2
considerate vor fi declarate virtuale. Declararea virtual a acestor
clase va afecta definirea constructorului clasei ClasaNouă, deoarece
compilatorul nu poate hotărî care date vor fi transferate către
constructorul ClasaDeBază, specificate de constructorii Clasa1 şi
Clasa2. Constructorul ClasaNouă va trebui modificat astfel încît să
trimită datele pentru constructorul ClasaDeBază. De asemenea, într-
o ierarhie de clase derivate, constructorul clasei virtuale este
întotdeauna apelat primul. De exemplu,
class A
{ …};
class B1: virtual public A
{…}
class B2: virtual public A
{…};
class C1: public B1, public B2
{ …};
În mod grafic acest exemplu se poate de prezentat în modul
următor: class A

(virtual
virtual
class B1 class B2

113
class C

Întrebări pentru verificarea cunoştinţelor:


1. Ce se numeşte moştenire multiplă? Exemple.
2. Ce se numeşte clasă virtuală? Exemple.

Temele pentru acasă:


1. Ce se va afişa la ecran după rularea programului următor?
#include <iostream.h>
cassBl
{ public :
Bl() { cout « "Constructorul B1 \n";}
~B1() { cout « "Destructorul B1 \n";}
class B2
{ int b;
public :
B2 ( ) { cout « "Constructorul B2\n"; }
~B2 ( ) { cout « "Destructorul B2 \n"; }
};
// Moştenirea a două clase de bază
KOHCTp
( cout « "
yKTOpa
cout «
main ()
{
C ob;
return 0;

class D : public Bl, public B2


{ public:

114
D() { cout « "Constructorul D \n";}
~D() { cout « "Destructorul D\n";}
};
void main()
{ D obj; }
2. Care va fi rezultatul lansării programului de mai jos? Corectaţi
greşelile din program.
#include <iostream.h>
class A
{ int i;
public :
Al(int a=0) { i=a;cout « "Constructorul A \n";}
~B1() { cout « "Destructorul B1 \n";
class B
{ int j;
public :
B2 (int a=0 ) { j=a; cout « "Constructorul B2\n"; }
~B2 ( ) { cout « "Destructorul B2 \n"; }
};
// Moştenirea a două clase de bază
KOHCTp
( cout « "
yKTOpa
cout «
main ()
{
C ob;
return 0;

class C : public Bl, public B2


{ int k,
public:

115
/* Scrieţi constructorul pentru clasa C astfel ca el să activeze
constructorii claselor de bază A şi B*/
c() { cout « "Constructorul D \n";
~C() { cout « "Destructorul D\n";
};
void main()
{ C obj; }
Temele pentru lucrări de laborator:
1. La un depozit a fost adusă marfă de export = {denumire, ţara de
exportare, cantitatea, preţul, data livrării}. şi anume, procesor
= {tip, viteza de lucru}, monitor = {tipul, diagonala}, wincester
= {firma, dimensiune}. Să se determine suma de bani necesară
pentru achitarea mărfii aduse. Plata se efectuează în lei ţinînd
cont de cursul valutar {valuta, data. vînzarea, cumpărarea}.
2. Să se efectueze următoarele operaţii asupra figurilor geometrice:
de rotire a figurii, de mutare pe ecran, de colorare, de decupare a
unei părţi din obiect, de a scrie text în figură. Figurile
geometrice au următoarele structuri:. triunghi = {vîrfl, vîrf2,
vîrf3}, dreptunghi ={vîrfl, vîrf2}, cerc = {centru, raza}, elipsa =
(centru, raza1, raza2}.
3. Să se calculeze cît timp a trecut de la începutul erei noastre.
Structurile de bază sînt deceniu = {veac, era}, timp = {ora,
minuta, secunda}, data = {zi, luna, an}.
4. Să se efectueze operaţii algebrice şi conversii asupra următoarelor
structuri: număr complex= {partea reală, partea imaginară},
număr raţional= {numărător, numitor}, punct din plan =
{coordonata x, coordonata y}.
5. Să se efectueze operaţii de conversie a următoarelor structuri:
punct din scatiu = { coordonata x, coordonata y,. coordonata
z}. şi coordonate polare = {raza, unghiul}, unde unghi = {grad,
minuta, secunda),

116
6. Într-un oraş a fost construit un centru de prestare a serviciilor
telefonice. Informaţia despre oraş are următoarea structură
oraş= {denumire, cod telefonic, cod poştal}. La acest centru au
fost conectaţi 100 de utilizatori cu adresa: {strada, numarul
casei, scara, apartamentul}. Fiecare utilizator este abonat
telefonic pentru care se indică {numele, adresa, numărul
telefonului, data montării}. Informaţia despre convorbirea-
telefonică a utilizatorului are următoarea structură: convorbire
telefonică = {cod ţara, numarul solicitatului, oraşul, numărul
solicitantului, numărul de minute}. Să se afle cît trebuie să
achite serviciile prestate aceşti utilizatori dacă ei au ţinut
convorbiri telefonice cu alte oraşe. Plata pentru servicii se
primeşte în diferite unităţi valutare. cost = {lei., bani}, sau
{ruble, copeici}, sau {dolari, cenţi}....
7. La competiţii sportive de diferite probe sportive au participat
persoane din mai multe ţări. Denumirea ţării are următoarea
structură ţară= {denumire, cod telefonic, cod poştal}. Fiecare
participant are greutatea = {kilogram, gram} şi poate participa
numai la una din probele sportive propuse. Să se determine cele
mai bune rezultate sportive obţinute în diferite probe sportive.
Rezultatele sportive pot fi estimate în diferite unităţi, cum ar fi
{minute, secunde, sutimi} {kilogram, gram}, {metri, centimetri},
ş.a.
8. O bibliotecă s-a completat cu 100 de cărţi. Fiecare carte a fost
pusă la evidenţă cu următoarea structură: carte ={autor,
denumire, anul de editare, locul de editare}. Să se determine
frecvenţa de utilizare a fiecărei cărţi. Cititorul este înregistrat la
bibliotecă după următoarea structură: {strada, numărul casei,
scara, apartamentul, numărul de telefon}.
9. În municipiul Chişinău sunt multe cinematografe unde rulează
filme. Filmele sunt înregistrate în baza de date cu următoarea
structură: film = {denumire, ţara, regizor, gen, anul filmării}.
117
Fiecare cinematograf este înregistrat cu următoarea structură =
{denumire, telefon, adresa, numărul de locuri}. Să se determine
care gen de filme este mai solicitat de vizitatorii cinematografele
din Chişinău.
10. Hotelurile din Chişinău prestează servicii pentru mai multe
persoane din mai multe ţări. Pentru fiecare hotel în baza de date
se va introduce următoarea structură {denumirea, adresa,
numărul de telefon, numărul de stele}. Fiecare număr la hotel are
structura: {număr , numele locatarului, data sosirii, data
plecării, costul}. Fiecare persoană este înregistrată la hotel:
{ţara, strada, numarul casei, scara, apartamentul, numărul de
telefon}. Să se determine din ce ţară ne vizitează mai mult.
11. La depozitele unor farmacii a fost adusă marfa de export =
{denumire, tara de exportare, cantitatea, preţul, data livrării}.
Medicamentele sînt înregistrate în baza de date cu următoarea
structură: {denumire, tip, ambalaj, cantitate, cost}. Fiecare farmacie
este pusă la evidenţă cu următoarea structură farmacie={număr,
telefon, adresa, deschiderea, închiderea} . Medicul prescrie reţetă
pacientului. Pacientul este înregistrat la policlinică după următoarea
structură: {nume, adresă, diagnostică, data îmbolnăvirii}. Pacientul
doreşte să cumpere aceste leacuri. Să se afle adresele farmaciilor unde
sînt depozitate medicamentele solicitate. Să se determine frecvenţa de
solicitare a medicamentelor.
12. Se se afle ruta cea mai optimală (din punct de vedere a costului, a ora
sosirii la destinaţie, a timpului) dintre două oraşe. Comunicarea între
aceste două oraşe are loc cu autocarele ( autocar ={model, data
fabricării, punctul de destinaţie, costul rutei ), cu trenul (mersul
trenurilor ={număr, destinaţie, ora plecării , ora sosirii, categoria }),
cu avionul (ruta de avion = {număr , destinaţia, ora decolării, ora
aterizării}).
13. La Universitatea Tehnică din Moldova sînt specialităţi la care se învaţă
obiecte ce ţin de informatică: specialitatea={facultatea, catedra,
118
obiectul}, obiect ={denumire, an, tipul lecţiei, numar ore). Să se
determine din punct de vedere geografic cîţi studenţi din diferite
judeţe la facultăţi au ore de informatică ( studentul = {numele, grupa,
data şi locul naşterii, media }. )
14. În Chişinău sînt multe muzee. Informaţia despre un muzeu poate fi
introdusă în următoarea structură: muzeu ={ denumire, adresa,
telefon, începutul lucrului, sfîrşitul lucrului} . Pentru diferite categorii
de vizitatori (pensionari, studenţi, oameni maturi, copii mici, elevi)
costul biletului de intrare este diferit. Să se determine frecvenţa de
vizitate a muzeului ţinînd cont de numărul de bilete de intrare vîndute.
Informaţia despre bilet are următoarea structură
bilet_de_intrare={muzeu, tip_bilet, costul}.
15. La aeroportul din Chişinău la depozitul de lucruri pierdute se află
bagaje uitate de pasageri. Fiecare bagaj este etichetat cu următoarea
informaţie: bagaj = {numar rută, stăpîn, data, greutate}. Pentru
recuperarea bagajului uitat pasagerul trebuie să prezinte paşaportul
(buletinul de identitate). Să se determine persoanele a cărei ţări mai
des uită bagajul în aeroportul din Chişinău.
16. La o casă de vindere a imobilului se duce evidenţa caselor
(casa.={adresa, telefon, numar camere, cost, comodităţi }} şi a
apartamentelor de vînzare ( apartament.={adresa, telefon, numar
camere, etaj, cost}). Să se determine cele mai solicitate tipuri de
vînzări efectuate de casa de vindere a imobilului.

Lucrarea de laborator nr. 5.


Fluxurile Input şi Output standard şi definite de utilizatori.
Formatarea fluxurilor numerice şi textuale. Fluxurile stringuri şi de
memorie.

Scopul lucrării: familiarizarea studenţilor cu fluxurile input şi


119
output standard şi definite de utilizatori, cu formatarea fluxurilor
numerice şi textuale, cu fluxurile stringuri şi de memorie.

Consideraţiile teoretice necesare:


Noţiune de Fluxuri
Limbajul C++ foloseşte conceptul abstract de stream (flux)
pentru realizarea operaţiilor input/output. Din punctul de vedere al
limbajului C++ fluxul este o clasă în sens obişnuit. El oferă metode
de scriere şi citire a datelor independente de dispozitivul I/O.
Operaţiile de input/output se fac prin intermediul obiectelor de tip
flux. Acestea pot fi clasificate în funcţie de dispozitivul fizic asociat
respectivei operaţii. Din acest punct de vedere limbajul C++ distinge
trei categorii de fluxuri:
- input/output standard (de exemplu, tastatura şi ecranul);
- input/output prin intermediul fişierelor;
- input/output în memorie.
Fluxurile incapsulează (ascund) problemele specifice
dispozitivului cu care se lucrează, sub biblioteca standard
iostream.h.. Biblioteca iostream.h. este scrisă însăşi în limbajul C++
şi este o bibliotecă a claselor.
Ierarhia claselor de flux este ilustrată în Fig. 1.
ios

iostream iostream
iostream

istrstream strstream ostrstream

ifstream fstream ofstream


120
(Abreviaturile utilizate: I– input, o–output, str–string, f– file)
Fig.1. Ierarhia claselor de flux

Alt avantaj al utilizării fluxurilor se datorează implementării


bibliotecii iostream.h, care utilizează un sistem de zone tampon.
Operaţiile de intrare/ieşire cu dispozitivele periferice sînt
consumatoare de timp. Informaţiile trimise către un flux nu sînt
scrise imediat în dispozitivul în cauză, ci sînt transferate într-o zonă
de memorie tampon, din care sînt descărcate către dispozitiv în
momentul umplerii acestei zone de memorie.
In limbajul C++ fluxurile au fost implementate utilizînd
clase, după cum urmează:
clasa streambuf gestionează zonele tampon,
clasa ios este clasa de bază pentru clasele de stream-uri de
intrare şi de ieşire. Clasa ios are ca variabilă
membru un obiect de tip streambuf,
clasele istream sînt derivate din ios,
şi ostream
clasa iostream este derivată din istream şi ostream şi oferă
metode pentru lucrul cu terminalul,
clasa fstream oferă metode pentru operaţii cu fişiere.
.Obiecte standard. Cînd un program C++ care include
iostream.h este lansat în execuţie, sînt create şi iniţializare automat
patru obiecte:
cin gestionează intrarea de la intrarea standard (tastatura),
cout gestionează ieşirea către ieşirea standard (ecranul),
cerr gestionează ieşirea către dispozitivul standard de eroare
(ecranul), neutilizînd zone tampon,
clog gestionează ieşirea către dispozitivul standard de eroare
(ecranul), utilizînd zone tampon
121
Expresia "cout «" se utilizează pentru afişarea diverselor valori.
De fapt, cout este un obiect de tip flux, şi anume obiectul “flux
standard output”. Operaţia output (afişare) se realizează prin
intermediul operatorului supraîncărcat «.
Obiectul de tip flux standard input este cin, iar operaţia input
(citirea) se realizează prin intermediul operatorului supraîncărcat ».
Obiectele cout şi cin sînt declarate în biblioteca iostream.h.
Redirectări. Dispozitivele standard de intrare, ieşire şi eroare
pot fi redirectate către alte dispozitive. Erorile sînt de obicei
redirectate către fişiere, iar intrarea şi ieşirea pot fi conduse ("piped")
către fişiere utilizînd comenzi ale sistemului de operare (utilizarea
ieşirii unui program ca intrare pentru altul). Sintaxa pentru operaţii
de ieşire, cout:
cout << InformatieDeTrimisLaIesire;
Respectiv pentru intrare, cin:
cin >> NumeVariabilă;
De fapt, cin şi cout sînt nişte obiecte definite global, care au
supraîncărcat operatorul >> respectiv << de mai multe ori, pentru
fiecare tip de parametru în parte (int, char *, etc.):
istream &operator >> (TipParametru &)
De exemplu:
#include <iostream.h>
void main()
{int IntegerNumber;
cout << "IntegerNumber = "; cin >> IntegerNumber;
cout<<"\nWhat you entered = "<<IntegerNumber<<endl; }
Acest scurt program citeşte de la intrarea standard o valoare întreaga,
pe care o trimite apoi către ieşirea standard. Se observă posibilitatea
de a utiliza simbolurile '\n', '\t', s.a.m.d (ca la printf, scanf, etc.).
Utilizarea simbolului endl va forţa golirea zonei tampon, adică
trimiterea datelor imediat către ieşire.

122
Atît operatorul >> cît şi << returnează o referentă către un
obiect al clasei istream. Deoarece cin, respectiv cout, este şi el un
obiect istream, valoarea returnată de o operaţie de citire/scriere din/în
stream poate fi utilizată ca intrare/ieşire pentru următoarea operaţie
de acelaşi fel.
Operaţia de intrare cin. Funcţia cin.get() poate fi
utilizată pentru a obţine un singur caracter din intrare, apelînd-o fără
nici un parametru, caz în care returnează valoarea utilizată, sau ca
referinţă la un caracter.
get(); //fără parametri
În această formă, funcţia întoarce valoarea caracterului găsit. Spre
deosebire de operatorul >>, funcţia nu poate fi utilizată pentru a citi
mai multe intrări, deoarece valoarea returnată este de tip întreg, nu
un obiect istream. Un exemplu de utilizare:
#include <iostream.h>
void main()
{char c;
while((c = cin.get()) != EOF)
{cout << "c = " << c << endl;}
}
Citirea de şiruri de caractere utilizînd get(). Operatorul >>
nu poate fi utilizat pentru a citi corect şiruri de caractere de la intrare
deoarece spaţiile sînt interpretate ca separator între diverse valori de
intrare. În astfel de cazuri trebuie folosită funcţia get(). Sintaxa de
utilizare a funcţiei get în acest caz este următoarea:
cin.get(char *PointerLaSirulDeCaractere, int Lungime
Maximă, char Sfîrşit);
Primul parametru este un pointer la zona de memorie în care va fi
depus şirul de caractere. Al doilea parametru reprezintă numarul
maxim de caractere ce poate fi citit plus unu. Cel de-al treilea
parametru este caracterul de încheiere a citirii, care este opţional
(implicit considerat '\n').
123
În cazul în care caracterul de încheiere este întîlnit înainte de
a fi citit numarul maxim de caractere, acest caracter nu va fi extras
din flux. Există o funcţie similară funcţiei get(), cu aceeaşi sintaxă,
numită getline(). Funcţionarea sa este identică cu get(), cu excepţia
faptului că acel ultim caracter menţionat mai sus este şi el extras din
flux.
Funcţia cin.ignore() se utilizează pentru a trece peste un
număr de caractere pînă la întîlnirea unui anume caracter. Sintaxa sa
este:
cin.ignore(int NumarMaximDeCaractere, char Sfîrşit);
Primul parametru reprezintă numarul maxim de caractere ce vor fi
ignorate, iar al doilea parametru caracterul care trebuie găsit.
Funcţia cin.peek() returnează următorul caracter din flux,
fără însă a-l extrage.
Funcţia cin.putback() inserează în flux un caracter.
cout . Funcţii membre ale cout
Funcţia cout.flush()) determină trimiterea către ieşire a
tuturor informaţiilor aflate în zona de memorie tampon. Această
funcţie poate fi apelată şi în forma cout << flush.
Funcţia cout.put()) scrie un caracter către ieşire. Sintaxa sa
este următoarea:
cout.put(char Caracter);
Deoarece această funcţie returnează o referinţă de tip ostream, pot fi
utilizate apeluri succesive ale acesteia, ca în exemplul de mai jos:
#include <iostream.h>
void main()
{cout.put('H').put('i').put('!').put('\n');}
Funcţia cout.write() are acelaşi rol ca şi operatorul <<, cu
excepţia faptului că se poate specifica numarul maxim de caractere
ce se doresc scrise. Sintaxa funcţiei cout.write() este:
cout.write(char *SirDeCaractere, int CaractereDeScris);

124
Formatarea ieşirii
Funcţia cout.width() permite modificarea dimensiunii valorii
trimise spre ieşire, care implicit este considerată exact mărimea
cîmpului în cauză. Ea modifică dimensiunea numai pentru
următoarea operaţie de ieşire. Sintaxa este:
cout.width(int Dimensiune);
Funcţie cout.fill() permite modificarea caracterului utilizat
pentru umplerea eventualului spaţiu liber creat prin utilizarea unei
dimensiuni mai mari decît cea necesară ieşirii, cu funcţia
cout.width(). Sintaxa acesteia este:
cout.fill(char Caracter);
Opţiuni de formatare a ieşirii. Pentru formatarea ieşirii sînt
definite doua funcţii membre ale cout, şi anume:
Funcţia cout.setf() activează o opţiune de formatare a ieşirii,
primită ca parametru:
cout.setf(ios::Opţiune);
unde Opţiune poate fi:
showpos determină adăugarea semnului plus (+) în faţa
valorilor numerice pozitive;
left, right, schimbă alinierea ieşirii (la stînga. La dreapta,
internal centrează);
dec, oct, hex schimbă baza de numeraţie pentru valori
numerice;
showbase determina adăugarea identificatorului bazei de
numeraţie în faţa valorilor numerice.
Funcţia cout.setw() modifică dimensiunea ieşirii, fiind
similară funcţiei cout.width(). Sintaxa sa este:
cout.setw(int Dimensiune);
În continuare vom exemplifica utilizarea funcţiilor pentru formatarea
ieşirii:
#include <iostream.h>
#include <iomanip.h>
125
void main()
{int number = 783;
cout << "number = " << number;
cout.setf(ios::showbase);
cout<<"Numar în sistem hexazecimal="<<hex << number;
cout.setf(ios::left);
cout << "Numar însistemul octal, aliniat la stînga = " <<
oct << number;
}
Redefinirea operatorilor de intrare şi ieşire pentru fluxul
standard. Operatorii de intrare şi ieşire pentru fluxul standard pot fi
redefiniţi pentru citirea şi înregistrarea obiectului de tipul clasei
definite de utilizator.
Operatorul de intrare a fluxului standard pentru un obiect din
clasa obj se redefineşte în modul următor:
istream & operator >> (istream &s, class obj)
{ //citirea componentelor din clasa obj
return s; }
Operatorul de ieşire a fluxului standard pentru un obiect din
clasa obj se redefineşte în modul următor:
ostream & operator<< (ostream &s, class obj)
{ // tipărirea componentelor din clasa obj
return s; }
De exemplu, vom redefini operatorii menţionaţi pentru clasa
persoana.
# include <stdio.h>
# include <string.h>
class persoana
{ private:
char nume[40];
char prenume[40];
long int telefon;
126
int virsta;
public:
persoana(char *Nume, char *n, int v, long int t)
{strcpy(nume,Nume0; strcpy(prenume,n);
virsta=v, telefon= t; }
friend istream & operator >> (istream &s, persoana P);
friend ostream & operator<< (ostream &s, persoana P);
void set(char n, char *p, int v, long int t)
{strcpy(nume,Nume0; strcpy(prenume,n);
virsta=v, telefon= t; };
};
istream & operator >> (istream &s, persoana P)
{ if ((s >>P.nume) && (s >>P.prenume)&&
(s>>P.virsta) &&(P.telefon))
P.set(P.nume,P.prenume,P.virsta, P.telefon);
return s; }
ostream & operator << (ostream &s, persoana P)
{return(s<<P.nume<<P.prenume<<P.virsta<<P.telefon);}
void main()
{ persoana p;
cout<<”Introdu datele despre persoana”
cin >>p; cout<< p;
}
Operaţii de intrare/ieşire cu fişiere. În afară de fluxurile
standard input/output se pot defini fluxuri ale utilizatorului definite
prin:
ifstream nume pentru un flux input (citire)
ofstream nume pentru un flux output (scriere),
Fstream nume flux utilizat şi pentru citire şi pentru scriere
în oricare din cazurile de mai sus.
Numele va fi un obiect de tipul clasei corespunzătoare. Lucrul cu
fişiere se face prin intermediul clasei ifstream pentru citire respectiv
127
ofstream pentru scriere. Pentru a le utiliza, aplicaţiile trebuie să
includă fstream.h. Clasele ofstream şi ifstream sînt derivate din clasa
iostream, ca urmare toţi operatorii şi toate funcţiile descrise mai sus
sînt moştenite şi de această clasă.
Definirea fluxurilor se poate face numai prin includerea în
program a fişierului fstream.h. ifstream, ofstream şi fstream sînt
clase. Declararea unui flux al utilizatorului este o declarare a unui
obiect din clasa respectivă. Clasele pot conţine atît date, cît şi metode
(funcţii). Operaţiile input/output se fac prin intermediul unor funcţii
membri ale claselor respective. Sintaxele pentru constructorii acestor
două clase sînt:
ofstream Variabila(char *NumeFişier, ios::Mod);
ifstream Variabila(char *NumeFişier);
Funcţia de deschidere a unui stream este funcţia open. Fiind o
funcţie membru, ea va fi apelată numai prin intermediul unui obiect
declarat de tipul uneia din clasele de mai sus. De exemplu:
#include<fstream.h>
void main()
{ ofstream scriere;
scriere.open(“disk.dat");
scriere.close();
}
Programul de mai sus exemplifică faptul, că nu există nici o diferenţă
între lucrul obişnuit cu clase şi definirea unui flux output. Astfel,
variabila scriere a fost declarată ca fiind de tipul (clasa) ofstream.
Definiţia clasei ofstream poate fi găsită în fişierul fstream.h. Funcţia
membru open, apelată prin intermediul obiectului scriere, creează un
fişier cu numele disk.dat. În continuare acesta este închis prin apelul
funcţiei membru close(), prin intermediul obiectului scriere.
Execuţia programului va determina crearea unui fişier, care, însă nu
va conţine nimic. Scrierea propriu-zisă se face cu operatorul

128
(supraîncărcat) «, iar citirea cu operatorul », – la fel ca în cazul
fluxurilor standard input/output.
Ideea de a utiliza noţiunea abstractă de flux s-a impus din
dorinţa de a asigura independenţa operaţiilor input/output faţă de
calculator sau de sistemul de operare. Producătorii de compilatoare
livrează împreună cu compilatorul biblioteci, care conţin clase,
funcţii, variabile, care permit ca, în mare măsură, această
independenţă să fie asigurată.
Funcţia open are primul parametru de tip şir de caractere
modificată prin modificatorul const. Acesta reprezintă numele
fişierului, care va fi asociat fluxului. Al doilea parametru specifică
modul de acces la fişierul asociat fluxului şi poate avea următoarele
valori:
ios:: app; accesul se face prin adăugare la sfîrşitul
fişierului,
ios:: ate; poziţionarea se face la sfîrşitul fişierului,
ios:: binary; fişierul este interpretat ca fişier binar,
ios::in; se asociază unui flux de intrare,
ios::out; se asociază unui flux de ieşire,
ios::nocreate; fişierul trebuie să existe deja,
ios::noreplace; fişierul trebuie să nu existe,
ios::trunc; distruge un fişier preexistent, avînd acelaşi
nume.
Aceşti constructori au rolul de a deschide fişierul specificat ca
parametru.
Valorile de mai sus sînt constante definite în clasa ios,
definite în fişierul ifstream.h. Al treilea parametru este ignorat în
cazul, în care parametrul al doilea este ios :: nocreate.
Caracteristicile de tip text şi binary (binar) trebuie privite în
legătură cu operatorii folosiţi în operaţiile input/output. Operatorii,
pe care i-am folosit pînă acum ( « şi »), utilizează codificarea ASCII
a datelor. De exemplu:
129
#include<fstream.h>
void main()
{ofstream s1;
s1.open("dl.dat",ios::binary);
s1«"text \n"«12«"\n"«4.2;
s1.close();
ofstream s2;
s2.open("d2.dat");
s2«"text \n"«12«"\n"«4.2;
s2.close ();
}
Funcţiile specializate pentru operaţii input/output, care realizează
copii ale memoriei, sînt read şi write.
Funcţiile read şi write.
Funcţia write scrie într-un fişier conţinutul unei zone de
memorie, care trebuie să fie adresată printr-un pointer de tip caracter.
0 zonă de memorie poate fi privită ca un şir de octeţi, indicat prin
adresă de început şi lungime. De exemplu:
#include<fstream.h>
void main ()
{ofstream s1;
int i;
s1. open ("d1. dat", ios:: binary) ;
s1.write((char *) &i, sizeof(i)) ;
s1.write("\n",4) ;
s1.close();
ofstream s2;
s2.open("d2.dat");
s2.write((char *) &i, sizeof(i));
s2. write (" \n", 4);
s2. close ();
}
130
Funcţia de conversie (char *) se utilizează pentru a converti adresele
oricăror obiecte la tipul adresă de caracter. Astfel, se poate de
interpretat orice adresă din memorie ca, fiind adresa unui şir de
caractere. Operatorul sizeof se utilizează pentru determinarea
lungimii acestei zone de memorie.
Citirea unui fişier, utilizînd funcţia read, se face respectînd
aceleaşi principii ca în cazul funcţiei write.
Diferenţa între fişierele de tip text şi binary se păstrează şi în
cazul utilizării funcţiilor read şi write şi constă în reprezentarea
diferită a caracterelor de control. De exemplu:

#include<fstream.h>
void main()
{char *p=''\n";
ofstream s1; s1.open("d1.dat",ios::binary);
s1.write(p,sizeof(p));
s1.close();
ofstream s2; s2.open("d2.dat") ;
s2,write(p,sizeof(p));
s2.close() ;
}
Reprezentarea datelor într-un fişier depinde exclusiv de
funcţiile, care se utilizează pentru înregistrarea (<< sau write)
respectiv, citirea acestora (>> sau read).
De cîte ori citim un fişier, despre care ştim cum a fost creat,
este bine să-l citim cu aceleaşi caracteristici şi cu aceeaşi operatori.
Cînd vrem să citim din fişier, despre care nu ştim cum a fost creat,
este bine să-l citim în mod binar, caracter cu caracter. De exemplu:
#include<fstream.h>
void main()
{int i,j; float k;
char p[10];
131
ofstream s1; s1.open("d.dat") ;
s1« 122 « "\n" « 147;
s1« "\n abcd\n" « 9.3;
1.close();
ofstream s2; s2.open("d.dat",ios::binary);
s2 « i « 3 « p « k;
s2.close();
}
În alt exemplu:

#include<fstream>.h>
void main()
{ char p[20];
ifstream s;
s.open("d.dat",ios::binary);
s.read(p,19);
s.close();
p[19]=NULL; cout « p;
}
fişierul creat poate fi citit în mod binar, utilizînd funcţia read. În
acest mod conţinutul fişierului este considerat copia unei zone de
memorie. Deoarece fişierul a fost creat printr-o codificare ASCII,
conţinutul acestuia se ia ca un şir de caractere. Instrucţiunea read
copie conţinutul fişierului în şirul de caractere p. Ultima instrucţiune
din programul de mai sus afişează la ieşirea standard conţinutul
vectorului p. Atribuirea explicită p[19]=NULL este obligatorie
pentru a marca sfîrşitul şirului de caractere.
Pentru a închide aceste fişiere trebuie apelată funcţia membră
close().
Fluxuri în memorie constituie o interfaţă între program şi un
dispozitiv fizic. Am utilizat fluxurile standard şi operatorii «, » şi, de
132
asemenea, fluxurile asociate cu fişiere. Există posibilitatea de
definire a unor fluxuri în memorie. Acestea sînt fişiere, care fizic sînt
localizate în memorie. Un flux în memorie este un şir de caractere,
care are exact aceeaşi structură ca un fişier obişnuit. Clasele, care
definesc aceste fluxuri, sînt:
istrstream(input string stream); flux input,
ostrstream(output string stream); flux output,
strstream (string stream); flux input/output.
Funcţii de formatare. Clasele bazate pe fluxuri conţin o serie
de funcţii, care oferă o mare flexibilitate a operaţiilor de scriere cu
formatare. Pentru datele numerice cele mai importante sînt funcţiile,
care permit tipărirea într-un cîmp de lungime fixă, alinierea (la stînga
sau la dreapta) şi precizia, cu care se face afişarea.
Funcţia width stabileşte dimensiunea cîmpului, calculată în
caractere, pe care se va face scrierea. Funcţia are un singur parametru
de tip întreg şi trebuie apelată prin intermediul unui obiect de tip
flux. Astfel, obiectul de tip flux în memorie, declarat prin simbolul a,
va avea funcţia asociată a. width, prin care am cerut ca fiecare dată,
să fie înregistrată într-un cîmp format din 8 caractere.
Funcţia setf are, de asemenea, un singur parametru în cazul
dat ios::right, avînd semnificaţia de aliniere la dreapta.
Funcţia precision are un singur parametru de tip întreg, prin
care se specifică numărul de poziţii, scrise după punctul zecimal. De
exemplu:
#include<fstream.h>
#include<strstream.h>
void main ()
{ float a[4][5];
int i, j;
for ( i=0; i<4; i++)
for ( j=0; j<5; j++)
a[i][j]=(float) (i+2)/(j+1);
133
char s[200];
ostrstream scrie(s,sizeof(s));
for ( i =0; i<4; i++)
{for ( j=0; j<5; j++)
{scrie.width(8);
scrie.setf(ios::right);
scrie.precision(2);
scrie << a[i][j];}
scrie << "\xOA";};
s[164]=NULL;
cout << s; }
În programul de mai sus parametrul funcţiei precision este 2, deci
numerele vor fi scrise cu două semne zecimale. Programul reprezintă
un bun exemplu, prin care putem scoate în evidenţă utilitatea
fluxurilor de memorie.
Rezultatul operaţiilor de intrare/ieşire poate fi testat prin
intermediul a patru funcţie membre:
– eof() verifica daca s-a ajuns la sfîrşitul fişierului;
– bad() verifica daca s-a executat o operaţie invalida;
– fail() verifica daca ultima operaţie a eşuat;
– good() verifica daca toate cele trei rezultate precedente sînt false
Funcţiile de formatare pot fi aplicate oricărui obiect de tip
flux, fie că este un flux standard, un flux de tip fişier sau un flux în
memorie.

Întrebări pentru verificarea cunoştinţelor:


1. Care sînt tipurile de fluxuri standard
definite de utilizator?
2. Scrieţi un exemplu de utilizare a funcţiilor read şi write a
conţinutului unei zone de memorie.

134
3. Definiţi parametrii funcţiei membru
open ale unei clase flux.
4. Cum se definesc fluxurile de memorie?
5. Care sînt funcţiile de formatare pentru
fluxuri?

Temele pentru acasă:


1. Corectaţi greşelile şi lansaţi următoarele exemple la execuţie. Ce
rezultate vor apărea pe ecran?
Exemplul 1:
#include <iostream.h>
define N 80
void main(0
{ inc c;
int cnt=0; charcnt=0;
While(1)
{ c=cin.get();
if (c==EOF) break;
if (c=’/’ && cin.peek() ==’/’)
{ cnt++;
cin.ignore(n,”\n”);
charcnt++=cin.gcount();
charcnt++;
}
}
cout<<”In” << cnt << “ comentarii avem ” << charcnt <<
“caractere”;
}
Exemplul 2 .
#include <iostream.h>
void main()
{While(1)
135
{ char c; cin.get©;
if(cin.eof()) break;
cout.put( c);
}
Exemplul 3.
#include <iostream.h>
void main()
{cout <<setfill(‘%’}<<setw(4)<< 17;}
Exemplul 4.
#include <iostream.h>
#include <fstream.h>
void main()
{ int I=42;
fstream f (“dat.txt”,ios::out|ios::binary);
f.seekp(17,ios::beg);
f.write((const char*)&I,2) ;
f.close();
f.open(“dat.txt”, ios::in|ios::binary);
f.seekg(17,ios::beg);
f,read((char*)&I,2);
f.close();
cout<<I;
}
Exemplul 5.
#include <iostream.h>
#include <strstream.h>
void main()
{char buffer [80];
strstream s (buffer,80,ios::out);
float pret=123.45;
s<<”Preţul stocului este de”;
s;;serw(6)<<setprecision(2) ;
136
}

Temele pentru lucrări de laborator:


1. Scrieţi un program care compară două fişiere date.
2. Scrieţi un program care calculează numărul de elemente ale unui
fişier care sînt mai mici ca valoarea medie aritmetică a tuturor
elementelor acestui fişier.
3. Scrieţi un program care tipăreşte toate cuvintele diferite de ultimul
cuvînt dintr-un fişier. Cuvintele din fişier sînt separate prin
virgulă, iar.după ultimul cuvînt se pune punct.
4. Scrieţi un program care din 100 de numere aleatoare dintr-un fişier
se determină numărul maximal şi cel minimal.
Subconsecutivitatea de elemente dintre numărul maximal şi cel
minimal determinat să se înregistreze într-un nou fişier.
5. Scrieţi un program care din trei fişiere se selectează în unul nou
mai întîi numerele divizibile la 3, la 5 şi la 7, apoi numerele
pozitive pare de pe locuri impare.
6. Scrieţi un program care din trei fişiere se selectează în unul nou
mai întîi numerele negative, zerourile, apoi numerele pozitive.
7. Scrieţi un program care ordonează lexicografic o consecutivitate
de înregistrări (dintr-un fişier) de tipul
struct { char nume [30];
int ani} înregistrare;
Rezultatul ordonării să se înscrie într-un nou fişier.
8. Scrieţi un program care din două fişiere ordonate descrescător se
vor uni în unul nou în care se va păstra ordinea descrescătoare de
sortare.
9. Scrieţi un program care sortează lexicografic cuvintele dintr-un
text. Pentru fiecare element sortat să se indice numărul de
repetări ale cuvîntului în textul dat.
10. Scrieţi un program care calculează suma înmulţirii numerelor
vecine pe pe locuri pare dintr-un fişier dat.
137
11. Scrieţi un program care va tipări în ordine inversă
subconsecutivitatea de numere dintre valoarea minimă şi
maximă ale unei consecutivităţi de numere citită dintr-un fişier.
12. Scrieţi un program care formează un fişier nou din cel dat după
următoarea legitate: elementele fişierului nou se obţine din
inversul numerelor din fişierul dat.
13. Scrieţi un program care determină frecvenţa cu care a fost
generat fiecare element în fişierului creat. Valorile elementelor
sînt cuprinse între 1 şi 100.
14. Scrieţi un program care determină numărul maximal şi cel
minimal din numerele unui fişier dat. Să se determine elementele
mai mari ca cel minimal şi mai mici ca numărul maximal.
15. Scrieţi un program care efectuează reformatarea unui fişier
textual în felul următor. Lungimea rîndului de caractere în
fişierul nou are lungimea de 60 de caractere. Dacă în rîndul dat
se depistează punctul, restul rîndului din fişierul dat se scrie din
rînd nou.
16. Scrieţi un program care din două fişiere ordonate crescător se vor
uni în unul nou în care se va păstra ordinea crescătoare de
sortare.

Lucrarea de laborator nr. 6.


Templates: Template pentru clase şi funcţii.

Scopul lucrării: familiarizarea studenţilor cu clase şi funcţii


generice.

Consideraţiile teoretice necesare:


Clase şi funcţii generice

138
Template-ul implementează asa-zisul concept de "tip parametrizat"
("parametrized type"). Un template reprezintă o familie de tipuri sau
funcţii, cu alte cuvinte, un şablon sau model. Acest concept a fost
introdus în primul rînd pentru a creşte gradul de reutilizabilitate a
codului. De exemplu, pentru a implementa o listă de numere întregi
este necesară în mod normal realizarea unei clase speciale (să
spunem ListOfIntegers), iar pentru o listă de şiruri altă clasă (să
spunem ListOfStrings). Conceptul de template permite realizarea
unei clase generale (să spunem List), care să accepte orice tip de
element, inclusiv tipuri necunoscute la momentul implementării
acesteia. Tipul template este stabilit în momentul instanţierii sale.
Template-urile sînt foarte utile pentru realizarea de biblioteci care
trebuie să ofere metode generice de prelucrare a datelor. Sintaxa
generală de declarare a unui template este următoarea:
template < ListaDeParametri > Declaratie
unde Declaratie reprezintă declararea sau definirea unei clase sau
unei funcţii, definirea unui membru static al unei clase template,
definirea unei clase sau funcţii membre al unei clase template, sau
definirea unui membru template al unei clase.
Clasele parametrizate (sau clasele template) se declară astfel:
template <class NumeParametru>
class NumeClasa
{ // ...
// definirea clasei
}
Particularizarea or stabilirea tipului clasei template se face prin
intermediul unei construcţii de genul:
NumeClasa <NumeParametru>
unde NumeParametru reprezintă tipul obiectului.
Funcţiile template se declară astfel:
template <class NumeParametru>
//
139
...
// declaraţia funcţiei
Să considerăm în continuare ca exemplu implementarea unei stive
generice folosind template-uri.
#include <iostream.h>
template <class Tip> class StackItem
{public:
StackItem *NextStack;
Tip *Data;
StackItem(Tip Date, StackItem <Tip> *Next)
{Data = new Tip(Data);
NextStack = Next;}
};
template <class Tip> class Stack
{public:
Tip pop()
{Tip result = *(Data->Data);
StackItem <Tip> *temp = Data;
Data = Data->NextStack;
delete temp;
return result;}
Tip top() {return *(Data->Data);}
void push(T __Data)
{Data = new StackItem <Tip>(Date, Date);}
int isEmpty() {return Data == 0;}
Stack() {Data = 0;}
private:
StackItem <Tip> *Data;
};
void main()
{Stack <int> anIntegerStack;
anIntegerStack.push(5); anIntegerStack.push(7);
140
if(anIntegerStack.isEmpty()) cout << "Stiva goala" << endl;
else cout << anIntegerStack.pop() << endl;
cout << anIntegerStack.top() << endl;
}
În exemplul următor a fost implementată o listă generică
(List). Ca elemente a listei s-au folosit obiecte de tip Point. Pentru
parcurgerea uşoară a listei a fost implementată o clasă de tip
"iterator", care poate fi considerată ca fiind un "cursor" care străbate
lista. Funcţia List.begin() returnează un iterator poziţionat pe primul
element al listei, List.end() pe ultimul element al listei. Saltul la
următorul element al listei se face cu ajutorul operatorului ++ din
clasa Iterator.
#include <iostream.h>
class Point
{friend ostream& operator << (ostream& output, Point p);
protected:
unsigned x, y;
public:
Point() {x = 0;y = 0;}
Point(unsigned X, unsigned Y) {x = X;y = Y;}
~Point() {}
unsigned GetX() {return x;}
unsigned GetY() {return y;}
void SetX(unsigned X) {x = X;}
void SetY(unsigned Y) {y = Y;}};
ostream& operator << (ostream& output, Point p)
{output<<"(" << p.x <<", " << p.y << ")"; return output;}
template <class Tip> class Item
{public:
Item *Next;
Tip *Data;
Item(Tip __Data, Item <Tip> *__Next)
141
{Data = new Tip(__Data); Next = __Next;}
};
template <class T> class List
{public:
T pop_front()
{T result = *(Data->Data);
Item <T> *temp = Data;
Data = Data->Next;
delete temp;
return result;}
Tip front() {return *(Data->Data);}
void push_front(Tip __Data)
{Data = new Item <Tip>(__Data, Data);}
int empty() {return Data == 0;}
List() {Data = 0;}
class Iterator
{ friend class List <Tip>;
protected:
Item <Tip> *Current;
Iterator(Item <Tip> *x) {Current = x;}
public:
Iterator() {}
int operator == (Iterator& x){return Current == x.Current;}
int operator != (Iterator& x){return Current != x.Current;}
T operator *(){return *(Current->Data);}
Iterator& operator ++(int)
{Current = Current->Next; return *this;}
};
Iterator begin(){return Iterator(Data);}
Iterator end()
{Item <Tip> *temp;
for(temp = Data; temp; temp = temp->Next);
142
return Iterator(temp);}
private:
Item <Tip> *Data;
};
void main()
{List <Point> anPointList;
List <Point>::Iterator index, end;
anPointList.push_front(Point(1, 1));
anPointList.push_front(Point(3, 14));
index = anPointList.begin(); end = anPointList.end();
if(anPointList.empty()) cout << "Lista vida" << endl;
else
for(; index != end; index++)
cout << *index << " ";
cout << endl; }
Clasele template pot avea trei tipuri de prieteni (friends):
– o clasă sau funcţie care nu este de tip template;
– o clasă sau funcţie template;
– o clasă sau funcţie template avînd tipul specificat.
Dacă este necesară particularizarea unei funcţii template sau a unei
funcţii membre a unei clase template pentru un anumit tip, funcţia
respectivă poate fi supraîncărcată pentru tipul dorit.
Trebuie remarcat de asemenea că în cazul în care o clasă
template conţine membri statici, fiecare instanţă a template-ului în
cauză va conţine propriile date statice.

Întrebări pentru verificarea cunoştinţelor:


1. Ce reprezintă un
template?
Exemple de
funcţie template,

143
clasă template.
2. Care este
corelaţia dintre
clase derivate şi
clase template?
3. Pot oare fi
redefinite
funcţiile
template?
4. Clasele template pot avea trei tipuri de prieteni (friends). Care sînt
ele? Exemple.

Temele pentru acasă:


1. Ce efectuează următoarea funcţie template?
template <class X> void swap(X &a, X &b)
{X temp; temp=a; a=b; b=temp;}
void main()
{int 1=10, j=20;
float x=10.1, y=23.3;
cout<<”Valorile i, j sînt:”<< i << ' '<< j << endl;
cout<<”Valorile х, у sînt”<< x<< ' '<< у <<endl;
swap(i,j); swap (x,y); // schimb de variabile
cout<<”Valorile i, j după schimb:”<<i<<' '<<j<<endl;
cout<<”Valorile х, у după schimb:”<<x<<' '<<у <<endl;
}
2. Ce rezultate obţinem la rularea următorului program?
#include<iostream.h>
template <class data_t> class list
{ data_t data;
list *next;
public:
list (data_t d);
144
void add(list *node) {node->next=this; next-0;}
list *getnext() {return next;}
data_t getdataO {return data;}
}
template <class data_t> list<data_t>::list(data_t d)
{data=n; next=0; }
void main()
{list<char> start('a');
list<char>*p, *last;
// crearea listei
last=&start;
for(int i=l; i<26; i++)
{p=new list<char>(‘a’+1); last=p;}
//tiparul listei
p=&start;
while(p)
{cout<<p->getdata(); p=p->getnext();}
return 0;
}

Temele pentru lucrări de laborator:


1. Sortare prin selecţie. Se dă consecutivitatea de obiecte a1, …, an.
Fie elementele consecutivităţii se rearanjează în aşa mod ca după
sortare elementele consecutivităţii să fie aranjate în ordine
crescătoare: a1 ≤ a2≤ …≤ an. Această problemă poartă
denumirea de problemă de sortare sau de aranjare a
consecutivităţii care poate fi aranjată şi în ordine descrescătoare:
a1 ≥ a2 ≥ …≥ an.; dacă numerele două cîte două sînt diferite
consecutivitatea poate fi aranjată în creştere sau descreştere. Să se
afle elementul tabelului care are valoare minimală. El se va
înlocui cu primul element din consecutivitate, apoi se procedează

145
cu restul elementelor din consecutivitate începînd cu al doilea
element ş.a.m.d.
2. Sortarea prin interclasare. Se dă consecutivitatea de obiecte a1,
…, an. Fie elementele consecutivităţii se rearanjează în aşa mod ca
după sortare elementele consecutivităţii să fie aranjate în ordine
crescătoare: a1 ≤ a2≤ …≤ an. Această problemă poartă
denumirea de problemă de sortare sau de aranjare a
consecutivităţii care poate fi aranjată şi în ordine descrescătoare:
a1 ≥ a2 ≥ …≥ an.; dacă numerele două cîte două sînt diferite
consecutivitatea poate fi aranjată în creştere sau descreştere. Prin
analiza consecutivă a elementelor a1, …, an să se afle cel mai mic i
astfel ca ai > ai+1. Să se schimbe aii şi ai+1 cu locul. Să se efectueze
această procedură, să se reînceapă analiza consecutivităţii cu
elementul ai+1 ş.a.m.d. Prin aceste manipulării elementul cel mai
mare se va mişca pe ultimul loc al consecutivităţii. Următoarele
analize se vor începe de la început micşorînd cantitatea de
elemente cercetate cu o unitate. Consecutivitatea va fi sortată după
analiza elementelor , în care au participat numai elementul unu şi
doi.
3. Sortarea prin inserţie. Se dă consecutivitatea de obiecte a1, …, an.
Fie elementele consecutivităţii se rearanjează în aşa mod ca după
sortare elementele consecutivităţii să fie aranjate în ordine
crescătoare: a1 ≤ a2≤ …≤ an. Această problemă poartă
denumirea de problemă de sortare sau de aranjare a
consecutivităţii care poate fi aranjată şi în ordine descrescătoare:
a1 ≥ a2 ≥ …≥ an.; dacă numerele două cîte două sînt diferite
consecutivitatea poate fi aranjată în creştere sau descreştere. Să se
cerceteze consecutiv a2, …, an şi fiecare element nou ai inclus la
locul potrivit în consecutivitatea deja sortată a1, …, ai-1. Acest loc
se determină prin compararea consecutivă a elementului ai cu
elementele sortate a1, …, ai-1 .

146
4. Să se cerceteze şi să se calculeze numărul de comparări şi de
schimbări (adică permutaţii dintr–un loc în altul) ale elementelor
a1, …, an în procesul de utilizare a algoritmilor descrişi în varianta
I. Să se demonstreze că numărul de schimbări pentru algoritmul
de sortare prin selectare este mărginit de o funcţie liniară de n. În
acelaşi timp şi numărul de comparări pentru algoritmul de sortare
prin selectare şi pentru algoritmii de sortare prin interclasare şi
prin inserţie în unele cazuri (de exemplu, dacă consecutivitatea
iniţială este de tipul a1>a2>…>an) sînt funcţii pătratice de n.
5. Din definiţia problemei din punctul 1 rezultă că algoritmul de
sortare prin selectare se deosebeşte de celălalţi algoritmi în acele
cazuri cînd schimbarea locului elementului este cu mult mai
dificilă decît compararea elementelor. Să se utilizeze acest fapt la
executarea următoarelor probleme. Se dă un obiect matrice de
numere reale de dimensiunea n× m; să se sorteze (să se schimbe
cu locul) liniile matrice:
а) după creşterea valorile primelor elemente ale liniilor matricii;
b) după descreşterea sumelor elementelor liniilor matricei;
c) după descreşterea valorilor minimale ale elementelor liniilor
matricei;
d) după descreşterea valorilor maximale ale elementelor liniilor
matricei.
Pentru punctele b), c), d) să se utilizeze o consecutivitate
auxiliară de numere a1, …, an.
6. Fie se dă în ordine descrescătoare o consecutivitate de numere
întregi sau reale a1 ≤ a2≤ …≤ an şi fie se dă un număr b
(respectiv întreg sau real), pentru care trebuie să-I căutăm locul
între numerele a1, …, an, astfel ca după introducerea numărului b
în acest loc consecutivitatea să rămînă sortată în aceeaşi ordine.
Dacă unele elemente sînt egale după valoare cu numărul b acesta
poate fi introdus în diferite locuri mai aproape de începutul

147
consecutivităţii. Această problemă poartă numele de problemă de
căutare a locului elementului.. Pentru b sînt n+1 posibilităţi: b ≤
a1, a1<b≤ a2, … an-1 <b ≤ an, an < b şi Ca soluţie a acestei
probleme de căutare a locului elementului b va fi corespunzător
unul din numerele 1, ..., n+1. Pentru rezolvarea acestei probleme
va fi util următorul algoritm care se numeşte algoritmul împărţirii
în jumătăţi (algoritmul căutării binare). Se ia mai întîi 1 şi n + 1 în
calitate de hotare de căutare a locului elementului, apoi pînă cînd
valorile hotarelor nu coincid, pas cu pas se mişcă aceste hotare în
modul următor: se compară b cu as, unde s – partea întreagă
mediei aritmetice a valorilor hotarelor; dacă аs<b, atunci se
înlocuieşte hotarul de jos precedent cu s+1, iar cel de sus rămîne
fără schimbare, în caz contrar se lasă fără nici o schimbare hotarul
de jos, iar hotarul de sus se schimbă cu s; Cînd valorile hotarelor
coincid, fiind egale cu careva număr t, executarea algoritmului
descris se finalizează cu rezultatul t. (Numărul de comparări
necesare pentru acest algoritm nu excedă [log2 (n +1)]+1)
a) Sunt date numere reale a1, …, an, b1, …, bm (a1 ≤ a2≤ …≤ an). Să
se obţină numerele naturale k1, …, km astfel încît ki – să fie soluţia
problemei căutării locului bi printre a1, …, an (i= 1, ..., m). Să se
utilizeze algoritmul căutării binare.
b). Tabela de cîştiguri a loteriei este reprezentată prin tabela de
numere cîştigătoare a1, …, an şi tabela de cîştiguri în lei p1, …, pn
(pi este cîştigul pentru numărul ai (i = 1, ..., n)). Să se determine
suma cîştigurilor pentru biletele cu numerele b1, …, bm. Să se
utilizeze algoritmul căutării binare.
c). Fie locul pentru careva număr b printre cele ale consecutivităţii
sortate crescător a1, …, an se alege ca cel mai îndepărtat loc de la
începutul consecutivităţii neafectînd legitatea sortării crescătoare.
Să se introducă schimbări în descrierea algoritmului de împărţire
în jumătăţi şi respectiv să se rezolve problema a).

148
7. Căutare bînară. Fie T[1..n} un vector sortat crescător şi x un
obiect care se află printre obiectele lui T. Să se determine locul lui
x în T.Consecutivitatea se organizează în formă de listă dublu
lănţuită.
Să se folosească algoritmul de sortare a vectorului T[1..n], divizîndu-
l în doi vectori F şi V, sortîndu-i pe aceştia şi apoi interclasîndu-i
(amestecîndu-i).
8. Se presupune că numerele a1, …, an sînt elementele unui fişier.
Valoarea n nu se cunoaşte. Să se rezolve următoarele probleme: să
se afle valoarea maximală din consecutivitatea dată a1, …, an după
ce se elimină din ea
a) un element cu valoarea mах (a1, …, an);
b) toate elementele cu valoarea max(a1, …, an) se subînţelege că nu
toate numerele a1, …, an sînt egale între ele.
c) să se afle max (a1, …, an) şi min (a1, …, an) utilizînd următorul
algoritm. Pas cu pas să se obţină perechile max(a1, …, ai), min(a1,
…, ai) (i= 1, ..., n). Ca să se obţină max(a1, …, ai+1), min(a1, …,
ai+1), se compară ai+1 cu max(a1, …, ai), apoi, dacă ai+1 < max(a1,
…, ai), suplimentar se compară ai+1 cu min(a1, …, ai).
9. Se presupune că numerele a1, …, an sînt elementele unui fişier.
Valoarea n nu se cunoaşte. Să se rezolve următoarele probleme: să
se afle valoarea maximală din consecutivitatea dată a1, …, an după
ce se elimină din ea
a) un element cu valoarea mах (a1, …, an);
b) toate elementele cu valoarea max(a1, …, an) se subînţelege că nu
toate numerele a1, …, an sînt egale între ele.
c) să se afle max (a1, …, an) şi min (a1, …, an) utilizînd următorul
algoritm. Fie n este un număr par, adică n = 2k. Atunci pas după
pas să se obţină max (a1, …, a2l), min(a1, …, a2l) (l=1,..., k). Ca să
se obţină max(a1, …, a2l+2), min(a1, …, a2l+2), mai întîi se compară
între ele a2l+1, a2l+2 şi max(a2l+1, a2l+2) se compară cu mах(a1, …,
a2l), iar min(a2l+1, a2l+2) cu min(a1, …, a2l) Dacă n este un număr
149
impar, se va efectua încă un pas suplimentar: compararea
ultimului element an cu max(a1, …, an-1) şi, posibil, cu min(a1, …,
an-1).
10. Algoritmul de insertare simplă poate fi schimbat astfel. Locul
unde trebuie insertat şi deja în consecutivitatea sortată a1, …, an,
se determină prin algoritmul împărţirii în jumătăţi (p.4). Se obţine
un nou algoritm de sortate care se numeşte algoritm de sortate
prin insertare binară (îmbinarea de cuvinte “insertarea binară” se
subînţelege ca “ insertare prin împărţirea în jumătăţi”). Acest
algoritm necesită aproximativ n⋅ log2n comparări ale elementelor
consecutivităţii. Să se scrie programul care realizează acest
algoritm.
11. Sunt date obiectele a1, …, an. Să se obţină în ordine crescătoare
toate numerele diferite ce intră în a1, …, an. Pentru aceasta să se
folosească algoritmul de sortare prin insertare binară. În procesul
de sortare să se elimine elementele care se repetă. Dacă în urma
căutării locului ai în consecutivitatea deja sortată a1, …, ak (k < i)
se va depista că printre a1, …, ak este deja un element egal cu ai,
trebuie de cercetat următorul ai+1 fără schimbarea consecutivităţii
a1, …, ak.
12. Algoritmul fon Newmann de sortare a tabelei a1, …, an în ordine
nedescrescătoare (algoritm de sortare prin unire) se bazează pe
unirile de mai multe ori ale grupelor de elemente ale tabelei deja
sortate. Mai întîi tabela este privită ca o totalitate de grupe sortate
ce conţin cîte un element. Unirea grupelor vecine ne dau noi
grupe deja sortate care conţin cîte două elemente (poate, posibil,
cu excepţia ultimei grupe nu s-a găsit pereche).
Apoi grupele se măresc prin aceeaşi metodă pînă cînd obţinem o
grupă care conţine toate elementele consecutivităţii iniţiale de
acum sortate. Pentru sortate la etapele intermediare se utilizează

150
nu numai tabela a1, …, aт, dar şi una auxiliară b1, …, bn. Figura 1
ne ilustrează

Fig.1.
cum se unesc două etape a tabele ia1, …, an şi b1, …, bn
reprezentîndu-le în formă de segmente care sînt împărţite în părţi
cu grupe de elemente sortate. Numărul de grupe sortate se
micşorează. Deci se va obţine acel moment cînd tabela a1, …, an
sau b1, …, bn va conţine o grupă de elemente deja sortate. Aceasta
înseamnă că tabela este sortată Pentru unirea a două grupe sortate,
care conţine corespunzător р şi q elemente, este suficient de a
produce nu mai mult de p+q comparări. Rezultă că pentru o
singură etapă de este suficient de a efectua nu mai mult de n
comparări. Tot atîtea permutări trebuiesc efectuate. Demonstraţi
că algoritmul lui fon Newmann necesită aproximativ nlog2 n
comparări şi tot atîte permutări. Spre deosebite de alţi algoritmi de
sortare numai algoritmul de insertare binară necesita acelaşi
număr de comparări. Însă algoritmul lui fon Newmann se
deosebeşte de cel menţionat prin aceea că necesită mai puţine
permutări ale elementelor a1, …, an (cu toate că necesită o tabelă
suplimentară b1, …, bn).
De scris programul care realizează algoritmul lui fon Newmann.
13 Fie se dă tabela de obiecte a1, …, an. Să se rearanjeze elementele
tabelei a1, …, an astfel ca la începutul tabelei să fie grupa de
elemente care componentele obiectului după valoare sînt mai mari
ca elementul de pe primul loc, apoi primul element al
consecutivităţii date şi pe urmă elementele mai mici sau egale cu

151
primul element. Numărul de comparări fiecare în parte nu trebuie
să întreacă n – 1.
14. Fie a şi b sînt fişiere, k este un număr natural. Vom spune că
fişierele а şi b sînt corelate k-sortate, dacă
a) în fiecare din fişierele a şi b primele k componente, următoarele
după ele k componente etc. formează grupe sortate; ultima grupă a
componentelor din fişier (tot sortată) poate conţine mai puţin de k
elemente, dar numai un singur fişier poate avea o grupă
necompletă;
b) numărul grupelor sortate a fişierului a se deosebesc de numărul
grupelor sortate a fişierului b nu mai mult decît cu o unitate;
c) dacă într-un fişier numărul grupelor sortate sînt mai puţin cu o
unitate decît în celălalt fişier, grupul incomplet de elemente (mai
puţin de k elemente) poate fi numai în fişierul cu mai multe
componente.
d) Componentele a două fişiere а şi b corelate k-sortate pot fi alocate
în fişierele g şi h astfel, încît g şi h să fie corelate 2k-sortate.
Aceasta se efectuează prin intermediul unirii grupelor sortate.
Rezultatele unirii se aranjează alternativ cînd în fişierul g, cînd în
fişierul h. Fig.2 ilustrează acest proces la primele uniri a grupelor
sortate. Fişierele se prezintă în formă de segmente, componentele
cărora se prezintă ca grupe sortate cu numărul determinat de
componente. Să se finalizeze descrierea acestui algoritm
cercetînd etapa finală. Să se demonstreze că fişierele g şi h într-
adevăr vor fi corelate 2k- sortate.
Să se realizeze acest algoritm în formă de program.

152
Fig.2.
15. Fie dată tabela a1, …, an. Trebuie de permutat elementele a1, …,
an astfel ca la început de tabelă să fie grupa de elemente mai
mari decît elementul care se află pe primul loc în tabela iniţială,
apoi însăşi acest element, după care urmează cele mai mici sau
egale cu el. Numărul de comparări şi permutări nu trebuie să
întreacă n - 1. Algoritmul recursiv de sortate (algoritmul de
sortare rapidă) care se bazează pe transformările descrise mai
sus este următorul. Dacă tabela conţine numai mult de un
element tabela este sortată. În caz contrar efectuăm
transformarea descrisă în p.10 şi determinăm rezultatele de
utilizare a algoritmului de sortare rapidă pentru tabela a1, …, an
în felul următor: mai întîi se formează prima grupă sortată cu
ajutorul algoritmului de sortare rapidă, apoi fără schimbare acel
element care împarte grupa întîi şi doi, după care urmează grupa
doi de element, sortată cu ajutorul algoritmului de sortare rapidă.
Acest algoritm nu utilizează tabelă suplimentară şi necesită
aproximativ nlog2 n comparări ăi tot atîtea permutări de
elemente. Acestea sînt caracteristicele numerice medii, Pentru
cel mai rău caz numărul de comparări poate ajunge la n(n—1)/2;
în afară de aceasta acest algoritm necesită recursie. Să se scrie
un algoritm care realizează algoritmul de sortare rapidă.

153
Lucrarea de laborator nr. 7.
Prelucrarea excepţiilor. Blocul try{…} throw() catch()…

Scopul lucrării: familiarizarea studenţilor cu prelucrarea excepţiilor,


lucrul cu blocul try{…} throw () catch()…

Consideraţiile teoretice necesare:


Tratarea excepţiilor
Este un fenomen "natural" ca în programe să se strecoare
erori, de diverse naturi. Activitatea de programare implica şi acţiuni
mai putini plăcute, adică testarea, depanarea şi corectarea erorilor.
Costurile de îndepărtare a erorilor creşte de obicei direct proporţional
cu întîrzierea din cadrul procesului de dezvoltare cînd sînt
descoperite.
Trebuie însă înţeleasă diferenţa dintre erori (bug-uri) şi
excepţii. Excepţiile sînt situaţiile neaşteptate apărute în cadrul
sistemului care rulează un program. Programele trebuie să fie
pregătite pentru a trata aceste situaţii excepţionale.
In C++ s-a realizat un mecanism facil de tratare a excepţiilor.
Astfel, o excepţie este un obiect a cărui adresa este trimisă dinspre
zona de cod unde a apărut problema către o zona de cod care trebuie
s-o rezolve.
Paşii care trebuiesc în general urmaţi în vederea tratării
excepţiilor în cadrul programelor C++ sînt:
– se identifică acele zone din program în care se efectuează o
operaţie despre care se cunoaşte că ar putea genera o excepţie şi
se marchează în cadrul unui bloc de tip try. In cadrul acestui
bloc, se tastează condiţia de apariţie a excepţiei, şi în caz pozitiv
se semnalează apariţia excepţiei prin intermediul cuvîntului
cheie throw;

154
– se realizează blocuri de tip catch pentru a capta excepţiile atunci
cînd acestea sînt întîlnite.
Blocurile catch urmează un bloc try, în cadrul cărora sînt tratate
excepţiile.
Sintaxa pentru try:
try {
// cod
throw TipExcepţie;
}
Sintaxa pentru throw:
throw TipExcepţie;
Sintaxa pentru catch:
catch(TipExcepţie)
{
// cod tratare excepţie
}
Dacă TipExcepţie este "...", este captată orice excepţie apărută.
După un bloc try, pot urma unul sau mai multe blocuri catch.
Dacă excepţia corespunde cu una din declaraţiile de tratare a
excepţiilor, aceasta este apelată. Dacă nu există definită nici o rutină
de tratare a excepţiei, este apelata rutina predefinită, care încheie
execuţia programului în curs. După ce rutina este executată,
programul continuă cu instrucţiunea imediat următoare blocului try.
TipExcepţie nu este altceva decît instanţierea unei clase vide
(care determină tipul excepţiei), putînd fi declarat ca:
class TipExcepţie {};
În continuare vom prezenta un exemplu de program care utilizează
tratarea excepţiilor.
#include <iostream.h>
#define MAXX 80
#define MAXY 25

155
class Point
{ public:
class xZero {};
class xOutOfScreenBounds {};
Point(unsigned x1, unsigned y1)
{ x =x1; y =y1; }
unsigned GetX() { return x; }
unsigned GetY() { return y; }
void SetX(unsigned x1)
{ if(x1 > 0)
if(x1 < = MAXX) x =x1;
else throw xOutOfScreenBounds();
else throw xZero();
}
void SetY(unsigned y1)
{ if( y1 > 0)
if( y1 < = MAXY) y =y1;
else throw xOutOfScreenBounds();
else throw xZero();
}
protected:
int x, y;
};
void main()
{ Point p(1, 1);
try
{ p.SetX(5);
cout<<"p.x successfully set to "<<p.GetX()<<"."<< endl;
p.SetX(100);
}
catch(Point::xZero)
156
{ cout << "Zero value!\n"; }
catch(Point::xOutOfScreenBounds)
{ cout << "Out of screen bounds!\n"; }
catch(...)
{ cout << Unknown exception!\n"; }
}
Datorită faptului că excepţia este instanţierea unei clase, prin
derivare pot fi realizate adevărate ierarhii de tratare a excepţiilor.
Trebuie avută însă în vedere posibilitatea de apariţie a unor excepţii
chiar în cadrul codului de tratare a unei excepţii, situaţii care trebuie
evitate.

Întrebări pentru verificarea cunoştinţelor:


1. Cum sînt realizate excepţiile în cadrul programelor C++ ?
Exemple.

Temele pentru acasă:


1. Ce rezultate vor fi obţinute la rularea următorului program:
void main()
{ cout<<”Inceput”;
try{ // inceputul blocului try
cout<<”interiorul blocului try \n”;
throw 10; // generarea erorii
cout<<”Aceasta nu se va executa”;
}
catch (int i) {
cout<<”Este prelucrată eroarea: ”;
cout<<I<<”\n”;
}
cout << “Sfirsit”;
}

157
Temele pentru lucrări de laborator:
1. Scrieţi un program care calculează numărul de elemente ale unui
fişier care sînt mai mici ca valoarea medie aritmetică a tuturor
elementelor acestui fişier.
2. Scrieţi un program care compară două fişiere date.
3. Scrieţi un program care din 100 de numere aleatoare dintr-un fişier
se determină numărul maximal şi cel minimal.
Subconsecutivitatea de elemente dintre numărul maximal şi cel
minimal determinat să se înregistreze într-un nou fişier.
4. Scrieţi un program care tipăreşte toate cuvintele diferite de ultimul
cuvînt dintr-un fişier. Cuvintele din fişier sînt separate prin
virgulă, iar.după ultimul cuvînt se pune punct.
5. Scrieţi un program care din trei fişiere se selectează în unul nou
mai întîi numerele negative, zerourile, apoi numerele pozitive.
6. Scrieţi un program care ordonează lexicografic o consecutivitate
de înregistrări (dintr-un fişier) de tipul
struct { char nume [30];
int ani} înregistrare;
7. Scrieţi un program care din trei fişiere se selectează în unul nou
mai întîi numerele divizibile la 3, la 5 şi la 7, apoi numerele
pozitive pare de pe locuri impare. Rezultatul ordonării să se
înscrie într-un nou fişier.
8. Scrieţi un program care sortează lexicografic cuvintele dintr-un
text. Pentru fiecare element sortat să se indice numărul de repetări
ale cuvîntului în textul dat.
9. Scrieţi un program care din două fişiere ordonate descrescător se
vor uni în unul nou în care se va păstra ordinea descrescătoare de
sortare.
10. Scrieţi un program care va tipări în ordine inversă
subconsecutivitatea de numere dintre valoarea minimă şi maximă
ale unei consecutivităţi de numere citită dintr-un fişier.
158
11. Scrieţi un program care calculează suma înmulţirii numerelor
vecine pe pe locuri pare dintr-un fişier dat.
12. Scrieţi un program care determină frecvenţa cu care a fost
generat fiecare element în fişierului creat. Valorile elementelor
sînt cuprinse între 1 şi 100.
13. Scrieţi un program care determină numărul maximal şi cel
minimal din numerele unui fişier dat. Să se determine elementele
mai mari ca cel minimal şi mai mici ca numărul maximal.
14. Scrieţi un program care formează un fişier nou din cel dat după
următoarea legitate: elementele fişierului nou se obţine din
inversul numerelor din fişierul dat.
15. Scrieţi un program care din două fişiere ordonate crescător se vor
uni în unul nou în care se va păstra ordinea crescătoare de
sortare.
16. Scrieţi un program care efectuează reformatarea unui fişier
textual în felul următor. Lungimea rîndului de caractere în
fişierul nou are lungimea de 60 de caractere. Dacă în rîndul dat
se depistează punctul, restul rîndului din fişierul dat se scrie din
rînd nou.

Bibliografia
1. Б. Керниган, Д. Ритчи. Язык программрования Си.– Пер. с
англ Москва,1985.
2. М. Уэйт. Язык Си (руководство для начинающих).–
Москва,1988.
3. М. Т. Болски. Язык программирования Cи. Справочник:.–
Москва, Радио и cвязь, 1988.
4. Н Джехани. Программирование на языке Си. Пер. с англ.–
Москва, Радио и cвязь, 1988.
5. В. А Юлин., И.Р Булатова. Приглашение к Си.– Минск,
Выcшая школа,1990.

159
6. A.Трой, A.Дуглас. Программирование на языке Cи для
персонального компьютера IBM PC.– Москва, Радио и
связь, 1991.
7. А. Н. Маслов. Введение в язык программирования Си.–
Москва, Радио и cвязь, 1991.
8. Ю.А. Стариков. Мобильность программ и особенности
реализаций языка Си.– Москва, Радио и cвязь, 1992.
9. D. Somnea. D. Turturea. Introducere în C++. Programarea obiect
orientata. – Bucureşti, ed. Teora, 1993.
10. Р. Джонс. Программируем на Си, – Москва, Радио и cвязь,
1994.
11. O. Catrina, L. Cojocaru. Turbo C++.– Bucureşti, ed. Teora,
1994.
12. D. Turturea. Programarea aplicatiilor Windows în limbajul C.–
Bucureşti, ed.Teora, 1995.
13. D. Costea. Iniţiere în limbajul C. – Bucureşti, ed. Teora, 1996.
14. D. M. Popovici, I. M. Popovici, I. Tănase. C++. Tehnologia
orientată pe obiecte. Aplicaţii. –Bucureşti, ed. Teora, 1996.
15. L. Negrescu. Iniţiere în limbajul C, C++. – Cluj, 1996.
16. А. Голуб С и С++ . Правила программирования. – Москва,
Радио и cвязь, 1996
17. Г. Шилдт. Самоучитель С++. –Пер. с англ., Санкт
Петербург, BHV, 1997.
18. Дж. Крис. Учимся программировать на языке С++. –
Москва, Радио и cвязь, 1997.
19. G. D. Mateescu. C++ limbaj de programare. – Bucureşti, ed.
Petrion, 1998.
20. Бьерн Страустрап. Язык Программирования С++.М.-
BINOM, 2000.
21. D. M. Popovici, I. M. Popovici, I. Tănase. C++. Tehnologia
orientată pe obiecte. Aplicaţii. –Bucureşti, ed. Teora, 2002.
160