Sunteți pe pagina 1din 166

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 prezintă 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.

3
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 darea
de seamă pe care o susţine în faţa profesorului.
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ătorul 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
8 8 8 8 8 8 8 8
9 9 9 9 9 9 9 9
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
4
15 1 1 1 1 1 1 15
5 5 5 5 5 5

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.

5
CUPRINS

Introducere .......................................................................... 3

Lucrarea de laborator nr. 1 ................................................ 6

Lucrarea de laborator nr. 2 ................................................ 61

Lucrarea de laborator nr. 3 ................................................ 81

Lucrarea de laborator nr. 4 ................................................ 10


7

Lucrarea de laborator nr. 5 ................................................ 11


4

Lucrarea de laborator nr. 6 ................................................ 13


3

Lucrarea de laborator nr. 7 ................................................ 14


8

Bibliografie ......................................................................... 15
3

6
Lucrarea de laborator nr. 1
Tema: 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. Cele mai frecvente cuvintele-cheie ale limbajul C++sînt
auto delete float interrupt register template
break do for long return this
7
case double friend near short typedef
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
Cuvintele-cheie nu trebuie utilizaţi ca nume de
variabile.
Declararea variabilelor trebuie efectuată înainte de
a fi folosite, la începutul programului sau chiar în
funcţie de contextul problemei în interiorul
programului nemijlocit înainte de utilizare, cînd apare
necesitarea introducerii variabilei. 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
8
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
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) ;
9
fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000ffffffff
enum BOOLEAN {fals, adevărat) condiţie;
enum DIRECŢIE {SUS, JOS, DREAPTA,
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
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;
while (! condiţie)
{ cout <<”Utilizarea variabilei enumerabile”;
condiţie=true; }
Este bine ca astfel de atribuiri să fie însoţiţi 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;
10
*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;
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;
11
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)
++);
}
Rezultatul îndeplinirii programului:
pi=8F1C, pf=0758, pd=074C, pc=1330*pi=10, *pf=12.500000,
*pd=1.000000e-03, *pc=a pi++ =8F1C, pf++ =0758,
pd++ =074C, pc++=1330(*pi)++ =1B57, (*pf)++ =0000,
(*pd)++ =0000, (*pc)++ = 0000
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;
copiază conţinutul lui pi în pj, adică pj va indica la
variabila adresa căreia este indicată în pi, astfel că pj
se va modifica odată cu pi. Pointerii pot conţine
adrese 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 “&”.
12
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,
– 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;
13
atunci atribuirea pc=&1inie[0]; face ca pc să indice 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) indică 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, în pc+i i este înmulţit cu
lungimea obiectelor pe care le indică 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
14
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 şi 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.
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
15
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:
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);
16
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
indici, de la stînga la dreapta, variază mai repede
decît primii. Prima dimensiune a unui tablou se
foloseşte numai pentru a determina spaţiul ocupat de
acesta, ea nefiind luată în consideraţie decît la
determinarea unui element de indici daţi. Este
permisă omiterea primei dimensiuni a 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.
17
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 şirului 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ă î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 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
18
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 trebuie inversate, se
inversează pointerii lor în tabelul de pointeri, nu
însăşi liniile. 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[])
19
{ 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);
printf("\n Textul după sortare \n");
scrie_linii(linieptr,nlinii);
20
}
else printf("Input prea mare pentru sortare \n"); }
Rezultatul îndeplinirii programului:
Introdu un text (maxlen=1000)
Pentru introducerea unei linii noi se utilizează
tasta ENTER.
Sfîrsitul textului se va marca prin '*'.
ashdgasghddgjahsdgjaghdjhagsdhgdhjgdh*
Textul pînă la sortare:
ashdgasghddgjahsdgjaghdjhagsdhgdhjgdh va marca prin
'*'.
Textul după sortare
ashdgasghddgjahsdgjaghdjhagsdhgdhjgdh va marca prin
'*'.
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
21
b[5][5] sînt referinţe legale ale aceluiaşi int. Toate cele
100 celule de memorie ale tabloului a trebuie 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 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ă] { tip_1 element_1;
………
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 listă 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
22
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;
Definirea unei structuri permite determinarea unui nou tip de date.
În continuare definind pointeri la această structură,
tablouri, ale căror elemente sînt de tipul acestei
structuri, şi elemente de acest tip pot fi definite noi
structuri de date mai compuse.
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
23
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 &.
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
24
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 are acelaşi tip ca şi tipul lvalue.
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
25
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
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
26
26
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 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
număr_biti() controlează numărul de biţi pe 1 dintr-un
argument întreg
număr_biti (unsigned n)
{ int b;
for (b=0; n!=0; n»=1)
if (n&O1) b++;
return b; /}
27
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 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
28
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.
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ă
29
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:
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’;
30
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.
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’ : număr_caractere++; break;
31
case ‘B’: număr_caractere++; break;
… // se vor completa toate cazurile posibile
case 'Z' : număr_caractere++; break;
case ' a’ : număr_caractere++; break;
… // se vor completa toate cazurile posibile
case ‘z’: număr_caractere++; break;
case ‘0’: :număr cifre++;
… // se vor completa toate cazurile posibile
case '9':număr 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;
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
32
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:;
O posibilitate de căutare a primului element negativ
într-un tablou bidimensional ar fi:
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;
for (i=0; i<N && found; i++)
for (j=0; j<M && found; j++) found = v[i][j]<0;
if (found) { …….. 32 } // a fost găsit la i-1, j-1
else {…………..} // nu s-a găsit
33
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:
34
d[0]=7; d[1]=10; l=2;
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
35
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);
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;
36
p=new(nod);
r=p;
p->val=x;
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 р

