Explorați Cărți electronice
Categorii
Explorați Cărți audio
Categorii
Explorați Reviste
Categorii
Explorați Documente
Categorii
1.1
Tehnici de programare
1.2
Convenii lexicale
continue
default
delete
do
double
else
enum
extern
float
for
friend
goto
if
inline
int
long
new
operator
private
protected
public
register
return
short
signed
sizeof
static
struct
switch
template
this
throw
try
typedef
union
unsigned
virtual
void
volatile
while
O constant real (cu virgul flotant) este alctuit dintr-o parte ntreag,
punctul zecimal, o parte fracionar i opional, un exponent ntreg cu semn precedat
de caracterul e sau E. Pot lipsi partea ntreag sau partea fracionar, dar nu
amndou. Tipul constantei flotante este double, dac nu conine un sufix care s
specifice explicit tipul (f sau F, pentru float; l sau L pentru long double).
O constant ir de caractere este o secven de caractere cuprins ntre
ghilimele duble i este de tipul vector de caractere. O constant ir de caractere are
clasa de memorare static i se iniializeaz cu caracterele date, la care se adaug
caracterul \0 (care are valoarea zero).
Este de asemenea posibil s fie definite constante simbolice (constante cu
nume). Acestea sunt prezentate n subseciunea urmtoare.
1.3
Declaraii, definiii
struct S;
typedef int Int;
//
//
//
//
corect,
corect,
corect,
eroare,
redeclarare
redeclarare
definiie
redefinire
Domeniul de definiie (scope) al unui nume este zona din program n care
numele este cunoscut i poate fi folosit. Dat fiind c un nume este fcut cunoscut
printr-o declaraie, domeniile numelor se difereniaz n funcie de locul n care este
introdus declaraia n program. Exist patru categorii de domenii: local, funcie, clas
i fiier.
Domeniul local: un nume declarat ntr-un bloc (o secven de instruciuni
cuprins ntre dou acolade) este local blocului i poate fi folosit numai n acel bloc,
ncepnd din locul declaraiei i pn la sfritul blocului i n toate blocurile incluse
dup punctul de declaraie. Argumentele formale ale unei funcii sunt tratate ca i cnd
ar fi declarate n blocul exterior al funciei respective.
Domeniul funcie: un nume declarat ntr-o funcie poate fi folosit n funcia
respectiv, ncepnd din punctul de declaraie i pn la sfritul blocului n care se
afl declaraia. Domeniul funcie poate fi considerat un caz particular de domeniu
local.
Domeniul clas: Un nume al unui membru al unei clase este local clasei
respective. Posibilitile de utilizare al acestor tipuri de nume vor fi prezentate ulterior.
Domeniul fiier: Un nume declarat n afara oricrui bloc sau clas are ca
domeniu fiierul n care a fost declarat i poate fi utilizat din punctul declaraiei pn
la sfritul fiierului. Numele cu domeniu fiier se numesc nume globale.
Domeniul de vizibilitate. Un nume este vizibil n ntregul su domeniu de
definiie dac nu este redefinit ntr-un bloc inclus n domeniul respectiv. Dac ntr-un
bloc interior domeniului unui nume se redefinete (sau se redeclar) acelai nume,
atunci numele iniial (din blocul exterior) este parial ascuns, i anume n tot domeniul
redeclarrii. De exemplu:
void fv1(){
int i = 10;
// definitie variabila i
{
// in acest bloc variabila i din blocul exterior
// este ascunsa datorit redefinirii
int i = 100;
cout << i << endl; // afiseaza 100
}
cout << i << endl;
// afiseaza 10
}
10
produce mesajele:
a = 1
a = 2
a = 3
b
b
b
= 2
= 2
= 2
c = 3
c = 5
c = 7
O variabil (obiect) global sau static, care nu este iniializat explicit, este
iniializat automat cu 0. n afara acestui mod de creare a obiectelor, se mai pot crea
obiecte n memoria liber (heap), a cror durat de via este controlat explicit
folosind funcii sau operatori de alocare. Acest mod de creare a obiectelor este
prezentat n subseciunea 2.4.
Categorii de memorare. Exist patru specificatori de categorii de memorare
a obiectelor: auto, static, register, extern.
11
1.4
Tipuri
Fiecare nume ntr-un program C++ are un tip asociat lui, care determin ce
operaii se pot aplica entitii la care se refer acest nume.
Un nume folosit pentru a specifica tipul unui alt nume ntr-o declaraie este un
nume de tip. Singurele operaii care se pot aplica unui nume de tip sunt: sizeof,
(care determin cantitatea de memorie necesar memorrii unui obiect de acel tip) i
new (operaia de alocare n memoria liber a unui obiect de tipul respectiv).
1.4.1
Tipuri fundamentale
char
short int
1 octet
2 octei
12
int
long
2 sau 4 octei
4 sau 8 octei
float
double
long double
4 octei
8 octei
12 sau 16 octei
Aceste tipuri sunt denumite mpreun tipuri aritmetice. Pentru tipurile ntreg,
exist variante de declaraie cu semn (signed) i fr semn (unsigned).
1.4.2
Tipuri derivate
13
// variabila c1
// p memoreaz adresa lui c1
// dereferentiere, c2 = a;
Operatorul & este operatorul adres, care se utilizeaz pentru a obine adresa
unei variabile.
Tipul void* este folosit pentru a indica adresa unui obiect de tip necunoscut.
Asupra pointerilor sunt admise unele operaii aritmetice. De exemplu, se
consider un vector de caractere dintre care ultimul este caracterul 0 (se mai numete
ir de caractere terminat cu nul). Pentru calculul numrului de caractere se pot folosi
operaii cu pointeri astfel:
int strlen(char* p){
int i = 0;
while (*p++) i++;
return i;
}
14
//
//
//
//
15
16
// prototip functie f2
// definitie functie f3
void fp(){
double r1 = f1(7, 8.9);
double r2 = f2(7, 8.9);
char str[] = "abcde";
double r3 = f3(7, str);
// eroare,
// identificator nedeclarat
// corect, fol. prototipul
// eroare de tip argument
double f1(int
/*..*/
double t =
return t;
}
double f2(int
/*...*/
double t =
return t;
}
a, double f) {
a + f;
a, double f) {
a*f;
17
din declaraia funciei, n ordinea din declaraie. Argumentele unei funcii se pot
transfera n dou moduri: apelul prin valoare i apelul prin referin.
n apelul prin valoare se copiaz valoarea argumentului real n argumentul
formal corespunztor al funciei. n acest caz, modificrile efectuate asupra
argumentului funciei nu modific argumentul real.
n apelul prin referin este accesat direct variabila din argumentul real
transmis funciei, care poate fi deci modificat. Ca exemplificare, se definete o
funcie swap() care realizeaz intershimbul ntre valorile a dou variabile. Dac nu
se folosesc referine, argumentele de apel ale funciei trebuie s fie pointeri la
variabilele respective. Pointerii, ca argumente de apel, nu vor fi modificai, dar
variabilele indicate de acetia pot fi modificate. Funcia swap() cu argumente
pointeri arat astfel:
void swap(int* x, int* y){
int t;
t = *x;
// dereferentiere
*x = *y;
*y = t;
}
18
struct data{
int zi;
int luna;
int an;
} g_data;
void setdata(int zi, int luna=9, int an =1999){
g_data.zi = zi;
g_data.luna = luna;
g_data.an = an;
}
void main(){
setdata(15);
// 15 9 1999
setdata(21,7);
// 21 7 1999
setdata(20,1,2000);
// 21 1 2000
}
pf = func1;
float z = (*pf)(3, 1.2f);
cout << z << endl;
pf = func2;
z = (*pf)(3, 1.2f);
cout << z << endl;
Dei din acest exemplu simplu nu reiese care ar putea fi avantajul folosirii
apelului prin pointer a unei funcii, exist totui situaii cnd apelul prin pointeri este
foarte avantajos. De exemplu, poate s fie nlocuit o instruciune switch care
selecteaz dintre mai multe apeluri de funcii, cu un vector de pointeri la funcii care
se pot apela prin pointerii corespunztori.
19
// apeleaza abs(int)
// apeleaza abs(double)
Acceptarea mai multor versiuni ale unei funcii cu acelai nume este
condiionat de posibilitatea selectrii fr ambiguitate a uneia dintre acestea dup
tipul sau numrul argumentelor. Nu este admis ca funciile s difere doar prin tipul
returnat.
Dou funcii declarate cu acelai nume se refer la aceeai funcie dac sunt n
acelai domeniu i au numr i tipuri identice de argumente. O funcie declarat local
nu este n acelai domeniu cu o funcie cu domeniul la nivel de fiier. De exemplu:
int f(char*);
void g(){
extern f(int);
f(abcd); // eroare, f(int) ascunde f(char*)
//deci nu exista f(char*) n acest domeniu
}
20
//
//
//
//
//
selecteaza
selecteaza
selecteaza
selecteaza
selecteaza
power(long, int)
power(int, int)
power(double, int)
power(double, double)
power(double, double)
Dac prin astfel de conversii nu se poate stabili cea mai bun potrivire a
argumentelor, atunci sunt ncercate i conversii pentru tipuri definite de utilizatori.
Apelul este acceptat numai dac selecia unei versiuni a funciei (pe baza criteriului de
cea mai bun potrivire a argumentelor) este unic. Conversiile ntre date de tipuri
definite de utilizatori sunt prezentate n seciunea 4, dedicat suprancrcrii
operatorilor.
1.4.2.5 Constante simbolice
O constant simbolic (sau constant cu nume) este un nume a crui valoare
nu poate fi modificat n cursul programului. n C++ exist trei modaliti de a defini
constante simbolice:
Orice valoare, de orice tip care poate primi un nume, poate fi folosit ca o
constant simbolic prin adugarea cuvntului-cheie const n declaraia
acesteia.
Orice nume de funcie sau de tablou este o constant simbolic.
O enumeraie definete o mulime de constante ntregi.
De exemplu, urmtoarele declaraii introduc constante simbolice prin folosirea
cuvntului-cheie const:
const int val = 100;
const double d[] = {1.2, 2.8, 9.5};
// eroare
// eroare
21
22
good,
bad,
fail
};
void fe (states st ){
if (st != good){
//.
}
}
Un nume declarat de tipul unei enumeraii poate lua orice valoare particular
cuprins n enumeraie.
n sfrit, este posibil iniializarea elementelor unei enumeraii:
enum states{
good = 0x00,
bad = 0x01,
fail = 0x10
};
23
este i o definiie a clasei X, care introduce un tip nou de date. Prin aceast definiie se
asociaz numele X cu entitatea definit n corpul clasei. Dup definirea unui astfel de
tip de date, se pot declara (defini) obiecte de tipul respectiv, la fel ca i variabilele
(obiecte) de tipuri predefinite:
X obx1;
1.5
Expresii i operatori
24
i comutativitate se aplic pentru acei operatori care sunt n mod real asociativi sau
comutativi.
O expresie care se refer la un obiect sau funcie este denumit valoare stnga
(left value, prescurtat lvalue). Aceast denumire provine din faptul c o astfel de
valoare poate fi folosit ca membru stng al unei expresii de asignare: E1 = E2.
Operandul E1 trebuie s fie expresie lvalue. n expresii, unii operatori produc expresii
lvalue, alii necesit expresii lvalue. O expresie lvalue este modificabil dac nu este
numele unei funcii, numele unui tablou, sau constant.
25
unde tip_data este un tip de date predefinit sau definit de utilizator (clas), p este
pointerul (adresa de nceput) a zonei alocate n memoria liber, returnat la execuia
operatorului new, iar initializare este o expresie care depinde de tipul datei i
permite iniializarea zonei de memorie alocate. Dac alocarea nu este posibil,
pointerul returnat este NULL.
Forma de utilizare a operatorului new pentru alocarea unui vector de date
(tablou unidimensional) de dimensiune dim, este urmtoarea:
tip_data* p = new tip_data[dim];
Prima form se utilizeaz pentru eliberarea unei zone de memorie ocupat de o singur
dat (obiect), nu de un vector. Pointerul p trebuie s fie un pointer la o zon de
memorie alocat anterior printr-un operator new. Operatorul delete trebuie s fie
folosit doar cu un pointer valid, alocat numai cu new i care nu a fost modificat sau nu
a mai fost eliberat zona de memorie mai nainte (cu un alt operator delete sau prin
apelul unei funcii free()). Folosirea operatorului delete cu un pointer invalid
este o operaie cu rezultat nedefinit, cel mai adesea producnd erori de execuie grave.
Cea de-a doua form a operatorului delete[] se folosete pentru eliberarea
unei zone de memorie ocupat de un vector de date. Pentru tipurile de date predefinite
ale limbajului, se poate folosi i prima form pentru eliberarea unui vector, dar, n
cazul obiectelor de tipuri definite de utilizator, acest lucru nu mai este valabil. Aceast
situaie va fi detaliat n seciunea urmtoare.
Cteva exemple de utilizare a operatorilor new i delete:
int *pi = new int(3);
double *pd = new double;
char *pc1 = new char[12];
char *pc2 = new char[20];
delete pi;
delete pd;
//
//
//
//
26
delete pc1;
delete []pc2;
n legtur cu cele dou metode de alocare dinamic, prin operatorii newdelete i prin funciile de bibliotec malloc-free, fr s fie o regul precis, se
recomand evitarea combinrii lor, deoarece nu exist garania compatibilitii ntre
ele.
1.5.2
Precedena operatorilor
n tabelul urmtor sunt prezentai concis operatorii din limbajul C++ grupai
n ordinea precedenei lor. n fiecare compartiment sunt trecui operatorii cu aceeai
preceden. Un operator dat are o preceden mai ridicat dect un altul dintr-un
compartiment aflat mai jos n tabel. Operatorii se aplic n ordinea precedenei lor. De
exemplu, a+b*c nseamn a+(b*c), deoarece nmulirea (*) are o preceden mai
ridicat dect adunarea (+).
Operatori C++
::
operator rezoluie
nume_clas::membru
::
nume global
::nume
selecie membru
obiect.membru
->
selecie membru
pointer->membru
[]
indexare
pointer[expr]
()
apel funcie
expr (lista_expr)
()
conversie explicit
tip(list_expr)
sizeof
dimensiune obiect
sizeof expr
sizeof
dimensiune tip
sizeof (tip)
++
post incrementare
lvalue++
++
pre incrementare
++lvalue
--
post decrementare
lvalue--
--
pre decrementare
--lvalue
complement
~expr
negaie
!expr
minus unar
-expr
plus unar
+expr
&
adres
&lvalue
27
derefereniere
*expr
new
alocare
new tip
delete
eliberare(dezalocare)
delete pointer
delete[]
eliberare tablou
delete[]pointer
()
conversie cast
(tip)expr
.*
selecie membru
obiect.*pointer_la_membru
->*
selecie membru
pointer->*pointer_la_membru
nmulire
expr * expr
mprire
expr / expr
modulo
expr % expr
adunare
expr + expr
scdere
expr - expr
<<
>>
<
mai mic
<=
>
mai mare
>=
expr >=expr
==
egal
expr == expr
!=
diferit
expr != expr
&
expr ^ expr
OR orientat pe bii
expr | expr
&&
||
OR logic (SAU)
expr || expr
?:
expresie condi.
asignare simpl
lvalue = expr
*=
nmulire i asignare
expr *= expr
/=
mprire i asignare
expr /= expr
%=
modulo i asignare
expr %= expr
+=
adunare i asignare
expr += expr
-=
scdere i asignare
expr -= expr
28
<<=
>>=
&=
AND i asignare
|=
OR i asignare
expr |= expr
^=
XOR i asignare
expr ^= expr
throw
lansare excepie
throw expresie
virgul(secveniere)
expr, expr
1.6
Conversii standard
Unii operatori pot provoca conversia valorii unui operand de la un tip la altul,
n funcie de tipul operanzilor. Conversiile standard executate pentru tipurile
predefinite n C++ sunt identice cu cele din limbajul C i de aceea vor fi descrise
foarte succint. Astfel de conversii intervin n urmtoarele situaii:
Conversia unor operanzi de tipuri diferite ntr-o expresie aritmetic.
Conversia unui argument de apel al unei funcii la tipul argumentului
formal corespunztor.
Conversia valorii returnate de o funcie din tipul folosit n instruciunea
return n tipul din declaraia funciei.
n toate aceste situaii se respect cteva reguli de conversie care sunt descrise
n continuare.
1.6.1
Conversiile aritmetice
29
1.6.2
Conversia pointerilor
1.7
Orice program C++ care nu este foarte simplu este alctuit din mai multe
uniti de compilare, numite convenional fiiere.
Din punct de vedere al limbajului C++, un fiier reprezint un domeniu de
definiie (domeniul fiier), care este domeniul pentru funciile globale de tip static
i inline i pentru variabilele globale de tip static. Un fiier este, de asemenea,
o unitate de memorare n sistemul de fiiere i o unitate de compilare (un modul).
30
Dezvoltarea unui program ntr-un singur fiier este practic imposibil, deoarece
sunt apelate funcii din biblioteci i funcii ale sistemului de operare care sunt
memorate n mai multe fiiere. Chiar i partea de program scris de utilizator este
inconvenabil s fie toat cuprins ntr-un singur fiier, datorit dificultii de
organizare i de evideniere a diferitelor pri ale programului. Mai mult, dac
programul este curins ntr-o singur unitate de compilare, orice modificare trebuie
urmat de recompilarea ntregului fiier.
n organizarea pe mai multe fiiere a unui program, este necesar ca
programatorul s prevad declaraii care s permit analiza de ctre compilator a
fiecrei uniti de compilare luat izolat dar, n acelai timp, i utilizarea unitar a
numelor i a tipurilor definite. Orice sistem de programare permite o astfel de
organizare i legare a unitilor compilate separat, n principal prin programul de
linkare (linker).
1.7.1
Linkarea modulelor
Dac nu este specificat altfel, un nume care nu este local (definit ntr-o funcie
sau clas) trebuie s se refere la aceeai entitate n oricare din unitile de compilare
(fiiere) ale programului, adic trebuie s existe o singur funcie, valoare, tip sau
obiect nelocal cu acelai nume. De exemplu, se consider fiierele:
// fisier1.cpp
int x = 0;
void f() {/* corpul functiei*/}
// fisier2.cpp
extern int x;
void f();
int g() {
x = f();
}
1.7.2
31
Fiiere antet
#include antet2.h
#define BOOL int
struct point {double x, y;};
const double epsilon = 0.1;
enum state{good, false};
inline char get(char*p)
{return *p++;}
template<class T>
class V{/* */};
extern int x;
class X;
extern int func(char c);
/* comentariu */
*/};
32
Exerciii
E1.1 S se rescrie funciile strlen(), care returneaz lungimea unui ir,
strcmy(), care compar dou iruri i strcpy() care copiaz un ir n alt ir. S
se stabileasc ce fel de tipuri de argumente sunt necesare, dup aceea s se compare cu
versiunea standard declarat n <string.h>.
E1.2 Se consider urmtoarea operaie de copiere a dou iruri de caractere
terminate cu nul:
int len = strlen(q);
for (int i = 0; i<=len; i++) p[i] = q[i];
2
Clase i obiecte
34
muli specificatori de acces. Un specificator_acces poate fi unul din cuvintelecheie din C++:
public
private
protected
2.1
Datele declarate ntr-o clas se numesc date membre i, de obicei, ele sunt
protejate (private sau protected), dar exist i situaii n care sunt declarate
public. Nu se pot declara auto, extern sau register datele membre ale unei
clase.
2. Clase i obiecte
35
Funciile definite ntr-o clas se numesc funcii membre (sau metode ale
clasei) i de obicei ele sunt de tip public, dar pot fi i protejate.
n exemplul urmtor se consider definiia unui tip de date pentru
reprezentarea numerelor complexe, clasa Complex.
Exemplul 2.1
#include <iostream.h>
class Complex{
double re;
double im;
public:
void init(){
re = 0;
im = 0;
}
void set(double x, double y){
re = x;
im = y;
}
void display(){
cout << re << << im << endl;
}
};
void main(){
Complex c1;
c1.init();
c1.display();
// afiseaza 0 0
c1.set(7.2, 9.3);
c1.display();
// afiseaza 7.2 9.3
}
2.1.1
O clas definete un tip de date al crui nume este numele clasei, precum i un
domeniu al clasei. n acelai timp, o clas care nu este o clas local sau o clas
intern altei clase (acestea sunt descrise n subseciunea 2.8), are un domeniu de
definiie (este cunoscut n acest domeniu) care ncepe de la prima poziie dup
ncheierea corpului clasei i se ntinde pn la sfritul fiierului n care este introdus
definiia ei i al fiierelor care l includ pe acesta.
36
n domeniul de definiie al unei clase se pot crea obiecte (instane) ale clasei.
Fiecare obiect conine cte o copie individual a fiecrei variabile a clasei respective
(dac nu este de tip static; acest caz va fi descris ntr-o subseciune urmtoare) i
pentru fiecare obiect se poate apela orice funcie membr public a clasei.
Accesul la datele membre publice sau apelul funciilor membre publice ale
unui obiect se poate face folosind un operator de selecie membru: operatorul punct
(.) dac se cunoate obiectul, sau operatorul -> dac se cunoate pointerul la obiect.
Datele i funciile membre protejate ale clasei (private sau protected)
au ca domeniu de definiie domeniul clasei respective i de aceea nu pot fi accesate
dect prin funciile membre ale clasei.
Mai trebuie remarcat nc un aspect referitor la domeniul de definiie al
claselor. Domeniul n care pot fi definite obiecte de tipul unei clasei este domeniul n
care este definit clasa. nainte de definiia clasei nu se pot defini sau declara obiecte
din acea clas. Acest lucru nseamn, implicit, c nici n corpul unei clase nu se pot
declara obiecte de tipul aceleiai clase (deoarece nu s-a completat definiia clasei).
Numele unei clase se poate declara (sau redeclara) folosind construcia:
class nume_clasa;
ntr-un domeniu n care a fost declarat numele unei clase se pot declara
pointeri la clasa respectiv. De asemenea, n corpul unei clase se pot declara pointeri
sau referine la aceeai clas.
n exemplul urmtor sunt prezentate cu comentarii mai multe situaii
referitoare la declaraii i definiii de clase, obiecte, date i funcii membre ale
acestora. Erorile sunt specificate chiar cu mesajul produs de compilator.
Exemplul 2.2
#include <iostream.h>
void K::set(int x){
a = x;
}
2. Clase i obiecte
37
class K;
// declaratie nume clasa K
K* pob1;
// corect, pointer la K
K pb2;
// error: 'pb2' uses undefined class 'K'
class K{
int a;
// a este membru privat al clasei
K k;
// error:'k'uses'K'which is being defined
K* pobk;
// corect, pointer la K
public:
int b;
// b este membru public al clasei
K(){}
K(K& r);
// corect, contine o referinta la K
int get(); // declaratie (prototip) functie membra
void set(int x);
};
// corect: definitii functii membre in
// domeniul de definitie al clasei
int K::get(){
return a;
}
void K::set(int x){
a = x;
}
K pob3;
// corect, clasa K este definita
void fk(){
K ob4;
K ob5;
K* pob5 = &ob5;
ob4.b = 5;
// corect, b este membru public
pob5->b = 6;
ob4.a = 2;
ob4.set(2);
pob5->get();
2.1.2
38
accesate prin intermediul pointerului la acesta (cu numele this), primit ca argument
implicit la apelul funciei. Acest lucru s-ar putea scrie mai detaliat astfel:
void init(){
this->re = 0;
this->im = 0;
}
Dar, odat acest mecanism stabilit i cunoscut, nu mai este nevoie s fie scris
de fiecare dat, deci nu se va ntlni niciodat o astfel de utilizare a pointerului this.
n schimb, pointerul this este folosit n funcii membre care manevreaz pointeri.
n general, la apelul unei funcii n care un argument este de tip pointer la
constant sau referin la constant, se interzice modificarea obiectului indicat sau
referit astfel. Dar, pentru funciile membre ale claselor, folosirea unui pointer sau
referin la constant necesit condiii suplimentare fa de folosirea acestora n funcii
nemembre. De exemplu:
#include <iostream.h>
class U{
int u;
public:
int get() {return u;}
void set(int x) {u = x;}
};
void fu1(const U* pu, int i){
pu->set(i);
pu->get();
}
void fu2(const U& r, int i){
r.set(i);
r.get();
}
void main(){
U ob;
fu1(&ob,2);
fu2(ob,3);
}
2. Clase i obiecte
39
acestei probleme este de a declara de tip const acele funcii care au dreptul de acces
la un obiect indicat prin pointer sau referin la constant.
Pentru clasa U, se poate declara de tip const funcia get() i atunci
obiectul poate fi accesat numai pentru citire atunci cnd este transmis ca argument
pointer (sau referin) la constant. Programul modificat arat astfel:
#include <iostream.h>
class U{
int u;
public:
int get() const {return u;}
void set(int x) {u = x;}
};
void fu1(const U* ps, U* pd){
int i = ps->get();
// corect, get()const{.}
// poate accesa ob. const U* ps
ps->set(i);
// eroare, set() {.} nu poate
// accesa obiectul const U* ps
pd->set(i);
// corect, set() {} poate
// accesa obiectul U* pd
i = pd->get();
// corect get()const{.}
// poate accesa obiectul U* pd
}
void main(){
U ob1, ob2;
fu1(&ob1,&ob2);
}
2.1.3
40
fi declarate funcii externe, deci nu pot fi utilizate dect n modulul de program n care
au fost definite i nu pot conine instruciuni ciclice (while, for, do-while).
Atributul inline poate fi neglijat de compilator dac funcia nu poate fi tratat
astfel.
O funcie membr a unei clase definit (nu doar declarat) n interiorul clasei
este implicit funcie inline. Acest lucru nseamn c, de exemplu, funcia init()
din clasa Complex este implicit inline. O funcie membr definit n afara clasei
este implicit o funcie normal (nu este inline). Dar i o astfel de funcie poate fi
declarat explicit inline . De exemplu, funcia set() din clasa K:
inline void K::set(int x){ a = x;}
2.1.4
Exemplul 2.3
2. Clase i obiecte
41
pdm = &W::a;
// eroare, a este privat
pdm = &W::b;
//corect pdm indica b din clasa W
cout << pw->*pdm << endl;
// afiseaza 6
2.1.5
ncapsularea datelor
Aparent, accesul la datele sau funciile membre ale unei clase din orice punct
al domeniului de definiie al clasei s-ar putea rezolva simplu prin declararea de tip
public a acestora. ntr-adevr, urmtoarea implementare este posibil:
class Complex{
public:
double re;
double im;
};
//
42
void fc1(){
Complex c1;
c1.re = 5.6;
c1.im = 7.9;
}
2. Clase i obiecte
43
void init() { re = 0; im = 0; }
void set(double x, double y){
re = x;
im = y;
}
void setre(double x) {re = x;}
void setim(double y) {im = y;}
double getre() {return re;}
double getim() {return im;}
void display();
};
inline void Complex::display(){
cout << re << << im << endl;
}
void main(){
Complex c1;
c1.set(7.2, 9.3);
c1.display();
// afiseaza 7.2
c1.setre(1.3);
c1.setim(2.8);
c1.display();
// afiseaza 1.3
}
9.3
2.8
Datele membre ale clasei (re i im) sunt date de tip private, iar accesul la
acestea este posibil prin intermediul funciilor membre publice set(), setre(),
setim(), etc.
ntr-o astfel de implementare, n care majoritatea funciilor sunt inline
(este posibil n acest caz simplu, cu funcii de dimensiuni mici) nu apar dect apeluri
de funcii prin expandare, deci implementarea este att elegant ct i eficient.
2.1.6
Exemplul 2.4
44
clasei S
endl;
endl;
endl;
endl;
Diferena ntre comportarea unei date membre de tip static i a unei date
normale este evident: dup incrementarea variabilelor s i v pentru obiectul x,
obiectele x i y vd aceeai valoare a variabilei statice s i valori diferite ale variabilei
normale v.
Funciile membre ale unei clase pot fi de asemenea declarate de tip static.
O funcie membr de tip static se declar n interiorul clasei i se definete n
interiorul clasei sau n afara acesteia, folosind operatorul de rezoluie. O funcie
membr static are vizibilitatea limitat la fiierul n care a fost definit i este
independent de instanele (obiectele) clasei. Fiind independent de obiectele clasei, o
funcie static nu primete pointerul this, chiar dac apelul se face pentru un obiect
al clasei respective.
Fie, de exemplu clasa Task care conine o dat membr static i o funcie
membr static:
2. Clase i obiecte
class Task{
static Task *chain;
public:
static void schedule(int);
};
Task *Task::chain=0;
void Task::schedule(int p){
Task::chain = 0;
}
void fs2(){
Task T1;
T1.schedule(4);
Task::schedule(2);
}
45
Apelul unei funcii statice se poate face fie ca funcie membr a unui obiect
din clasa respectiv, aa cum apare n primul apel din funcia fs2(), fie prin
specificarea clasei creia i aparine, folosind operatorul de rezoluie. n prima situaie
se folosete doar tipul obiectului T1 pentru apel, nu obiectul n sine, i nici pointerul
acestuia nu este transmis implicit (ca un pointer this) funciei schedule().
Compilatorul chiar d un mesaj de atenionare (warning): 'T1' : unreferenced local
variable.
Alt restricie referitoare la funciile membre statice este aceea c ele nu pot
avea acces dect la datele statice ale clasei i la datele i funciile globale ale
programului.
Se poate remarca faptul c specificatorul static are n C++, ca i n C, dou
semnificaii: aceea de vizibilitate restricionat la nivelul fiierului n care sunt definite
variabilele sau funciile i aceea de alocare static, adic obiectele exist i-i menin
valorile lor de-a lungul execuiei ntregului program.
2.2
46
};
inline void Complex::display(){
cout << re << << im << endl;
}
void main(){
Complex c1;
c1.set(7.2, 9.3);
c1.display();
// afiseaza 7.2
}
9.3
2. Clase i obiecte
2.3
47
Constructori i destructori
Utilizarea unor funcii membre ale unei clase, aa cum este funcia init()
din clasa Complex, pentru iniializarea obiectelor este neelegant i permite
strecurarea unor erori de programare. Deoarece nu exist nici o constrngere din
partea limbajului ca un obiect s fie iniializat (de exemplu, nu apare nici o eroare de
compilare dac nu este apelat funcia init() pentru un obiect din clasa Complex),
programatorul poate s uite s apeleze funcia de iniializare sau s o apeleze de mai
multe ori. n cazul simplu al clasei prezentate ca exemplu pn acum, acest lucru poate
produce doar erori care se evideniaz uor. n schimb, pentru alte clase, erorile de
iniializare pot fi dezastruoase sau mai greu de identificat.
Din aceast cauz, limbajul C++ prevede o modalitate elegant i unitar
pentru iniializarea obiectelor de tipuri definite de utilizator, prin intermediul unor
funcii speciale numite funcii constructor (sau, mai scurt, constructori).
2.3.1
Constructori
Exemplul 2.5
#include <iostream.h>
#define MAX_SIZE 1000
class IntStack {
int vect[MAX_SIZE];
int tos;
public:
IntStack(){tos = 0;}
void push (int x);
int pop();
};
void IntStack::push(int v){
if (tos < MAX_SIZE)
vect[tos++] = x;
else cout <<Eroare depasire stiva\n;
}
int IntStack::pop(){
if (tos > 0)
return vect[--tos];
else {
cout << Eroare stiva goala\n;
return 1;
}
}
void fs1(){
IntStack stack;
stack.push(4);
48
stack.push(9);
cout << stack.pop()
cout << stack.pop()
stack.push(1);
stack.push(2);
cout << stack.pop()
cout << stack.pop()
<< ;
<< endl;
// afiseaza 9
// afiseaza 4
<< ;
<< endl;
// afiseaza 2
// afiseaza 1
2. Clase i obiecte
49
compilatorul genereaz un constructor implicit de tip public, ori de cte ori este
necesar.
In Exemplul 2.6 se definesc mai multe funcii constructor pentru clasa
Complex: constructor fr argumente, cu un argument i cu dou argumente.
Exemplul 2.6
#include <iostream.h>
class Complex{
double re;
double im;
public:
Complex(){cout << "Constructor fara argumente\n";
re = 0;
im = 0;
}
Complex(double v){cout << "Constructor cu 1 arg\n");
re = v;
im = v;
}
Complex(double x, double y){
cout << "Constructor cu 2 arg\n";
re = x;
im = y;
}
};
void fc2 (){
Complex c1;
Complex c2(5);
Complex c3(4,6);
}
Un constructor este apelat ori de cte ori este creat un obiect dintr-o clas care
are un constructor (definit sau generat de compilator). Un obiect poate fi creat ntrunul din urmtoarele moduri:
ca variabil global,
ca variabil local,
prin utilizarea explicit a operatorului new,
ca obiect temporar,
prin apelul explicit al unui constructor.
50
Exemplul 2.7
2. Clase i obiecte
int DStack::pop(){
if (tos>0) return pvect[--tos];
else {
cout << Eroare stiva goala\n;
return -1;
}
}
void fd1(){
DStack stack1(100);
stack1.push(4);
stack1.push(9);
cout << stack1.pop() << ;
//
cout << stack1.pop() << endl;
//
DStack stack2(200);
stack2.push(1);
stack2.push(2);
cout << stack2.pop() << ;
//
cout << stack2.pop() << endl;
//
}
51
afiseaza 9
afiseaza 4
afiseaza 2
afiseaza 1
2.3.2
Destructori
Multe din clasele definite ntr-un program necesit o operaie invers celei
efectuate de constructor, pentru tergerea complet a obiectelor atunci cnd sunt
distruse (eliminate din memorie). O astfel de operaie este efectuat de o funcie
membr a clasei, numit funcie destructor. Numele destructorului unei clasei X este
~X() i este o funcie care nu primete nici un argument i nu returneaz nici o
valoare.
n implementarea din Exemplul 2.7 a stivei de numere ntregi, la ieirea din
funcia fd1(), obiectele stack1 i stack2 sunt eliminate din memorie, dar
vectorii corespunztori lor, alocai dinamic n memoria heap, trebuie s fie i ei teri,
pentru a nu ocupa n mod inutil memoria liber. Aceast operaie se poate executa n
funcia destructor astfel:
class DStack{
//.
public:
~DStack(){
if (pvect){
delete pvect;
pvect = NULL;
}
//..
};
52
2.3.3
Constructori de copiere
//
//
//
//
Constructor
Constructor
Constructor
afiseaza 4
initializare
copiere
copiere
5
2. Clase i obiecte
53
class Complex {
//..
public:
Complex(Complex &r);
};
Complex::Complex(Complex &r){
cout << Constructor copiere\n;
re = r.re;
im = r.im;
}
Cu totul alta este situaia n cazul n care un obiect conine date alocate
dinamic n memoria liber. Constructorul de copiere generat implicit de compilator
copiaz doar datele membre declarate n clas (membru cu membru) i nu tie s aloce
date dinamice pentru obiectul nou creat. Folosind un astfel de constructor, se ajunge la
situaia c dou obiecte, cel nou creat i obiectul referin, s conin pointeri cu
aceeai valoare, care indic spre aceeai zon din memorie. O astfel se situaie este o
surs puternic de erori de execuie subtile i greu de depistat. Exemplul urmtor
evideniaz aceast problem.
Exemplul 2.8
Se consider clasa DStack prezentat anterior i o funcie fd2() definit
astfel:
void fd2(){
DStack stack1(100);
DStack stack2(stack1);
stack1.push(11);
stack1.push(12);
stack2.push(21);
stack2.push(22);
cout << stack1.pop()<< ;
cout << stack1.pop() << endl;
}
// afiseaza 22
// afiseaza 21
54
Exemplul 2.9
Se definete constructorul de copiere al clasei DStack astfel:
DStack::DStack(DStack &r){
size = r.size;
tos = r.tos;
pvect = new int[size];
for (int i=0; i< size; i++)
pvect[i] = r.pvect[i];
}
//
//
//
//
afiseaza
afiseaza
afiseaza
afiseaza
12
11
21
11
2. Clase i obiecte
55
Exemplul 2.10
}
void fd4(){
DStack stack(100);
stack.push(55);
stack.push(66);
g(stack);
}
// afiseaza 66
// afiseaza 55
Exemplul 2.11
56
int tos;
public:
DStack(int s){
Cout << "Constructor initializare\n";
pvect = new int[s];
size = s;
tos = 0;
}
DStack(DStack &r);
~DStack();
void push(int x);
int pop();
};
DStack::DStack(DStack &r){
Cout << "Constructor copiere\n";
size = r.size;
tos = r.tos;
pvect = new int[size];
for (int i=0; i< size; i++)
pvect[i] = r.pvect[i];
}
DStack::~DStack(){
cout << "Destructor\n";
if (pvect){
delete pvect;
pvect = NULL;
}
}
DStack h(){
DStack stack(200);
return stack;
}
void fd5(){
h();
cout << "Revenire din h()\n";
}
2. Clase i obiecte
57
Dac a fost definit constructorul de copiere al clasei (ca mai sus), execuia este
corect. Dac se elimin acest constructor, apare eroare de execuie, datorit tentativei
de a terge a doua oar vectorul de numere, care este acelai pentru cele dou obiecte.
2.3.4
Conversia unei variabile ntre dou tipuri dintre care cel puin unul este un tip
definit de utilizator (clas) se poate face prin constructori sau prin suprancrcarea
operatorului de conversie. n aceast seciune se prezint cazul de conversie prin
constructori care este o conversie de la un tip de date predefinit la un tip definit de
utilizator (clas). Conversia prin suprancrcarea operatorilor este descris n
seciunea 4.
Un constructor cu un argument T al unei clase X folosete valoarea acestuia
pentru iniializarea obiectului de clas X construit. Acest mod de iniializare poate fi
privit ca o conversie de la tipul de date T, la tipul de date X. Dac T este un tip
predefinit, un astfel de constructor este definit simplu, ca membru nestatic al clasei :
class X{
public:
X(T t);
//
};
X::X(T t){
// initializare obiect X
};
creaz mai nti obiectul c2 de tip Complex, folosind constructorul implicit al clasei;
dup aceasta este creat un obiect temporar folosind constructorul cu un argument,
58
pentru conversia de la valoarea 9.3 de tip double i acest obiect este utilizat pentru
operaia de asignare al crui membru stng este obiectul c2.
Se poate remarca ineficiena unei astfel de seciuni de program. Mai mult,
asignarea ntre obiecte de tip definit de utilizator ridic aceleai probleme ca i
copierea prin constructorii de copiere: operaia de asignare predefinit pe care o
execut compilatorul este o asignare prin copiere membru cu membru a datelor. Se
poate intui c problemele care apar sunt aceleai ca i n cazul constructorilor de
copiere: pentru obiectele care conin date alocate dinamic n memoria liber, copierea
membru cu membru conduce la situaia c dou obiecte, cel asignat (membru stnga)
i cel din care se execut asignarea (membru dreapta) s conin pointeri cu aceeai
valoare, deci care indic spre aceeai zon de memorie. De aici, evident, apar
numeroase probleme.
Se poate observa acest comportament folosind conversii i asignri ale
obiectelor din clasa DStack:
DStack stack1 = 8;// corect,conversie prin constructor
DStack stack2;
// construire cu constructor implicit
stack2 = 7;
// conversie pentru obiect temporar,
// apoi asignare cu eroare de executie
2.4
Datele membre ale unei clase pot fi att variabile de tipuri predefinite ct i
obiecte de tipuri definite de utilizator. Membrii care sunt de tip definit de utilizator
(clas) trebuie s fie obiecte de tipuri (clase) definite mai nainte, nu doar declarate ca
nume. Dac o clas conine obiecte membre de tipuri definite de utilizator,
argumentele necesare pentru construirea acestora sunt plasate n definiia (nu n
declaraia) constructorului clasei care le conine. Fie urmtoarele definiii de clase i
funcii:
class X{
int *px;
public:
X();
X(int sx);
~X();
};
inline X::X(){
cout << "Constructor X implicit\n";
px = NULL;
}
inline X::X(int sx){
cout << "Constructor X cu 1 arg\n";
px = new int[sx];
}
2. Clase i obiecte
59
inline X::~X(){
cout << "Destructor X\n";
if (px){
delete px;
px = NULL;
}
}
class Y{
int *py;
X x;
// data membra de clasa X
public:
Y(int sy);
Y(int sx, int sy);
~Y();
};
inline Y::Y(int sy){
cout << "Constructor Y cu 1 arg\n";
py = new int[sy];
}
inline Y::Y(int sx, int sy):x(sx){
cout << "Constructor Y cu 2 arg\n";
py = new int[sy];
}
inline Y::~Y(){
cout << "Destructor Y\n");
if (py){
delete py;
py = NULL;
}
}
void fx(){
Y y1(7);
Y y2(4,5);
}
implicit
cu 1 arg
cu 1 arg
cu 2 arg
60
trece n orice ordine, separate prin virgul n definiia constructorului obiectului care le
conine. Constructorii obiectelor membre sunt apelai n ordinea n care acestea sunt
specificate n declaraia clasei. La distrugerea unui obiect, se execut mai nti
destructorul obiectului i apoi, n ordinea invers declaraiei, destructorii datelor
membre ale acestuia.
2.4.1
2.4.2
O clas local este o clas definit n interiorul unei funcii. O astfel de clas
este cunoscut numai n interiorul acelei funcii i este supus mai multor restricii:
toate funciile clasei locale trebuie s fie definite n interiorul clasei; clasele locale nu
admit variabile de tip static; clasele locale nu au acces la variabilele locale ale
funciei n care au fost declarate. Din cauza acestor restricii, clasele locale sunt rar
utilizate n programarea C++.
O clas imbricat (denumit i clas intern) este o clas definit n interiorul
altei clase. O astfel de clas este cunoscut numai n domeniul clasei n care a fost
definit, de aceea numele acesteia trebuie s fie calificat cu numele clasei care o
conine folosind operatorul de rezoluie (::). Utilizarea specific a claselor imbricate
este n mecanismele de tratare a excepiilor. Deoarece excepiile sunt definite pentru o
anumit clas, este mai normal ca tipul excepiei (definit ca o clas) s aparin clasei
care o definete. Exemple de clase imbricate folosite n tratarea excepiilor sunt
prezentate n seciunea 8.
2.5
2. Clase i obiecte
61
declararea unei funcii f() de tip friend a clasei X se include prototipul funciei
f(), precedat de specificatorul friend n definiia clasei X, iar funcia nsi se
definete n alt parte n program astfel:
class X{
//..
friend tip_returnat f(lista_argumente);
};
..
tip_returnat f(lista_argumente){
// corpul functiei
}
Exemplul 2.12
62
Vector::Vector(){
for (int i=0;i<3;i++)
v[i]=0.0;
v[3] = 1.0;
}
Vector::Vector(double *pv){
for (int i=0;i<4;i++)
v[i]= pv[i];
}
Dar acest mod de operare poate fi foarte ineficient, dac funciile de interfa
get() i set()ar verifica ncadrarea argumentului (indicele) n valorile admisibile.
Dac o astfel de verificare nu se face, alte funcii care le folosesc (n afar de funcia
multiply()) ar putea provoca erori de execuie.
Soluia pentru aceast problem o constitue declararea funciei multiply()
ca funcie friend n cele dou clase. Modificrile care se intoduc n cele dou clase
i n funcia multipy() arat astfel:
class Vector;
class Matrix {
//..
friend Vector multiply(const
const
};
class Vector {
//..
friend Vector multiply(const
const
};
Matrix &mat,
Vector &vect);
Matrix &mat,
Vector &vect);
2. Clase i obiecte
63
Este posibil ca o funcie membr a unei clase s fie declarat friend n alt
clas. De asemenea, este posibil ca o ntreag clas Y s fie declarat friend a unei
clase X i, n acest situaie, toate funciile membre ale clasei Y sunt funcii friend
ale clasei X i pot accesa toate datele i funciile membre ale acesteia. De exemplu:
class Y{
//.
};
class X{
friend class Y;
//.
};
2.6
64
constructor a clasei care prezint cea mai bun potrivire cu argumentele de apel.
Eliberarea memoriei ocupate de un obiect se realizeaz prin operatorul delete, care
apeleaz implicit destructorul clasei.
Exemple de alocri i eliberri pentru obiecte de tipurile Complex i
DStack:
Complex *pc0 = new Complex;
// apel constr. implicit
Complex *pc1 = new Complex(2);// apel constr. cu 1 arg
Complex *pc2 = new Complex(3.5,2.9); //apel constr. 2 arg
delete pc0;
// apel implicit destructor
delete pc1;
// apel implicit destructor
delete pc2;
// apel implicit destructor
DStack *pstack = new DStack(100); // creare stiv dim 100
pstack->push(5);
int x = pstack->pop();
delete pstack;
// apel implicit destructor
Exerciii
2. Clase i obiecte
65
E2.1 S se defineasc o clas Date pentru memorarea datei sub forma (zi, lun,
an). Clasa va conine atia constructori ct sunt necesari pentru urmtoarele definiii
de obiecte:
Date
Date
Date
Date
d1(15, 3, 99);
d2(20, 4);
d3(18);
d4;
//
//
//
//
zi, lun, an
zi, lun, an curent
zi, lun curent, an curent
zi curent,lun curent, an curent
Care sunt mesajele care apar la consol la execuia acestui program? S se explice
evoluia numrului de obiecte n via n cursul execuiei.
66
2. Clase i obiecte
67
";
*p++ = *p1++;
S se explice diferena i cauza erorii, dac aceasta apare la compilare sau la execuie.
E2.7 S se defineasc pentru clasa IntArray o funcie int InsertAt(int
x, int i) care s permit inserarea n vector a unui numr ntreg x n poziia dat
i, i o alt funcie int RemoveAt(int i) care s elimine un element din vector
de la poziia dat i. Folosind aceste funcii, s se insereze n vectorul creat mai sus un
element cu valoarea 0 ntre numerele 22 i 33 i s se elimine numrul 44 din vector.
S se afieze la consol vectorul rezultat.
E2.8 Pentru operaii asupra unui vector de ntregi de tipul IntArray se definete
o funcie fint() care are ca argument de apel un obiect din clasa IntArray, astfel:
void fint(IntArray a){
// corpul functiei
}
68
3
Implementarea modelelor de date
3.1
Liste liniare
O list liniar este o secven finit de elemente de un tip dat. Secvena n care
apar elementele este definitorie. De exemplu, lista (a1, a2,an ) este diferit de
lista (a2, a1,..an). De asemenea, n list este posibil s existe elemente cu acceai
valoare (elemente duplicat, ceea ce n mulimi nu este admis). Lungimea listei este
dat de numrul de elemente n list. Modelul de list include i lista vid, care are
zero elemente. Dac lista nu este vid, atunci ea conine un element de nceput, numit
capul listei (head), iar restul elementelor se numete coada listei (tail). Elementele
listei pot fi identificate prin poziia lor n list, un indice i, care poate avea valori
1 i n , unde n este lungimea listei. De multe ori (mai ales n implementrile n
limbajul C sau C++) elementele listei se indexeaz de la 0 la n-1.
O sublist este format dintr-o parte a elementelor unei liste, cu indici cuprini
ntre i i j, (ai, ai+1,aj) unde 1 i j n.
70
Operaiile care se pot executa asupra listelor sunt foarte variate: inserri,
extrageri, cutri, concatenri, divizri, sortri. Dintre acestea, primele trei operaii
sunt operaii fundamentale de tip dicionar:
3.1.1
Liste ordonate
71
3.1.2
Stive i cozi
Pe baza modelului de date list liniar sunt definite alte tipuri de date
abstracte, cum sunt stiva (stack) i coada (queue), prin restricionarea modului de
execuie a operaiilor asupra listei.
Stiva este o list n care toate operaiile (de inserare i de extragere) au loc
printr-un singur capt al listei, care se numete vrful stivei (top) (Fig. 3.1-a). Datorit
faptului c operaiile n stiv se execut printr-un singur capt, aceste operaii se
desfoar ntotdeauna n ordinea ultimul inserat, primul extras (last-in, first-out LIFO), de unde i numele LIFO sub care este frecvent ntlnit acest tip de date.
Coada este o list n care operaiile de inserare i extragere au loc prin capete
diferite ale listei. Operaia de inserare se execut la sfritul cozii (spate, rear), n timp
ce extragerea se execut din nceputul (fa, front) cozii (Fig. 3.1-b). Termenul de list
FIFO, frecvent folosit pentru coad, provine din acest mod de execuie: primul inserat,
primul extras (first-in, first-out, FIFO).
Vrf
Inserare
sau
extragere
Extragere
Partea
de jos
(a)- Stiv
Inserare
Fa
Spate
(b)- Coad
Fig. 3.1 Dou tipuri de liste: stiva (a) i coada (b)
72
3.1.3
= new T[d];
a0
a1
..
an-1
n-1
..
..
d-1
73
clasa template TStack vrful stivei este memorat printr-un pointer (top), dar aceast
diferen este nesemnificativ.
Cealalt posibilitate de implementare a listelor liniare, prin alocarea dinamic
a vectorului n memoria liber, cu o dimensiune care se stabilete n momentul
execuiei, a fost prezentat n Exemplul 2.7, prin clasa DStack pentru o stiv de
numere ntregi.
Ambele versiuni, folosind vectori de date de dimensiuni fixe, stabilite la
compilare sau la crearea dinamic a stivei, au dezavantajul inflexibilitii i al
consumului nejustificat de memorie. n plus, implementarea complet trebuie s
prevad tratarea situaiilor de eroare: depirea dimensiunii vectorului (la operaia de
inserare) sau element inexistent (la extragere). Tratarea unor astfel de situaii de eroare
prin definirea, lansarea i captarea excepiilor este prezentat n seciunea 8.
O variant mbuntit de implementare a unei liste prin vector alocat
dinamic n memorie este propus n Exerciiul 2.6, prin clasa IntArray, n care
dimensiunea vectorului crete atunci cnd se depete spaiul de memorare existent la
un moment dat cu o cantitate fix (grows) stabilit n definiia clasei. O astfel de
implementare este avantajoas din punct de vedere al spaiului de memorie ocupat, dar
este ineficient ca timp de execuie, dac au loc operaii frecvente de ajustare a
dimensiunii vectorului. Pentru creterea dimensiunii vectorului, se aloc un nou vector
n memorie, se copiaz tot coninutul precedent i se elibereaz vechiul vector.
Implementarea unei cozi se poate face folosind un vector de elemente, dar
pentru accesul la ambele capete ale listei, aa cum este necesar n structora FIFO, se
aranjeaz astfel ca vectorul s fie parcurs circular.
Vectorul de elemente are dimensiune p locaii posibile (elemente) i o
structur circular. Dou variabile memoreaz poziia (indicele) de scriere (writer)
i poziia de citire din vector (reader) (Fig. 3.3).
writer
..
d-2
d-1
reader
Fig 3.3 Implementarea unei cozi printr-un vector cu parcurgere circular
Iniial coada este goal, numrul de elemente ocupate n vector (n) este egal
cu zero, iar indicii de inserare i de extragere sunt de asemenea iniializai cu valoarea
zero. Un element se insereaz n vector n poziia (indicele) writer, dup care acesta
este incrementat circular: writer = (writer+1)%d; i de asemenea este
incrementat numrul n de elemente ale listei. Dac lista nu este vid (n > 0) se poate
extrage un element din vector din poziia reader, dup care acest indice este
incrementat circular: reader = (reader+1)%d; iar numrul de elemente n
74
este decrementat. Operaia de inserare este permis numai dac mai exist poziii
libere n vector (n < d). Operaia de extragere este permis numai dac exist cel
puin un element n vector (n > 0).
Listele, mpreun cu ale modele de date (cum sunt mulimile sau arborii) sunt
denumite i colecii de date, deoarece conin mai multe elemente de acelai tip, cu
anumite relaii ntre ele.
3.1.4
a1
a2
an-1
Primul nod
Ultimul nod
a0
a1
a2
an-1
75
76
77
78
IntSList::~IntSList(){
IntSListNode* p = first;
while (p){
IntSListNode* current = p;
p = p->link;
delete current;
}
}
last
obiect
IntSList
obiect
IntSListNode
Fig. 3.4 Obiect (list simpl nlnuit de numere ntregi) din clasa IntSetList
79
nod
nod
nod
nod
nod
v
v
v
v
v
=
=
=
=
=
1
2
3
4
5
80
Alte funcii membre ale clasei IntSList sunt funcii de aflare a elementelor
de nceput i de sfrit ale listei, GetHead() i GetTail() i funcia Lookup()
care returneaz numrul de elemente cu o valoare dat aflate n list. Definiiile
acestor funcii sunt lsate ca un exerciiu simplu.
Lista simplu nlnuit definit prin clasele IntSListNode i IntSList
se poate folosi pentru a implementa o stiv de numere ntregi. Funcia AddHead()
este echivalent operaiei push, iar funcia RemoveHead() este echivalent
operaiei pop. Funcia fslist2() evideniaz acest lucru:
void fslist2(){
IntSList list2;
list2.AddHead(1);
list2.AddHead(2);
list2.AddHead(3);
cout << list2.RemoveHead();
cout << list2.RemoveHead();
cout << list2.RemoveHead();
}
push
push
push
pop,
pop,
pop,
1
2
3
afiseaza 3
afiseaza 2
afiseaza 1
81
82
};
Ceilali constructori se pot implementa mai simplu folosind una din funciile
de inserare a unui element, AddHead() sau AddTail(). La inserarea unui element
ntr-o list dublu nlnuit trebuie s fie refcute legturile ntre nodul nou introdus i
nodurile existente pentru ambele direcii de nlnuire. n Fig. 3.5 sunt reprezentate
modificrile de pointeri la inserarea unui element nou n faa primului element al listei
(funcia AddHead()). Definiiile funciilor de inserare elemente AddHead() i
AddTail() sunt urmtoarele:
void IntDList::AddHead(int x){
IntDListNode* elem = new IntDListNode(x);
if (count == 0){
first = elem;
last = elem;
}
else{
first->prev = elem;
elem->next = first;
first = elem;
}
count++;
}
void IntDList::AddTail(int x){
IntDListNode* elem = new IntDListNode(x);
if (count==0){
first = elem;
last = elem;
}
83
else{
last->next = elem;
elem->prev = last;
last = elem;
}
count++;
nod
nod
nod
nod
nod
v
v
v
v
v
=
=
=
=
=
1
2
3
4
5
84
Avantajul principal al listei dublu nlnuite este acela c permite accesul la fel
de rapid din ambele capete ale listei, fiind prin aceasta mult mai eficient dect lista
simplu nlnuit. De exemplu, implementarea funciei RemoveTail() din clasa
IntSList (list simplu nlnuit) este excesiv de ineficient, datorit parcurgerii
ntregii liste pn la atingerea penultimului nod, al crui link trebuie trecut n 0. De
aceea, este puin probabil ca o list simplu nlnuit s fie folosit n programe care
necesit astfel de operaii.
Listele nlnuite sunt folosite n implementarea i a altor modele de date, cum
sunt arborii n-ari, mulimile, tabelele de dispersie (hash table), vectori asociativi.
n seciunile care urmeaz sunt date astfel de exemple de utilizare a listelor nlnuite.
Listele liniare (implementate prin tablouri sau liste nlnuite), ca i alte
modele de date (cum sunt arborii sau mulimile) reprezint colecii de obiecte de un
anumit tip. Exemplele prezentate pn acum au permis crearea unor colecii de obiecte
de un singur tip (numere ntregi). Pentru implementarea unor colecii de un tip de date
oarecare se pot folosi mai multe metode:
Elementul dintr-un nod al listei este un pointer generic (void*) care este
convertit n pointer la tipul de date al coleciei respective. Astfel de
exemple sunt propuse ca exerciii n aceast seciune (E3.6 i E3.7). Cu
ocazia acestor implementri se vor remarca diferitele complicaii i lipsa
eleganei n acest mod de generalizare a unei colecii de date.
85
3.2
Arbori
86
Un nod poate avea zero sau mai muli fii, dar orice nod, cu exepia rdcinii,
are un printe. Nodurile care sunt fiii aceluiai printe se numesc frai.
O alt definiie care se poate da arborelui este o definiie recursiv: un arbore
este format dintr-o mulime finit T de unul sau mai multe noduri, astfel nct:
Exist un nod special numit rdcin;
Toate celelate noduri cu excepia rdcinii sunt repartizate n m 0
mulimi disjuncte T1, T2,Tm, fiecare mulime fiind la rndul ei un
arbore.
n Fig. 3.6 este reprezentat grafic un arbore, conform acestei definiii
recursive.
r
T1
T2
Tm
Din definiia recursiv a arborelui rezult c fiecare nod al unui arbore este
rdcina unui subarbore. Numrul subarborilor unui nod se numete gradul nodului.
Nodurile de grad zero se numesc noduri terminale sau noduri frunz. Nodurile care nu
sunt terminale sau rdcin se numesc noduri interioare sau intermediare. Nivelul
unui nod se definete fa de rdcin: rdcina are nivelul egal cu 0; fiii rdcinii au
nivelul egal cu 1; n general, nivelul unui nod este cu o unitate mai mare dect nivelul
printelui su.
Un arbore etichetat (labeled) este un arbore n care oricrui nod i este
asociat o valoare (etichet). Eticheta unui nod este informaia asociat acestuia i
poate fi un simplu numr ntreg sau o informaie complex, de exemplu, un ntreg
document sau fiier.
Dac ordinea relativ a subarborilor T1, T2,Tm este important, se spune
c arborele este ordonat. Pentru un arbore ordonat cu m 2 , are sens s fie numit T1
primul subarbore, T2 al doilea subarbore, etc. Arborii ordonai se mai numesc i arbori
plani. Aceast ordonare a arborilor se face, n general, n funcie de etichetele
nodurilor, de aceea, pentru arborii ordonai, etichetele nodurilor trebuie s fie elemente
ale unei mulimi ordonabile liniar, la fel ca i n cazul listelor ordonate.
3.2.1
87
Reprezentarea arborilor
Arborii se pot reprezenta prin diferite tipuri de date, iar alegerea unuia dintre
ele depinde de cerinele programului de aplicaie. La modul general, fiecare nod se
reprezint prin dou cmpuri: un cmp care conine eticheta (informaia din nod) i un
cmp n care se memorez o list de pointeri (sau oricare alt fel de informaie de
legtur) ai tuturor nodurilor fii. La rndul ei, lista de pointeri poate fi reprezentat
ntr-unul din modurile obinuite de reprezentare a listelor: prin vectori sau liste
nlnuite.
Reprezentarea arborilor este posibil n orice limbaj procedural, prin definirea
structurilor compuse din cmpurile corespunztoare (eticheta nodului i pointerii la
nodurile fii) i a funciilor de prelucrare ale acestora.
n modelarea orientat pe obiecte a arborilor se pot folosi una sau mai multe
clase, n funcie de cerinele de prelucrare. n orice situaie va exista o clas care va
defini un nod al arborelui. Aceast clas are ca date membre eticheta nodului (sau un
pointer ctre aceasta) i lista pointerilor ctre nodurile fii, list reprezentat ca vector
sau list simplu sau dublu nlnuit. Arborele nsui poate fi indicat prin pointerul la
nodul rdcin, restul nodurilor nlnuindu-se corespunztor pointerilor din fiecare
nod. La fel ca i n cazul listelor nlnuite, o astfel de reprezentare simpl are unele
neajunsuri (de exemplu, nu se poate reprezenta un arbore vid) astfel c, de cele mai
multe ori, pentru modelarea unui arbore se mai definete nc o clas care menine
informaii despre arborele nsui (pointer la nodul rdcin, numr de noduri, criteriul
de ordonare a nodurilor, etc).
3.2.2
Arbori binari
Un arbore binar este un arbore n care fiecare nod are cel mult doi fii, numii
fiul din stnga i fiul din dreapta. Fiecare nod dintr-un arbore binar se reprezint de
obicei printr-un cmp al informaiei (eticheta) i doi pointeri ctre noduri, pointerul
ctre nodul fiu din stnga i pointerul ctre nodul fiu din dreapta. Dac unul sau
amndoi fiii unui nod lipsesc, pointerii corespunztori au valoarea zero.
Parcurgerea unui arbore binar se poate realiza n trei moduri: parcurgerea n
preordine, n inordine sau n postordine. Metodele de parcurgere sunt definite recursiv:
un arbore binar vid este parcurs fr s se ntreprind nimic; altfel, parcurgerea se
execut astfel:
Parcurgerea n preordine
Parcurgerea n inordine
Parcurgerea n postordine
Se viziteaz rdcina
Se viziteaz rdcina
Se viziteaz rdcina
88
Un arbore binar ordonat (sau arbore binar de cutare binary lookup tree) este
un arbore binar etichetat n care se respect urmtoarea proprietate pentru fiecare nod
n: etichetele tuturor nodurilor subarborelui stng al nodului n au valori mai mici dect
eticheta nodului n; etichetele tuturor nodurilor subarborelui drept al lui n sunt mai
mari dect eticheta nodului n.
Arborii binari ordonai se folosesc n implementarea coleciilor de date de tip
dicionar, n care se pot insera, se pot terge sau se pot cuta elementele dup valoarea
etichetei acestora. ntr-un arbore binar ordonat se definesc aceste operaii de dicionar
astfel ca dup fiecare operaie de inserare sau de tergere, arborele s pstreze
proprietatea de arbore binar ordonat. Algoritmii generali ai acestor operaii se gsesc
n bibliografia indicat [Aho], [Knuth], [Horowitz]. n continuare este prezentat
implementarea prin clase a unui arbore binar ordonat de numere ntregi.
3.2.3
Pentru implementarea unui arbore binar ordonat se definesc dou clase: clasa
IntTreeNode, care reprezint un nod n arbore i clasa IntTree, care reprezint
arborele nsui. Informaia (eticheta) din fiecare nod este un numr ntreg, (int d;)
n clasa IntTreeNode. Aceste dou clase, IntTreeNode i IntTree sunt
definite astfel:
class IntTreeNode{
int d;
// informatia din nod (eticheta)
IntTreeNode *left, *right;
friend class IntTree;
public:
IntTreeNode(int x) { d = x; left = right = NULL; }
~IntTreeNode() { left = right = NULL; }
};
class IntTree{
IntTreeNode *root;
int count;
IntTreeNode *insert1(IntTreeNode *root,
IntTreeNode *r, int data);
void inorder1(IntTreeNode *root);
void preorder1(IntTreeNode *root);
void postorder1(IntTreeNode *root);
void delTree1(IntTreeNode *root);
public:
IntTree() { root = NULL; count = 0;}
int getcount() {return count;}
void insert(int data);
int lookup(int data);
void remove(int data);
void inorder();
void preorder();
void postorder();
~IntTree();
};
89
90
inorder1(root->left);
cout << root->d<<" ";
inorder1(root->right);
}
void IntTree::inorder(){
inorder1(root);
}
3.3
Mulimi
Cea mai obinuit definiie a unei mulimii (set) este aceea prin a specifica
dac un obiect aparine sau nu mulimii date, adic:
x S, nseamn c x aparine mulimii S.
Dac x1, x2,xn sunt toate elementele unei mulimi S, se poate scrie:
S = { x1, x2,xn }. Acest mod de a defini o mulime prin
elementele ei componente nu nseamn c elementele mulimii sunt
ordonate, adic putem scrie i S = { x2, x1,xn }.
Nu exist elemente duplicat ntr-o mulime.
Aceste particulariti deosebesc mulimile de liste, n care ordinea elementelor
este esenial i pot exista elemente duplicat.
Pentru reprezentarea n programe a mulimilor se utilizeaz diferite tipuri de
colecii de date (vectori, liste nlnuite, arbori).
91
92
93
}
return 0;
// nu este gasit
3.3.2 Iteratori
Pentru parcurgerea elementelor unei colecii de date pentru a fi utilizate (de
exemplu, afiate la consol) se definete un mecanism care itereaz colecia n ordinea
dorit. Cel mai simplu mod de a defini un iterator al unei colecii este prin intermediul
unor funcii publice ale clasei care definete colecia, care stabilesc punctul de pornire
al iteraiei i ordinea n care sunt parcurse elementele coleciei. Un astfel de mecanism
ascunde utilizatorului modul de organizare intern a datelor n clase i poate fi utilizat
chiar dac se modific modul de implementare al coleciei, de exemplu dac se
folosete o list nlnuit n locul unui vector.
Un iterator simplu pentru mulimea definit prin clasa IntSet se
implementeaz prin intermediul a trei funcii publice: funcia start() care
stabilete punctul de pornire a parcurgerii, funcia next() care poziioneaz
iteratorul pe elementul urmtor i funcia inside() care returneaz 0 dac poziia de
iterare a depit dimensiunea mulimii.
Pentru testarea mulimilor reprezentate prin clasa IntSet se genereaz n
mod aleator o secven de numere ntregi folosind funcia rand() din biblioteca
standard stdlib i se insereaz ntr-o mulime. Coninutul mulimii se afieaz n
ordine cresctoare folosind funciile de iteraie ale clasei i o variabil local pos, prin
care se controleaz parcurgerea mulimii:
void fset1(){
IntSet set(100);
int n = 10;
for(int i=0; i<n ;i++)
set.insert(rand()%100);
int position;
// variabil de control iteraie
set.start(position);
while(set.inside(position))
cout << set.next(position) << " ";
cout<<endl; //afiseaza 0 24 34 41 58 62 64 67 69 78
94
public:
SetIter(IntSet* p){s = p; pos = 0;}
void start() {pos = 0;}
int inside() {return pos < s->count;}
int next() {return s->set[pos++];}
};
set.remove(0);
iter.start();
while(iter.inside())
cout << iter.next() << " ";
cout<<endl;
//afiseaza 24 34 41 58 62 64 67 69 78
95
Exerciii
E3.1 Se implementeaz o list simplu nlnuit de numere ntregi prin intermediul
unei singure clase, clasa IntNode, definit astfel:
class IntNode{
int v;
IntNode* link;
public:
IntNode(int x) {v = x; link = NULL; }
~IntNode();
IntNode* AddHead(int x);
int GetHead(){return v;}
IntNode *RemoveHead();
void Display();
};
96
3 2 1
3
nod
2 1
E3.7 Pentru implementarea unui arbore binar ordonat pentru orice tip de date,
informaia (eticheta) din fiecare nod se reprezint printr-un pointer generic (pointer
void*). Relaia de preceden pentru compararea etichetelor nodurilor este definit
printr-o funcie al crei pointer este transmis ca argument la construcia arborelui i
memorat ca dat membr n clasa PointTree (pointerul COMPARE). De asemenea,
funcia care se execut la vizitarea unui nod este transmis printr-un pointer ca
argument la construcia arborelui i memorat n clasa PointTree (pointerul
EXECUTE). Astfel de funcii precizate de utilizator printr-un pointer i apelate n
derularea unui program se numesc funcii callback.
Tipul de date al informaiei din nodurile arborelui poate fi un tip predefinit
sau, aa cum este n implementarea descris n continuare, un tip definit de utilizator.
Exemplificarea este dat pentru o clas de obiecte, clasa info, care poate fi nlocuit
cu orice clas n aplicaia dorit. Aceste trei clase, clasa info, clasa PointNode i
clasa PointTree pot fi definite astfel:
97
98
99
100
root->PostOrder();
delete root;
public:
//..
};
IntSet result(d);
set1.reunion(set2, result);
SetIter iter3(&result);
while(iter3.inside())
cout << iter3.next() << "
cout << endl;
";
";
4
Suprancrcarea operatorilor
delete
() []
*
/
=
<
>
&=
|=
<<
>=
&&
||
%
+=
>>
++
^
-=
>>=
--
&
*=
<<=
,
|
/=
==
->*
~
%=
!=
->
.* :: ?: sizeof
102
Funciile operator pentru o anumit clas pot s fie sau nu funcii membre ale
clasei. Dac nu sunt funcii membre ele sunt, totui, funcii friend ale clasei i
trebuie s aib ca argument cel puin un obiect din clasa respectiv sau o referin la
aceasta. Excepie fac operatorii =, (), [], ->, care nu pot fi suprancrcai folosind
funcii friend ale clasei. De asemenea, funciile operator new() i operator
delete()au implementri mai deosebite care vor fi detaliate mai jos.
4.1
Exemplul 4.1
Fie o clas Point care descrie un vector ntr-un plan bidimensional prin
dou numere de tip double, x i y. Valorile x i y reprezint coordonatele punctului
de extremitate al vectorului. Pentru aceast clas se pot defini mai multe operaii cu
vectori, ca de exemplu:
Suma a doi vectori
Diferena a doi vectori
Produsul scalar a doi vectori
Multiplicarea unui vector cu o constant (scalare)
Incrementarea/decrementarea componentelor vectorului
Oglindire (negarea fiecrei componente).
Aceste operaii se pot implementa prin suprancrcarea corespunztoare a
operatorilor. n continuare se vor defini: funcia operator+(Point) pentru
calculul sumei a doi vectori, funcia operator(Point) pentru calculul
diferenei a doi vectori, funcia operator*(Point) pentru calculul produsului
scalar a doi vectori i funcia operator*(double) pentru calculul multiplicrii
unui vector cu o constant.
class Point{
double x;
double y;
public:
Point(){ x = 0; y = 0;}
Point(double a, double b){x = a; y = b;}
void display() {
cout << x << " " << y << endl;
}
4. Suprancrcarea operatorilor
Point operator+(Point op2);
Point operator-(Point op2);
double operator*(Point op2);
Point& operator*(double v);
103
//
//
//
//
};
Point Point::operator+(Point op2){
Point temp;
temp.x = x + op2.x;
temp.y = y + op2.y;
return temp;
}
Point Point::operator-(Point op2){
point temp;
temp.x = x - op2.x;
temp.y = y - op2.y;
return temp;
}
double Point::operator*(Point op2){
return x*op2.y + y*op2.x;
}
Point& Point::operator*(double v){//!!modific operandul
x *= v;
y *= v;
return *this;
}
void f1(){
Punct pct1(10,20);
Punct pct2(30,40);
Punct pct3;
pct1.display();
// afiseaza 10 20
pct2.display();
// afiseaza 30 40
pct3 = pct1 + pct2;
pct3.display();
// afiseaza 40 60
pct3 = pct2 pct1;
pct3.display();
// afiseaza 20 20
}
i chiar poate fi apelat astfel. Acest lucru nseamn c obiectul din stnga
operatorului este cel pentru care se apeleaz funcia operator, care are acces la acesta
prin pointerul this transmis implicit funciei operator membr a clasei. Pentru
funcia operator+() ordinea operanzilor nu are importan, dar aceast convenie
de apel este important pentru alte funcii, ca de exemplu funcia operator-().
104
public:
Point
Point
Point
Point
Point
};
operator~();
operator++();
operator--();
operator++(int a);
operator--(int a);
4. Suprancrcarea operatorilor
105
Point operator~(){
// oglindire
Point temp;
temp.x = -x;
temp.y = -y;
return temp;
}
Point Point::operator++(){
// incrementare prefix
++x;
++y;
return *this;
}
Point Point::operator--(){
// decrementare prefix
--x;
--y;
return *this;
}
Point Point::operator++(int a){ // incrementare postfix
Point temp(*this);
x++;
y++;
return temp;
}
Point Point::operator--(int a){ // decrementare postfix
Point temp(*this);
x--;
y--;
return temp;
}
4.2
106
friend
friend
friend
friend
friend
friend
Point
Point
Point
Point
Point
Point
};
Point operator+(Point op1, Point op2){
Point temp;
temp.x = op1.x + op2.x;
temp.y = op1.y + op2.y;
return temp;
}
// incrementare prefix
// decrementare prefix
void f2(){
Punct pct1(10,20);
Punct pct2(30,40);
Punct pct3;
pct1.display();
pct3 = pct1 + pct2;
pct3.display();
pct3 = pct2 pct1;
pct3.display();
pct1++;
pct1.display();
}
// afiseaza 10 20
// afiseaza 40 60
// afiseaza 20 20
// afiseaza 11 21
4. Suprancrcarea operatorilor
107
4.3
Exist mai muli operatori de asignare: =, *=, /=, %=, +=, -=,
>>=, <<=, &=, ^=, |=, dintre care primul este operatorul simplu de asignare,
ceilali fiind combinaii cu ali operatori.
n asignarea simpl (=), valoarea expresiei care reprezint operandul dreapta
nlocuiete valoarea operandului stnga. Dac ambii operanzi sunt de tip aritmetic,
operandul dreapta este convertit la tipul operandului stnga, dup care are loc
atribuirea valorii. Nu exist o conversie implicit la tipul enumerare, astfel nct, dac
operandul stnga este o enumerare, cel din dreapta trebuie s fie de acelai tip.
Pentru tipurile definite de utilizator, funcia operator de asignare
(operator=()) trebuie s fie o funcie membr nestatic a clasei i nu poate fi
motenit. n multe privine, funcia operator=() seamn mai mult cu
108
Exemplul 4.3
Clasa String implementeaz un ir de caractere folosind constante de tip ir:
#include <string.h>
#include <iostream.h>
class String{
char *str;
int size;
public:
String();
String(const char *p);
String(const String& r);
~String();
friend ostream& operator <<(ostream &stream,
const String &r);
friend istream& operator >> (istream &stream,
String &r);
4. Suprancrcarea operatorilor
};
109
110
stream.get(buf,256);
r = buf;
return stream;
// constructor initializare
// constructor initializare
// constructor copiere
// operator=(String&)
// operator=(char*)
4. Suprancrcarea operatorilor
111
Constructor initializare
abcd
Constructor copiere
123456
Operator=(String&)
abcd
Operator=(char*)
mnp
Destructor
Destructor
Destructor
are ca efect folosirea operatorului implicit de asignare, care copiaz n variabila str a
obiectului str3 valoarea pointerului str din obiectul str2, deci ambii pointeri
indic acelai ir de caractere abcd: str2.str = str3.str. Eroarea se
evideniaz la ieirea din funcia f3(): eliminarea obiectului str3 produce
tergerea irului de caractere abcd, acelai pe care ncearc s-l tearg apoi i
destructorul apelat pentru obiectul str2. Aceast operaie de tergere a unor date care
au fost deja terse din memorie provoac execuia anormal a programului i la
consol va aprea un mesaj de eroare.
Mai sunt posibile i alte erori de execuie provenite din aceast asignare
eronat, care pot fi studiate n exerciiile propuse. Tot ca exerciii se vor studia i alte
situaii care evideniaz comportamentul funciilor operator de asignare.
4.4
112
this) este transmis implicit funciei operator de asignare care este membr nestatic
a clasei.
n clasa String se poate aduga funcia operator[](int i), care
returneaz referina la caracterul din poziia i a irului de caractere str coninut de
un obiect String:
char& String::operator[](int i){
return str[i];
}
Se poate remarca faptul c aceast implementare este cea mai simpl posibil,
dar pot aprea erori de execuie atunci cnd se execut indexarea pentru valori ale
argumentului care depesc dimensiunea irului de caractere. Modul cum se trateaz
astfel de erori n C++ este prezentat n seciunea 8.
Exemplul 4.4
Fie funcia f4():
void f4(){
char v[] = "123456789";
String string(v);
int s = strlen(v);
for(int i=0;i<s;i++)
cout << string[i];
cout << endl;
for(i=0;i<s;i++){
string[i] = 65 + i;
cout << string[i];
}
cout << endl;
}
4.5
4. Suprancrcarea operatorilor
113
Exemplul 4.5
114
p1 = new Point(7);
p1->display();
delete p1;
p1=new Point(5,8);
delete p1;
}
Din exemplul prezentat mai sus se poate observa c la construcia unui tablou
de obiecte de tip Point, (new Point[7]) este folosit operatorul new predefinit
(global) i nu funcia operator new a clasei Point. La fel, la tergerea tabloului se
folosete operatorul global delete [].
Aceast situaie apare datorit faptului c, pentru alocarea dinamic a
tablourilor de obiecte folosind operatori suprancrcai, trebuie s fie suprancrcai
operatorii new i delete pentru tablouri de obiecte. Funciile operator de alocare i
de tergere pentru tablouri de obiecte arat astfel:
void* operator new[](size_t lungime);
void operator delete[](void* p);
4. Suprancrcarea operatorilor
4.6
115
// afiseaza 11 22
// apel funcie afiseaz Operator()
// afiseaza 4 5
116
4.7
unde obiect este o instan a unei clase oarecare X pentru care s-a suprancrcat
operatorul pointer. Evaluarea pentru expresie se execut n funcie de tipul de dat
returnat de funcia suprancrcat a clasei X operator ->()astfel:
Dac funcia operator->() a unei clase X returneaz un obiect de
tipul X, atunci se acceseaz elementul corespunztor (obinut prin
evaluarea expresie) a clasei X.
Dac funcia operator->() a unei clase Y returneaz un pointer la o
dat de un tip oarecare, atunci se aplic operatorul -> predefinit, adic se
selecteaz o component a obiectului ctre care indic pointerul returnat.
Exemplul 4.6
4. Suprancrcarea operatorilor
117
};
void f7(){
X obX;
obX->x = 10;
cout << obX->x <<"
Y obY("test.txt");
obY->display();
}
//10 10
4.8
118
4.8.1
Pentru conversia unui tip definit de utilizator (clas) ntr-un tip predefinit se
poate defini o funcie operator membr nestatic a clasei respective. O astfel de funcie
nu are nici un argument, dat fiind c operatorul de conversie este un operator unar
(folosete numai obiectul pentru care a fost apelat, al crui pointer this l primete
implicit) i nu specific nici o valoare de returnare, deoarece returneaz implicit
valoarea obiectului convertit la tipul pentru care este definit conversia.
Pentru o clas dat se pot suprancrca mai muli operatori de conversie de la
clasa respectiv la unul din tipurile predefinite. Conversia definit prin suprancrcarea
operatorului de conversie poate fi apelat explicit sau implicit. Apelul explicit al
operatorului de conversie pentru un obiect din clasa X ctre tipul predefinit T poate
avea dou forme:
T(obiect) sau (T)obiect
Conversia implicit (deci apelul implicit al funciei operator de conversie) are loc la
utilizarea unui obiect dintr-o clas n care s-a definit o astfel de funcie ntr-o expresie
aritmetic sau condiional. Dac sunt definite mai multe funcii operator de conversie,
pot s apar ambiguiti n selectarea uneia dintre acestea n cazul conversiei implicite.
Cteva situaii de conversie sunt prezentate n exemplul urmtor.
Exemplul 4.7
4. Suprancrcarea operatorilor
119
4.8.2
Exemplul 4.8
120
};
void f9(){
Complex c1(2.3, 9.7);
Point p1(c1);
// conversie prin constructor
Point p2 = c1;
// alternativa de apel
cout << p1;
// afiseaza 2.3 9.7
}
Conversia dintr-un tip definit de utilizator ntr-un alt tip definit de utilizator se
poate realiza i prin suprancrcarea operatorului de conversie. Pentru conversia
obiectelor de tip X n obiecte de tip Y, n clasa X se definete funcia operator de
conversie:
X::operator Y();
Pentru ca aceast funcie membr nestatic a clasei X s aib acces la date
private sau protejate ale clasei Y, ea se declar funcie friend n clasa Y.
n exemplul urmtor se reia operaia de conversie din clasa Complex n clasa
Point prin suprancrcarea operatorului de conversie n clasa Complex.
Exemplul 4.9
class Point;
class Complex{
double re;
double im;
public:
Complex(){re = 0; im = 0;}
Complex(double r, double i) {re = r; im = i;}
operator Point();
};
class Point {
//..
public:
friend Complex::operator Point();
};
Complex::operator Point(){
Point tmp;
tmp.x = re;
tmp.y = im;
return tmp;
}
void f10(){
Complex c1(4,7);
Point p1;
p1 = c1;
cout << p1;
// afiseaza 4 7
}
4. Suprancrcarea operatorilor
121
Exerciii
E4.1 Pentru clasa Date definit n E2.1 s se suprancarce funcia operator <<
pentru afiarea datei sub forma: Ziua: zz Luna: ll An: aaaa. S se
afieze la consol cele patru date memorate n obiectele d1, d2, d3 i d4 definite n
E2.1.
E4.2 S se defineasc o funcie operator++() pentru clasa Date care s
incrementeze ziua memorat, cu trecerea corect la luna sau anul urmtor, dac este
cazul. Ce mesaje se afieaz la consol la execuia instruciunilor?
++d3;
cout << d3;
E4.3 S se suprancarce operatorul de indexare al clasei Date, astfel nct la
indicele 0 s corespund variabila zi, la indicele 1, s corespund luna, iar la indicele 2
sa corespund anul. S se atribuie variabilei lun din obiectul d2, valoarea variabilei
corespunztoare din obiectul d1 i s se afieze rezultatul la consol.
E4.4 Pentru clasa Point, s se defineasc urmtoarele operaii folosind
suprancrcarea unor operatori:
Rotaia vectorului cu un unghi u.
Calculul lungimii vectorului.
E4.5
122
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
==(const
==(const
==(const
!=(const
!=(const
!=(const
<(const
<(const
<(const
>(const
>(const
>(const
<=(const
<=(const
<=(const
>=(const
>=(const
>=(const
4. Suprancrcarea operatorilor
int
int
int
int
int
123
//
//
*GetBuffer(); //
MakeUpper(); //
MakeLower(); //
MakeReverse();//
124
5
Clase derivate. Moteniri
5.1
Clase derivate
Angajat::display(){
cout << nume << << salariu << endl;
}
126
Posibilitatea de a include ntr-o clas date descrise ntr-o alt clas are n
limbajele orientate pe obiecte un suport mai eficient i mai simplu de utilizat dect
includerea unui obiect din tipul dorit: derivarea claselor, care motenesc (date i
funcii membre) de la clasa de baz
Un administrator este un angajat, de aceea clasa Administrator
se poate construi prin derivare din clasa Angajat astfel:
class Administrator : public Angajat {
int sectie;
public:
void display();
}
Angajat
clasa derivat
Administrator
Clasa de baz mai este denumit uneori superclas, iar clasa derivat,
subclas. Aceast terminologie este ns confuz, deoarece s-ar putea crede c un
obiect dintr-o clas derivat este un subobiect al clasei de baz, cnd, de fapt, lucrurile
stau invers: un obiect din clasa derivat conine ca subobiect un obiect al clasei sale de
baz. n cele ce urmeaz, se va folosi terminologia de clas de baz-clas derivat,
care de altfel, este recomandat i de Stroustrup. n general, derivarea unei clase se
specific n felul urmtor:
class nume_derivata : specificator_acces nume_baza {
// corpul clasei
};
127
derivat motenete toi membrii clasei de baz (cu excepia unora: constructori,
destructor i funcia operator de asignare). Tipul de acces din clasa derivat la membrii
clasei de baz este dat de specificatorul de acces. Dac nu este indicat, specificatorul
de acces este implicit private.
Cnd specificatorul de acces este public, toi membrii de tip public ai
clasei de baz devin membri de tip public ai clasei derivate; toi membrii
protected ai clasei de baz devin membri protected ai clasei derivate. Membrii
private ai clasei de baz rmn private n clasa de baz i nu sunt accesibili
membrilor clasei derivate. Aceast restricie de acces ar putea pare surprinztoare, dar,
dac s-ar permite accesul dintr-o clas derivat la membrii private ai clasei de
baz, noiunile de ncapsulare i ascundere a datelor nu ar mai avea nici o semnificaie.
ntr-adevr, prin simpla derivare a unei clase, ar putea fi accesai toi membrii clasei
respective.
Metoda cea mai adecvat de acces la membrii private clasei de baz din
clasa derivat este prin utilizarea funciilor membre publice ale clasei de baz. De
exemplu, nu se poate implementa funcia display() din clasa Administrator
prin accesarea membrilor private ai clasei Angajat:
void Administrator::display(){
cout << nume << << salariu << endl;
cout << sectie << endl;
}
// eroare
128
class Derived {
public:
int b, c;
};
void fb() {
Derived d;
d.a = 1;
d.Base::b = 2;
d.b = 3;
d.c = 4;
}
// b este redefinit
//
//
//
//
a
b
b
c
din
din
din
din
Base
Base
Derived
Derived
Personal { /* */ };
Angajat : public Personal{ /* */ };
Administrator : public Angajat { /* */ };
Director : public Administrator { /* */ };
129
Temporar
Angajat
Secretara
Consultant
Administrator
Director
Clasa (nodul) din vrful ierarhiei reprezint categoria cea mai general a
ierarhiei, iar toate celelalte motenesc, direct sau indirect, din aceast clas de baz,
fiind clase mai specializate. Astfel de ierarhii sunt ntlnite n toate ramurile cunoterii
tiinifice, rezultate n urma clasificrii cunotinelor acumulate de-a lungul timpului.
Prin intermediul claselor derivate, programarea orientat pe obiecte n
limbajul C++ asigur suport pentru motenire, care reprezint unul din principiile de
baz ale modelului obiect.
130
Exemplul 5.1
class Angajat {
char *nume;
float salariu;
public:
Angajat(const char *n, float sal);
Angajat(Angajat& r);
~Angajat() {
cout << "Destructor Baza" << endl;
delete nume;
}
void display() const {
cout << "Nume: " << nume
<< " Salariu: " << salariu << endl ;
}
};
Angajat::Angajat(const char *n, float sal)
{
cout << "Constructor baza" << endl;
int size = strlen(n);
nume = new char[size+1];
strcpy(nume, n);
salariu = sal;
}
131
Angajat::Angajat(Angajat &r){
cout << "Constructor copiere baza\n";
int size = strlen(r.nume);
nume = new char[size+1];
strcpy(nume, r.nume);
salariu = r.salariu;
}
Administrator::Administrator(Administrator& r)
:Angajat(r){
cout << "Constructor copiere derivata\n";
sectie = r.sectie;
}
void fa1(){
Angajat a1("Ionescu", 2000.0f);
a1.display();
// execut Angajat::display()
Administrator m1("Popescu", 3000.0f, 1);
m1.display();
// execut Administrator::display()
}
n funcia fa1() sunt create dou obiecte, obiectul a1 din clasa Angajat i
obiectul m1 din clasa Administrator. Dou din argumentele de apel ale
constructorului obiectului m1 sunt transferate constructorului clasei de baz
(argumentele const char* n i float sal). La execuia funciei fa1() sunt
afiate mesajele:
Constructor baza
Nume: Ionescu Salariu: 2000
Constructor baza
Constructor derivata
Nume: Popescu Salariu: 3000
Sef sectie: 1
Destructor derivata
Destructor baza
Destructor baza
132
Obiectul m2 este creat printr-un constructor de copiere: este apelat mai nti
constructorul de copiere al clasei de baz, apoi constructorul de copiere al clasei
derivate. Mesajele afiate la execuia funciei fa2() sunt urmtoarele:
Constructor baza
Constructor derivata
Constructor copiere baza
Constructor copiere derivata
Nume: Popescu Salariu: 3000
Sef sectie: 1
Destructor derivata
Destructor baza
Destructor derivata
Destructor baza
Aceasta este situaia de execuie corect, deoarece au fost definii corect
constructorii de copiere n clasa de baz i n clasa derivat.
O situaie de execuie eronat apare dac nu se definete constructorul de
copiere n clasa de baz i n aceast clas exist date alocate dinamic. Pentru
exemplul dat ns, dac se definete corect constructorul de copiere al clasei de baz
Angajat, dar nu se definete constructor de copiere n clasa derivat
Administrator, execuia este totui corect, deoarece constructorul de copiere
implicit creat de compilator pentru clasa derivat Administrator apeleaz
constructorul de copiere definit al clasei de baz Angajat care previne copierea
membru cu membru.
Pe exemplul de mai sus se pot verifica toate aceste situaii diferite care pot s
apar n legtur cu definirea constructorilor de copiere.
133
5.2
5.2.1
Dac specificatorul de acces din declaraia unei clase derivate este public,
atunci:
Datele de tip public ale clasei de baz sunt motenite ca date de tip
public n clasa derivat i deci pot fi accesate din orice punct al
domeniului de definiie al clasei derivate.
Datele de tip protected n clasa de baz sunt motenite protected
n clasa derivat, deci pot fi accesate numai de funciile membre i
friend ale clasei derivate.
Exemplul 5.2
134
protected:
int b;
public:
int c;
void seta(int x){a = x; cout << "seta din baza\n";}
void setb(int y){b = y; cout << "setb din baza\n";}
void setc(int z){c = z; cout << "setc din baza\n";}
};
class Derived : public Base {
int d;
public:
void seta(int x) {
a = x;
// eroare, a este private
}
void setb(int y) {
b = y;
cout << "setb din derivata\n";
}
void setc(int z) {
c = z;
cout << "setc din derivata\n";
}
};
void fb(){
Derived obd;
obd.a = 1;
// eroare, a este private in baza
obd.seta(2);
// corect, se apeleaz baza::seta
obd.b = 3;
// eroare, b este protected
obd.Base::setb(5);// corect, Base::setb este public
obd.setb(4);
// corect, Derived::setb este public
obd.c = 6;
// corect, c este public
obd.Base::setc(7);// corect, Base::setc este public
obd.setc(8);
// corect, Derived::setc este public
}
5.2.2
din
din
din
din
din
baza
baza
derivata
baza
derivata
135
Dac se comenteaz toate liniile din funcia fb() care produc erori, la
execuia acesteia se afieaz urmtoarele rezultate:
setb din derivata
setc din derivata
5.2.3
136
o nou clas derivat (care motenete indirect clasa de baz) nu va mai putea accesa
nici unul din membrii clasei de baz.
5.2.4
5.3
Motenirea multipl
Aa cum s-a artat n seciunea 5.1, o clas poate avea mai multe clase de baz
directe, dac acestea sunt specificate n declaraia clasei. n exemplul urmtor este
prezentat clasa Derived care motenete dou clase de baz, Base1 i Base2.
Exemplul 5.3
Se definesc clasele Base1, Base2 i Derived astfel:
137
class Base1 {
protected:
int x;
public:
Base1(int i) {
cout << "Constructor baza 1\n";
x = i;
}
~Base1() { cout <<"Destructor baza 1\n"; }
int getx(){return x;}
};
class Base2{
protected:
int y;
public:
Base2(int j){
cout << "Constructor baza 2\n";
y = j;
}
int gety() { return y;}
~Base2() { cout <<"Destructor baza 2\n"; }
};
class Derived : public Base1, public Base2 {
int d;
public:
Derived (int i, int j);
~Derived(){ cout << "Destructor derivata\n"; }
};
Derived::Derived(int i, int j): Base1(i), Base2(j){
cout << "Constructor derivata\n";
}
void fbm(){
Derived obd(3,4);
cout << obd.getx() << " "<< obd.gety() << endl;
}
138
5.3.1
L
A
B
D
{
:
:
:
*/};
B
D
Un obiect din clasa D va conine membrii clasei L de dou ori, o dat prin
clasa A (A::L) i o dat prin clasa B (B::L). Se pot reprezenta prile distincte ale
unui astfel de obiect:
Partea L (din A)
Partea A
Partea L (din B)
Partea B
Partea D
139
// corect, x din A
// corect, x din B
L
A
B
D
{
:
:
:
public: int x; };
virtual public L { /*
virtual public L { /*
public A, public B { /*
*/ };
*/ };
*/ };
B
D
O clas poate avea att clase de baz virtuale ct i nevirtuale, chiar de acelai
tip. De exemplu, n declaraiile:
class
class
class
class
class
L
A
B
C
D
{
:
:
:
:
public: int x; };
virtual public L { /*
*/ };
virtual public L { /*
*/ };
public L { /*
*/ };
public A, public B, public C { /*
*/ };
clasa D motenete indirect clasa L: de dou ori ca o clas de baz virtual prin
motenirea din clasele A i B i nc o dat direct, prin motenirea clasei C.
Reprezentarea grafic a unei astfel de moteniri este urmtoarea:
L
B
D
140
Un obiect din clasa D va conine dou copii ale clasei L: o singur copie prin
motenirea de tip virtual prin intermediul claselor A i B i o alt copie prin
motenirea clasei C. Ambiguitile care pot s apar n astfel de situaii se rezolv prin
calificarea membrilor cu numele clasei din care fac parte, folosind operatorul de
rezoluie.
5.4
O funcie virtual este o funcie care este declarat de tip virtual n clasa
de baz i redefinit ntr-o clas derivat. Redefinirea unei funcii virtuale ntr-o clas
derivat domin (override) definiia funciei n clasa de baz. Funcia declarat
virtual n clasa de baz acioneaz ca o descriere generic prin care se definete
interfaa comun, iar funciile redefinite n clasele derivate precizeaz aciunile
specifice fiecrei clase derivate.
Mecanismul de virtualitate asigur selecia (dominarea) funciei redefinite n
clasa derivat numai la apelul funciei pentru un obiect cunoscut printr-un pointer. n
apelul ca funcie membr a unui obiect dat cu numele lui, funciile virtuale se
comport normal, ca funcii redefinite.
Deoarece mecanismul de virtualitate se manifest numai n cazul apelului
funciilor prin intermediul pointerilor se vor preciza mai nti aspectele privind
conversiile de pointeri ntre clasele de baz i clasele derivate.
5.4.1
Exemplul 5.4
class B { /*
*/ };
class D:public B { /*
void main(){
D d;
B* pb = &d;
//
B ob;
D* pd = &ob;
//
//
//
D* pd =(D*)&ob;
//
}
//
*/ };
corect, conversie implicita
eroare la compilare, nu se
poate converti implicit de la
class B* la class D1*
se compileaza corect, dar
rezultatul este nedeterminat
141
5.4.2
Funcii virtuale
Atunci cnd o funcie normal (care nu este virtual) este definit ntr-o clas
de baz i redefinit n clasele derivate, la apelul acesteia ca funcie membr a unui
obiect pentru care se cunoate un pointer, se selecteaz funcia dup tipul pointerului,
indiferent de tipul obiectului al crui pointer se folosete (obiect din clasa de baz sau
obiect din clasa derivat).
Dac o funcie este definit ca funcie virtual n clasa de baz i redefinit n
clasele derivate, la apelul acesteia ca funcie membr a unui obiect pentru care se
cunoate un pointer, se selecteaz funcia dup tipul obiectului, nu al pointerului. Sunt
posibile mai multe situaii:
Dac obiectul este de tip clas de baz nu se poate folosi un pointer la o
clas derivat (Exemplul 5.4).
Dac obiectul este de tip clas derivat i pointerul este pointer la clas
derivat, se selecteaz funcia redefinit n clasa derivat respectiv.
Dac obiectul este de tip clas derivat, iar pointerul folosit este un
pointer la o clas de baz a acesteia, se selecteaz funcia redefinit n
clasa derivat corespunztoare tipului obiectului.
Acesta este mecanismul de virtualitate i el permite implementarea
polimorfismului n clasele derivate. O funcie redefinit ntr-o clas derivat domin
funcia virtual corespunztoare din clasa de baz i o nlocuiete chiar dac tipul
pointerului cu care este accesat este pointer la clasa de baz.
Pentru precizarea acestui comportament al funciilor virtuale se analizeaz mai
multe situaii n exemplul urmtor.
Exemplul 5.5
142
indicat printr-un pointer la clasa derivat respectiv (D1* pd1, respectiv D2* pd2),
precum i printr-un pointer la baz corespunztor (B* pb1 = pd1, respectiv
B* pb2 = pd2).
Mesajele afiate la consol la apelul funciilor f() i g()pentru obiecte
indicate prin pointeri de diferite tipuri evideniaz diferena de comportare a unei
funcii virtuale fa de o funcie normal.
class B {
public:
void f() { cout << "f() din B\n"; }
virtual void g(){ cout << "g() din B\n"; }
};
class D1:public B {
public:
void f() { cout << "f() din D1\n"; }
void g() { cout << "g() din D1\n"; }
};
class D2:public B {
public:
void f() { cout << "f() din D2\n"; }
void g() { cout << "g() din D2\n"; }
};
void fv1 {
B* pb = new B;
D1* pd1 = new D1;
D2* pd2 = new D2;
B* pb1 = pd1;
B* pb2 = pd2;
//
// Obiect B, pointer B*
pb->f(); // f() din B
pb->g(); // g() din B
};
// Obiecte
pd1->f();
pd2->f();
pd1->g();
pd2->g();
D1, D2
// f()
// f()
// g()
// g()
// Obiecte
pb1->f();
pb2->f();
pb1->g();
pb2->g();
delete pb;
delete pd1;
delete pd2;
D1, D2
// f()
// f()
// g()
// g()
, pointeri B*, B*
din B
din B
din D1
din D2
143
Polimorfismul, adic apelul unei funcii dintr-o clas derivat prin pointer de
tip clas de baz, este posibil numai prin utilizarea pointerilor la obiecte. Obiectele
nsele determin univoc varianta funciei apelate, deci nu se pot selecta alte funcii
dect cele ale obiectului de tipul respectiv. De exemplu, pentru aceleai clase definite
ca mai sus, se consider funcia fv2():
void fv2(){
B obB;
D1 obD1;
D2 obD2;
obB.f();
obB.g();
obD1.f();
obD1.g();
obD2.f();
obD2.g();
}
//
//
//
//
//
//
f()
g()
f()
g()
f()
g()
din
din
din
din
din
din
B
B
D1
D1
D2
D2
144
5.4.3
din
din
din
din
din
din
B
D1
DD1
DD1
DD1
DD1
S-a creat un obiect de tip clas derivat DD1 n memoria liber folosind
operatorul de alocare dinamic new. Acest obiect poate fi indicat prin trei pointeri: un
pointer la clasa derivat DD1 (DD1* pdd1), returnat de operatorul new, un pointer la
clasa de baz B (B* pb) i un pointer la clasa de baz D1 (D1* pd1) . Ultimii doi
pointeri au fost obinut prin conversie implicit de la primul pointer.
Dac se apeleaz funciile f() i g() pentru obiectul creat, cu fiecare dintre
cei trei pointeri, se poate observa c funcia g() se comport ntotdeauna ca funcie
virtual, chiar dac nu a fost folosit specificatorul virtual la redefinirea funciei
g() n clasele derivate D1 i DD1.
Pot s apar i alte situaii n motenirea funciilor virtuale. De exemplu, dac
o funcie virtual nu este redefinit ntr-o clas derivat, atunci se folosete funcia
motenit din clasa de baz i pentru obiecte de tip clas derivat. Pentru precizarea
acestei situaii se reiau clasele B, D1 i D2 din Exemplul 5.5, modificate astfel:
class B {
public:
virtual void g(){ cout << "g() din B\n"; }
};
145
class D1:public B {
public:
void g() { cout << "g() din D1\n"; }
};
class D2:public B { };
void main() {
D1* pd1 = new D1();
D2* pd2 = new D2();
pd1->g();
// g() din D1
pd2->g();
// g() din B
};
Funcia virtual g()a fost redefinit n clasa D1 i apoi a fost apelat pentru
un obiect de clas D1. n clasa D2 funcia virtual g() nu a fost redefinit, i, de
aceea, la invocarea pentru un obiect din clasa D2, este folosit funcia g() din clasa
de baz B.
5.4.4
Destructori virtuali
Eroarea care apare la execuia acestui program este c a fost creat un obiect de
clas D i a fost ters numai un subobiect al acestuia, subobiectul din clasa de baz B.
Mesajele afiate evideniaz acest lucru:
Constructor B
Constructor D
Destructor B
Aceasta face ca memoria liber (heap) s rmn ocupat n mod inutil (cu
partea de date a clasei D), dei se inteniona eliminarea complet din memorie a
obiectului creat. Declararea destructorului din clasa de baz de tip virtual rezolv
aceast problem:
146
class B{
public:
B(){ cout << "Constructor B\n";}
virtual ~B(){cout << "Destructor B virtual\n";}
};
5.4.5
Clase abstracte
De cele mai multe ori, o funcie declarat de tip virtual n clasa de baz nu
definete o aciune semnificativ i este neaprat necesar ca ea s fie redefinit n
fiecare din clasele derivate. Pentru ca programatorul s fie obligat s redefineasc o
funcie virtual n toate clasele derivate n care este folosit aceast funcie, se declar
funcia respectiv virtual pur. O funcie virtual pur este o funcie care nu are
definiie n clasa de baz, iar declaraia ei arat n felul urmtor:
virtual tip_returnat nume_functie(lista_argumente) = 0;
O clas care conine cel puin o funcie virtual pur se numete clas
abstract. Deoarece o clas abstract conine una sau mai multe funcii pentru care nu
exist definiii (funcii virtuale pure), nu pot fi create instane (obiecte) din acea clas,
dar pot fi creai pointeri i referine la astfel de clase abstracte. O clas abstract este
folosit n general ca o clas fundamental, din care se construiesc alte clase prin
derivare.
Orice clas derivat dintr-o clas abstract este, la rndul ei clas abstract (i
deci nu se pot crea instane ale acesteia) dac nu se redefinesc toate funciile virtuale
pure motenite. Dac o clas redefinete toate funciile virtuale pure ale claselor ei de
baz, devine clas normal (ne-abstract) i pot fi create instane (obiecte) ale acesteia.
Exemplul urmtor (5.6) evideniaz caracteristicile claselor abstracte i ale
funciilor virtuale pure.
Exemplul 5.6
class X {
// clasa abstracta
public:
virtual void fp()= 0; // functie virtuala pura
};
147
Exemplul 5.7
class Convert{
protected:
double x;
// valoare intrare
double y;
// valoare iesire
public:
Convert(double i){x = i;}
double getx(){return x;}
double gety(){return y;}
virtual void conv() = 0;
};
// clasa FC de conversie grade Farenheit in grade Celsius
class FC: public Convert{
public:
FC(double i):Convert(i){}
void conv(){y = (x-32)/1.8;}
};
// clasa IC de conversie inch in centimetri
class IC: public Convert{
public:
IC(double i):Convert(i){}
void conv(){y = 2.54*x;}
};
148
Acest exemplu este un mic program de conversie a unor date dintr-o valoare
de intrare ntr-o valoare de ieire; de exemplu, din grade Farenheit n grade Celsius,
din inch n centimetri, etc.
Clasa de baz abstract Convert este folosit pentru crearea prin derivare a
unei clase specifice fiecrui tip de conversie de date dorit. Aceast clas definete
datele comune, necesare oricrui tip de conversie preconizat, de la o valoare de intrare
x la o valoare de ieire y. Funcia de conversie conv() nu se poate defini n clasa de
baz, ea fiind specific fiecrui tip de conversie n parte; de aceea funcia conv() se
declar funcie virtual pur i trebuie s fie redefinit n fiecare clas derivat.
n funcia main() se execut o conversie a unei valori introduse de la
consol, folosind un tip de conversie (o clas derivat) care se selecteaz pe baza unui
caracter introdus la consol.
Acesta este un exemplu n care este destul de pregnant necesitatea funciilor
virtuale: deoarece nu se cunoate n momentul compilrii tipul de conversie care se va
efectua, se folosete un pointer la clasa de baz pentru orice operaie (crearea unui
obiect de conversie nou, apelul funciei conv(), afiarea rezultatelor, distrugerea
obiectului la terminarea programului). Singura difereniere care permite selecia
corect a funciilor, este tipul obiectului creat, care depinde de tipul conversiei cerute
de la consol.
5.4.6
Polimorfism
149
5.5
150
5.5.1
class DList{
DListNode* first;
DListNode* last;
int count;
public:
DList(){ first = 0; last = 0; count = 0;}
virtual ~DList();
int GetCount(){return count;}
void AddHead(DListNode*);
void AddTail(DListNode*);
DListNode* GetHead();
DListNode* GetTail();
DListNode* RemoveHead();
DListNode* RemoveTail();
};
151
5.5.2
152
153
else {
Object **p1 = p;
size = count + grows;
p=(Object**)new BYTE[size*sizeof(Object*)];
for(int j=0;j<i;j++)
p[j] = p1[j];
p[i]=x;
for(j=i;j<count;j++)
p[j+1]=p1[j];
delete p1;
}
count++;
return 1;
}
Funciile membre RemoveAt() i RemoveAll() elimin un pointer dintro poziie dat, fr s tearg obiectul indicat de acesta, sau elimin ntreg vectorul de
pointeri, transformndu-l ntr-un vector de dimensiune nul. Celelate funcii ale clasei
sunt uor de neles.
int ObArray::RemoveAt(int i) {
if(i<0 || i>=count) return 0;
for(int j=i;j<count-1;j++)
p[j]=p[j+1];
count--;
return 1;
}
void RemoveAll(){
delete [] (BYTE *)p;
p = 0;
count = 0;
}
}
//
154
Pentru definirea unui vector de obiecte de tip Point, o prim modalitate este
aceea de a crea o clas nou, clasa PointArray derivat din clasa coleciei de
baz, ObArray, n felul urmtor:
class PointArray : public ObArray {
public:
PointArray() {};
~PointArray();
Point *GetAt(int i){
return (Point*)ObArray::GetAt(i); }
};
PointArray::~PointArray(){
int size = GetSize();
for(int i=0;i<size;i++) {
Point *p = GetAt(i);
delete p;
}
}
RemoveAll();
155
=
=
=
=
=
0
1
2
9
4
Y
Y
Y
Y
Y
=
=
=
=
=
0
1
2
9
4
5.5.3
156
(model de date). De exemplu, o mulime poate fi implementat printr-o list sau printrun vector, dar clasele concrete care reprezint aceste structuri nu pot exprima
conceptul de mulime comun celor dou modaliti de implementare.
O metod de a exprima partea comun a diferitelor abordri ale unui concept
este de a introduce o clas de baz abstract care reprezint interfaa comun mai
multor implementri diferite ale acelui concept. O astfel de clas abstract folosit
pentru definirea coleciilor de date se numete clas container.
Se consider reprezentarea unei mulimi de obiecte de un tip oarecare T,
pentru care se definete o interfa prin care pot fi inserate, terse sau cutate obiecte
n mulime i un mecanism de parcurgere a mulimii (iterator), folosind clasa abstract
Set:
class Set {
public:
virtual void insert(T*) = 0;
virtual void remove(T*) = 0;
virtual int lookup(T*) = 0;
// iterator
virtual T* first() = 0;
virtual T* next() = 0;
virtual ~Set(){};
};
157
Exerciii
E5.1 S se defineasc o clas de obiecte CPoint care s descrie un punct ntr-un
spaiu d-dimensional, unde d poate avea orice valoare. Vectorul care memoreaz
coordonatele punctului (fiecare coordonat de tip double), care se creeaz dinamic
n memoria liber la construcia unui obiect, ca i dimensiunea d a acestui vector sunt
date private ale clasei. Pentru aceast clas trebuie s se defineasc toate funciile
membre necesare pentru funcionarea corect (constructori, destructori) i o funcie
membr:
void Compare(const CPoint &p);
158
E5.4 Pentru fiecare din cele 2 clase s se defineasc cte o funcie de afiare
Display() care s afieze mesajul:
Display 2d x = .. y = ..
Display 3d x = .. y = .. z = ..
Se compileaz i se execut corect aceast funcie? Care este cauza erorii care
apare i cum se poate elimina aceast eroare?
Presupunnd c n fiecare funcie constructor i destructor a clasei de baz i a
claselor derivate a fost introdus un mesaj care afieaz la consol numele acesteia, s
se explice mesajele afiate n execuia funciei fpd().
E5.5
159
//
//
//
//
inserare
inserare
inserare
inserare
0
1
2
3
160
6
Sistemul de intrare/ieire din C++
Intrare standard
Ieire standard
Ieire standard pentru eroare
Versiune cu buffer de memorie pentru cerr
Tastatur
Ecran
Ecran
Ecran
162
6.1
};
// iruri
Exemplul 6.1
163
O alt soluie pentru citirea unei secvene de intrare este folosirea uneia din
funciile get() definite n clasa iostream astfel:
class istream : public virtual ios {
//
istream& get(char& c);
istream& get(char* p, int n, char ch=\n);
};
164
O utilizare tipic a funciei get() cu trei argumente este citirea unei linii de intrare
ntr-un buffer de dimensiune fix, pentru o analiz ulterioar. De exemplu:
void fbuf(){
char buf[100];
cin >> buf;
// operaie suspect, pot apare erori
}
cin.get(buf,100,'\n');
// citire sigur
6.2
165
cazul funciilor operator << sau >> operandul din stnga trebuie s fie un stream, nu
un obiect din clasa respectiv, deci aceste funcii nu pot fi funcii membre ale claselor.
Ca exemple de suprancrcare a funciilor operator << i >>, se reiau clasele
definite n lucrrile precedente, Complex i String, crora li se adaug aceste
funcii.
class Complex {
double x, y;
public:
Complex(){x = 0; y = 0}
Complex(double r, double i){x = r; y = i; }
// ..
friend ostrem& operator << (ostrem& os,Complex z);
friend istream& operator >>(istream& is,Complex& z);
};
ostream& operator<<(ostream& os, Complex z){
os << ( << z.x << ,<< z.y << );
return os;
}
istream& operator>>(istream& is, Complex& z){
is >> z.x >> z.y;
return is;
}
class String{
char *str;
int size;
public:
//.
friend ostream& operator <<(ostream& stream,
const String &r);
friend istream& operator >>(istream& stream,
String &r);
};
ostream& operator <<(ostream &stream, const String &r){
stream << r.str;
return stream;
}
istream& operator >>(istream &stream, String &r){
char buf[256];
stream.get(buf,256);
r = buf;
return stream;
}
Exemplul 6.2
Fie funcia fc() n care se citesc de la consol un numr complex (de tip
Complex) i un sir (de tip String) care apoi sunt afiate pe ecran.
166
void fc(){
Complex z;
cout << "Introduceti x, y :";
cin >> z;
cout << "z = " << z << '\n';
cin.get();
int size = 10;
char buf[] = "9999999999";
cout << "Introduceti un sir:";
cin.get(buf,size,'\n');
6.3
Starea streamurilor
Fiecare stream (istream sau ostream) are asociat o stare, memorat ntrun ntreg n clasa ios, iar condiiile nestandard sau erorile aprute n operaiile cu
acesta pot fi setate sau testate folosind operaii publice ale clasei de baz ios.
Valorile folosite pentru descrierea strii unui stream i funciile de testare a strii
definite n clasa de baz ios sunt urmtoarele:
class ios {
// .
public:
enum io_state {
goodbit=0,
eofbit=1,
failbit=2,
badbit=4
};
int rdstate() const;
int good() const;
int eof() const;
int fail() const;
};
167
6.4
Formatarea I/O
6.4.1
168
enum {
skips=0x00001,
left=0x00002,
right=0x00004,
internal=0x0008,
dec=0x0010,
oct=0x0020,
hex=0x0040,
showbase=0x080,
showpoint=0x0100,
uppercase=0x0200,
showpos=0x0400,
scientific=0x0800,
fixed=0x1000,
unitbuf=0x2000,
};
// funcii de formatare
int width(int w);
int width() const;
char fill(char);
char fill() const;
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// lrgime cmp
// caracter de umplere
};
int precision(int);
int precision() const;
169
seteaz baza de numeraie fr efecte asupra altor pri ale strii streamului.
Funcia unsetf() este complementar funciei setf() i are ca efect
tergerea unuia sau mai multor indicatori de format. Funcia unset() opereaz ntrun mod asemntor cu funcia setf().
Exemplul 6.3
170
Exemplul 6.4
171
6.4.2
Scop
Intrare/Ieire
dec
endl
ends
flush
hex
oct
resetiosflags(long f)
setbase(int base)
setfill(int ch)
setiosflags(long f)
setprecision(int p)
setw(int w)
ws
Intrare i ieire
Ieire
Ieire
Ieire
Intrare i ieire
Intrare i ieire
Intrare i ieire
Ieire
Ieire
Intrare i ieire
Ieire
Ieire
Intrare
172
Exemplul 6.5
6.5
6.5.1
Pentru utilizarea unui fiier pe disc acesta trebuie s fie asociat unui stream.
Pentru aceasta se creaz mai nti un stream, iar apelul funciei open() a streamului
execut asocierea acestuia cu un fiier ale crui caracteristici se transmit ca argumente
ale funciei open(). Funcia open() este funcie membr a fiecreia dintre cele trei
clase stream (ifstream, ofstream i fstream) i are prototipul:
173
de acces. Argumentul mode definete modurile n care poate fi deschis un fiier i are
valorile definite n clasa de baz ios astfel:
class ios{
public:
// ..
enum open_mode {
in=1,
out=2,
ate=4,
app=010,
trunc=020,
nocreate=040,
noreplace=0100
};
//
};
//
//
//
//
//
//
//
Dup execuia unei funcii de deschidere open(), starea streamului este zero,
dac deschiderea s-a efectuat cu succes, i diferit de zero, dac nu s-a putut efectua
deschiderea fiierului.
174
Deschiderea unui fiier prin apelul funciei open() poate fi evitat dac la
declararea unui stream se folosesc argumente care definesc fiierul cu care se asociaz
streamul. La astfel de declaraii este apelat un constructor de iniializare al streamului,
care execut exact aceleai operaii de deschidere, ca i apelul funciei open(). De
exemplu, se pot rescrie definirile de streamuri i fiiere asociate de mai sus astfel:
ifstream input("intrare.txt");
ofstream output("iesire.txt");
fstream inout("fisier", ios::in|ios::out);
Pentru nchiderea unui fiier se apeleaz funcia close(), care este funcie
membr a claselor stream (ifstream, ofstream i fstream).
Pentru scrierea i citirea dintr-un fiier de tip text se folosesc funciile operator
<< i >> ale streamului asociat acelui fiier. Aceste funcii operator suprancrcate
pentru un anumit tip de date pot fi folosite fr nici o modificare att pentru a scrie sau
citi de la consol ct i pentru a scrie sau citi dintr-un fiier pe disc.
Acest lucru este posibil deoarece clasele ifstream, ofstream i
fstream sunt derivate din clasele istream, ostream i iostream respectiv, iar
conversia de la un tip derivat (de exemplu, ifstream) la un tip de baz al acestuia
(de exemplu, istream) este implicit. Exemplul urmtor evideniaz acest mod de
operare.
Exemplul 6.6
175
output.close();
6.5.2
Pentru accesul aleator la date n fiiere pe disc, sistemul I/O din C++ folosete
doi pointeri asociai fiecrui fiier: pointerul de citire (get) care specific poziia de
unde va avea loc urmtoarea citire din fiier i pointerul de scriere (put), care
specific poziia unde va avea loc urmtoarea scriere n fiier. Poziia ntr-un fiier
este poziia unui caracter n fiierul respectiv i are valori ncepnd de la valoarea zero,
pn la valoarea maxim a numrului de caractere coninute n fiier. Dup fiecare
operaie de citire sau de scriere, pointerul corespunztor este avansat secvenial n
fiier n mod automat. Se poate considera un fiier ca un tablou de caractere, accesate
secvenial (n mod automat) sau aleator (prin modificarea pointerilor de scriere i de
citire). Pentru poziionarea ntr-o poziie dorit a unuia dintre aceti pointeri se
folosesc funcii membre publice ale claselor de baz istream, ostream.
Pentru accesul aleator n operaiile de ieire se pot folosi urmtoarele funcii
membre publice ale clasei ostream:
class ostream: public virtual ios {
// .
public:
ostream& seekp(streampos pos);
ostream& seekp(streamoff offset, seek_dir orig);
streampos tellp();
//
};
176
Exemplul 6.7
177
file1.get(c2);
file1.seekp(i, ios::beg);
file1.put(c2);
file1.seekp(j, ios::beg);
fisier.put(c1);
}
fisier.seekg(0);
for(i=0;i<n;i++){
fisier.get(c1);
cout << c1;
}
cout << endl;
fisier.close();
return 0;
6.6
178
ifstream
istrstream
ostream
ofstream
iostream
fstream
strstream
ostrstream
Fig. 6.1. Ierarhia claselor care definesc streamuri n C++.
179
Exerciii
E6.1 S se citeasc un fiier care conine numere flotante, s se construiasc
numere complexe din perechi de cte dou numere flotante i s se afieze la consol
numerele complexe.
E6.2 S se proiecteze mai multe tipuri de date i funcii de citire i afiare a
acestora n formate ct mai sugestive pentru semnificaia acestora. Sugestii pentru
tipuri de date: adrese de mail, data calendaristic, nume de fiiere care s includ calea
de acces, etc.
E6.3 S se scrie un program care tiprete (a) toate literele mici, (b) toate literele,
(c) toate literele i cifrele, (d) toate caracterele spaii albe, (e) toate caracterele
tipribile.
E6.4 S se modifice funcia operator >> a clasei String, astfel nct s se poat
citi de la consol iruri de caractere de orice lungime.
E6.5 Folosind clasa String, aa cum a fost descris n seciunea 4, cu
suprancrcarea operatorilor de inserie i extragere, se definete o funcie fs() n
care se declar un obiect de tip String, pentru care se execut o operaie de intrare
de la consol:
void fs(){
String string1;
cin >> string1;
}
Dac n fiecare din funciile constructor i destructor ale clasei String este adugat
cte o instruciune de afiare a unui measj, cum se interpreteaz afiarea a trei mesaje
de constructori i respectiv a trei mesaje de destructor la execuia acestei funcii
fs()? Ce se ntmpl dac se suprim funcia operator =() din clasa String?
Care este explicaia erorii aprute?
E6.6 Se definete un tip de date (clas) NumeAdresa, pentru care se suprancarc
operatorii << i >>.
class NumeAdresa{
String nume;
String oras
String strada;
int numar;
public:
NumeAdresa();
NumeAdresa(const String& n, const String& o,
const String& s,int n);
NumeAdresa(const NumeAdresa& r);
180
};
7
Clase i funcii template
7.1
Clase template
182
};
Clasa template TStack conine ca date private doi pointeri: pointerul bot,
pentru memorarea adresei de nceput a stivei i pointerul top, pentru memorarea
capului stivei. Dimensiunea size a tabloului unidimensional (vector), creat dinamic
la construcia unui obiect stiv, este necesar pentru testarea depirii capacitii stivei
n funcia push(), dar aceste teste nu au mai fost prezentate, pentru simplitate.
Prefixul template<class T> specific declararea unui template cu un
argument cu numele T. Dup aceast introducere, T este folosit exact la fel ca orice tip
de date, n tot domeniul clasei template declarate.
Numele unei clase template urmat de numele tipurilor de date folosite ca
argumente, ncadrate de paranteze ascuite < > este numele unei clase (definite aa
cum specific template-ul) i poate fi folosit la fel ca oricare alt clas. De exemplu,
un obiect stiv de numere ntregi istack poate fi declarat folosind clasa template
TStack astfel:
TStack<int> istack(100); // stiv de numere ntregi
Exemplul 7.1
183
istack.push(2);
istack.push(7);
cout << istack.pop() <<" ";
cout << istack.pop() << endl;
TStack<String> sstack(100);
sstack.push("Primul sir\n");
sstack.push("Al doilea sir\n");
sstack.push("Al treilea sir\n");
cout << sstack.pop();
cout << sstack.pop();
cout << sstack.pop();
TStack<Complex> cstack(100);
Complex z1(1.2, 3.4);
Complex z2(7.6, 5.4);
cstack.push(z1);
cstack.push(z2);
cout << cstack.pop();
cout << cstack.pop();
184
Funciile membre ale unei clase template nu trebuie s fie obligatoriu definite
inline. n clasa template TStack definit mai sus, se poate ca o funcie s fie doar
declarat n interiorul clasei (de exemplu T pop(); ) i s fie definit n afara clasei
astfel:
template<class T> T TStack<T>::pop(){
return *--top;
}
7.2
Funcii template
Toate funciile membre ale unei clase template sunt funcii template, adic
funcii care definesc un set general de operaii care vor fi aplicate unor tipuri de date
diferite. n afar de funciile template membre ale unei clase, se pot defini i funcii
template nemembru, care definesc familii de funcii n acelai mod n care clasele
template definesc familii de clase.
Un astfel de exemplu este ilustrat printr-o funcie template de sortare
sort(), pentru care s-a folosit cel mai simplu algoritm, prin inserie linear,
deoarece n acest moment interesul este orientat ctre tehnica de funcie template i
trsturile limbajului i mai puin ctre algoritmul propriu-zis.
template<class T> void sort(T* vect, int n){
int i,j;
T x;
for(i=1; i<n; i++){
x = *(vect + i);
j = i - 1;
while((j >= 0) && (x < *(vect + j))){
*(vect + j + 1) = *(vect + j);
j--;
}
*(vect + j + 1) = x;
}
}
Exemplul 7.2
185
void ft2(){
double dvect[] = {4.7, 0.66, 7.0, 1,8, 3.0};
int size = sizeof(dvect)/sizeof(double);
sort(dvect, size);
for (int i=0; i<size; i++)
cout << dvect[i] << " ";
int ivect[] = {10, 9, 5, 3, -2, 4};
size = sizeof(ivect)/sizeof(int);
sort(ivect, size);
for (i=0; i<size; i++)
cout << dvect[i] << " ";
}
Compilatorul creeaz cte o funcie de sortare pentru fiecare tip de dat folosit
ca argument de apel, deci se creeaz o funcie de sortare pentru vectori de numere
ntregi i, respectiv pentru vectori de numere de tip double. Dup apelul funciei
sort() datele sortate sunt afiate la consol. Rezultatul execuiei acestei funcii este
urmtorul:
0.66 1.8 3.0 4.7 7.0
-2 3 4 5 9 10
7.3
Una din modalitile cele mai frecvent utilizate de creare a coleciilor sigure
ca tip (type-safe) pentru obiecte de orice tip de date, este prin utilizarea claselor
template. Clasa sau clasele care descriu forma coleciei (vector, list, etc.) se definesc
ca i clase template, avnd ca argument tipul de date din care se vor crea coleciile
respective. La declararea unei colecii pentru un tip dat ca argument, compilatorul
creeaz colecia de forma dat de clasa template pentru tipul de date primit ca
argument. Nu se folosesc conversii explicite de pointeri, astfel nct aceste clase de
colecii sunt sigure ca tip.
n continuare va fi prezentat modul de implementare prin clase template a unei
liste dublu nlnuite, a unui arbore binar ordonat i a unui vector asociativ.
7.3.1
Implementarea unei liste dublu nlnuite prin clase template se poate urmri
cu uurin prin comparaie cu clasele prin care a fost implementat o list dublu
nlnuit de numere ntregi: IntDListNode i IntDlist, prezentate n
seciunea 3. Singurele modificri care intervin se refer la sintaxa instruciunilor, care
trebuie s introduc tipul argumentului formal. Declaraiile claselor template
TListNode i TList sunt urmtoarele:
template <class E> class TListNode{
E elem;
186
Parametrul formal al acestor clase template este tipul datelor din nodurile listei
nlnuite, class E, care se nlocuiete cu tipul dat ca argument la declaraia unui
obiect din clasa TList. Modul de implementare a funciilor nu aduce nimic nou fa
de implementarea lor anterioar. De exemplu, funciile AddHead() i
RemoveHead():
template <class E> void TList<E>::AddHead(E x){
TListNode<E>* elem = new TListNode<E>(x);
if (count == 0){
first = elem;
last = elem;
}
else{
first->prev = elem;
elem->next = first;
first = elem;
}
count++;
}
187
1
2
3
afiseaza 3
afiseaza 2
afiseaza 1
7.3.2
188
Funciile celor dou clase se definesc n mod similar cu cele ale arborelui
binar ordonat de numere ntregi prezentat n seciunea 3. De exemplu:
template <class E>
TBTreeNode<E>* TBTree<E>::insert1(TBTreeNode<E> *root,
TBTreeNode<E> *r, E data){
if(!r) {
r = new TBTreeNode<E>(data);
count++;
if(!root) return r;
if(data < root->d) root->left = r;
else root->right = r;
return r;
}
189
}
template <class E> void TBTree<E>::insert(E data) {
if(!root) root = insert1(root, root, data);
else insert1(root, root, data);
}
De asemenea este utilizat operatorul de scriere ntr-un stream << , care trebuie
s fie suprancrcat n clasa Complex.
7.3.3
190
care este utilizat pentru a accesa celalalt parte a elementului, numit valoare
(value).
Un vector asociativ se poate implementa, desigur, n mai multe moduri, dintre
care o soluie simpl i nu neaprat cea mai eficient este prezentat n programul
MapIter, printr-o list dublu nlnuit n care elementele se menin sortate n ordine
cresctoare a cheii. Pstrarea elementelor ordonate n list permite regsirea rapid a
informaiilor.
Pentru definirea nodurilor listei, a listei i a operaiilor de parcurgere a listei se
folosesc trei clase template (MapNode, Map, MapIter), fiecare dintre ele avnd
dou argumente: tipul de date al cheii i tipul de date al valorii elementelor.
Un element (nod) al listei dublu nlnuite este definit prin clasa template
MapNode, care are ca argumente dou tipuri generice, class K i class V,
corespunztoare tipului de date al cheii (key) i tipului de date al valorii (value).
Dat fiind c aceast clas descrie un element dintr-o list dublu nlnuit, mai sunt
necesari doi pointeri de nlnuire,left i right. Implementarea acestei clase este
urmtoarea:
template <class K, class V> class MapNode{
friend class Map<K,V>;
friend class MapIter<K,V>;
K key;
V value;
MapNode *left;
MapNode *right;
public:
MapNode(const K& k, const V& v):key(k), value(v){
left = NULL;
right = NULL;
}
~MapNode();
};
191
public:
Map() {init();}
Map(const K& k, const V& v)
:def_k(k),def_v(v) {init(); }
Map(const Map& r);
~Map();
void remove(const K& k);
Map& operator=(const Map& m);
V& operator[] (const K& k);
int GetSize() const {return size;}
};
Valorile implicite ale cheii (key) i ale valorii (value) sunt memorate n
datele membre def_k i def_v. La crearea unui obiect din clasa Map, aceste valori
implicite se transmit ca argumente ale constructorului. O dat membr din clasa Map,
pointerul current la un obiect de tipul MapNode, memoreaz poziia curent n
lista nlnuit, adic ultima poziie accesat prin funcia operator de indexare.
Pentru nceput se pot ignora funciile membre care se refer la clasa
MapIter, care vor fi explicate mai trziu.
Operaia cea mai important a clasei Map este operaia de indexare, pentru
care s-a implementat funcia operator[] (). Vectorul asociativ trebuie s se
comporte ca un vector de date n care indexarea printr-un indice este nlocuit cu
indexarea printr-o valoare a cheii. Ca urmare, suprancrcarea operatorului de indexare
returneaz o referin la valoarea (value) corespunztoare cheii date, astfel nct
valoarea dintr-un element (value) s poat fi folosit att n membrul drept al unei
expresii (valoarea este citit), ct i n membrul stng al unei expresii (valoarea este
scris). Implementarea funciei operator de indexare este urmtoarea:
template<class K, class V>
V& Map<K, V>::operator[](const K& k){
if(head == 0){
current = head = new MapNode<K, V>(k, def_v);
current->left = current->right = 0;
return current->value;
}
MapNode<K,V>* p = head;
for (;;){
if(p->key == k) { // cheie gasita
current = p;
return current->value;
}
if (k < p->key){ // inserare inainte de p
current = new MapNode<K, V>(k, def_v);
current->left = p->left;
current->right = p;
if (p == head)
head = current;
else
p->left->right = current;
192
p->left = current;
return current->value;
}
MapNode<K, V>* s = p->right;
if(s == 0) { // inserare dupa p, la sfarsit
current = new MapNode<K,V>(k, def_v);
current->left = p;
current->right = 0;
p->right = current;
return current->value;
}
p = s;
Exemplul 7.3
193
De cele mai multe ori, pentru parcurgerea ntr-o ordine dorit a elementelor
unei colecii de obiecte, aa cum este un vector asociativ, se folosete un mecanism
numit iterator. Un iterator permite parcurgerea n ordinea dorit a elementelor, fr ca
aceast ordine s depind de modul de ordonare intern a elementelor coleciei.
Pentru implementarea unui iterator al vectorului asociativ descris de clasele
Map i MapNode se folosete o alt clas template, MapIter, ale crei funcii
membre vor fi funcii de parcurgere n ordinea dorit a vectorului asociativ. De
exemplu, se pot implementa funcii de parcurgere n ordine cresctoare, respectiv
descresctoare a cheilor elementelor. Aceast clas template este definit astfel:
template <class K, class V> class MapIter {
friend class Map<K,V>;
Map<K, V>* map;
MapNode<K,V>* node;
public:
MapIter(){map = 0; node = 0;}
MapIter(Map<K,V>* pmap, MapNode<K,V>* pnode){
map = pmap;
node = pnode;
}
MapIter(Map<K,V>& mm){
map = &mm;
node = map->head;
}
operator void*() {return node;}
const K& key(){
if (node) return node->key;
else return map->def_k;
}
V& value(){
if (node) return node->value;
else return map->def_v;
};
MapIter& operator++();
// increment prefix
MapIter& operator--();
// decrement prefix
void operator++(int);
// increment postfix
void operator--(int);
// decrement postfix
};
template <class K, class V>
MapIter<K,V>& MapIter<K,V>::operator--(){
if(node) node = node->left;
return *this;
}
194
Exemplul 7.4
Un vector asociativ (obiect din clasa Map) poate fi parcurs iterativ folosind un
obiect din clasa MapIter (numit i iterator), care acceseaz succesiv elementele
listei nlnuite. n programul MapIter se adaug funcia fm2() n care este creat
un vector asociativ cu numele table, cu cheie de tip String i valoare de tip ntreg.
Cheia i valoarea elementelor listei se afieaz la consol la parcurgerea iterativ a
listei folosind iteratorul iter. Prin apelul operatorului suprancrcat de incrementare
al clasei MapIter, elementele listei sunt parcurse n ordine cresctoare a cheii.
void fm2(){
Map<String,int> table("nil", 0);
table["word1"] = 5;
table["word3"] = 2;
table["word3"] +=4;
table["word2"] = 8;
MapIter<String,int> iter(table);
195
5
8
6
Pentru acelai vector asociativ (obiect din clasa Map) se pot crea mai multe
obiecte iterator (din clasa MapIter), fiecare dintre ele parcurgnd lista ntr-un mod
diferit, n funcie de necesitile programului. Fiecare iterator memoreaz un anumit
nod curent din list (n variabila node), n timp ce n lista nsi, nodul curent
(variabila current) memoreaz ultimul nod accesat de oricare dintre iteratorii care o
parcurg. n utilizarea din funcia fm2(), tipul argumentului cheie este clasa String.
Dat fiind c n funcia operator []() a clasei Map se execut operaii de
comparaie asupra cheii, este necesar ca n clasa String s fie suprancrcai aceti
operatori i acest lucru apare n implementarea clasei String.
Exemplul 7.5
196
}
5
8
6
9
7
Pentru parcurgerea dup o alt regul a vectorului (de exemplu, din dou n
dou elemente a listei nlnuite n ordinea cresctoare a cheii), trebuie s fie
suprancrcat funcia operator+=() a clasei MapIter astfel:
template <class K, class V>
MapIter<K,V>& MapIter<K,V>::operator+=(int inc){
for (int i=0;i<inc;i++){
if (node)
node = node->right;
else break;
}
return *this;
}
Exerciii
E7.1 S se modifice clasa String, astfel nct s nu se mai suprancarce
operatorul de asignare. Ce se ntmpl la execuia funciei ft1()? Care este
explicaia erorii care apare? S se introduc mesaje de identificare n fiecare funcie
executat, astfel nct s se evidenieze locul i cauza erorii de execuie aprute.
E7.2 S se modifice clasa Complex, astfel nct s nu se mai suprancarce
operatorul de asignare. Ce se ntmpl la execuia funciei ft1() n programul
Template? Care este diferena fa de execuia de la punctul E7.1.?
E7.3 S se modifice clasa String, astfel nct s nu mai fie definit constructorul
de copiere. Ce se ntmpl la execuia funciei ft1()din programul Template?
Care este explicaia erorii care apare? Folosind mesaje de identificare n fiecare
funcie executat, s se evidenieze locul i cauza erorii de execuie aprute.
197
Se cere s fie definit o funcie template de copiere pentru un tip de date oarecare
(class T).
198
199
nici o legtur ntre aritatea relaiei, care este binar i aritatea arborelui binar, care
permite sortarea. Un arbore binar ordonat poate reprezenta o relaie de orice grad).
S se defineasc una sau mai multe clase template (una dintre acestea fiind
clasa RBTree) care s implementeze o relaie binar reprezentat printr-un arbore
binar ordonat. Informaia dintr-un nod al arborelui este un tuplu (cheie, valoare). Cheia
este folosit pentru definirea relaiei de preceden pentru inserare i cutare n relaia
binar.
S se defineasc una sau mai multe clase template (una dintre acestea fiind
clasa RTree) care s implementeze o relaie de un grad oarecare n reprezentat
printr-un arbore binar ordonat. Informaia dintr-un nod al arborelui este un tuplu
format din n atribute fiecare atribut fiind definit pe un domeniu dat printr-o clas. Cele
n atribute ale unui tuplu se reprezint ca o mulime, iar unul dintre atribute are
propietatea de cheie i este folosit pentru inserare i cutare n relaia binar.
E7.22 n algebra relaional se definesc mai multe operaii asupra relaiilor. Trei
dintre acestea sunt operaii obinuite asupra mulimilor: reuniunea, intersecia i
diferena. Ceilali operatori ai algebrei relaionale (propui de Codd) sunt: produsul
cartezian, restricia, proiecia, jonciunea i mprirea. Condiia pentru execuia
operaiilor de reuniune, diferen i intersecie este ca cei doi operanzi (relaiile) s fie
compatibile ca tip (s conin tupluri ca aceeai aritate i cu atribute definite n
aceleai domenii). S se implementeze operaiile de reuniune, diferen i intersecie a
relaiilor binare reprezentate ca vectori asociativi sau ca arbori binari ordonai i
asupra relaiilor n-are reprezentate ca arbori binari ordonai dup valoarea atributului
cheie.
E7.23 Produsul cartezian a dou relaii A i B, notat A x B este definit ca o relaie
compus din toate tuplurile posibile formate din dou componente: prima component
este un tuplu care aparine relaiei A i cea de-a doua component este un tuplu care
aparine relaiei B. S se implementeze operaia de produs cartezian a dou relaii
binare reprezentate prin vectori asociativi sau prin arbori binari ordonai.
De exemplu, programul implementat trebuie s calculeze astfel de produse
carteziene:
A = {(1,2), (4,5), (8,4), (9,2)};
B = {(2.6,A), (5.1,D)}
AxB={(1,2),(2.6,A)),((1,2),(5.1,D)), ..((9,2),(5.1,D))}
200
operaia de proiecie asupra unei relaii binare reprezentate printr-un vector asociativ
(clasa Map) i asupra unor relaii n-are reprezentate prin arbori binari ordonai (clasa
RTree) dup valoarea atributului cheie.
E7.26 Operaia de jonciune natural (natural join) a dou relaii este una din cele
mai importante operaii care se execut n algebra relaional. Fiind dat o relaie A ale
crei tupluri sunt compuse din atributele X1, X2,Xm, Y1, Y2, Yn i o relaie
B ale crei tupluri sunt compuse din atributele Y1, Y2, Yn, Z1, Z2, Zp,
jonciunea natural a celor dou relaii este o relaie compus din toate tuplurile care
au valori egale ale atributelor comune corespunztoare (Y1, Y2, Yn). Un tuplu din
relaia rezultat este compus din atributele: X1, X2,Xm, Z1, Z2, Zp.
S se implementeze operaia de jonciune natural a dou relaii care au un
numr n de atribute comune.
8
Tratarea excepiilor
202
Construcia:
catch ( /*.. */){
// .
}
este numit rutin de tratare a excepiei (exception handler). Ea este apelat imediat
dup un bloc prefixat de cuvntul cheie try sau dup o alt rutin de tratare a unei
excepii. Paranteza care urmeaz cuvntului cheie catch conine o declaraie care
este folosit ca un argument: aceast declaraie specific tipul excepiei de care se
ocup rutina de tratare a erorii i, uneori, numele argumentului.
Dac o excepie (ceea ce nsemn o eroare) apare ntr-un bloc prefixat de
specificatorul try, ea este lansat de ctre instruciunea throw, dup care este
captat ntr-o rutin de tratare a excepiilor (deci n blocul instruciunii catch) i
prelucrat. Blocul try trebuie s conin acea seciune a programului n care se
dorete s fie cutate erorile. El poate cuprinde cteva instruciuni ntr-o funcie sau
chiar ntregul corp al funciei main(), ceea ce nseamn urmrirea erorilor n ntregul
program.
Atunci cnd o funcie genereaz o excepie, ea nu i mai continu execuia ca
n situaia n care ar apela o funcie de tratare a erorii, din care se revine printr-o
instruciune return, ci execuia este captat n rutina de tratare, dup care se
continu programul cu instruciunile care urmeaz rutinei de tratare a excepiei.
8.1
Discriminarea excepiilor
n mod obinuit, ntr-un program pot aprea mai multe tipuri de erori de
execuie (run-time errors) i fiecare tip de eroare poate fi asociat unei excepii cu un
nume distinct. Captarea excepiilor este executat de mai multe blocuri catch, care
pot fi asociate unuia sau mai multor blocuri try. Forma general pentru un bloc try
urmat de mai multe blocuri catch care trateaz diferite tipuri de excepii este
urmtoarea:
try {
// bloc try
}
8. Tratarea Excepiilor
203
Exemplul 8.1
void fd(){
char tip;
while (tip !='q'){
try{
int argi;
double argd;
char argc;
cout << Start bloc try\n;
cout << "Introduceti tipul si arg. exceptiei: ";
cin >> tip;
switch (tip){
case 'i':
cin >> argi;
cout << "Lansare exceptie int\n";
throw argi;
break;
case 'd':
cin >> argd;
cout<<"Lansare except. double\n";
throw argd;
break;
case 'c':
cin >> argc;
cout<<"Lansare exceptie char\n";
cout.flush();
throw argc;
break;
}
cout << "Nu s-a lansat exceptie\n";
}
catch(int i){
cout<<"Captare exceptie int i="<<i<<endl;
}
catch(double d){
cout<<"Captare exceptie double d="<<d<<endl;
}
204
n aceast funcie se poate selecta tipul i argumentul unei excepii prin valori
introduse de la consol. La selecia tipului de argument int (prin introducerea de la
tastatur a caracterului i), se lanseaz o excepie pentru tipul de date integer, cu o
valoare a argumentului egal cu valoarea citit de la consol. Aceast excepie este
captat de instruciunea catch(int i), care primete n argumentul i valoarea cu
care a fost lansat excepia de tip integer i o afieaz.
Cu acest program se pot testa diferitele situaii care pot aprea n execuia
blocurilor try-catch: selectarea uneia din rutinele de tratare a excepiilor (unul din
blocurile catch) n funcie de tipul excepiei lansate n blocul try, ignorarea tuturor
blocurilor catch dac n blocul try nu se lanseaz nici o excepie, precum i
terminarea anormal a programului n situaia n care s-a lansat o excepie, dar nu
exist o rutin de tratare pentru tipul excepiei lansate.
Liniile de afiare care se pot obine la execuia funciei fd(), pentru diferite
situaii selectate, sunt prezentate mai jos:
Start bloc try
Introduceti tipul si argumentul excepiei: i 2
Lansare exceptie int
Captare exceptie int i=2
End bloc try-catch
Start bloc try
Introduceti tipul si argumentul excepiei: d 3.5
Lansare exceptie double
Captare exceptie double d=3.5
End bloc try-catch
Start bloc try
Introduceti tipul si argumentul excepiei: g
Nu s-a lansat exceptie
End bloc try-catch
Start bloc try
Introduceti tipul si argumentul excepiei: c a
Lansare exceptie char
Abnormal program termination
n mod asemntor, n clasa Vector pot fi tratate mai multe tipuri de erori:
n afar de eroarea de depire a dimensiunii vectorului, poate fi tratat i eroarea care
apare datorit unei valori inacceptabile la construcia vectorului:
class Vector{
int* p;
int sz;
enum {max=1000};
8. Tratarea Excepiilor
205
public:
class Range {};
// exceptie la depasire domeniu
class Size {};
// exceptie la constructie
Vector(int s);
int& operator[](int i);
// ....
};
Vector::Vector(int s){
if (s < 0 || max < s) throw Size();
sz = s;
p = new int[s];
}
8.2
n C++ exist mai multe modaliti de tratare a excepiilor, dintre care o parte
vor fi descrise n continuare.
206
astfel de excepie este lansat n blocul try corespunztor. Astfel de erori pot fi
evitate dac se folosete o construcie de captare a tuturor erorilor, care are urmtoarea
sintax:
catch(){
// tratare general excepii
}
8. Tratarea Excepiilor
207
Exemplul 8.2
n clasa Vector sunt definite clasele excepie Range i Size care permit
stocarea i regsirea unor informaii prin intermediul obiectelor lansate la apariia unei
erori de un anumit tip. La apelul unei funcii operator de indexare [] a clasei
Vector cu o valoare a indicelui i < 0 sau i >= sz, se lanseaz excepia de
depire a domeniului prin construirea unui obiect din clasa Range, cu argumentul i:
Range(i). Obiectul lansat memoreaz (prin construcie) aceast valoare. Rutina de
tratare a excepiei de depire a domeniului are ca argument un obiect r de clas
Vector::Range, care este chiar obiectul lansat la apariia erorii n funcia operator
de indexare. Folosind funciile membre ale acestui obiect, rutina de tratare a excepiei
poate regsi informaii privind condiia de apariie a erorii.
class Vector{
int* p;
int sz;
enum {max=1000};
public:
Vector(int s);
class Range {
// clasa exceptie
int index;
public:
Range(int i){index = i;}
int getindex(){return index;}
};
class Size {
// clasa exceptie
int dim;
public:
Size(int d){dim=d;}
int getdim() {return dim;}
};
int& operator[](int i);
int getsize() {return sz;}
// ....
};
Vector::Vector(int s){
if (s < 0 || max < s) throw Size(s);
sz = s;
p = new int[s];
}
208
while(fex(v1,d)){
cout << "Introduceti dimensiunea:
cin >> d;
}
";
8. Tratarea Excepiilor
209
8.2.3
210
8. Tratarea Excepiilor
211
class Matherr { };
class Overflow : public Matherr{ };
class Underflow : public Matherr { };
class Dividebyzero : public Matherr { };
void fder(){
try{
// lansare excepii
}
catch (Overflow){
// tratare depasire superioara
}
catch (Matherr){
// tratare orice exceptie Matherr
// care nu este Overflow
}
}
n acest exemplu, dintre toate excepiile care pot fi lansate n blocul try,
excepia Overflow are o tratare special, n timp ce toate celelalte excepii derivate
din clasa Matherr au o tratare global prin rutina catch(Matherr), deci nu vor
provoca terminarea anormal a programului.
Din exemplele prezentate pn acum se poate observa c excepiile pot fi
definite global, pentru ntregul program, cum sunt excepiile Matherr sau
Overflow, sau pot fi excepii locale ale unei clase, cum sunt excepiile Range sau
Size, definite n clasa Vector. Pentru astfel de excepii, n instruciunea catch
trebuie s fie specificat domeniul clasei excepie: catch (Vector::Range).
Exerciii
E8.1 S se implementeze o funcie de copiere de la un fiier surs ntr-un fiier
destinaie, n care s se lanseze excepii pentru urmtoarele situaii: erori de deschidere
sau nchidere fiiere, erori de citire din fiierul surs, erori de scriere n fiierul
destinaie. Pentru fiecare tip de eroare s se defineasc o clas excepie, iar apelul
funciei de copiere s fie executat ntr-un bloc try, urmat de rutinele de tratare a
excepiilor corespunztoare.
E8.2 S se defineasc o clas Int care s se comporte exact ca tipul fundamental
int, cu deosebirea c lanseaz excepii n situaiile de depire inferioar i
superioar care apar n diferite operaii aritmetice.
E8.3 Se consider clasa IntArray, vector de numere ntregi, descris n E2.6 i
E4.10. S se modifice funcia membr GetAt() i funcia operator de indexare,
astfel nct s lanseze o excepie atunci cnd sunt apelate cu un indice n afara
domeniului vectorului de numere. Se va defini o clas excepie Range local clasei
IntArray() i o rutin de tratare a execepiei.
212