Documente Academic
Documente Profesional
Documente Cultură
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
5
CUPRINS
Introducere .......................................................................... 3
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.
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.
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ă:
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:
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:
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
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?
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.
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
81
2. Ce se numesc clase prietene?
3. Ce se numesc funcţii prietene?
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;}
};
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.
115
Lucrarea de laborator nr. 4.
Tema: Clase derivate cu moştenire multiplă.
class C
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) ;
}
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.
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()…
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.
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