p=prim;
37
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);
r->val=newval;

38
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ă.
Astfel, operaţia de includere şi eliminare a

39
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*62/-
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.
40
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)
{ 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:
41
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, 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(“numărul = %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
42
//în subarborele stîng al nodului spre
care indică
//pointerul p
if (q->nr > p-> nr )
return 1; // insertarea nodului curent
//î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* insert_nod()
{ 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;
43
int i;
for (q=parb;q;)
if ((i=criteriu(q,parb))==0) return q;
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 elib_nod(TNOD *p)
{ delete(p); }
void sterge_arbore (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ă
45
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 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 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’); }
46
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>
int smacc( int n,int x ) // funcţie auxiliară
int ackr( int n, int x, int y) // funcţie recursivă
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; }
Rezultatul îndeplinirii programului:
146 // datele iniţiale

-
10 // rezultatul obţinut
Fişierele input/output ale limbajul C++.
47
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).
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
48
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 parametri: df – descriptorul fişierului,
deplasarea indică numărul de octeţi pentru a 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
49
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 permit
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("intreg:%6i \n real: %9.3f ",316,144.82) ;
int z;
printf("Introdu valoarea z:");
scanf("%d",&z);
printf("%6d",z);
}
Rezultatul îndeplinirii programului:
intreg: 316
real: 144.820 Introdu valoarea z:500
Valoarea z: 500
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:
50
\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, un întreg zecimal este aşteptat la intrare;
%i 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-un
semn opţional, un şir de numere care pot
să conţină şi un punct zecimal şi un cîmp
51
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
va asigna 25 lui i, 44.32 lui x, iar nume va obţine
52
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
53
un comentariu legat de valoarea care urmează să fie
introdusă. De exemplu, în programul de mai jos am
afişat mesajul “număr 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;
int i;
printf ("Introdu un număr real: ");//utilizatorul va
introduce, //la
scanf("%f", &a); //tastatura, un număr urmat
de Enter.
printf("\n Introdu un număr intreg: "); //la fel se
procedează //în cazul variabilei i
scanf ("%i,", &i); // afişarea textului "număr
intreg"
printf("\n Număr întreg: %6i \n Număr real:
%9.3f", i, a);
} // va fi urmată de introducerea, la tastatură, a
numărului //dorit.
Rezultatul îndeplinirii programului:
Introdu un număr real: 3.14
Introdu un număr intreg: 20
Număr întreg: 20
Număr real: 3.140
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:
54
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(), 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) ;
}
Rezultatul îndeplinirii programului:
s-a format fişierul “disc.dat” (pentru înregistrare) cu
următorul conţinut:
29
2.710

Întrebările 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
55
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.

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; }
56
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();
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);
57
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 operaţiile efectuate asupra stivei în
programul de mai jos:
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!='.');
58
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;
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; }
59
return j; }
Valoarea returnată de funcţia index va fi numărul de
elemente cercetate din listă.
22. Scrieţi un program care să extragă în lista L2
elementele cu numere impare din lista L1. Lista L1 conţine
numere întregi.
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;
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; 64
int &r=i;
60
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ă 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.
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
61
î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 aleatoare 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.
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
62
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ă.
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 determină numărul
maximal şi cel minimal într-o listă circulară de 100
de numere aleatoare. Să se determine
consecutivitatea de elemente ce se află între
numerele maximal şi cel minimal determinate.
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.
VII.
63
1. Scrieţi un program care formează o listă nouă
simplu lănţuită după următoarea legitate: din trei
liste simplu lănţuite mai întîi se selectează
numerele divizibile la 3, 5 şi 7, apoi – cele 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
64
minimă şi maximă ale unei liste simplu lănţuită.
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.
65
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.
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.

66
Lucrarea de laborator nr. 2
Tema: Clase (constructori, destructori).Funcţii şi clase
prietene.

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:
class NumeClasă
{...
declaraţii variabile membri...
declaraţii funcţii membri...
}
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 membri, iar pentru funcţii –
denumirea de funcţii 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
67
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
{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 atribuie valoare
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 atribuie
//valoare componentei a egală cu 10
ob2.set_a (99); // pentru obiectul ob2 se atribuie
//valoare componentei a egală cu 99
cout << ob1.get_a ()<<“ \n”; // pentru obiectul ob1
68
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
}
Rezultatul îndepliniri programului:
10
99
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>
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ă
69
//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
}
Rezultatul îndepliniri programului:
10
99
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:
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 atribuie valori pentru elementele
membri nume
persoana(long int telefon) {return telefon;}; //şi telefon
70
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>
#include<stdlib.h>
#define Size 255
class strtype
{ private:
char *p;
int len;
public:
strtype() // constructorul
{ p=new char;
71
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();
}
Rezultatul îndeplinirii programului:
Test- lungimea 4
Program C++- lungimea 11
Eliberarea memoriei
Eliberarea memoriei
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 obiectului, iar destructorul
înaintea eliberării memoriei aferente, deci în faza
iniţială a distrugerii sale.
Constructorii şi destructorii se declară şi se
72
definesc similar cu celelalte funcţii membri, 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ăMembru = valoare;
pentru accesul la o variabilă membru, şi
obiect.FuncţieMembru();
pentru apelarea unei funcţii membri.
Pentru exemplificare să consideram o
implementare a noţiunii de punct. Ca variabile membri
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();
unsigned GetY();
void SetX(unsigned X);
void SetY(unsigned Y);
73
};
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 membri 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 membru a ei, deşi este definită în
afara declaraţiei.
Cuvîntul-cheie this. Toate funcţiile membri ale unei
clase primesc un parametru ascuns, pointer-ul this,
care reprezintă adresa obiectului în cauză. Acesta
poate fi utilizat în cadrul funcţiilor membri. De exemplu:
unsigned long Point::Arie()
{return this->x * this->y; }
Crearea şi distrugerea obiectelor. 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
74
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 rezolvă toate problemele. De aceea, pentru
crearea şi distrugerea obiectelor în C++ se folosesc
nişte funcţii membri speciale, numite constructori şi
destructori, despre care s-a menţionat mai sus.
Să completăm în continuare clasa Point cu un
constructor şi un destructor:
Point::Point() // constructor implicit
{ x = 0; y = 0; }
75
Point::Point(unsigned X, unsigned Y)
{ x = X; y = Y; }
Point::~Point() { }
Aţi remarcat cu această ocazie modul de marcare a
comentariilor în C++: tot ce se află după caracterul
// este considerat comentariu.
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 membri x
şi y. Deoarece una din regulile programării C++ este
de a proteja variabilele membri, 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
metodă să 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
De exemplu:
class Point {
friend unsigned long Calcul(unsigned X, unsigned Y);
public:
friend unsigned long AltăClasă::Calcul(unsigned
76
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
{ void Al (B &x) ; );
class B
{…
friend A;
77
…};
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
prietenă a unei clase C, aceasta nu înseamnă ca A
este prietenă 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;
class nod
{ private:
78
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 in *");
while ((c=getch ())!='*')
{ cs.push (c);
putch(c); }
putch(c);
while ((c=cs.pop ())!=-1)
{ putch (c); }

79
c='\n'; putch(c);
}
Rezultatul îndeplinirii programului:
Introdu un sir de caractere, ce se termina in *ertyuioppoiu*uiop
poiuytre
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 a 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:

80
#include<stdio.h>
class punct
{ private:
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");
}
Rezultatul îndeplinirii programului:
p este mal apropiat de origine

Întrebările 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
8
unei clase? Cum sînt activaţi constructorii şi
destructorii?

81
2. Ce se numesc clase prietene?
3. Ce se numesc funcţii prietene?

Temele pentru acasă:


1. Introduceţi şi lansaţi în execuţie următorul
program. Ce rezultat furnizează?
#include <stdio.h>
class număr
{public:
int n;
număr(int i) { printf( "num() %d \n",n); };
-număr() { printf("-num() %d \n",n); };
};
class numere
{ public:
număr 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:
82
alta_clasa *ac;
};
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; }
};
5. Descrieţi funcţiile de modificare a stringului

83
(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:
– member(x,L) care determină apartenenţa
elementului x listei L;
84
– 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:
– tipăreşte inversat conţinutul fiecărui cuvînt al liniei
85
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,
86
– 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,
– 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):
87
– adunarea,
– înmulţirea cu un coeficient,
– scăderea,
– împărţirea la un coeficient,
– 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, minutul, secunda):
– 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


Tema: 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

88
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 membri ale clasei de bază; ea poate adăuga
noi date la cele existente şi poate suprascrie sau
adăuga funcţii membri. 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 membri
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ă în realizarea unei aplicaţii orientate
obiect. Sintaxa simplificată a derivării este:
89
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);
};
GraphicPoint::GraphicPoint(unsigned X, unsigned Y,
unsigned Color) : Point(X, Y) {color = Color; }
GraphicPoint::~GraphicPoint() {}
GraphicPoint::Draw()
{……
// apelarea funcţiilor 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:
90
ClasaDerivată::ClasaDerivată() : ClasaDeBază()
În clasa GraphicPoint s-a adăugat o funcţie membru
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ţieMembru()
apelînd apoi funcţie de desenare, Draw().
Regulile de funcţionare ale constructorilor şi
destructorilor, descrise în lucrarea de laborator nr. 2,
rămîn valabile şi în cazul claselor derivate, ţinîndu-se
cont de următoarele observaţii privind ordinea de
apelare a acestora:
– 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 inversă 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
membri şi prietene ale clasei;
– protected, similar cu private, însă accesul se extinde
şi la funcţiile membri şi prietene ale claselor
derivate.
O funcţie membru a unei clase are acces la toţi
membrii clasei, indiferent de specificatorul de acces.
Aşa dar, sintaxa declaraţiei unei clase derivate,
91
incluzînd controlul accesului, este:
class NumeClasăDerivată : SpecificatorAcces
NumeClasaDeBază
unde SpecificatorAcces poate fi public sau private.
Accesul
Atributul
Modificator moştenit de Accesul din
din clasa
de acces clasa exterior
de bază
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 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
bază descrise cu specificatorul private sau protected).
Pentru respectarea principiului incapsulării datelor,
datele membri 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 cu specificatorul de acces 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.
92
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;
public: GraphicPoint(unsigned X, unsigned Y, unsigned
Color);
~GraphicPoint();
void Draw();
void SetX(unsigned X);
void SetY(unsigned Y);
};
Variabilele membri 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;
93
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 membri
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ă 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 membri. 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;
94
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);
}
Rezultatul îndeplinirii programului:
Aria unui punct este: 0.00
Aria cercului este: 42.00
Clasa Cerc este derivată de la clasa Punct. Cerc şi
Punct sînt 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()
95
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.
Identificarea dinamică se va realiza în momentul
execuţiei, funcţia nefiind identificată. 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ă 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ă
96
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.
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);
97
printf(“k = %d \n”,k);
}
Rezultatul îndeplinirii programului:
k = 42
Î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.
Funcţiile operator membri vor avea cu un
argument mai puţin decît cele non-membri. 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 membru (vezi Tabelul 1.)
98
Există două variante de definire a operatorilor:
– ca funcţie membru a clasei;
– ca funcţie prietenă a clasei.
Tabelul 1.
Tipul Simbolul
Asociativitate Observaţii
operatorului operatorului
Se definesc ca
Binar () [] -> -> funcţii membri
Unar + - ~ * & (tip) <-
Nu se poate dis-
Unar ++ -- <- tinge între pre- şi
postfixare
Poate fi supra-
Unar new, delete <- definit şi pentru o
clasă
-> * / % + - &
Binar | && || ->
<< >> < <=
Binar > >= == != ->
= += -=
*= /= %= &= Se definesc ca
Binar ^= |= <<= <- funcţii membri
>>=
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);
99
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)
{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 membri.
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 membri a unei

100
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.
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); };
101
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);
}
Rezultatul îndeplinirii programului:
nume Cristina, telefon 234567, virsta 20
Pentru aceasta, vom defini o funcţie avînd numele
operator +=() 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
102
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
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 membru fără parametri sau o
103
funcţie prietenă cu un parametru de tipul clasei respective.
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 verifică mai întîi
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şadar, 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
104
tip clasă, funcţia operator trebuie să aibă acces la
datele membri 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
membri 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
membri 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:
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
105
membru cu membru.
Clase abstracte. În limbajul C++ există
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 NumeFunctieMembru() = 0
Funcţiile virtuale pure trebuie definite în clasele
derivate, altfel şi acestea vor fi considerate abstracte.
Membrii statici ai unei clase. În mod normal, datele
membri ale unei clase sînt alocate în cadrul fiecărui
obiect. În C++, se pot defini date membri 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 clasei
respective, iar crearea, iniţializarea şi accesul la aceste
date sînt independente de obiectele clasei. Sintaxa este:
static DeclarareMembru
Funcţiile membri 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 membri statice nu primesc ca
parametru implicit adresa unui obiect, aşadar, în
106
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ările 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);}
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 membri, cîte argumente
107
explicite va avea aceasta?
4. Scrieţi o funcţie operator membru 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()
( 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()
108
{ cout«"Prinţul vede prinţesa\n";
act2(); }
108
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 ţeapă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 variantă doriţii să vedeţi
(L/B) ?\n" ;
cin»c;
if (p=='L') | | (c=='l’)) mes=new Mesaje_bune;
else mes=new Mesaje rele;
mes->act1();
delete mes;}

Temele pentru lucrări de laborator:


109
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
display 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 display
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 măsurată în mile, viteza fiind de 60
mile/oră. În clasa derivată metric se va redefini
funcţia trav_time(), astfel ca ea să afişeze timpul
necesar pentru parcurgerea distanţei măsurată î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 display valorile tabloului pe linii şi
pe coloane. Să se calculeze suma elementelor
tabloului, să se efectueze operaţiile algebrice
110
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
display 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 display
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 display
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 şirul de biţi dat,
111
– să se compare două şiruri de biţi,
– să se extragă un subşir de biţi din şirul de biţi dat,
– 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 display
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 display
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ă).
112
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 display
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ărului dat,
– să se determine numărul de cifre din partea
fracţionară a numărului 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 display 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
113
î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
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 display 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ărului dat,
– să se determine numărul de cifre din partea
fracţionară a numărului 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 display
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
114
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 display
valorile unei stive. Să se efectueze următoarele
operaţii asupra stivei:
– 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 display
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 datei calendaristice să se tipărească cu
cuvinte.

115
Lucrarea de laborator nr. 4.
Tema: Clase derivate cu moştenire multiplă.

Scopul lucrării: familiarizarea studenţilor cu clasele


derivate cu moştenire multiplă.

Consideraţiile teoretice necesare:


Moştenirea multiplă
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ă permite formarea unei
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
116
la rîndul lor sînt derivate dintr-o clasă comună,
ClasaDeBază. În acest caz, noua clasă, ClasaNouă, va
conţine datele membri 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ă duplicare nu este necesară şi duce la
consum inutil de memorie. De aceea, în C++ a fost
creat un mecanism care va evita această 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ă. Î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
{ …};
Grafic acest exemplu se poate de prezentat în modul
următor:
class A
117
virtual virtual
class B1 class B2

class C

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


1. Ce se numeşte moştenire multiplă? Daţi exemple.
2. Ce se numeşte clasă virtuală? Daţi 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:
118
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:
/* Scrieţi constructorul pentru clasa C astfel ca
el să activeze constructorii claselor de bază A şi
B*/
c() { cout « "Constructorul D \n";
119
~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, minutul, 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),
6. Într-un oraş a fost construit un centru de prestare a
120
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,
numărul 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ă = {codul ţării, numărul solicitatului,
oraşul, numărul solicitantului, numărul de minute
(durata conversaţiei)}. Să se calculeze plata pentru
achitarea serviciilor prestate acestor 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
121
unde rulează filme. Filmele sunt înregistrate în
baza de date cu următoarea structură: film =
{denumire, ţara, regizor, gen, anul filmării}.
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, numărul 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ă marfă de
export = {denumire, tara de exportare,
cantitatea, preţul mărfii, 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,
adresă, deschiderea, închiderea farmaciei} . 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
122
al costului, orei sosirii la destinaţie, 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, obiectul}, obiect
={denumire, an, tipul lecţiei, număr 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
= {număr 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, număr camere,
123
cost, comodităţi }} şi a apartamentelor de vînzare
(apartament.={adresa, telefon, număr 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.


Tema: 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 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.
124
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
fluxuri 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. La lansarea în execuţie a unui
program C++, care include iostream.h, in mod automat
compilatorul limbajului C++ creează şi iniţializează
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
125
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=50;
cout << "IntegerNumber = "; cin >> IntegerNumber;
cout<<"\nWhat you entered = "<<IntegerNumber<<endl;
}
Rezultatul îndeplinirii programului:
IntegerNumber = 200
What you entered = 200
Acest scurt program citeşte de la intrarea standard o
valoare întreagă, pe care o trimite apoi către ieşirea
126
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.
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()) != ‘*’)
{cout << "c = " << c << endl;}
}
Rezultatul îndeplinirii programului:
asdfgh*
c=a
c=s
c=d
c=f
c=g
c=h
127
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ă numărul 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').
În cazul în care caracterul de încheiere este
întîlnit înainte de a fi citit numărul 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 NumărMaximDeCaractere, char Sfîrşit);
Primul parametru reprezintă numărul 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 membri ale cout
Funcţia cout.flush()) determină trimiterea
128
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 numărul maxim de caractere ce se doresc
scrise. Sintaxa funcţiei cout.write() este:
cout.write(char *SirDeCaractere, int CaractereDeScris);
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 două funcţii membri ale
cout, şi anume:
Funcţia cout.setf() activează o opţiune de
formatare a ieşirii, primită ca parametru:
129
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
internal dreapta, centrează);
dec, oct, hex schimbă baza de numeraţie pentru
valori numerice;
showbase determină 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>
void main()
{int number = 783;
cout << "Număr = " << number<<endl;
cout.setf(ios::showbase);
cout<<"Număr în sistem hexazecimal =
"<<hex << number<<endl;
cout.setf(ios::left);
cout << "Număr în sistemul octal, aliniat la
stînga = " << oct << number<<endl;
}
Rezultatul îndeplinirii programului:
Număr = 783
Număr în sistem hexazecimal = 0x30f
Număr în sistemul octal, aliniat la sîinga = 01417
Redefinirea operatorilor de intrare şi
ieşire pentru fluxul standard. Operatorii de
intrare şi ieşire pentru fluxul standard pot fi redefiniţi
130
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 <iostream.h>
# include <string.h>
class persoana
{ private:
char nume[40];
char prenume[40];
long int telefon;
int virsta;
public:
persoana(char *Nume=NULL, char *n=NULL, int v=0,
long int t=0)
{strcpy(nume,Nume); strcpy(prenume,n);
virsta=v; telefon= t; }
friend istream & operator >> (istream &s, persoana &P);
friend ostream & operator<< (ostream &s, persoana &P);
void set(char* Nume, char *n, int v, long int t)
{strcpy(nume,Nume); strcpy(prenume,n);
virsta=v; telefon= t; };
};
131
istream & operator >> (istream &s, persoana &P)
{ if((s >>P.nume) && (s >>P.prenume) &&
(s >>P.virsta) && (s >>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:"<<endl;
cin >>p; cout<< p;
}
Rezultatele îndeplinirii programului:
Introdu datele despre persoana:
Bradu
Maria
20
123456
Bradu Maria 20 123456
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şierele se face prin
intermediul clasei ifstream pentru citire, respectiv ofstream
pentru scriere. Pentru a utiliza fişiere, aplicaţiile
trebuie să includă fstream.h. Clasele ofstream şi ifstream sînt
derivate din clasa iostream, ca urmare toţi operatorii şi
132
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 (supraîncărcat) «, iar citirea cu operatorul », –
133
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:
134
#include<fstream.h>
void main()
{ofstream s1;
s1.open("dl.dat",ios::binary);
s1<<"text \n"<<12<<"\n"<<4.2;
s1.close();
ofstream s2("d2.dat");
s2<<"text \n"<<12<<"\n"<<4.2;
s2.close ();
}
Rezultatul îndeplinirii programului:
Conţinutul fişierului “d1.dat”:
text
12
4.2
Conţinutul fişierului “d2.dat”:
text
12
4.2
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();
135
ofstream s2;
s2.open("d2.dat");
s2.write((char *) &i, sizeof(i));
s2. write (" \n", 4);
s2. close ();
}
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
136
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];
ofstream s1;
s1.open("d1.dat") ;
s1« 122 « "\n" « 147;
s1« "\n abcd\n" « 9.3;
s1.close();
ofstream s2;
s2.open("d.dat",ios::binary);
s2 « i « 3 « p « k;
s2.close();
}
Rezultatul îndeplinirii programului:
În fişierul textual “d1.dat” s-a înscris
următoarele
122
147
abcd
9.3
În fişierul binar s-a înscris următoarele:
102273BO39¤ _3.138909e-42
În alt exemplu:
#include<fstream>.h>
void main()
{ char p[20];
ifstream s;
s.open("d.dat",ios::binary);
s.read(p,19);
137
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 membru close().
Fluxuri în memorie constituie o interfaţă între
program şi un dispozitiv fizic. Am utilizat fluxurile
standard şi operatorii «, » şi, de 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,
138
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);
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 << "\n";};
s[164]=NULL;
cout << s;
}
Rezultatul îndeplinirii programului:
139
2 1 0.67 0.5 0.4
3 1.5 1 0.75 0.6
4 2 1.33 1 0.8
5 2.5 1.67 1.25 1
Î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ţii membri:
– eof() verifică dacă s-a ajuns la sfîrşitul
fişierului;
– bad() verifică dacă s-a executat o operaţie
invalidă;
– fail() verifică dacă ultima operaţie a eşuat;
– good() verifică dacă 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ările 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.
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?
140
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)
{ char c; cin.get©;
if(cin.eof()) break;
cout.put( c);
}
Exemplul 3.
#include <iostream.h>
void main()
{cout <<setfill(‘%’}<<setw(4)<< 17;}

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

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ă
142
ultimul cuvînt se pune punct.
4. Scrieţi un program care determină numărul
maximal şi cel minimal dintr-un şir de 100 de
numere aleatoare dintr-un fişier.
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 formează un fişier nou
după următoarea legitate: din trei fişiere date mai
întîi se selectează numerele divizibile la 3, la 5 şi la
7, apoi numerele pozitive pare de pe locuri impare.
6. Scrieţi un program care formează un fişier nou
după următoarea legitate: din trei fişiere date mai
întîi se selectează 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 va forma unul î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.
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 citite dintr-un fişier.
143
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 date
ordonate crescător se va forma unul nou, în care
se va păstra ordinea crescătoare de sortare.

Lucrarea de laborator nr. 6.


Tema: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
Template-ul implementează aşa-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
144
reutilizare a codului. De exemplu, pentru a
implementa o listă de numere întregi este necesară
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 > Declaraţie
unde Declaraţie reprezintă declararea sau definirea
unei clase sau unei funcţii, definirea unui membru
static al unei clase template, definirea unei clase sau
funcţii membri 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
}
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>
//
...
// declaraţia funcţiei
145
Să considerăm în continuare ca exemplu
implementarea unei stive generice folosind template-
uri.
#include <iostream.h>
template <class T> class StackItem
{ public:
StackItem *Next;
Tip *Data;
StackItem(Tip Data_new, StackItem <Tip> *Next_new)
{ Data = new Tip(Data_new);
Next = Next_new; }
};
template <class Tip> class Stack
{public:
Tip pop()
{ Tip result = *(Data->Data);
StackItem <Tip> *temp = Data;
Data = Data->Next;
delete temp;
return result;
}
Tip top()
{ return *(Data->Data); }
void push(Tip Data_new)
{ Data = new StackItem <Tip>(Data_new, Data); }
int isEmpty()
{ return Data == 0; }
Stack()
{ Data = 0; }
private:
StackItem <Tip> *Data;
};
void main()
{ Stack <int> anIntegerStack;
anIntegerStack.push(5);

146
anIntegerStack.push(7);
if(anIntegerStack.isEmpty())
cout << "Stiva goala" << endl;
else
cout << anIntegerStack.pop() << endl;
cout << anIntegerStack.top() << endl;
}
Rezultatul îndeplinirii programului:
7
5
Î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;}
147
template <class Tip> class Item
{public:
Item *Next;
Tip *Data;
Item(Tip __Data, Item <Tip> *__Next)
{Data = new Tip(__Data); Next = __Next;}
};
template <class Tip> class List
{public:
Tip pop_front()
{Tip result = *(Data->Data);
Item <Tip> *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;}
Tip operator *(){return *(Current->Data);}
Iterator& operator ++(int)
{Current = Current->Next; return *this;}
};
Iterator begin(){return Iterator(Data);}

148
Iterator end()
{Item <Tip> *temp;
for(temp = Data; temp; temp = temp->Next);
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;
}
Rezultatul îndeplinirii programului:
(3, 14 ) (1, 1)
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 membri a unei clase
template pentru un anumit tip, funcţia respectivă
poate fi supraîncărcată pentru tipul dorit.
Trebuie de 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.
149
Întrebările pentru verificarea cunoştinţelor:
1. Ce
reprezintă
un
template?
Daţi
exemple de
funcţie
template,
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? Daţi 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
150
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);
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 sînt
151
aranjate în ordine crescătoare: a1 ≤ a2≤ …≤ an.
Această problemă poartă denumirea de problemă
de sortare 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ă 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 sînt
aranjate în ordine crescătoare: a1 ≤ a2≤ …≤ an.
Această problemă poartă denumirea de problemă
de sortare 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 ai ş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 sînt
aranjate în ordine crescătoare: a1 ≤ a2≤ …≤ an.
152
Această problemă poartă denumirea de problemă
de sortare 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 .
4. Să se cerceteze şi să se calculeze numărul de
comparări şi de schimbări (adică permutări dintr-un
loc în altul) ale elementelor a1, …, an în procesul de
utilizare a algoritmilor descrişi în varianta 1. 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 valorilor primelor elemente ale
liniilor matricei;
b) după descreşterea sumelor elementelor liniilor
153
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 o consecutivitate de numere întregi sau reale a1 ≤ a2≤ …≤
an se dă în ordine descrescătoare ş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
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 poate 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 este 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

154
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) Tabelul de cîştiguri a loteriei este reprezentat prin
tabelul de numere cîştigătoare a1, …, an şi tabelul 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 numărului b printre cele ale
consecutivităţii sortate crescător a1, …, an se va
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).
7. Căutare binară. 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
155
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 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
156
astfel. Locul unde trebuie insertat în
consecutivitatea sortată a1, …, an, se determină prin
algoritmul împărţirii în jumătăţi (V. 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*⋅ log2 n 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 von Newmann de sortare a tabelului a1, …, an în
ordine nedescrescătoare (algoritm de sortare prin
unire) se bazează pe unirea de mai multe ori ale
grupelor de elemente deja sortate ale tabelului dat.
Mai întîi tabelul 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 pentru care 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 deja sortate.
Pentru sortare, la etapele intermediare, se
157
utilizează nu numai tabelul a1, …, aт, dar şi unul
auxiliar b1, …, bn. Figura 1 ne ilustrează

Fig.1.
cum se unesc două etape ale tabelelor a1, …, an şi b1,
…, bn reprezentîndu-le în formă de segmente
împărţite în părţi corespunzătoare grupelor de
elemente sortate. Numărul de grupe sortate se
micşorează. Deci se va obţine acel moment cînd
tabelul a1, …, an sau b1, …, bn va conţine o grupă de
elemente deja sortate. Aceasta înseamnă că
tabelul 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 sortare este
suficient de a efectua nu mai mult de n comparări
şi tot atîtea permutări. Demonstraţi că algoritmul
lui fon Newmann necesită aproximativ n log2 n
comparări şi tot atîtea permutări. Spre deosebite
de alţi algoritmi de sortare numai algoritmul de
insertare binară necesită acelaşi număr de
comparări. Însă algoritmul lui fon Newmann se
deosebeşte de cel menţionat prin utilizarea a mai
puţine permutări ale elementelor a1, …, an (cu toate
că necesită un tabel suplimentar b1, …, bn). De scris
programul care realizează algoritmul lui von
Newmann.
13 Fie se dă tabelul de obiecte a1, …, an. Pentru
sortarea tabelului să se utilizeze următoarea
procedură. Să se aranjeze elementele tabelului a1,
158
…, an astfel, ca la începutul tabelului să fie grupa de
elemente valorile cărora sînt mai mari ca
elementul de pe primul loc, urmează valoarea
primul element al consecutivităţii date şi apoi vor
urma elementele cu valori mai mici sau egale ca
primul element. Astfel, pas cu pas efectuînd
procedura descrisă, să se obţină tabelul sortat.
Numărul de comparări fiecare etapă 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 ale grupelor
159
sortate. Fişierele se prezintă în formă de
segmente, componentele cărora se
prezintă ca grupe sortate cu numărul
determinat
Fig.2. 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.

15. Fie dat tabelul 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 tabelul 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ă tabelul conţine nu mai mult de un element,
tabelul se consideră sortat. În caz contrar,
efectuăm transformarea descrisă în V..10 şi
determinăm rezultatele de utilizare a algoritmului
de sortare rapidă pentru tabelul 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
160
grupa întîi şi doi, după care urmează grupa doi de
elemente, sortată cu ajutorul algoritmului de
sortare rapidă. Acest algoritm nu utilizează
tabelul suplimentar şi necesită aproximativ n log2 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ă.

161
Lucrarea de laborator nr. 7.
Tema: 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
Excepţiile sînt situaţiile neaşteptate apărute în
cadrul sistemului care rulează un program.
Programele trebuie să conţină proceduri de tratare a
acestor situaţii excepţionale.
In C++ s-a realizat un mecanism de tratare a
excepţiilor. Astfel, o excepţie este un obiect a cărui
adresă este trimisă dinspre zona de cod, unde a
apărut problema, către o zonă de cod, care trebuie s-
o rezolve.
Paşii care trebuie, î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;
– 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.
162
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 este definită nici o funcţie de
tratare a excepţiei, sistemul apelează funcţia
predefinită, care încheie execuţia programului în
curs. După ce funcţia 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), care
poate 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

class Point
{ public:
163
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)
{ cout << "Zero value!\n"; }
catch(Point::xOutOfScreenBounds)
{ cout << "Out of screen bounds!\n"; }
catch(...)
{ cout << Unknown exception!\n"; }

164
}
Rezultatul îndeplinirii programului:
p.x successfuly set to 5.
Datorită faptului că excepţia este instanţierea unei
clase, prin derivare pot fi realizate adevărate ierarhii
de tratare a excepţiilor. Trebuie de 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ările pentru verificarea cunoştinţelor:
1. Cum sînt realizate excepţiile în cadrul programelor C+
+ ? Daţi exemple.

Temele pentru acasă:


1. Ce rezultate vor fi obţinute la rularea următorului
program:
void main()
{ cout<<”Început”;
try{ // începutul 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 << “Sfîrşit”; }

Teme 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 determină numărul
165
maximal şi cel minimal dintr-un şir de numere
aleatoare dintr-un fişier. 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 formează un fişier nou
selectîndu-se din trei fişiere date 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 formează un fişier nou din
trei fişiere date după următoarea legitate: se
selectează mai întîi numerele divizibile la 3, la 5 şi
la 7, apoi numerele pozitive pare de pe locuri
impare.
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.
11. Scrieţi un program care calculează suma înmulţirii
numerelor vecine pe locuri pare dintr-un fişier dat.
12. Scrieţi un program care determină frecvenţa cu
166
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 să formeze un fişier nou
care conţine elementele a două fişiere ordonate
crescător.Elementele fişierului format 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. D. Somnea, D. Turturea. Introducere în C++. Programarea
obiect orientata. – Bucureşti, ed. Teora, 1993.
2. O. Catrina, L. Cojocaru. Turbo C++.– Bucureşti, ed.
Teora, 1994.
3. D. Costea. Iniţiere în limbajul C. – Bucureşti, ed. Teora,
1996.
4. D. M. Popovici, I. M. Popovici, I. Tănase. C++.
Tehnologia orientată pe obiecte. Aplicaţii. –
Bucureşti, ed. Teora, 1996.
5. L. Negrescu. Iniţiere în limbajul C, C++. – Cluj, 1996.
6. А. Голуб. С и С++ . Правила программирования.

167
– Москва, Радио и cвязь, 1996
7. Г. Шилдт. Самоучитель С++. –Пер. с англ.,
Санкт Петербург, BHV, 1997.
8. Дж. Крис. Учимся программировать на языке
С++. – Москва, Радио и cвязь, 1997.
9. G. D. Mateescu. C++ limbaj de programare. –
Bucureşti, ed. Petrion, 1998.
10.L. Negrescu. Limbajele C, C++ pentru începători. –
Cluj–Napoca, ed. Albastra, 2000, v.II.
11.Бьерн Страустрап. Язык Программирования
С++. М.-BINOM, 2000.
12.D. M. Popovici, I. M. Popovici, I. Tănase. C++.
Tehnologia orientată pe obiecte. Aplicaţii. –
Bucureşti, ed. Teora, 2002.

168

S-ar putea să vă placă și