Sunteți pe pagina 1din 212

1

Concepte de baz ale limbajului C++

Modalitile (tehnicile, paradigmele) de programare au evoluat de-a lungul


anilor, reflectnd trecerea de la programe de dimensiuni reduse, la programe i
aplicaii de dimensiuni foarte mari, pentru coordonarea crora sunt necesare tehnici
evoluate.
Software-ul de dimensiuni mari, care nu poate fi realizat de o singur
persoan, intr n categoria sistemelor complexe, cu o mare diversitate de aplicaii.
Situaiile reale i experiene ale psihologilor au relevat limitele capacitii
umane n perceperea sistemelor complexe, adic imposibilitatea unei persoane de a
percepe i controla simultan un numr mare de entiti de informaie. De aceea, este
esenial descompunerea i organizarea sistemelor complexe, pentru a fi percepute,
proiectate sau conduse. n sistemele complexe, ordinea este introdus n general printro organizare ierarhic, pe mai multe tipuri i nivele de ierarhie. n organizarea
ierarhic a sistemelor complexe se evideniaz dou tipuri de ierarhii: ierarhia de
agregare sau structural i ierarhia de tip.
n ierarhia de agregare se poate afirma despre un obiect c este o parte a altui
obiect, mai complex. Exemple de astfel de descompuneri se pot da din orice domeniu.
De exemplu, un calculator poate fi studiat prin descompunerea lui n subansamble
componente: placa de baz, placa video, monitor, etc; la rndul ei, placa de baz este
compus din placheta de circuit imprimat, procesor, memorie, etc. Acest fel de
ierarhie poate fi exprimat printr-o afirmaie de genul este o parte din.
Pe de alt parte, fiecare obiect poate fi ncadrat ntr-o categorie (clas, tip) mai
larg, care conine mai multe obiecte cu proprieti comune. De exemplu, procesorul
este o component electronic; monitorul este un dispozitiv de afiare, etc. Aceast
ierarhie se refer la apartenena obiectelor la o anumit clas (sau tip - este de
tipul).
Este esenial s privim sistemele complexe din ambele perspective, studiindule att din perspectiva ierarhiei de agregare, deci a obiectelor care le compun, ct i a
ierarhiei de tip, deci a claselor crora le aparin obiectele componente. La rndul lor,
clasele din care fac parte obiectele pot fi organizate sau studiate ca elemente
componente ale unei ierarhii, prin care o clas este considerat ca primitiv (printe) a
unei alte clase. Cele dou tipuri de ierarhii, ierarhia de agregare (structural) i ierarhia

Elemente de Programare Orientat pe Obiecte

de clase (de tip) nu sunt independente, i, mpreun, pot s reprezinte un sistem


complex.
La fel ca oricare sistem complex, software-ul poate fi controlat prin
descompunerea lui. Rolul descompunerii unui sistem n general (i al programelor n
special) este de a permite nelegerea i manevrarea acestuia: sistemul este descompus
n pri din ce n ce mai mici, fiecare dintre ele putnd fi rafinat i dezvoltat
independent. Principiul divide-et-impera, care se aplic n multe situaii, este util i
n programare. Se pot identifica dou metode de descompunere a programelor:
descompunerea algoritmic i descompunerea orientat pe obiecte.
Descompunerea algoritmic permite abordarea structurat a programrii.
Programul este mprit n module, n funcie de aciunile pe care trebuie s le
efectueze, fiecare modul se mparte n elemente funcionale (blocuri, proceduri,
funcii), ntr-o structurare de sus n jos (top-down), care urmrete diagrama de trecere
a datelor n cursul execuiei.
Descompunerea orientat pe obiecte este o alternativ de descompunere n
care programul se mparte dup obiectele care pot fi identificate, fiecare obiect avnd
asociate o mulime de operaii care sunt apelate n cursul execuiei.
O problem nu poate fi abordat simultan prin ambele metode. Care dintre ele
este cea mai bun? Nu exist un rspuns absolut i universal valabil, dar experiena a
dovedit c pentru sisteme de dimensiuni mari, descompunerea orientat pe obiecte este
mai eficient, mai sigur i mai flexibil.

1.1

Tehnici de programare

Modul n care este abordat programarea, din punct de vedere al


descompunerii programelor, definete mai multe tehnici de programare (paradigme),
care s-au dezvoltat i au evoluat odat cu evoluia sistemelor de calcul.
Programarea procedural este prima modalitate de programare care a fost i
este nc frecvent folosit. n programarea procedural accentul se pune pe
descompunerea programului n proceduri (funcii) care sunt apelate n ordinea de
desfurare a algoritmului. Limbajele care suport aceast tehnic de programare
prevd posibiliti de transfer a argumentelor ctre funcii i de returnare a valorilor
rezultate. Limbajul Fortran a fost primul limbaj de programare procedural. Au urmat
Algol60, Algol68, Pascal, iar C este unul din ultimele invenii n acest domeniu.
Programarea modular. n cursul evoluiei programrii procedurale,
accentul n proiectarea programelor s-a deplasat de la proiectarea procedurilor ctre
organizarea datelor, aceast deplasare reflectnd creterea dimensiunilor programelor.
O mulime de proceduri corelate, mpreun cu datele pe care le manevreaz, sunt
organizate ca un modul. Tehnica de programare modular decide descompunerea unui
program n module, care ncorporeaz o parte din datele programului i funciile care
le manevreaz. Aceast tehnic este cunoscut ca tehnic de ascundere a datelor (datahiding). n programarea modular stilul de programare este n continuare procedural,
iar datele i procedurile sunt grupate n module, existnd posibilitatea de ascundere a

1. Concepte de baz ale limbajului C++

unor informaii definite ntr-un modul, fa de celelalte module. Gruparea de date i


proceduri n module nu implic i o asociere strict ntre acestea.
Modularitatea i ascunderea informaiilor sunt caracteristici implicite n
programarea orientat pe obiecte.
Programarea orientat pe obiecte reprezint aplicarea n domeniul
programrii a unei metode larg rspndite n tehnic, numit tehnologia orientat pe
obiecte, care se bazeaz pe modelul obiect. Modelul obiect al unei aplicaii implic
patru principii importante i anume:
abstractizare;
ncapsulare;
modularitate;
ierarhizare.
n afara acestor principii, modelul obiect mai presupune: tipizare, concuren
i persisten. Aproape nici unul dintre aceste principii nu este nou, dar utilizarea lor
mpreun n modelul obiect, are un rol sinergetic, de potenare reciproc.
Primele aplicaii ale acestor principii au fost introduse n limbajul Simula, care
a stat la baza dezvoltrii ulterioare i a altor limbaje care permit abordarea modelului
obiect: Smaltalk, Object Pascal, C++, Clos, Ada, Eiffel.
n momentul de fa modelul obiect se dovedete a fi un concept unificator n
tiina calculatoarelor n general, fiind aplicabil nu numai n programare, ci i n
arhitectura calculatoarelor, n proiecatarea interfeelor utilizator, n baze de date.
Programarea orientat pe obiecte (object-oriented programming) este o
metod de programare n care programele sunt organizate ca i colecii de obiecte
cooperante, fiecare dintre ele reprezentnd o instan a unei clase, iar clasele sunt
membre ale unei ierarhii de clase, corelate ntre ele prin relaii de motenire.
Exist trei pri importante ale acestei definiii:
Se folosesc obiecte, nu algoritmi, ca uniti constructive de baz, obiectele
fiind componente ale unei ierarhii de agregare.
Fiecare obiect este o instan (un exemplar) al unei clase.
Clasele sunt componente ale unei ierarhii de tip (este de tipul), fiind
corelate ntre ele prin relaii de motenire.
Dac lipsete una din aceste caracteristici, programarea nu se mai numete
orientat pe obiecte, ci programare prin abstactizarea datelor, deoarece o clas este un
tip de date abstract.
Un limbaj este considerat un limbaj de programare orientat pe obiecte dac
satisface mai multe cerine, ca de exemplu:
Suport obiecte (instane ale unor clase), clasele fiind tipuri definite de
utilizator (numite i tipuri abstracte de date).
Tipurile (clasele) pot moteni atribute de la alte clase, numite clase de
baz.
Dac un limbaj nu suport direct motenirea ntre clase se numete limbaj de
programare bazat pe obiecte (object-based), cum este, de exemplu, limbajul Ada.

Elemente de Programare Orientat pe Obiecte

Principiile modelului obiect aplicate n programare, enunate mai sus, vor fi


prezentate pe scurt n continuare, detalierea lor fiind reluat n cursul expunerii, dup
descrierea suportului oferit de limbajul C++ fiecreia dintre acestea.
Abstractizarea nseamn identificarea similitudinilor ntre diferite entiti,
situaii sau procese din lumea real, decizia de a se concentra atenia asupra acestor
aspecte comune i ignorarea pentru nceput a detaliilor. Abstractizarea identific
trsturile caracteristice eseniale ale unui obiect, care l deosebesc de toate celelalte
feluri de obiecte.
ncapsularea este procesul de compartimentare a elementelor unei
abstractizri n dou pri: structura i comportarea; ncapsularea separ comportarea
(accesat prin interfa) de structur, definit prin implementare. ncapsularea prevede
granie ntre diferite abstractizri, ceea ce conduce la o separare clar a acestora.
Modularizarea este procesul de partiionare a unui program n componente
individuale (module) ceea ce permite reducerea complexitii programului prin
definirea unor granie bine stabilite i documentate n program. n practica
programrii, modularizarea const n partiionarea programului n module care pot fi
compilate separat, dar care au conexiuni cu alte module ale programului. Modulele
servesc ca i containere n care sunt declarate clasele i obiectele programului.
Ierarhizarea este o modalitate de a ordona abstractizrile (tipurile abstracte de
date). Ierarhiile pot s denote relaii de tip sau relaii de agregare. Relaiile de tip sunt
definite prin motenirile ntre clase, prin care o clas (clasa derivat) motenete
structura sau comportarea definit n alt clas (clasa de baz). Relaiile de agregare
specific compunerea unui obiect din mai multe obiecte mai simple. Agregarea nu este
un concept specific programrii orientate pe obiecte, deoarece i limbajele de
programare procedural prezint suport pentru agregare prin structuri de tip
nregistrare (record n Pascal, struct n C, etc). Combinaia ntre motenire i
agregare reprezint ns un instrument puternic de exprimare a ierarhizrii n
programarea orientat pe obiecte.
Tipizarea. Tipul este o caracterizare precis, structural i comportamental a
unei colecii de entiti. Dei conceptual tipul este similar noiunii de clas, tipizarea se
refer la un aspect diferit al modelului obiect de cel la care se refer clasele. Tipizarea
este o accentuare a clasei unui obiect, astfel nct obiecte de clase diferite nu pot fi
intershimbate ntre ele sau, cel mult, pot fi interschimbate ntr-un mod foarte restrictiv.
Din punct de vedere al tipizrii, exist limbaje puternic tipizate (ca de exemplu,
Eiffel), n care nu este admis nici o operaie asupra unui operand dac aceasta nu a
fost exact prevzut pentru obiectul respectiv. Alte limbaje, cum este Smalltalk, sunt
limbaje netipizate, care admit la compilare orice fel de operaii asupra operanzilor,
eroarea violrii de tip manifestndu-se n timpul execuiei. Limbajul C++ este hibrid.
El are tendina de tipizare puternic, dar este posibil ignorarea sau inhibarea regulilor
de tipizare. Dei nu face parte din principiile fundamentale ale modelului obiect,
tipizarea aduce beneficii importante n programarea orientat pe obiecte.
Concurena permite ca mai multe obiecte diferite s fie n execuie n acelai
timp. Sistemele care implic concurena sunt sisteme cu procesoare multiple, n care
mai multe procese sau thread-uri (fire de execuie) pot fi executate concurent pe
procesoare diferite.

1. Concepte de baz ale limbajului C++

Persistena. Un obiect n software ocup un anumit spaiu n memorie i are o


existen ntr-o perioad determinat de timp n cursul execuiei programului. De
multe ori este necesar ca datele s supravieuiasc programului (exemplul tipic fiind
bazele de date), iar definirea de ctre fiecare programator a modului cum sunt salvate
datele pentru a fi pstrate dup terminarea programului poate conduce ctre scheme de
memorare dintre cele mai diferite i greu interschimbabile. Prin conceptul de
persisten se introduce un mod unitar de salvare i restaurare a datelor coninute de
obiecte ale programului.
Modelul obiect n domeniul programrii implic, pe lng implementarea
programului ntr-un limbaj anume, etape premergtoare de analiz i proiectare care
pregtesc structurarea efectiv a programului.
Analiza orientat pe obiecte este o metod de analiz i examinare a unei
aplicaii din perspectiva claselor i a obiectelor din domeniul problemei respective.
Proiectarea orientat pe obiecte este o metod de proiectare a sistemelor care
implic descompunerea orientat pe obiecte i asigur o modalitate de reprezentare
att static ct i dinamic a modelului sistemului proiectat.
Produsele analizei orientate pe obiecte servesc ca modele ale proiectrii
orientate pe obiecte, iar rezultatele acesteia folosesc direct n programare. Ca suport
pentru crearea modelului final al unei aplicaii, programarea i limbajele orientate pe
obiecte se recomand a fi studiate mai nti, pentru a beneficia de toate facilitile i
tehnicile oferite de acestea analitilor, proiectanilor i programatorilor.
Limbajul C++ este unul dintre cele mai utilizate limbaje de programare
orientate pe obiecte; compilatoare, biblioteci i instrumente de dezvoltare a
programelor C++ sunt disponibile att pentru calculatoare personale ct i pentru cele
mai dezvoltate sisteme i staii de lucru.
Limbajul C++ este o versiune extins a limbajului C, extensiile acestuia fiind
elaborate de ctre B. Stroustrup n anul 1980 n laboratoarele Bell din Murray Hill,
New Jersey. Extensiile dezvoltate de Stroustrup pentru limbajul C++ permit
programarea orientat pe obiecte, pstrnd eficiena, flexibilitatea i concepia de baz
a limbajului C. Numele iniial a fost C cu clase, numele de C++ fiindu-i atribuit n
anul 1983.
Scopul pentru care a fost creat C++ este acelai cu scopul pentru care este
abordat n general programarea orientat pe obiecte: dezvoltarea i administrarea
programelor foarte mari. Chiar dac superioritatea limbajului C++ este evident n
cazul dezvoltrii programelor foarte mari, nu exist limitri n a fi folosit n orice fel
de aplicaie, deoarece C++ este un limbaj tot att de eficient ca i limbajul C.
De la apariia sa, C++ a trecut prin trei revizii, n 1985, 1989 i ultima,
prilejuit de definirea standardului ANSI pentru acest limbaj. O prim versiune a
standardului a fost publicat n anul 1994, iar urmtoarea versiune este nc n lucru.
n general, limbajul C++ prevede mai multe faciliti i mai puine restricii
dect limbajul C, astfel nct majoritatea construciilor din C sunt legale i au aceeai
semnificaie i n C++.

Elemente de Programare Orientat pe Obiecte

n acest capitol sunt prezentate unitar i concis conceptele de baz n


programarea C++, att cele care sunt preluate din limbajul C ct i cele nou introduse.
Multe dintre ele sunt reluate i dezvoltate pe parcursul seciunilor urmtoare.
Cel mai scurt program C++ este:
main(){ }

Acesta definete o funcie numit main (), care nu primete nici un


argument, nu execut nimic i nu returneaz nici o valoare.
Dac se dorete ca programul s scrie un mesaj la consol (de tipul Hello,
World!), pe lng utilizarea funciei obinuite din limbajul C (funcia printf()),
n C++ se poate utiliza i o funcie de scriere la ieirea standard (cout). Aceast
funcie este funcia operator de scriere << care este definit n fiierul antet
iostream.h. Un astfel de program este urmtorul:
#include <iostream.h>
void main(){
cout << Hello, World!<< endl;
}

Prima operaie de scriere afieaz la consol irul Hello, World!, iar


urmtoarea (endl) introduce caracterul de linie nou. Operaia de citire de la tastatur
poate fi realizat n C++ printr-o instruciune care folosete funcia operator de citire
>>. De exemplu, n instruciunile care urmeaz se citete de la tastatur un numr
ntreg:
int i;
cin >> i;

Desigur, funcia scanf() de citire de la tastatur din limbajul C poate fi n


continuare folosit, dar pe parcursul exemplificrilor se vor folosi mai mult aceste
funcii C++ de citire i de scriere la consol. Descrierea detaliat a operaiilor de
intrare-ieire din C++ se gsete n seciunea 6.
Un program C++ const dintr-unul sau mai multe fiiere (files). Un fiier este
o bucat de text care conine cod surs C++ i comenzi de preprocesare i este
translatat din punct de vedere conceptual n mai multe faze. Prima faz este faza de
preprocesare care execut includerea altor fiiere i substituirea macrourilor.
Preprocesarea este controlat prin intermediul directivelor introduse prin linii care au
primul caracter #. Rezultatul preprocesrii este secvena de entiti (token semne). O
astfel de secven de entiti, adic fiierul rezultat dup preprocesare, se numete
unitate de translatare (translation unit).

1. Concepte de baz ale limbajului C++

1.2

Convenii lexicale

Exist cinci tipuri de entiti: identificatori (identifiers), cuvinte-cheie


(keywords), literale (literals), operatori (operators) i separatori (separators).
Caracterele: spaiu (blank), tabulatori orizontali i verticali (tabs), linie nou (new
line), caractere de formatare (formfeeds) i comentariile, numite n ansamblu spaii
albe (white spaces) sunt ignorate, cu excepia situaiei cnd folosesc pentru separarea
entitilor. Un spaiu alb este necesar pentru separarea identificatorilor, a cuvintelorcheie i a constantelor.
Comentariile din C, considerate ntre perechile de caractere /* i */ sunt
valabile la fel, fr posibilitatea de a fi incluse unele n altele (imbricate). Caracterele
// marcheaz nceputul unui comentariu care dureaz pn la sfritul liniei.
Identificatori. Un identificator este o secven de lungime oarecare de litere
i cifre, cu condiia ca primul caracter s fie liter sau caracterul de subliniere _
(underscore). Literele mici sunt diferite de literele mari corespunztoare. Toate
caracterele ntr-un identificator sunt semnificative.
Cuvinte-cheie. Urmtorii identificatori sunt rezervai pentru a fi utilizai ca i
cuvinte-cheie, i nu pot avea alt utilizare:
asm
auto
break
case
catch
char
class
const

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

n plus, identificatori care conin dou caractere de subliniere (underscore) sau


ncep cu un astfel de caracter sunt utilizai n implementrile compilatoarelor C++ i
ale bibliotecilor i se recomand s fie evitai.
Literale (constante). Constantele se refer la valori fixe, pe care programul
nu le poate modifica. Exist mai multe feluri de constante: constante ntregi, constante
caracter, constante reale i constante ir de caractere.
O constant ntreg este reprezentat ca o succesiune de cifre; ea este
considerat n baz zecimal dac prima cifr este diferit de 0, n baza hexazecimal
dac ncepe cu 0x sau 0X, sau n baz octal, dac ncepe cu 0 (i acesta nu este urmat
de x sau X). Tipul unei constante ntregi depinde de valoarea acesteia i este astfel
stabilit nct s poat cuprinde valoarea dat.
O constant caracter este compus din unul sau mai multe caractere cuprinse
ntre ghilimele simple (de exemplu, x). O constant de un singur caracter este de
tipul char. Constantele multicaracter au tipul ntreg. Trebuie remarcat diferena fa
de limbajul C, n care constantele caracter au tipul int.

Elemente de Programare Orientat pe Obiecte

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

Un nume (name) poate reprezenta un obiect, un tip, o enumeraie, o funcie, o


mulime de funcii, un membru al unei clase, o valoare sau o etichet. Un nume este
introdus n program printr-o declaraie (declaration). Un nume poate fi folosit numai
ntr-o zon a programului, denumit domeniul numelui (scope).
Un obiect (object) este o regiune de memorie, iar semnificaia valorilor care
se afl memorate ntr-un obiect depind de tipul lui. Orice obiect are o durat de via
(lifetime) care depinde de clasa de memorare creia i aparine.
O declaraie (declaration) introduce unul sau mai multe nume ntr-un
program. ntr-o declaraie a unui nume se poate introduce, opional, o expresie de
iniializare (iniializator) pentru acel nume (de exemplu: int i = 1;)
O definiie (definition) stabilete entitatea la care se refer numele respectiv.
Ea rezerv cantitatea necesar de memorie obiectului respectiv i execut iniializrile
corespunztoare. O declaraie este i o definiie, cu excepia urmtoarelor situaii:
conine specificatorul extern fr o expresie de iniializare sau corp al
funciei;
este o declaraie a unei funcii fr specificarea corpului acesteia;
este o declaraie a numelui unei clase;
este o declaraie typedef;
este o declaraie a unui membru de tip static al unei clase.
De exemplu, urmtoarele sunt definiii:
int a;
extern const c = 1;
int f(int x) {return x+1;}
enum {up, down};

n timp ce urmtoarele sunt doar declaraii:


extern int a;
extern const c;
int f(int);

1. Concepte de baz ale limbajului C++

struct S;
typedef int Int;

Trebuie s existe o singur definiie a fiecrui obiect, funcie, clas sau


enumeraie folosit n program. Dac o funcie nu este niciodat apelat sau dac
numele unei clase nu este niciodat folosit ntr-un mod care s necesite definiia
acesteia, ele pot s nu fie definite. O declaraie poate fi repetat de mai multe ori n
program, n timp ce definiiile nu pot fi repetate. De exemplu:
struct S;
struct S;
int a;
int a;

//
//
//
//

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
}

Elemente de Programare Orientat pe Obiecte

10

Un nume cu domeniu fiier poate fi accesat ntr-un domeniu n care este


ascuns prin redefinire, dac se folosete operatorul de rezoluie pentru nume globale
(operatorul :: fr nici un nume n faa lui).
Redefinirea unui nume este admis numai n domenii diferite. Dac unul din
domenii este inclus n cellalt domeniu, atunci redefinirea provoac ascunderea
numelui din domeniul exterior n domeniul interior. Redefinirea n domenii identice
produce eroare de compilare.
Durata de via a obiectelor. Un obiect este creat atunci cnd se ntlnete
definiia lui (care este unic) i este distrus (n mod automat) atunci cnd se prsete
domeniul lui de definiie. Un obiect cu un nume global se creeaz i se iniializeaz o
singur dat i are durata de via pn la terminarea programului. Obiectele locale se
creeaz de fiecare dat cnd execuia programului ajunge n punctul de definire a
acestora, cu excepia variabilelor locale declarate de tip static.
O variabil (obiect) se declar de tip static prin prefixarea declaraiei
acesteia cu cuvntul-cheie static. De exemplu, execuia programului:
#include <iostream.h>
int a = 0;
//variabila globala se initializeaza
// inainte de nceperea executiei
void f(){
int b = 1;
// se creaza si se initializeaza
// la fiecare apel al functiei f()
a++; b++;
static int c = a; //se initializeaza o singura data
//la prima executie a instruct.
c += 2;
cout << a = <<a << b = << b<< c = << c <<endl;
}
void main (){
for (int i=0;i<3;i++)
f();
}

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.

1. Concepte de baz ale limbajului C++

11

Obiectele care sunt declarate automatice (folosind specificatorul auto la


nceputul declaraiei) sunt obiecte locale (declarate ntr-un bloc) care se iniializeaz la
fiecare invocare a blocului. Utilizarea acestui specificator este redundant i rareori
este folosit.
O declaraie register este o declaraie auto care mai indic n plus
compilatorului c acel obiect este intens folosit i, ca urmare, compilatorul va ncerca
s-i aloce spaiu de memorare ntr-un registru al procesorului.
Un obiect global poate fi declarat o singur dat ntr-unul din fiierele
programului. Pentru a fi cunoscut i n alte fiiere din program, n celelate fiiere se
declar acel obiect folosind specificatorul extern.
Un obiect declarat folosind specificatorul static este memorat permanent i
nu este iniializat dect o singur dat. Un astfel de obiect nu este cunoscut dect n
interiorul fiierului sau funciei n care este declarat i nu poate fi declarat extern.
Se pot remarca cele dou semnificaii ale specificatorului static: o semnificaie se
refer la domeniul de vizibilitate, pe care l restrnge la nivel de funcie, respectiv
fiier; cealalt semnificaie se refer la clasa de memorare i asigur o memorare
permanent a obiectului i o durat de via egal cu durata de execuie a programului.
Aceast dubl aciune a specificatorului static difereniaz
comportamentul unui obiect declarat static dup domeniul lui de definiie.
Astfel, un obiect global de tip static este memorat permanent, dar este
cunoscut numai n fiierul n care a fost declarat. n aceast situaie, specificatorul
static are doar rolul de a restrnge domeniul de vizibilitate al obiectului.
Pentru un obiect local specificatorul static are rolul de a modifica clasa de
memorare: obiectul are domeniu de definiie local (este cunoscut numai n blocul n
care a fost declarat), dar este memorat permanent i i pstreaz valorile ntre invocri
succesive ale blocului respectiv.

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

n C++ sunt definite urmtoarele tipuri fundamentale:

Tipuri de ntreg, pentru definirea numerelor ntregi de diferite dimensiuni


(ca numr de octei ocupai n memorie):

char
short int

1 octet
2 octei

Elemente de Programare Orientat pe Obiecte

12
int
long

2 sau 4 octei
4 sau 8 octei

Tipuri de numere flotante, pentru definirea numerelor reale (reprezentate


ca numere cu virgul flotant):

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).

Tipul void specific o mulime vid de valori. Nu se poate declara un


obiect cu acest tip, dar acest tip poate fi utilizat n conversii de pointeri i
ca tip de returnare al unei funcii

1.4.2

Tipuri derivate

Se pot defini conceptual un numr infinit de tipuri derivate pornind de la


tipurile fundamentale. Tipurile derivate sunt:
tablouri de obiecte,
pointeri la obiecte,
referine,
funcii,
constante simbolice,
clase, structuri, uniuni,
pointeri la membrii claselor.
n continuare se vor prezenta primele cinci tipuri derivate, iar celelate vor fi
introduse pe parcursul seciunii urmtoare. Ca terminologie, clasele (mpreun cu
structurile i uniunile) sunt denumite tipuri definite de utilizator (user-defined types),
iar celelate tipuri sunt denumite tipuri predefinite (built-in types)
1.4.2.1 Tablouri de obiecte
Un tablou (array) de obiecte poate fi construit din obiecte dintr-un tip
fundamental (cu excepia tipului void), din pointeri, din enumeraii sau din alte
tablouri. n traducere, pentru array se mai ntlnesc termenii vector i matrice. n acest
text sunt folosii termenii tablou (pentru array multidimensional) i vector (pentru
array unidimensional).
Declaraia: T D[expresie] introduce un tablou de obiecte de tipul T,
cu numele D i cu un numr de elemente al tabloului dat de valoarea expresiei, care
trebuie s fie de tip constant ntreg. Pentru valoarea N a acestei expresii, tabloul are N
elemente, numerotate de la 0 la N-1.
Un tablou bidimensional se poate construi printr-o declaraie de forma:
T D[dim1][dim2];

1. Concepte de baz ale limbajului C++

13

i reprezint dim1 tablouri unidimensionale, fiecare de dimensiune dim2. Elementele


tabloului bidimensional se memoreaz cu valori succesive pentru indicele din dreapta
astfel:
D[0][0], D[0][1], D[0][dim2-1],
D[1][0], D[1][1], D[1][dim2-1],.
D[dim1-1][0], D[dim1-1][1], D[dim1-1][dim2-1].

ntr-un mod asemntor se pot construi tablouri multidimensionale, cu o


limitare a numrului de dimensiuni care depinde de implementare.
1.4.2.2 Pointeri
Pentru majoritatea tipurilor T, T* este un tip denumit pointer la T,
adic o variabil de tipul T* memoreaz adresa unui obiect de tipul T.
Operaia fundamental asupra unui pointer este operaia de derefereniere
(dereferencing), adic accesarea obiectului a crui adres o reprezint pointerul
respectiv. Operatorul de derefereniere este operatorul unar *. De exemplu:
char c1 = a;
char* p1 = &c1;
char c2 = *p1;

// 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;
}

Funcia strlen() returneaz numrul de caractere ale irului, fr


caracterul terminal 0, folosind operaia de incrementare a pointerului i operaia de
derefereniere pentru a testa valoarea caracterului. O alt implementare posibil a
funciei este urmtoarea:
int strlen(char* p){
char* q = p;
while (*q++);
return q-p-1;
}

Elemente de Programare Orientat pe Obiecte

14

n C++, ca i n limbajul C, pointerii i tablourile sunt puternic corelate. Un


nume al unui tablou poate fi folosit ca un pointer la primul element al tabloului. De
exemplu, se poate scrie:
char alpha[] = abcdef;
char* p = alpha;
char* q = &alpha[0]; // p = q

Rezultatul aplicrii operatorilor aritmetici +, -, ++, -- asupra pointerilor


depinde de tipul obiectului indicat. Atunci cnd se aplic un operator aritmetic unui
pointer p de tip T*, se consider c p indic un element al unui tablou de obiecte de
tip T; p+1 va indica urmtorul element al tabloului, iar p-1 va indica elementul
precedent al tabloului. Acest lucru nseamn c valoarea lui p+1 este cu sizeof(T)
octei mai mare dect valoarea lui p.
Pointeri la funcii vor fi prezentai la paragraful de descriere a funciilor.
Pointerii la membrii nestatici ai claselor nu sunt considerai pointeri la obiecte i vor fi
prezentai n seciunea urmtoare.
1.4.2.3 Referine
O referin (reference) este un nume alternativ al unui obiect. Utilizarea
principal a referinelor se face pentru specificarea argumentelor i a valorilor
returnate de funcii, n general, i pentru suprancrcarea operatorilor n special.
Notaia X& nseamn referin la un obiect de tipul X. De exemplu:
int i = 1;
int& r = i;
int x = r;
r++;

// r i i se refer la aceeai entitate


// x = 1
// i = 2;

Implementarea obinuit a unei referine se face printr-un pointer constant


care este derefereniat de fiecare dat cnd este utilizat.
Aa cum se poate observa, pentru definirea unei referine se folosete
operatorul adres &, dar difer tipul construciei n care este folosit. De exemplu:
int a = 5;
int* pi = &a;
int& r = a;

//
//
//
//

& calculeaz adresa;


pi este adresa lui a
& introduce o referinta;
r este o referin (alt nume) pt. a

O referin este utilizat ca argument pentru o funcie care poate s modifice


valoarea acestui argument. De exemplu:
void incr(int& x) {x++;}
void f(){
int i = 1;
incr(i); // i = 2;
}

1. Concepte de baz ale limbajului C++

15

O alt utilizare important a referinelor este pentru definirea funciilor care


pot fi folosite att ca membru drept ct i ca membru stng al unei expresii de
asignare. De exemplu:
#include <iostream.h>
int& fr(int v[], int i){
return v[i];
}
void main(){
int x[] = {1,2,3,4};
fr(x,2) = 7;
cout <<fr(x,0)<<fr(x,1)<<fr(x,2)<< fr(x,3)<<endl;
}

La execuia acestui program se obine mesajul:


1

Deoarece valoarea returnat de funcie este referina (numele) unui element al


vectorului, acesta poate fi modificat prin folosirea funciei fr() ca membru stng al
egalitii.
1.4.2.4 Funcii
Funciile sunt tipuri derivate i reprezint una din cele mai importante
caracteristici ale limbajelor C i C++. Forma general de definire a unei funcii este:
tip_returnat nume_func(tip1 arg1,tip2 arg2,,tipn argn)
{
//corpul functiei
}

Funcia cu numele nume_func returneaz o valoare de tip tip_returnat


i are un numr n de argumente formale declarate ca tip i nume n lista de argumente
formale. Argumentele formale din declaraia unei funcii se mai numesc i parametrii
funciei. Dac o funcie nu are argumente, atunci lista din parantezele rotunde este
vid. Notaia din C: f(void) este redundant.
O funcie este un nume global, dac nu este declarat de tip static. O
funcie declarat static are domeniul de vizibilitate restrns la fiierul n care a fost
definit.
Corpul funciei este propriu ei i nu poate fi accesat din afara acesteia (nici
printr-o instruciune goto). Corpul unei funcii este o instruciune compus, adic o
succesiune de instruciuni i declaraii incluse ntre acolade. n corpul funciei se pot
defini variabile, care sunt locale i se memoreaz n segmentul de stiv al programului.
Dac nu sunt declarate static, variabilele locale se creaz la fiecare apel al funciei
i se distrug atunci cnd este prsit blocul n care au fost definite. Nu se pot defini
funcii n interiorul unei funcii. Argumentele formale ale unei funcii sunt considerate
variabile locale ale funciei, ca orice alt variabil definit n funcia respectiv.

Elemente de Programare Orientat pe Obiecte

16

Dac o funcie nu are de returnat nici o valoare, atunci tip_returnat din


declaraia funciei este tipul void i nu este necesar o instruciune de returnare
(return) n funcie. n toate celelalte cazuri, n corpul funciei trebuie s fie
prevzut returnarea unei variabile de tipul tip_returnat, folosind instruciunea
return. Dac n definiie nu este prevzut un tip_returnat, se consider
implicit returnarea unei valori de tip ntreg.
Prototipurile funciilor. Pentru apelul unei funcii este necesar cunoaterea
definiiei sau a prototipului acesteia. Prototipul unei funcii este de forma:
tip_returnat nume_func(tip1 arg1,., tipn argn);

Numele argumentelor formale sunt opionale n prototipul unei funcii. Prototipurile


permit compilatorului s verifice tipurile argumentelor de apel i s semnaleze eroare
la conversii ilegale. Spre deosebire de limbajul C, unde este admis i simpla
declaraie a numelui funciei (fr tipurile argumentelor de apel), utilizarea
prototipurilor este obligatorie n C++. De exemplu:
double f2(int, double);
double f3(int a, double f){
/*..*/
double t = f/a;
return t;

// 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) {

// definiie funcie f2()

a*f;

La compilare apare o eroare datorit apelului funciei f1(), care nu este


definit, nici declarat prin prototip n domeniul funciei apelante fp() i o eroare
datorat apelului funciei f3() cu un argument (argumentul al doilea) care nu poate fi
convertit la tipul argumentului formal.
Transferul argumentelor funciilor. La apelul unei funcii, argumentele de
apel (se mai numesc i argumente reale sau efective) iniializeaz argumentele formale

1. Concepte de baz ale limbajului C++

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

Aceeai funcie swap(), folosind argumente de tip referin, arat astfel:


void swap(int& x, int& y){
int t;
t = x;
x = y;
y = t;
}

Se poate observa perfecta simetrie ntre cele dou implementri i c, n mod


evident, referina folosete adresa variabilei pentru a o putea modifica (deci un
pointer). Dar, n cazul referinelor, pointerul i defererenierea sunt ascunse,
programatorul nu trebuie s le prevad explicit, programul rezultat este mai concis i
mai clar.
Referinele sunt deosebit de utile n apelul funciilor ale cror argumente sunt
obiecte de dimensiuni mari i copierea lor n argumentele formale (plasate n
segmentul de stiv al programului) ar fi foarte ineficient.
Argumente implicite ale funciilor. Se ntmpl frecvent ca o funcie s aib
un numr mai mare de argumente dect sunt necesare n cazurile simple dar frecvente
de apel. Dac nu este necesar s fie transmis ntotdeauna valoarea real a unui
argument i acesta poate lua, de cele mai multe ori, o valoare implicit, atunci n
declaraia funciei se prevede o expresie de iniializare a acestui argument, iar din apel
poate s lipseasc valoarea argumentului corespunztor.
De exemplu, o funcie pentru stabilirea datei calendaristice, care prevede
valori implicite pentru argumentele luna i an:

Elemente de Programare Orientat pe Obiecte

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
}

Numai argumentele de la sfritul listei pot fi argumente implicite. De


exemplu, este eronat urmtoarea declaraie:
void setdata(int zi, int luna=9, int an); // eroare

Pointeri la funcii. Dat fiind c o funcie are o localizare n memorie, aceast


valoare poate fi atribuit unui pointer. Adresa unei funciei este punctul de intrare n
funcie i, de aceea, funcia poate fi apelat folosind un pointer.
Tipul unui pointer la o funcie se definete folosind tipul valorii returnate i
tipurile argumentelor formale ale funciei. nainte de apelul unei funcii prin pointer,
trebuie s fie asignat valoarea pointerului folosind numele funciei. De exemplu:
float func1(int x, float y){
return x + y;
}
float func2(int x, float y){
return x * y;
}
void main(){
float (*pf)(int, float);

pf = func1;
float z = (*pf)(3, 1.2f);
cout << z << endl;
pf = func2;
z = (*pf)(3, 1.2f);
cout << z << endl;

// definire tip pointer


//
//
//
//
//
//

asignare valoare pointer


apel functie
afiseaza 4.2
asignare valoare pointer
apel functie
afiseaza 3.6

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.

1. Concepte de baz ale limbajului C++

19

Suprancrcarea funciilor. n limbajul C, fiecare funcie definit n program


trebuie s aib un nume diferit. n C++, mai multe funcii pot avea acelai nume n
acelai domeniu de definiie, dac se pot diferenia prin numrul sau tipul
argumentelor de apel. Acest mecanism se numete suprancrcarea funciilor
(function overloading). n traducere, se mai ntrebuineaz i denumirile de
suprapunere, redefinire, supradefinire; n textul de fa este preferat termenul de
suprancrcare. Suprancrcarea se poate aplica att funciilor ct i operatorilor; n
seciunea aceasta este prezentat suprancrcarea funciilor, iar suprancrcarea
operatorilor este descris n seciunea 4.
Dac n acelai domeniu sunt definite mai multe funcii cu acelai nume, la
fiecare apel se selecteaz funcia corect prin compararea tipurilor argumentelor reale
de apel cu tipurile argumentelor formale ale funciei. De exemplu:
double abs(double);
int abs(int);
abs(1);
abs(1.0);

// 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
}

Situaii asemntoare, de ascundere i nu de suprancrcare n domenii diferite


vor mai fi prezentate n seciunea 5, dedicat claselor derivate.
Selecia funciei la apel se bazeaz pe cea mai bun potrivire a argumentelor
(best argument matching). Dac printre funciile suprancrcate exist o funcie care
are toate argumentele de tipuri identice cu argumentele din apel, atunci aceast funcie
prezint cea mai bun potrivire a argumentelor i este selectat.
Se consider de exemplu funcia suprancrcat de calcul a ridicrii la o putere
a unui numr, funcia power():
int power(int, int);
long power(long,int);
double power(double, int);
double power(double, double);
power(7, 8);
// selecteaza power(int, int)
power(5.6, 2);
// selecteaza power(double, int)
power(6.7, 2.5);
// selecteaza power(double, double)

Elemente de Programare Orientat pe Obiecte

20

n aceste apeluri s-a selectat versiunea funciei power() pe baza coincidenei


dintre argumentele de apel i argumentele formale.
n cazul n care nu exist o astfel de coinciden, se fac conversii ale
argumentelor efective (de apel) ctre tipul argumentelor formale. Compilatorul
ncearc cea mai bun potrivire pentru fiecare argument n parte prin conversii pentru
tipuri predefinite care s nu conduc la trunchieri (pierderi de informaie). De
exemplu:
long a = 100;
int b = 10;
float c = 2.3f;
float d = 5.6f;
double e = 7.0;
double f = 2;
power(a,b);
power((int)a, b);
power(c,a);
power(c,d);
power(e,d);

//
//
//
//
//

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

Deoarece constantele nu pot fi modificate, ele trebuie s fie iniializate n


declaraie. ncercarea de modificare ulterioar este detectat ca eroare n timpul
compilrii:
val++;
d = 200;

// eroare
// eroare

1. Concepte de baz ale limbajului C++

21

Cuvntul-cheie const modific tipul obiectului, restricionnd modul n care


acesta poate fi folosit.
Un aspect interesant i intens folosit n programare, este acela de a declara
pointeri la constante. Atunci cnd se folosete un pointer, sunt implicate dou obiecte:
pointerul nsui i obiectul ctre care indic pointerul.
Prin prefixarea declaraiei unui pointer cu cuvntul const, obiectul indicat
este fcut constant, nu pointerul nsui. De exemplu:
const char* pc = abcd;// pc este pointer la o constant
pc[2] = m;
// eroare, nu se poate modifica
// obiectul constant
pc = ghij;
// corect, este modificat
// valoarea pointerului
pc++;
// corect

Pentru ca pointerul nsui s fie constant, se folosete operatorul *const:


char *const cp = abcd;// cp este pointer constant;
cp[2] = m;
// corect, modifica valoarea
cp++;
// eroare, pointer constant

Posibilitatea de declarare a pointerilor la constante este folosit n special


pentru transmiterea argumentelor funciilor. Prin declararea unui argument de tip
pointer la constant, este interzis modificarea de ctre funcie a obiectului indicat. De
exemplu:
char* strcpy(char* d, const char* s);

n aceast funcie irul s nu poate fi modificat.


n mod asemntor, specificatorul const care nsoete un argument tip
referin la apelul unei funcii, mpiedic modificarea acestuia de ctre funcia
respectiv.
Enumeraii. O alt metod de a defini constante ntregi este prin enumeraie.
De exemplu, enumeraia:
enum {
INPUT,
OUTPUT,
INOUT,
};

este echivalent cu declararea urmtoarelor constante:


const INPUT = 0;
const OUTPUT = 1;
const INOUT = 2;

Valorile asignate implicit constantelor ncep de la 0 i sunt n ordine cresctoare.


Enumeraiile pot avea nume. De exemplu:
enum states{

Elemente de Programare Orientat pe Obiecte

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

Valorile de iniializare nu trebuie neaprat s ia valori succesive.


1.4.2.6 Specificatorul typedef
Declaraiile care conin cuvntul-cheie typedef declar identificatori care
pot fi folosii pentru denumirea tipurilor fundamentale sau derivate n domeniile de
definiie ale acestora. O declaraie care conine specificatorul typedef nu introduce
un nume nou n program, ci realizeaz o echivalen sintactic a unui nume nou cu un
alt nume, fundamental sau derivat. De exemplu, n declaraiile:
typedef int KM, *PKM;
KM dist;
PKM pd;

KM este de tip ntreg, iar pd este pointer la ntreg.


O structur fr nume definit printr-un specificator typedef capt numele
introdus de acesta:
typedef struct { /**/} S; //structura este denumit S.
S struct1;

Acesta este modul obinuit de declaraie n C, unde numele unei structuri nu


poate fi folosit ca un nume de tip fr s fie nsoit de cuvntul-cheie struct. Acest
stil este admis i n C++, pentru a se asigura compatibilitatea. Dar stilul mai normal
pentru C++ este de a declara o structur cu nume i fr typedef:
struct SC{/*...*/};
SC struct2;

1. Concepte de baz ale limbajului C++

23

1.4.2.7 Specificatorul volatile


Specificatorul volatile indic faptul c valoarea obiectului poate fi
modificat pe alte ci dect cele declarate explicit n program. De exemplu, adresa
unei variabile globale poate fi transmis unei rutine de interfa a sistemului, care
modific valoarea acesteia, fr o instruciune explicit din program. Acest lucru este
important, deoarece majoritatea compilatoarelor de C i C++ optimizeaz automat
evaluarea expresiilor prin presupunerea c o variabil rmne neschimbat atta timp
ct nu apare n partea stng a unei operaii de asignare. Specificatorul volatile
mpiedic aplicarea unor astfel de optimizri.
1.4.2.8 Clase, structuri, uniuni
Pe lng tipurile de date fundamentale ale limbajului, n C++ se pot defini
tipuri de date noi prin definirea unor clase. Aceste tipuri se numesc tipuri definite de
utilizator (user-defined types) i se pot utiliza ntr-un mod asemntor utilizrii
tipurilor predefinite. Aceste tipuri mai sunt denumite i tipuri de date abstracte
(abstract data types), dar n aceast lucrare s-a folosit termenul de tip definit de
utilizator, aa cum l prefer autorul limbajului. De exemplu, declaraia:
class X {
// corpul clasei X
// declaraii de date i funcii membre
};

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;

// obiect obx1 de tipul X

Clasele, structurile i uniunile n C++, precum i pointeri la membrii claselor,


vor fi prezentate detaliat n seciunea urmtoare. n acest punct au fost introduse doar
pentru precizrile legate de tipuri, declaraii i definiii.

1.5

Expresii i operatori

O expresie este o secven de operatori i operanzi care specific executarea


unor calcule. n C++ operatorii pot fi suprancrcai, adic li se poate atribui un anumit
mod de operare atunci cnd se aplic tipurilor definite de utilizator (clase). Operatorii
suprancrcai urmeaz aceleai reguli sintactice ca i operatorii normali, dar operaiile
efectuate pot s difere de ale acestora. Suprancrcarea operatorilor nu poate modifica
regulile de aplicare ale acestora asupra tipurilor pentru care ei sunt definii n limbaj.
Ordinea de evaluare a unei subexpresii este determinat de precedena
(precedence) i gruparea operatorilor. Regulile matematice obinuite de asociativitate

Elemente de Programare Orientat pe Obiecte

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.

1.5.1 Operatori specifici C++


Majoritatea operatorilor C++ sunt preluai din limbajul C, cu aceeai sintax i
reguli de operare. n plus fa de operatorii C, n C++ mai sunt introdui urmtorii
operatori:
operatorul de rezoluie (::)
operatorul de lansare excepie (throw)
operatorii de alocare-eliberare dinamic a memoriei new i delete.
Operatorul de lansare excepie (throw) este descris n seciunea 8. Ceilali
operatori sunt descrii n continuare.
1.5.1.1 Operatorul de rezoluie
Operatorul de rezoluie (::) este folosit pentru modificarea domeniului de
vizibilitate al unui nume. Pentru acest operator (scope resolution operator), n
traduceri se mai ntlnesc termenii de operator de domeniu sau operator de acces.
Operatorul de rezoluie permite folosirea unui identificator ntr-un bloc n care el nu
este vizibil. Dac operatorul de rezoluie nu este precedat de nici un nume de clas,
atunci este accesat numele global care urmeaz acestui operator. De exemplu:
int g = 10;
int f(){
int g = 20;
//
return ::g;
}
void main(){
cout << f() << endl; // afiseaza 10
}

n acest exemplu operatorul de rezoluie a fost folosit pentru a accesa variabila


global g, ascuns de variabila local cu acelai nume din funcie. Deoarece utilizarea
cea mai extins a operatorului de rezoluie este legat de utilizarea claselor, el va fi
reluat pe parcursul seciunilor urmtoare.

1. Concepte de baz ale limbajului C++

25

1.5.1.2 Operatorii new i delete


n limbajul C se pot aloca dinamic zone n memoria liber (heap) folosind
funcii de bibliotec (de exemplu, malloc(), calloc(), realloc()) i se pot
elibera folosind funcia free(). La aceste posibiliti, care se pstreaz n continuare
n C++, se adaug operatorii de alocare i eliberare dinamic a memoriei, new i
delete. Aceti operatori unari prezint avantaje substaniale fa de funciile de
alocare din C i de aceea sunt n mod evident preferai n programele scrise n C++.
Pentru alocarea unei singure date (obiect), operatorul new are urmtoarea
form general:
tip_data* p = new tip_data(initializare);

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];

La alocarea unui vector nu se poate transmite o expresie de iniializare a zonei


de memorie alocat.
Operatorul delete elibereaz o zon din memoria heap. El poate avea una
din urmtoarele forme:
delete p; delete []p;

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;

//
//
//
//

alocare int i iniializare


alocare double neinitializat
vector de 12 caractere
vector de 20 caractere

Elemente de Programare Orientat pe Obiecte

26
delete pc1;
delete []pc2;

//corect, char e tip predefinit


// corect, elibereaza vector

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

1. Concepte de baz ale limbajului C++

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

<<

depl. logic stnga

expr << expr

>>

depl. logic dreapta

expr >> expr

<

mai mic

expr < expr

<=

mai mic sau egal

expr <= expr

>

mai mare

expr > expr

>=

mai mare sau egal

expr >=expr

==

egal

expr == expr

!=

diferit

expr != expr

&

AND orientat pe bii

expr & expr

XOR orientat pe bii

expr ^ expr

OR orientat pe bii

expr | expr

&&

AND logic (I)

expr && expr

||

OR logic (SAU)

expr || expr

?:

expresie condi.

expr ? expr : expr

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

Elemente de Programare Orientat pe Obiecte

28
<<=

depl. stg i asignare

expr <<= expr

>>=

depl. dr. i asignare

expr >>= expr

&=

AND i asignare

expr &= expr

|=

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

Atunci cnd ntr-o expresie sunt combinate constante i variabile de tipuri


diferite, ele sunt convertite la acelai tip, n general n tipul cu valoarea cea mai mare,
aciune care se numete promovarea tipului. Mai nti toate valorile de tip char i
short int sunt convertite n int (promovare la ntreg). Dup aceea, toate celelate
conversii sunt efectuate dup urmtorul algoritm:
dac un element este long double
atunci urmtorul este convertit n long double
altfel dac un element este double
atunci urmtorul este convertit n double
altfel dac un element este float
atunci urmtorul este convertit n float
altfel dac un element este unsigned long
atunci urmtorul este convertit n unsigned long
altfel dac un element este long int i
urmtorul este unsigned int
dac long int poate reprezenta toate
valorile unui unsigned int
atunci unsigned int este convertit n long int

1. Concepte de baz ale limbajului C++

29

altfel amndou sunt conv.n unsigned long int


altfel dac un element este unsigned int
atunci urmtorul este convertit n unsigned int

Dup aplicarea acestor reguli de conversie, fiecare pereche de elemente este


de acelai tip, iar rezultatul fiecrei operaii este de acelai tip cu cel al ambelor
elemente.

1.6.2

Conversia pointerilor

Urmtoarele conversii standard se aplic pointerilor:


O expresie constant evaluat la zero este convertit ntr-un pointer, numit
pointer nul. Un pointer nul nu este neaprat reprezentat ca un ntreg cu
valoarea 0.
Un pointer la orice valoare neconstant i nevolatil poate fi convertit n
void*.
Un pointer la funcie poate fi convertit n void*, cu condiia ca pointerul
void* s aib capacitate de reprezentare suficient pentru toate valorile
posibile ale unui astfel de pointer la funcie.
Numele unui tablou poate fi convertit n pointer la primul element al
tabloului.
Conversiile ntre pointerii claselor de baz i derivate, precum i conversia
ntre referine la clase de baz i derivate sunt prezentate n seciunea 5.
Alte conversii (descrise n seciunile urmtoare) sunt posibile prin:
Iniializarea obiectelor de tipuri definite de utilizator (clase);
Suprancrcarea operatorului de conversie.
Conversiile ntre tipurile de date din C++ se supun principiului tipizrii
propriu modelului obiect. Se poate observa faptul c C++ este un limbaj hibrid din
punct de vedere al tipizrii: sunt verificate conversiile ntre tipurile de date i sunt
rejectate acele conversii considerate neadmisibile. Pe de alt parte, limbajul admite
forarea conversiilor (prin operatorul de conversie cast), situaie n care este inhibat
verificarea de tip a operanzilor. Dar aceast forare se face pe rspunderea
programatorului, care are posibilitatea s le prevad numai pe acelea pe care le
consider corecte.

1.7

Fiierele componente ale unui program C++

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).

Elemente de Programare Orientat pe Obiecte

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

Variabila x i funcia f() utilizate n funcia g() din fiierul


fisier2.cpp sunt definite n fisier1.cpp; cuvntul-cheie extern din
declaraia variabile x n fisier2.cpp este doar o declaraie, nu o definiie. La fel,
prototipul funciei f()este declarat n fiier2.cpp, iar definiia acesteia se
gsete n fiier1.cpp.
Erorile de programare care se refer la definiii multiple (n fiiere diferite), la
lipsa definiiilor sau la neconcordana ntre definiii i declaraii sunt detectate i
semnalate de linker.
Numele declarate static sunt locale n fiecare dintre fiiere. Obiectele sau
funciile cu nume cunoscute numai ntr-un fiier au linkare intern (internal linkage).
Obiectele sau funciile cu nume care sunt cunoscute n mai mult de un fiier au linkare
extern (external linkage). Numele tipurilor (numele claselor i ale enumeraiilor) nu
au nici o linkare, dar ele trebuie s fie definite n mod unic n program.

1. Concepte de baz ale limbajului C++

1.7.2

31

Fiiere antet

Pentru a se asigura consistena declaraiilor ntr-un program compus din mai


multe fiiere se folosesc fiiere antet (header files) care conin informaii de interfa
ntre unitile programului i sunt incluse n fiierele surs. Includerea unui fiier (cel
mai probabil fiier antet) ntr-un alt fiier surs se specific prin directiva:
#include nume_fisier

La ntlnirea acestei directive, se nlocuiete aceast linie surs cu ntreg fiierul


nume_fisier. Numele fiierului inclus se ncadreaz ntre ghilimele dac este n
directorul curent sau ntre paranteze ascuite <> dac este n directorul standard de
includeri. Fiierele antet au extensia .h, iar celelate fiiere surs au una din extensiile
admise de compilatorul respectiv (.C, .cpp, .cxx, .cc).
Includerea fiierelor antet n fiierele surs produce recompilarea acestora la
compilarea fiecrui fiier surs. Acest aspect poate s nu influeneze prea mult
eficiena de compilare, deoarece fiierele antet conin n general declaraii care nu
necesit analiz intens n compilare. Se poate, de asemenea ca sistemul de
programare s permit utilizarea fiierelor antet precompilate. Un fiier antet poate
conine:
Directive de includere
Macro definiii
Definiii de tipuri
Definiii de constante
Enumeraii
Definiii funcii inline
Definiii template
Declaraii de date
Declaraii de nume
Declaraii de funcii
Comentarii

#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 */

Nu se introduc n fiierele antet:


Definiii de funcii normale
Definiii de date
Definiii tablouri constante

char get(char *p)


{return *p++;}
int x;
const int tb[] = {/*

*/};

Acestea nu sunt reguli impuse cu strictee, ci sugestii pentru un mod rezonabil


de utilizare a fiierelor antet.
Numrul de fiiere antet ale unui program poate s varieze de la unul singur
(pentru programele de dimensiuni reduse) la mai multe fiiere antet, n general cte un
fiier antet pentru fiecare fiier surs. Posibilitatea de mprire a unui program C++ n
mai multe module care pot fi compilate separat, dar care au conexiuni cu alte module
ale programului, reprezint caracteristica de modularitate a limbajului, care respect n
acest fel principiul modularitii, propriu modelului obiect.

Elemente de Programare Orientat pe Obiecte

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];

S se rescrie aceste instruciuni folosind operaii asupra pointerilor.


E1.3 S se scrie o funcie strcat() care concateneaz dou siruri. Se va folosi
operatorul new pentru alocarea spaiului necesar rezultatului.
E1.4

Fie urmtorul program:


int& fr(int a, int b){
int x = a+b;
return x;
}
void main(){
int& v = fr(4,6);
cout << v << endl;
cout << "mesaj\n";
cout << v << endl;
}

S se explice cauza execuiei eronate a acestui program (pentru referina v se


afieaz dou valori diferite).
E1.5 S se scrie o funcie atoi() care returneaz valoare de tip ntreg a unui ir
de cifre zecimale. S se scrie o funcie itoa() care genereaz irul de caractere care
reprezint un numr ntreg dat.
E1.6 S se scrie declaraiile pentru urmtoarele tipuri de variabile: pointer la un
caracter, un vector de 10 valori ntregi, pointer la un vector de 10 valori ntregi, un
pointer la un pointer la un caracter.
E1.7 S se scrie un program care tiprete dimensiunea tipurilor fundamentale de
date. Se va folosi operatorul sizeof.
E1.8 S se scrie o funcie n care se detecteaz eroarea de alocare a memoriei cu
operatorul de alocare new.

2
Clase i obiecte

Un tip de date ntr-un limbaj de programare este o reprezentare a unui


concept. De exemplu, tipul float din C++, mpreun cu operaiile definite asupra
acestuia (+, -, *, etc.) reprezint o versiune a conceptului matematic de numere reale.
Pentru alte concepte, care nu au o reprezentare direct prin tipurile predefinite ale
limbajului, se pot defini noi tipuri de date care s specifice aceste concepte. Un
program care definete tipuri de date strns corelate cu conceptele coninute n
aplicaie este mai concis, mai uor de neles i de modificat.
O clas este un tip de date definit de utilizator. O declarare a unei clase
definete un tip nou care reunete date i funcii. Acest tip nou poate fi folosit pentru a
declara obiecte de acest tip, deci un obiect este un exemplar (o instan) a unei clase.
Forma general de declaraie a unei clase care nu motenete nici o alt clas
este urmtoarea:
class nume_clasa {
date i funcii membre private
specificator_de_acces
date i funcii membre
specificator_de_acces
date i funcii membre
.
specificator_de_acces
date i funcii membre
} lista_obiecte;

Cuvntul-cheie class introduce declaraia clasei (a tipului de date) cu


numele nume_clasa. Dac este urmat de corpul clasei (cuprins ntre acolade),
aceast declaraie este totodat o definiie. Dac declaraia conine numai cuvntulcheie class i numele clasei, atunci aceasta este doar o declaraie de nume de clas.
Corpul clasei conine definiii de date membre ale clasei i definiii sau
declaraii (prototipuri) de funcii membre ale clasei, desprite printr-unul sau mai

Elemente de Programare Orientat pe Obiecte

34

muli specificatori de acces. Un specificator_acces poate fi unul din cuvintelecheie din C++:
public

private

protected

Specificatorii private i protected asigur o protecie de acces la datele sau


funciile membre ale clasei respective, iar specificatorul public permite accesul la
acestea i din afara clasei. Efectul unui specificator de acces dureaz pn la urmtorul
specificator de acces. Implicit, dac nu se declar nici un specificator de acces, datele
sau funciile membre sunt de tip private. De aceea, toate datele sau funciile
declarate de la nceputul blocului clasei pn la primul specificator de acces sunt de tip
private. ntr-o declaraie de clas se poate schimba specificatorul de acces ori de
cte ori se dorete: unele declaraii sunt trecute public, dup care se poate reveni la
declaraii private, etc. Diferena ntre tipurile de acces private i protected
const n modul n care sunt motenite drepturile de acces n clase derivate i va fi
detaliat n seciunea care se ocup cu clasele derivate (seciunea 5). Declararea unor
obiecte de tipul definit de clas prin lista_obiecte este opional.
Definirea unei clase introduce n program un tip nou de date, definit de
utilizator, care reunete date i funcii membre ntr-un fel de cutie neagr (blackbox), care reprezint abstractizarea, adic identificarea caracteristicilor definitorii
pentru entitatea pe care o modeleaz clasa respectiv. n limbajele de programare
orientate pe obiecte, clasele permit abstractizarea datelor, care este o caracteristic
fundamental a modelului obiect.
Exist oarecare confuzie n terminologia utilizat pentru tipurile de date
introduse prin intermediul claselor. Dat fiind c acestea reprezint suportul
abstractizrii n programarea orientat pe obiecte, unii autori folosesc denumirea de tip
de date abstract (abstract data type) pentru tipul de date definit de o clas. ns,
autorul limbajului C++, B. Stroustrup, motiveaz preferina pentru termenul tip
definit de utilizator (user-defined type) prin aceea c termenul abstract ar necesita o
specificare matematic mai riguroas.
Mai mult, B. Stroustrup introduce denumiri mai nuanate ale tipurilor de date
definite prin clase, i anume: tipuri concrete (concrete types), introduse prin clase
normale (n sensul c nu sunt abstracte, adic permit instanierea unor obiecte) i
tipuri abstracte (abstract types), intoduse prin clase abstracte (clase care conin funcii
virtuale pure i nu permit instanierea unor obiecte). Aceste aspecte vor fi detaliate n
seciunea 5.6, dup descrierea derivrii claselor i a funciilor virtuale.
n cele ce urmeaz este folosit terminologia introdus de Stroustrup: un tip de
date introdus de o clas este denumit tip definit de utilizator, sau, uneori, tip clas.

2.1

Date i funcii membre ale clasei

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
}

Clasa Complex conine dou date membre private, re i im de tip


double i trei funcii membre public, init(), set() i display().
n funcia main() a programului de mai sus s-a declarat un obiect cu numele
c1 de tipul (clasa) Complex. Pentru un obiect dintr-o clas dat se poate apela
oricare funcie membr a clasei, folosind operatorul punct de acces la un membru
(membru al unei structuri, uniuni sau clase); deci dac se scrie: c1.init(), acest
lucru nsemn apelul funciei init() pentru obiectul c1 din clasa Complex.

2.1.1

Domeniul clasei. Operatorul de rezoluie

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.

Elemente de Programare Orientat pe Obiecte

36

Datele i funciile membre ale clasei care nu sunt declarate public au n


mod implicit, ca domeniu de definiie, domeniul clasei respective, adic sunt
cunoscute i pot fi folosite numai din funciile membre ale clasei.
Datele i funciile membre publice ale clasei au ca domeniu de definiie ntreg
domeniul de definiie al clasei, deci pot fi folosite n acest domeniu.
Funciile unei clase pot fi definite n interiorul clasei, aa cum sunt funciile
init(), set()i display() ale clasei Complex, sau pot fi declarate n
interiorul clasei (prin declararea prototipului) i definite n exteriorul ei.
Pentru definirea unei funcii n afara clasei (dar, bineneles n domeniul ei de
definiie) numele funciei trebuie s fie calificat (nsoit) de numele clasei respective
prin intermediul operatorului de rezoluie (::). Sintaxa de definire a unei funcii n
exteriorul clasei este urmtoarea:
tip_returnat nume_clasa::nume_functie(lista_argumente){
// corpul functiei
}

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

// error:'K':is not a class


// or namespace name

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();

// error:'a' : cannot access


// private member in class 'K'
// corect, functie publica

2.1.2

Pointerul this. Funcii membre de tip const

Orice funcie membr apelat pentru un obiect al unei clase primete un


argument ascuns, pointerul la obiectul pentru care a fost invocat funcia, numit
pointerul this. ntr-o clas X pointerul constant this este declarat implicit astfel:
X* const this;
Deoarece this este cuvnt cheie, el nu poate fi declarat explicit. De asemenea, fiind
declarat implicit pointer constant, el nu poate fi modificat, dar poate fi folosit explicit.
n exemplul de mai sus, funciei init() i se transmite implicit pointerul la
obiectul c1, cu numele this. Prin intermediul acestui pointer funcia acceseaz
datele membre ale obiectului c1 (instan a clasei Complex). Asignrile din funcia
init() {re = 0; im = 0;} sunt asignri ale datelor membre ale obiectului c1

Elemente de Programare Orientat pe Obiecte

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

La compilarea acestui program se obine de patru ori urmtorul mesaj de


eroare de compilare: 'get' : cannot convert 'this' pointer from 'const class U *' to
'class U *const'. Specificatorul const pentru argumentul formal de tip pointer
(respectiv referin) la clasa U ale celor dou funcii fu1() i fu2(), transform
tipul pointerului this transmis implicit funcii membre nestatice set() i get() n
pointer constant la constant, adic el are forma: const U* const this i se
interzice accesul funciilor membre (n acest caz, funciile get() i set()) la
obiectul indicat.
Dar, intenia cu care se utilizeaz argumente de tip pointer (sau referin) la
constant n apelul funciilor este de a interzice modificarea obiectului, nu de a
interzice accesul complet la acesta. Suportul oferit de limbajul C++ pentru rezolvarea

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

Se poate observa c funcia membr get()const {...} poate accesa att


un obiect dat prin pointer la constant (ps), ct i un obiect dat prin pointer normal
(pd), n timp ce funcia membr set() {} nu poate accesa dect obiecte date prin
pointer normal (n sensul c nu este pointer la constant).

2.1.3

Funcii membre inline

n programarea folosind clase, se obinuiete s fie definite i apelate multe


funcii mici (cu numr redus de instruciuni), i acest lucru poate produce un cost
ridicat de execuie, datorit operaiilor necesare pentru rezervarea spaiului n stiv
necesar funciei, apoi pentru transferul argumentelor i returnarea unei valori. De
multe ori este posibil ca aceste operaii implicate n apelul unei funcii s depeasc
timpul de execuie util al funciei. Acest problem se rezolv prin intermediul
funciilor inline.
n general, o funcie declarat inline se schimb la compilare cu corpul ei,
i se spune c apelul funciei se realizeaz prin expandare. n felul acesta se elimin
operaiile suplimentare de apel i revenire din funcie. Dezavantajul funciilor
inline este acela c produc creterea dimensiunilor programului compilat, de aceea
se recomand a fi utilizate pentru funcii de dimensiuni mici (maximum 3-4
instruciuni). n plus, mai exist i unele restricii privind funciile inline: ele nu pot

Elemente de Programare Orientat pe Obiecte

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

Aceast posibilitate de definire a funciilor inline n implementarea claselor


face ca numeroase apeluri de funcii s nu produc un cost suplimentar, aa cum, n
mod eronat, se consider uneori. n toate instruciunile programului din Exemplul 2.1
descris mai sus, apelurile de funcii se execut prin expandare i deci nu produc cost
suplimentar.

2.1.4

Pointeri la date i funcii membre

Este posibil accesul la un membru al unei clase printr-un pointer care


memoreaz adresa acelui membru. Un pointer la un membru poate fi obinut prin
aplicarea operatorului adres & numelui acestui membru calificat cu numele clasei; de
exemplu &X::m, este pointer la membrul m al clasei X. Se vor preciza modurile de
definire i utilizare a pointerilor la membrii claselor n exemplul urmtor.

Exemplul 2.3

Se definete o clas W i se acceseaz prin pointeri datele i funciile membre


ale clasei W astfel:
#include <iostream.h>
class W{
int a;
public:
int b;
void seta(int x){a = x;}
void setb(int x){b = x;}
int geta(){return a;}
int getb(){return b;}
};
void main (){
W ob1, ob2;
ob1.setb(5);
int W::*pdm;
W* pw = &ob2;
pw->setb(6);

// pdm este un pointer la o data


// membra de tip int a clasei W

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

void (W::*pfm)(int); // pfm este pointer la o


// functie membra a clasei W cu
// argument int si return void
pfm = &W::seta;
// pfm indic functia seta
(ob1.*pfm)(7);
// ob1.a = 7
(pw->*pfm)(10);
// ob2.a = 10
pfm = &W::setb;
// pfm indic functia setb
(ob1.*pfm)(8);
// ob1.b = 8
(pw->*pfm)(11);
// ob2.b = 11
cout << ob1.geta() << " ";
cout << ob1.getb() << endl;
// afiseaza 7 8
cout << ob2.geta() << " ";
cout << ob2.getb() << endl;
// afiseaza 10 11

Prin declaraia int W::*pdm; pointerul pdm este definit ca un tip de


pointer la o dat de tip ntreg membr a clasei W. Un pointer definit ca tip printr-o
astfel de declaraie, poate fi asignat cu adresa oricrei date membre de tip ntreg
neprotejat a clasei W. Dup asignare, pointerul poate fi folosit prin operatorul de
selecie membru .* pentru un obiect din clasa respectiv (ob1.*pdm), sau prin
operatorul de selecie ->* pentru un pointer la un obiect din clasa respectiv
(pw->*pdm).
Prin declaraia void (W::*pfm)(int); se definete pointerul pdf ca
un tip de pointer la o funcie membr a clasei W care are un argument de apel de tip
int i returneaz un void. Dup aceast definiie, pointerul pfm poate fi asignat cu
adresa oricrei funcii care ndeplinete condiia dat (este o funcie membr a clasei
W, are un argument de apel de tip int i returneaz un void). De exemplu, poate
primi adresa funciei seta() prin asignarea: pfm = &W::seta; Din acest
moment, funcia seta() poate fi apelat prin pointerul su pfm folosind operatorul
de selecie .* pentru un obiect din clasa W ((ob1.*pfm)(7); ) sau operatorul de
selecie ->* pentru un pointer la un obiect din clasa W ((pw->*pfm)(10);).

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

//

Elemente de Programare Orientat pe Obiecte

42
void fc1(){
Complex c1;
c1.re = 5.6;
c1.im = 7.9;
}

// nu apare eroare de compilare

Dar o astfel de implementare nu respect principiul ncapsulrii datelor i se


recomand s fie evitat.
Problema ce nseamn o clas bine definit are mai multe aspecte. Din punct
de vedere al dreptului de acces la membrii clasei, o clas bine definit permite
ncapsularea (sau ascunderea informaiilor), prin care un obiect poate ascunde celor
care-l folosesc secretele sale, adic modul de implementare, prin interzicerea
accesului la datele i funciile private sau protected.
n general, un obiect (instan a unei clase) are o stare, dat de totalitatea
variabilelor sale, i o comportare, reprezentat de funciile pe care le poate executa.
Starea unui obiect variaz n cursul existenei acestuia i depinde de desfurarea n
timp a tuturor funciilor pe care le-a executat.
ncapsularea prevede o barier explicit n calea accesului la starea unui
obiect. Conform acestui principiu al ncapsulrii, asupra unui obiect se poate aciona
numai prin funciile pe care acesta le pune la dispoziie n interfa i care sunt de tip
public.
ncapsularea este procesul de separare a elementelor unui tip de date abstract
(clas) n dou pri: structura, dat de implementarea acestuia, i comportarea sa,
accesat prin interfa. Implementarea const din definirea datelor i a funciilor
membre, iar interfaa const din declaraiile datelor i funciilor membre de tip
public. Ascunderea informaiilor este conceput n C++ pentru prevenirea
accidentelor, nu a fraudelor. Nici un limbaj de programare nu poate interzice unei
persoane s vad implementarea unei clase, dar poate interzice unei funcii din
program s citeasc date la care nu are dreptul de acces. (Un sisteme de operare poate,
totui, s interzic accesul de citire la unele fiiere, deci ascunderea s fie real, chiar
pentru persoane, nu numai pentru funcii din program.)
Revenind la modul n care se pot accesa datele membre ale unei clase, se poate
remarca c, n general, respectnd principiul ncapsulrii, datele membre sunt
declarate private sau protected i nu pot fi accesate direct (pentru citire sau
scriere) din funcii nemembre ale clasei care nu sunt de tip friend (sau nu aparin
unei clase friend a clasei respective). Pentru citirea sau modificarea unora dintre
datele membre protejate n clasa respectiv se pot prevedea funcii membre de tip
public, care pot fi apelate din orice punct al domeniului de definiie al clasei i fac
parte din interfaa clasei.
De exemplu, pentru clasa Complex, o implementare care respect principiul
ncapsulrii, dar, n acelai timp permite accesul la datele private ale clasei poate
arta astfel:
class Complex{
double re;
double im;
public:

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

Date i funcii membre de tip static

O dat membr a unei clase poate fi declarat static n declaraia clasei. Va


exista o singur copie a unei date de tip static, care nu aparine nici unuia dintre
obiectele (instanele) clasei, dar este partajat de toate acestea. Declaraia unei date de
tip static ntr-o clas este doar o declaraie, nu o definiie i este necesar definiia
acesteia n alt parte n program (n afara clasei). Aceasta se face redeclarnd variabila
de tip static folosind operatorul de rezoluie pentru clasa creia i aparine, i fr
specificatorul static.
O variabil membr de tip static a unei clase exist nainte de a fi creat un
obiect din clasa respectiv i, dac nu este iniializat explicit, este iniializat implicit
cu 0. Cea mai frecvent utilizare a datelor membre statice este de a asigura accesul la o
variabil comun mai multor obiecte, deci pot nlocui variabilele globale.

Exemplul 2.4

Se consider o clas S care conine o variabil normal v i o variabil static


s. Data de tip static este declarat n clasa S i definit n afara acesteia, folosind
operatorul de rezoluie. Se poate urmri evoluia diferit a celor dou variabile v i s

Elemente de Programare Orientat pe Obiecte

44

prin crearea a dou obiecte x i y de tip S, i prin apelul funciilor incs() i


incv() pentru obiectul x astfel:
class S{
int v;
static int s;
// declaratia var. statice s
public:
S() { v = 0;}
int gets() {return s;}
int getv() {return v;}
void incs() {s++;}
void incv() {v++;}
};
int S::s;
// definitia var. statice s a
void main (){
S x, y;
cout << Inainte de incrementare\n;
cout <<x.s: <<x.gets()<<y.s: <<y.gets()<<
cout <<x.v: <<x.getv()<<y.v: <<y.getv()<<
x.incs();
x.incv();
cout << Dupa incrementare\n;
cout <<x.s: <<x.gets()<<y.s: <<y.gets()<<
cout <<x.v: <<x.getv()<<y.v: <<y.getv()<<
}

clasei S

endl;
endl;

endl;
endl;

La execuia acestui program se afieaz coninutul variabilelor s i v pentru


obiectele x i y. Mesajele afiate sunt urmtoarele:
Inainte de incrementare
x.s: 0 y.s: 0
x.v: 0 y.v: 0
Dupa incrementare
x.s: 1 y.s: 1
x.v: 1 y.v: 0

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

// declaratie data statica


// decl. functie statica
// definirea datei statice
// definire func. statica

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

Clase, structuri i uniuni

n C++ structurile au o funcionalitate foarte apropiat de acea a claselor: ele


definesc tipuri de date noi, permit gruparea de date i funcii, permit motenirea. De
fapt, singura diferen ntre clase i structuri n C++ este aceea c, implicit, toi
membrii unei structuri sunt de tip public.
Se poate verifica uor acest lucru, chiar pe exemplul din aceast seciune.
Dac se nlocuiete cuvntul cheie class cu cuvntul cheie struct i se introduce
specificatorul de acces private care s modifice tipul implicit de acces la date, se
obine acelai program, cu aceeai funcionare:
struct Complex{
private:
double re;
double im;
public:

46

Elemente de Programare Orientat pe Obiecte


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
}

9.3

Se poate observa c, spre deosebire de C, n C++ obiectele de tip structur pot


fi declarate folosind doar numele structurii, fr s mai fie nevoie s fie precedat de
cuvntul-cheie struct.
Aceast dubl posibilitate de definire a unor tipuri noi de date (prin clase i
prin structuri) provine din modul n care a evoluat limbajul C++, pornind de la C.
Structurile au fost pstrate n C++ n primul rnd pentru translatarea direct a
programelor existente, din C n C++. Dac structurile tot trebuie s existe n C++,
atunci adugarea trsturilor suplimentare care sunt proprii claselor (funcii membru,
derivare, motenire, etc) este o problem simplu de rezolvat la nivelul proiectrii
compilatoarelor, iar structurile C++ au devenit astfel mai puternice.
n sfrit, existena n momentul de fa a dou cuvinte-cheie pentru definirea
tipurilor noi de date, permite evoluia liber a conceptului class, n timp ce
conceptul struct poate fi pstrat n continuare pentru asigurarea compatibilitii cu
programele C deja existente.
Chiar dac se poate folosi o structur acolo unde se dorete definirea unui tip
de date abstract (clas), o practic corect de scriere a programelor este considerat
aceea n care clasele sunt utilizate pentru definirea tipurilor de date noi, iar structurile
sunt utilizate atunci cnd se dorete o structur de tip C.
Ca i structurile, uniunile (union) n C++ definesc tipuri noi i pot conine
att date ct i funcii membru, care sunt implicit publice. n acelai timp, o uniune
C++ pstreaz toate capacitile din C, printre care cea mai important este aceea c
toate datele mpart aceleai locaii de memorie.
Exist mai multe restricii n utilizarea uniunilor n C++. n primul rnd
uniunile nu pot fi folosite n mecanismul de derivare a tipurilor de date, nici ca tipuri
de baz, nici ca tipuri derivate i deci, nu pot avea funcii membru de tip virtual
(acestea sunt legate de derivare i vor fi studiate n seciunea 5). Dei o uniune poate
avea constructori, nu sunt admise date membre care au un constructor. De asemenea,
o uniune nu poate avea variabile i funcii membre de tip static.

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

n exemplul urmtor se definete o clas care descrie o stiv de numere


ntregi, clasa IntStack. Detalii asupra acestui model de date se gsesc n
seciunea 3.

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

Elemente de Programare Orientat pe Obiecte

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

n clasa IntStack este definit un vector de numere ntregi de o dimensiune


maxim definit n program, vect[MAX_SIZE], n care se introduc i se extrag
numere n ordinea ultimul inserat-primul extras (last in-first out). Dou funcii
membre ale clasei, push() i pop() realizeaz introducerea, respectiv extragerea,
unui numr ntreg din obiectul de tip IntStack pentru care sunt apelate. n funcia
push() se interzice introducerea unui nou numr dac stiva este plin i se d un
mesaj de eroare. n funcia pop() se returneaz o valoare corect numai dac stiva nu
este goal; dac nu exist nici un numr n stiv, nu se citete nimic din memorie, se
afieaz un mesaj de eroare i se returneaz 1. Aceast tratare a situaiei de eroare nu
este suficient, deoarece valoarea 1 poate fi returnat i ca dat corect. O tratare
complet a situaiilor de eroare la execuia funciilor membre ale unei clase este
prezentat n seciunea 8.
Variabila tos indic prima poziie liber din stiv i ea trebuie neaprat s fie
iniializat la 0 (nceputul stivei) nainte ca stiva s poat fi folosit, altfel pot apare
erori de execuie impredictibile (scriere la adrese de memorie necontrolate).
Iniializarea s-ar putea face printr-o funcia membr care s fie apelat explicit, dar o
modalitate mai bun este aceea de a folosi o funcie membr special pentru
iniializare, denumit funcie constructor.
Un constructor este o funcie cu acelai nume cu numele clasei, care nu
returneaz nici o valoare i care iniializaz datele membre ale clasei.
De exemplu, n clasa de mai sus constructorul:
IntStack(){tos=0;}

iniializeaz la 0 variabila tos.


Pentru aceeai clas pot fi definite mai multe funcii constructor, ca funcii
suprancrcate, care pot fi selectate de ctre compilator n funcie de numrul i tipul
argumentelor de apel, la fel ca n orice suprancrcare de funcii.
Un constructor implicit pentru o clas X este un constructor care poate fi
apelat fr nici un argument. Deci un constructor implicit este un constructor care are
lista de argumente vid, sau un constructor cu unul sau mai multe argumente, toate
fiind prevzute cu valori implicite. De exemplu, X::X(int i=0) este un constructor
implicit, deoarece el poate fi apelat fr nici un argument, avnd definit o valoare
implicit a argumentului.
n general, constructorii se declar de tip public, pentru a putea fi apelai
din orice punct al domeniului de definiie al clasei respective. La crearea unui obiect
dintr-o clas oarecare este apelat implicit acel constructor al clasei care prezint cea
mai bun potrivire a argumentelor. Dac nu este prevzut nici o funcie constructor,

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

La execuia funciei fc2(), sunt afisate urmtoarele mesaje:


Constructor fara argumente
Constructor cu 1 arg
Constructor cu 2 arg

n fiecare dintre aceste situaii a fost creat un obiect de tip Complex, ca


obiect local funciei fc2() (c1, c2, c3) i de fiecare dat a fost apelat
constructorul care are acelai numr i tip de argumente cu cele de apel.

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.

Elemente de Programare Orientat pe Obiecte

50

n fiecare situaie, constructorul creeaz structura de baz a obiectului


(construiete obiectul compus din toate datele membre nestatice i tabelele care se
refer la derivare i motenire, dac este cazul) i, n final, execut codul specificat n
corpul constructorului. Un constructor generat de compilator pentru o clas X are
forma general X::X(){}, adic nu prevede execuia unui cod, ci doar asigur
construcia obiectelor membre i a tabelelor de derivare, dac este cazul. n toate
situaiile n afar de ultima, constructorul este apelat n mod implicit, atunci cnd se
creeaz obiectul. Apelul explicit al unui constructor, dei admis de limbaj, este puin
utilizat.
Pe lng iniializarea datelor membre, n constructori se execut, atunci cnd
este necesar, operaiile de alocare dinamic a unor date.
De exemplu, implementarea clasei IntStack prezentat n Exemplul 2.5
poate produce un mare consum de memorie, n mod nejustificat: orice obiect de tipul
IntStack este creat cu un vector de date de dimensiunea maxim MAX_SIZE
definit ca o constant n program, chiar dac un numr mare de obiecte ar necesita
dimensiuni mult mai reduse. Alocarea dinamic a spaiului strict necesar pentru
vectorul de numere ntregi se poate efectua la crearea obiectului, prin funciile
constructor. Exemplul urmtor prezint aceast posibilitate.

Exemplul 2.7

Se reia implementarea tipului de date stiv de numere ntregi cu alocarea


dinamic a vectorului n memoria liber, folosind clasa DStack:
class DStack{
int *pvect;
int size;
int tos;
public:
DStack(){
pvect = NULL;
size = 0;
tos = 0;
}
DStack(int s){
pvect = new int[s];
size = s;
tos = 0;
}
void push(int x);
int pop();
};
void DStack::push(int x){
if (tos < size)
pvect[tos++] = x;
else cout <<Eroare depasire stiva\n;
}

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

La declararea unui obiect de clas DStack se transmite ca argument


dimensiunea dorit a stivei, iar constructorul aloc spaiul necesar n memoria liber.
n rest, implementarea clasei DStack este asemntoare clasei IntStack,
prezentat mai sus.

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;
}
//..
};

Elemente de Programare Orientat pe Obiecte

52

Destructorii sunt apelai implicit n mai multe situaii:


atunci cnd un obiect local sau temporar iese din domeniul de definiie;
la sfritul programului, pentru obiectele globale;
la apelul operatorului delete, pentru obiectele alocate dinamic.
Apelul explicit al unui destructor este rar utilizat. Dac o clas nu are un
destructor, compilatorul genereaz un destructor implicit.

2.3.3

Constructori de copiere

Funcia principal a unui constructor este aceea de a iniializa datele membre


ale obiectului creat, folosind pentru aceast operaie valorile primite ca argumente.
Exemple de astfel de iniializri se gsesc n toi constructorii definii pn n prezent.
O alt form de iniializare care se poate face la crearea unui obiect este prin
copierea datelor unui alt obiect de acelai tip. Aceast operaie este posibil prin
intermediul unui constructor mai special al clasei, numit constructor de copiere. Forma
general a constructorului de copiere al unei clase X este:
X::X(X& r){
// initializare obiect folosind referina r
}

Constructorul primete ca argument o referin r la un obiect din clasa X i


iniializeaz obiectul nou creat folosind datele coninute n obiectul referin r. Pentru
crearea unui obiect printr-un constructor de copiere, argumentul transmis trebuie s fie
o referin la un obiect din aceeai clas.
De exemplu, pentru obiecte de tip Complex:
void fc3(){
Complex c1(4,5);
Complex c2(c1);
Complex c3 = c2;
c3.display();
}

//
//
//
//

Constructor
Constructor
Constructor
afiseaza 4

initializare
copiere
copiere
5

La crearea primului obiect (c1) este apelat constructorul de iniializare cu


dou argumente al clasei Complex. Cel de-al doilea obiect (c2) este creat prin apelul
constructorului de copiere al clasei Complex, avnd ca argument referina la obiectul
c1. Este posibil i declaraia (definiia) de forma Complex c3 = c2; a unui
obiect prin care se apeleaz, de asemenea, constructorul de copiere.
Constructorul de copiere poate fi definit de programator; dac nu este definit
un constructor de copiere al clasei, compilatorul genereaz un constructor de copiere
care copiaz datele membru cu membru din obiectul referin n obiectul nou creat.
Aceast modalitate de copiere mai este denumit copie la nivel de bii (bitwise copy)
sau copie membru cu membru. Pentru clasa Complex, constructorul de copiere
generat implicit de compilator sau definit de programator arat astfel:

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

Se pune ntrebarea urmtoare: de ce mai este nevoie s fie definit un


constructor de copiere dac el este oricum generat de compilator atunci cnd este
necesar?
Pentru obiecte din clasa Complex, funcionarea este aceeai, att n situaia
n care n clas nu este definit un constructor de copiere, i deci el este generat de
ctre compilator, ct i dac acesta a fost definit n clas. Mesajele care se afieaz la
consol dac funcia fc3() este executat dup introducerea constructorului de
copiere sunt:
Constructor cu 2 arg
Constructor copiere
Constructor copiere
4 5

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

Elemente de Programare Orientat pe Obiecte

54

Problema care apare este evident: deoarece prin copierea membru cu


membru pointerul pvect al obiectului stack2 primete valoarea pointerului pvect
al obiectului stack1, instruciunile stack2.push(21) i stack2.push(22)
scriu peste valorile introduse mai nainte n stiva stack1, astfel nct extragerile din
stiva stack1 gsesc valorile introduse n stiva stack2. Diferitele combinaii de
operaii pot da cele mai variate rezultate.
O alt problem apare la ieirea din funcia fd2(). Pentru clasa DStack a
fost definit un destructor care terge vectorul pvect din memorie folosind operatorul
delete. La distrugerea obiectului stack2 este ters din memorie vectorul indicat
de pointerul pvect al acestui obiect, iar la distrugerea obiectului stack1 se ncearc
din nou tergerea aceleiai zone de memorie, dat fiind c cei doi pointeri aveau valoare
egal. O astfel de operaie produce eroare de execuie i abandonarea programului,
ceea ce se i poate observa la execuia funciei fd2().
Soluia o reprezint definirea unui constructor de copiere care s previn astfel
de situaii. Un constructor de copiere definit de programator trebuie s aloce spaiu
pentru datele dinamice create n memoria liber i dup aceea s copieze valorile din
obiectul de referin. Un exemplu de constructor de copiere definit pentru clasa
DStack va explicita mai uor acest aspect.

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];
}

Bineneles, declaraia acestuia trebuie s apar n interiorul clasei DStack.


Fie funcia:
void fd3(){
DStack stack1(100);
stack1.push(11);
DStack stack2(stack1);
stack1.push(12);
stack2.push(21);
cout << stack1.pop() << ;
cout << stack1.pop() << endl;
cout << stack2.pop() << ;
cout << stack2.pop() << endl;
}

//
//
//
//

afiseaza
afiseaza
afiseaza
afiseaza

12
11
21
11

Execuia acesteia se termin normal; dup construcia prin copiere a obiectului


stack2, acesta are propriul vector de numere ntregi, n care a preluat o valoare

2. Clase i obiecte

55

introdus n stiva stack1 nainte de copiere (valoarea 11), i el continu operaiile


de introducere i extragere din acest punct. Cele dou obiecte sunt complet
independente i asupra lor se pot executa operaii n mod separat, inclusiv operaia de
distrugere care se execut n mod implicit la sfritul funciei. Acest lucru este
evideniat de mesajele afiate.

Se poate stabili cu certitudine c programatorul trebuie s prevad un


constructor de copiere inteligent, n orice clas n care exist date alocate dinamic.
Dac un astfel de constructor este definit, compilatorul nu mai genereaz constructorul
implicit de copiere membru cu membru i sunt evitate erorile de tipul celor descrise
mai sus.
Erori care au originea n construcia obiectelor prin copiere membru cu
membru (deci folosind constructorul de copiere implicit generat de compilator) pot s
apar n orice situaie n care se construiete un obiect prin copiere. Astfel de situaii
vor fi prezentate n exemplele care urmeaz.

Exemplul 2.10

La trasmiterea unui obiect ca argument prin valoare unei funcii, se


construiete un obiect local funciei (n stiv) folosindu-se constructorul de copiere.
De aici pot proveni toate problemele de tipul celor descrise mai sus. Se pot observa
mai uor aceste probleme, dac se execut funcia fd4() n situaia n care nu s-a
definit constructorul de copiere al clasei DStack, i n situaia n care s-a definit ca
mai sus un astfel de constructor.
void g(DStack stack){

cout << stack.pop() << ;


cout << stack.pop() << endl;

}
void fd4(){
DStack stack(100);
stack.push(55);
stack.push(66);
g(stack);
}

// afiseaza 66
// afiseaza 55

Execuia corect a funciei fd4() care apeleaz funcia g() avnd ca


argument un obiect de tip DStack are loc numai dac a fost definit (ca mai sus)
constructorul de copiere al clasei DStack. Altfel apare eroare de execuie.

Exemplul 2.11

La returnarea unui obiect dintr-o funcie se creeaz un obiect temporar


folosind constructorul de copiere. Pentru a observa comportarea obiectelor create se
adaug mesaje de identificare n constructorii i destructorul clasei DStack astfel:
class DStack{
int *pvect;
int size;

Elemente de Programare Orientat pe Obiecte

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";
}

La revenirea din funcia h(), se construiete un obiect temporar folosind ca


date de iniializare obiectul de tip DStack returnat de funcia h(). Mesajele care se
afieaz la execuia funciei fd5() sunt urmtoarele:
Constructor initializare
Constructor copiere
Destructor
Destructor
Revenire din h()

Primul mesaj indic construirea obiectului stack n funcia h() folosind


constructorul de iniialzare al clasei; al doilea mesaj se afieaz la construcia unui
obiect temporar avnd ca referin obiectul returnat de funcia h(). Aceste obiecte
sunt distruse nainte de ieirea din funcia fd5().

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 datelor prin constructori

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

Cazul n care i T este un tip definit de utilizator este prezentat n seciunea 4.


Dac ne referim la clasa Complex definit n aceast seciune, instruciunea:
Complex c1 = 7.8;

este o conversie de la tipul double la tipul Complex: se creeaz obiectul c1 de tip


Complex cu valori ale datelor membre iniializate folosind data de tip double din
care se face conversia. Dac implementarea clasei Complex este cea din Exemplul
2.6, care conine cte un mesaj de identificare pentru fiecare constructor, atunci, la
execuia acestei instruciuni se afieaz mesajul:
Constructor cu 1 arg

Operaia de conversie printr-un constructor cu un argument are loc direct, fr


alte operaii intermediare, dac intervine n declararea obiectului, aa cum este n
exemplul dat mai sus. n alte modaliti de declarare apar operaii suplimentare. De
exemplu, instruciunile:
Complex c2;
c2 = 9.3;

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,

Elemente de Programare Orientat pe Obiecte

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

Soluia de a suprancrca operatorul de asignare este prezentat n seciunea 4.

2.4

Obiecte de tipuri definite de utilizator membre


ale claselor. Clase locale. Clase imbricate

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

La execuia funciei fx(), data membr x de clas X a obiectului y2 se


iniializeaz folosind argumentul
cu valoare 4 transmis prin intermediul
constructorului clasei Y. Mesajele care se afiseaz la execuia funciei f6() sunt
urmtoarele:
Constructor X
Constructor Y
Constructor X
Constructor Y
Destructor Y
Destructor X
Destructor Y
Destructor X

implicit
cu 1 arg
cu 1 arg
cu 2 arg

Se observ c se construiete mai nti data membr x i apoi obiectul de clas


Y care o conine. Dac sunt mai multe date membre care se iniializeaz, acestea se pot

Elemente de Programare Orientat pe Obiecte

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

Ordinea de execuie a constructorilor i destructorilor

Un constructor este apelat la definirea obiectului, iar destructorul este apelat


atunci cnd obiectul este distrus. Dac exist mai multe declaraii de obiecte, atunci
ele sunt construite n ordinea declaraiei i sunt distruse n ordinea invers a
declaraiei.
Obiectele membre ale unei clase se construiesc naintea obiectului respectiv.
Destructorii sunt apelai n ordine invers: destructorul obiectului i apoi destructorii
membrilor (un exemplu este dat n subseciunea urmtoare)
Funciile constructor ale obiectelor globale sunt executate naintea execuiei
funciei main(). Constructorii obiectelor globale din acelai fiier sunt executai n
ordinea declaraiilor, de la stnga la dreapta i de sus n jos. Este greu de precizat
ordinea de apel a constructorilor globali distribuii n mai multe fiiere. Destructorii
obiectelor globale sunt apelai n ordine invers, dup ncheiere funciei main().
Alte precizri cu privire la ordinea de execuie a constructorilor i
destructorilor se vor face n seciunea 5, dedicat claselor derivate.

2.4.2

Clase locale. Clase imbricate

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

Funcii i clase friend

O funcie nemembr a unei clase poate accesa datele private sau


protected ale acesteia dac este declarat funcie de tip friend a clasei. Pentru

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
}

Pentru evidenierea utilitii funciilor de tip friend se consider urmtorul


exemplu.

Exemplul 2.12

Se consider problema de nmulire a unei matrice cu un vector. Astfel de


operaii sunt deosebit de frecvente, n multe domenii: fizic, proiectare automat,
grafic, etc. Cele dou clase care descriu o matrice 4x4 (folosit n transformrile
grafice tridimensionale) i un vector de dimensiune 4 sunt definite astfel:
class Matrix {
double m[4][4];
public:
Matrix();
Matrix(double pm[][4]);
void set(int i, int j, double e) { m[i][j]=e;}
double get(int i, int j)const{return m[i][j];}
};
Matrix::Matrix() {
for (int i=0;i<4;i++)
for (int j=0;j<4;j++)
if (i==j) m[i][j] = 1.0;
else m[i][j] = 0.0;
}
Matrix::Matrix(double pm[][4]){
for (int i=0;i<4;i++)
for (int j=0;j<4;j++)
m[i][j] = pm[i][j];
}
class Vector {
double v[4];
public:
Vector();
Vector(double *pv);
void set(int i, double e) {v[i]=e;}
double get(int i) const {return v[i];}
};

Elemente de Programare Orientat pe Obiecte

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];
}

Fiecare din cele dou clase are prevzut un constructor implicit i un


constructor de iniializare. Modificatorul const care este prezent n unele declaraii a
fost descris n subseciunea 2.1.2.
Dat fiind c o funcie nu poate fi membr a dou clase, cel mai natural mod de
nmulire a unei matrice cu un vector ar prea s fie prin definirea unei funcii
nemembre multiply()care acceseaz elementele celor dou clase (clasa Matrix
i clasa Vector) prin funciile de interfa get() i set().
Vector multiply(const Matrix &mat, const Vector &vect){
Vector vr;
for (int i=0;i<4;i++){
double x =0.0;
for (int j=0;j<4;j++)
x += vect.get(j)*mat.get(i,j);
vr.set(i,x);
}
return vr;
}

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

Declaraia friend poate fi plasat n orice parte, public, private sau


protected a clasei. Funcia multiply() se rescrie pentru accesul direct la
elementele vectorului i matricei astfel:
Vector multiply(const Matrix &mat, const Vector &vect){
Vector vr;
for (int i=0;i<4;i++) {
double x = 0.0;
for (int j=0;j<4;j++)
x += vect.v[j] * mat.m[j][i];
vr.v[i] = x;
}
return vr;
}

Apelul acestei funcii de nmulire multiply() ntr-o funcie oarecare


fm() este urmtorul:
void fm(){
double v[]={1,2,3,4};
double m[][4] = {1,2,3,4,5,6,7,8,
9,10,11,12,13,14,15,16};
Matrix m1(m);
Vector v1(v);
Vector v2 = multiply(m1,v1);
}

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;
//.
};

Aceast declaraie face ca toate datele i funciile membre ale clasei X


(inclusiv cele private sau protected) s fie accesate de funcii ale clasei Y.

2.6

Alocarea dinamic a obiectelor

Obiectele (instane ale claselor) se pot aloca n memoria liber folosind


operatorul new. La alocarea memoriei pentru un singur obiect se pot transmite
argumente care sunt folosite pentru iniializarea obiectului, prin apelul acelei funcii

Elemente de Programare Orientat pe Obiecte

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

Pentru alocarea dinamic a unui vector de obiecte de tipul X, trebuie s existe


un constructor implicit al clasei X, care este apelat pentru fiecare din elementele
vectorului creat. Pentru tergerea unui vector de obiecte se folosete operatorul
delete [] care apeleaz implicit destructorul clasei pentru fiecare din elementele
vectorului alocat. Dac, pentru un vector alocat dinamic, se apeleaz operatorul
delete (n loc de delete[]), se apeleaz destructorul clasei o singur dat, dup
care rezultatul execuiei este imprevizibil, cel mai adesea se produce eroare de
execuie i abandonarea programului. Cteva exemple de alocri i eliberri de vectori
de obiecte:
Complex *pv1 = new Complex[4];
delete []pv1;
DStack *stack1 = new DStack[3];
delete []stack1;

Pentru fiecare element al fiecruia dintre vectori, este apelat constructorul


implicit la creare i destructorul la tergere.
Operatorii new i delete prezint mai multe avantaje fa de funciile
similare malloc() i free(). n primul rnd, new aloc automat spaiul necesar
pentru memorarea unui obiect de tipul specificat, fr s necesite folosirea
operatorului sizeof. Apoi, pointerul returnat de new este de tipul specificat, nefiind
nevoie de conversie de la tipul void*, aa cum se procedeaz la apelul funciei
malloc(). n sfrit, operatorii new i delete pot fi suprancrcai, permind
modaliti difereniate de alocare dinamic pentru tipurile definite de utilizator.

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

E2.2 S se defineasc o clas CharStack pentru o stiv de caractere, a crei


dimensiune se stabilete printr-un parametru transmis la construcie. n programul
principal se vor crea dou stive de dimensiune 100, respectiv 200 caractere. S se
insereze un caracter n prima stiv, dup aceea s fie extras din aceasta i introdus n
cea de-a doua stiv. S se afieze rezultatul operaiei de extragere din a doua stiv.
E2.3 S se scrie un program care s determine dimensiunea obiectelor de diferite
clase, din care s se poat deduce unde sunt alocate datele i funciile membre ale
claselor. Se vor considera clase cu date membre normale, date membre statice, funcii
normale, funcii statice, funcii de tip friend.
E2.4 Se consider urmtorul program n care o variabil global numbers
memoreaz numrul obiectelor n via la un moment dat:
#include <iostream.h>
int numbers = 0;
class Item {
public:
Item(){
// constructorul creste cu 1 nr obiectelor
numbers++;
cout << "Nr. obiecte " << numbers << endl;
}
~Item() {
// destructorul descreste cu 1 nr ob.
numbers--;
cout << "Nr. obiecte " << numbers << endl;
}
};
void main() {
Item ob1, ob2, ob3;
{ // se deschide un nou bloc
Item ob4, ob5;
}
Item *pob6 = new Item[3];
delete [] pob6;
}

Care sunt mesajele care apar la consol la execuia acestui program? S se explice
evoluia numrului de obiecte n via n cursul execuiei.

66

Elemente de Programare Orientat pe Obiecte

E2.5 Se se modifice programul de mai sus astfel nct s se foloseasc un membru


static al clasei Item pentru contorizarea obiectelor n via. S se verifice
echivalena mesajelor afiate la consol.
E2.6 Se definete o clas de obiecte IntArray care permite reprezentarea unor
vectori de numere ntregi astfel:
class IntArray{
int* p;
int count;
// numar de elemente ale listei
int size;
// dimensiunea vectorului
int grows;
// nr. elem de crestere a dimensiunii
public:
IntArray(){
count = 0;
size = grows;
grows = 4;
p = new int[grows];
}
~IntArray() { delete p;}
int GetCount() {return count;}
int Add(int x);
int GetAt(int i, int *v) {
if (i >=0 && i < count)
return p[i];
else {
cout << Eroare depasire indice\n;
return -1;
}
}
};
int IntArray::Add(int x){
// adauga elem. la sf. vect.
// creste size. cu grows
// elemente, dac este nevoie
if (count < size)
p[count++] = x;
else {
int *p1 = p;
p = new int[count+grows];
for (int i=0; i<count;i++)
p[i] = p1[i];
delete p1;
p[count++] = x;
}
return count-1;
}

Aceast clas va reproduce n mare msur clasa CUIntArray din MFC,


dac i se vor mai aduga i alte funcii.

2. Clase i obiecte

67

Vectorul conine count elemente (numere ntregi) i ocup un spaiu n


memorie necesar pentru un numr de ntregi egal cu size. Atunci cnd este
necesar, creterea dimensiunii vectorului se face cu grows elemente.
(a) Se cere s se redea mesajele care se afieaz la consol la execuia
urmtoarei funcii:
void fci() {
IntArray array1;
array1.Add(11);
array1.Add(22);
array1.Add(33);
array1.Add(44);
array1.Add(55);
int count = array1.GetCount();
for (int i = 0; i < count; i++)
cout << array1.GetAt(i) << "
cout << endl;
}

";

(b) Ce se ntmpl dac, pentru copierea n noul vector p, se nlocuiete


instruciunea
p[i] = p1[i]; cu

*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
}

Ce se ntmpl la apelul acestei funcii din funcia main()? Ce fel de eroare


poate s apar i de ce? Care sunt remediile posibile?
E2.9 S se defineasc o funcie membr int Append(IntArray &array) a
clasei IntArray care s adauge un vector la sfritul unui alt vector. n funcia
main() se va crea un vector cu coninutul 1 2 3 4 5 i un alt vector cu
coninutul 11 12 13 14 15. S se ataeze primul vector la sfritul celui de-al
doilea vector i s se afieze vectorul rezultat obinut.

Elemente de Programare Orientat pe Obiecte

68

E2.10 S se definesc o funcie membr int InsertAt(int i, IntArray


&array) a clasei IntArray care s permit inserarea unui vector n interiorul altui
vector, ncepnd cu poziia dat i. n funcia main() se va crea un vector cu
coninutul 1 2 3 4 5 i un vector cu coninutul 11 12 13 14 15. S se
insereze primul vector ntre elementele 12 i 13 ale celui de-al doilea vector i s se
afieze vectorul rezultat obinut.
E2.11 Se modific funcia:
int InsertAt(int i, IntArray &array); astfel:
int InsertAt(int i, const IntArray &array);
Ce modificri trebuie s fie aduse celorlalte funcii membre ale clasei
IntArray pentru a se compila i executa corect programul de mai sus?

3
Implementarea modelelor de date

Programele pentru calculatoare utilizeaz de obicei tabele de informaii care


presupun anumite relaii structurale ntre datele care le compun. Aceste relaii
structurale difereniaz mai multe modele de organizare a datelor, dintre care cele mai
importante sunt: tablouri, liste, arbori. Toate aceste modele se reprezint n
programare prin colecii de date care, n programarea orientat pe obiecte, se
implementeaz prin clase (tipuri definite de utilizator).
Fiecare programator i poate defini propriile implementri ale modelelor de
date, sau poate s foloseasc unele tipuri definite n bibliotecile sistemului, pe care le
modific corespunztor cerinelor aplicaiei. n ambele situaii ns, este necesar
cunoaterea modului de organizare intern a unor astfel de modele de date, precum i
posibilitile de reprezentare i de exploatare ale acestora prin intermediul claselor de
colecii, al claselor container i al claselor template. Reprezentarea coleciilor de date
prin intermediul claselor container i al claselor template vor fi prezentate n seciunile
5 i, respectiv, 7.

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.

Elemente de Programare Orientat pe Obiecte

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:

Operaia de inserare (insert) const n adugarea unui nou element x unei


liste L. Poziia n care se poate insera noul element este oricare poziie n
list: naintea primului element (n aceast situaie se modific capul
listei), dup ultimul element, sau ntre oricare dou elemente. Este admis
inserarea unui nou element care are aceeai valoare cu un alt element
component al listei.
Operaia de tergere (extract) a unui element dintr-o list nseamn
eliminarea unui element x dintr-o list. Dac n list exist mai multe
elemente cu aceeai valoare, se specific suplimentar care dintre acestea
se elimin.
Operaia de cutare (lookup) returneaz valoarea adevrat dac
elementul cu valoarea x aparine listei, i valoarea fals, dac elementul
nu aparine listei.

3.1.1

Liste ordonate

Operaia de sortare ntr-o list necesit ca elementele listei s apain unei


mulimi n care se poate defini o relaie de ordine liniar. Fie o list:
L = (a, c, b, f, e, d), unde a, b, S
Mulimea S creia i aparin elementele listei trebuie s fie o mulime
ordonabil liniar. O mulime de elemente S este ordonabil liniar dac i numai dac:
pentru fiecare dou elemente a i b ale mulimii S este ndeplinit una din
condiiile: a < b, a = b sau a > b;
pentru oricare trei alemente a, b, c ale mulimii S, dac a < b i
b < c, atunci a < c.
Simbolul < semnific precede. Un exemplu de mulime care satisface
condiiile de ordine liniar este mulimea numerelor ntregi. Un alt exemplu este
mulimea literelor alfabetului Latin. ntr-o secven S = {s1, s2,.sn} compus
din elemente care aparin unei mulimi ordonabile liniar, rangul k al unui element si
este definit ca numrul de elemente n S care preced elementul si plus 1.
Sortarea unei liste (secven de elemente) nseamn efectuarea unei permutri
ntre elementele listei, astfel nct fiecare element s respecte regula de preceden fa
de elementul aflat naintea lui n list. Ca rezultat al unei operaii de sortare n lista L
rezult lista L :
L = (a, b, c, d, e, f), unde a < b < c < d < e < f
O astfel de list se numete list ordonat.

3. Implementarea modelelor de date

71

n programarea orientat pe obiecte elementele unei liste sunt de un tip dat,


predefinit sau definit de utilizator (clas). Pentru tipurile de date predefinite ale
limbajului C++ relaia de preceden < este definit printr-un operator de
comparaie. Pentru tipurile definite de utilizator, obiectele (instane ale unei clasei)
dintr-o list pot fi ordonate dac pentru clasa respectiv se definete operaia de
preceden. n general, aceast operaie se definete prin suprancrcarea operatorului
de comparaie < .

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)

Modelul de date abstract list liniar se poate reprezenta, n principal, prin


dou structuri (tipuri) de date: tablou de elemente i list nlnuit.

Elemente de Programare Orientat pe Obiecte

72

3.1.3

Implementarea listelor liniare prin tablouri

n aceast implementare elementele listei sunt memorate ntr-un tablou, ceea


ce nseamn c elementele vecine n list ocup locaii vecine n memorie.
Implementarea prin tablouri a listelor beneficiaz de acces rapid la un element ntr-o
poziie dat. n schimb, inserarea sau tergerea unui element necesit operaii
costisitoare de deplasare a unor elemente pentru a face loc unui nou element inserat,
sau pentru a le realipi, la tergerea unui element.
Pentru implementarea unei liste liniare se poate folosi un tablou
unidimensional (vector) de elemente ca tip derivat de date, alocat static sau dinamic n
memorie. De exemplu:
T vector[d];
definete un vector de elemente de tipul T, predefinit sau definit de utilizator (clas)
alocat static n memorie, n care se poate stoca o list de maximum d elemente.
Se poate defini i un vector alocat dinamic n memorie astfel:
T vector

= new T[d];

Numrul de elemente n ale listei este o informaie strict necesar n cazul


implementrii listelor ca tablouri, deoarece dimensiunea tabloului (d) nu coincide cu
numrul de elemente ale listei, fiind n general mai mare, pentru a permite inserarea
unor noi elemente (Fig. 3.2).

a0

a1

..

an-1
n-1

..

..

d-1

Fig. 3.2 Implementarea ca vector a unei liste liniare.

n abordarea orientat pe obiecte, o list de elemente de tipul T implementat


ca vector se poate reprezenta printr-o clas care, n general, memoreaz vectorul de
date de tipul respectiv T, dimensiunea vectorului d i numrul n de elemente ale listei.
Funciile membre ale clasei modeleaz operaiile impuse de destinaia listei. De
exemplu, pentru o clas care reprezint o stiv, sunt necesare operaiile de inserare
(push) i de extragere (pop).
Acesta a fost modul de implementare a unei stive din Exemplul 2.5 din
seciunea precedent: clasa IntStack implementeaz o stiv de numere ntregi,
reprezentat printr-un tablou (vector) alocat static n memorie, de dimensiune
(MAX_SIZE) care este o constant n program. n implementarea prin vector a stivei,
operaiile de inserare i de extragere a elementelor stivei (push() i pop()) se
execut fa de ultimul element al listei care reprezint stiva: se insereaz un element
dup ultimul element memorat (cu indice maxim n list) i se extrage ultimul element
memorat. Aceast poziie de vrf a stivei este memorat n clasa IntStack printr-un
indice (tos). ntr-o versiune similar de stiv (cea prezentat n subseciunea 7.1 ), n

3. Implementarea modelelor de date

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

Elemente de Programare Orientat pe Obiecte

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

Implementarea listelor liniare prin liste nlnuite

O list liniar ca model de date abstract (secven finit de elemente de acelai


tip) se poate implementa i printr-o structur de date de tip list simplu sau dublu
nlnuit.
O list simplu nlnuit este o schem de implementare a coleciilor de
elemente n care fiecare nod al listei este compus din dou cmpuri: un cmp conine
un element al coleciei, de un tip dat, iar cellalt cmp conine un pointer ctre
urmtorul nod al listei (Fig. 3.3-a).
O list dublu nlnuit este compus din noduri, fiecare nod avnd trei
cmpuri: un cmp conine un element al coleciei, al doilea cmp conine un pointer
ctre urmtorul nod al listei, iar al treilea cmp conine un pointer ctre nodul
precedent al listei (Fig. 3.3-b). n reprezentarea obinuit a listelor nlnuite, pointerii
se reprezint prin sgei ctre nodurile vecine, valoarea efectiv a acestora (adresa din
memorie a nodului) fiind de obicei nesemnificativ. Pointerii de nlnuire au valoarea
0 pentru nodurile terminale i n reprezentrile grafice sunt marcai printr-un punct sau
o legtur la mas (mpmntare). n lista simplu nlnuit, ultimul nod este nod
terminal i are pointerul ctre nodul urmtor 0. n lista dublu nlnuit primul nod are
pointerul ctre nodul precedent 0, iar ultimul nod are pointerul ctre urmtorul nod 0.
a0

a1

a2

an-1

Primul nod

Ultimul nod

(a) List simplu nlnuit reprezentnd lista L = (a0, a1,an-1)

a0

a1

a2

(a) List dublu nlnuit reprezentnd lista L = (a0, a1,an-1)


Fig. 3.3 Liste nlnuite

an-1

3. Implementarea modelelor de date

75

n listele nlnuite elementele succesive nu ocup, n general, locaii


succesive n memorie. Fiecare nod se aloc dinamic n memoria liber, iar cmpurile
de nlnuire (pointerii) se poziioneaz astfel nct s se asigure nlnuirea corect a
nodurilor. Listele nlnuite ofer avantajul c pot reprezenta colecii cu numr variabil
de elemente, fr s ocupe un spaiu suplimentar n memorie (cu excepia spaiului de
memorare a pointerilor). De asemenea, operaiile de inserare i de extragere a
elementelor sunt mai eficiente, implicnd numai modificri de valori ale pointerilor, i
nu deplasri ale elementelor (aa cum se poate ntmpla n cazul utilizrii vectorilor).
Principalul dezavantaj al listelor nlnuite l reprezint timpul mare i variabil
de cutare al unui element dat n list.
Reprezentarea listelor nlnuite este posibil n orice limbaj procedural, prin
definirea structurilor compuse din cmpurile corespunztoare (elementul listei i
pointerii la noduri vecine).
n modelarea orientat pe obiecte a listelor nlnuite se pot folosi una sau mai
multe clase, n funcie de destinaia listei respective. n orice situaie va exista o clas
care va modela un nod al listei, avnd ca date membre un element al coleciei de date
(sau un pointer ctre acesta) i pointerul sau pointerii de nlnuire.
Se poate ca o list nlnuit s fie reprezentat numai printr-o astfel de clas.
n Exerciiul 3.1 este propus o astfel de reprezentare a unei liste simplu nlnuite.
Nodurile se nlnuie ntre ele, iar lista se indic printr-un pointer la primul su nod.
Dac se analizaz o astfel de reprezentare se pot observa diferite anomalii n operaiile
efectute asupra listei. n primul rnd, o operaie asupra primului nod (ca, de exemplu
eliminarea primului nod) modific lista nsi, adic pointerul la primul nod al
nlnuirii prin care se cunoate lista. Mai sunt i alte anomalii n reprezentarea listelor
nlnuite folosind o singur clas (tip de date). Acestea sunt evideniate n
Exerciiul 3.1.
O reprezentare mai complet a unei liste nlnuite se poate face prin dou
tipuri de date (clase): o clas reprezint un nod al listei i o alt clas reprezint lista
nsi; n subseciunile care urmeaz sunt descrise astfel de reprezentri ale listelor
simplu i dublu nlnuite de numere ntregi.
3.1.4.1 List simplu nlnuit de numere ntregi
Se definesc dou clase pentru reprezentarea unei liste simplu nlnuite de
numere ntregi: o clas IntSListNode pentru reprezentarea unui nod al listei i o
clas IntSList, pentru reprezentarea listei. n clasa IntSListNode este
memorat elementul listei (un numr ntreg, variabila int v) i pointerul link la
nodul urmtor. n clasa IntSList este memorat pointerul la primul nod al listei
(first), pointerul la ultimul nod al listei (last) i numrul de elemente ale listei
(count). Memorarea ultimului nod al listei (last) i a numrului de elemente
(count) nu sunt strict necesare, deoarece ele pot fi calculate de fiecare dat pornind
de la primul nod. Dar aceste operaii parcurg secvenial ntreaga list, ceea ce
reprezint un consum de timp care poate fi considerabil pentru listele de dimensiuni
mari i de aceea memorarea acestor variabile n clasa IntSList mrete eficiena de
execuie a operaiilor cu listele simplu nlnuite.

76

Elemente de Programare Orientat pe Obiecte

Pentru fiecare clas s-au definit constructori, un destructor i funcii membre


care implementeaz operaii asupra listei. Clasa IntSList este declarat friend n
clasa IntSListNode, pentru ca aceasta din urm s poat accesa datele protejate ale
clasei IntSList.
class IntSListNode {
// nod in lista simplu inlantuita
int v;
// elementul listei, nr. intreg
IntSListNode* link; // pointer la nodul urmator
public:
friend class IntSList;
IntSListNode(int x) { v = x; link = NULL; }
~IntSListNode();
};
class IntSList{
// lista simplu inlantuita
IntSListNode* first; // pointer la primul nod
IntSListNode* last; // pointer la ultimul nod
int count;
// numarul de elemente
public:
IntSList(){ first = 0; last = 0; count = 0;}
IntSList(int x);
IntSList(int* p, int n);
IntSList(const IntArray& array);
IntSList(IntSList& r){
~IntSList();
int GetCount(){return count;}
int GetHead();
// citeste elem. din cap
int GetTail();
// citeste elem. din coada
void AddHead(int x); //adauga elem. in capul listei
void AddTail(int x); //adauga elem. in coada listei
int RemoveHead();
// extrage elem. din cap
int RemoveTail();
// extrage elem. din coada
int Lookup(int x);
// cutare element x
void Display();
// afiseaza toate elem. listei
};

Constructorul de iniializare al clasei IntSListNode construiete un nod al


listei i iniializeaz elementul nodului cu valoarea primit ca argument i pointerul de
nlnuire nul (link) cu valoarea zero. Constructorul de iniializare cu un argument
de tip ntreg al clasei IntSList creaz o list compus dintr-un singur nod
(element al listei) pe care l aloc dinamic n memoria liber folosind operatorul new:
inline IntSList::IntSList(int x){
IntSListNode* elem = new IntSListNode(x);
first = elem;
last = elem;
count = 1;
}

Constructorul de iniializare cu dou argumente creeaz o list nlnuit n


care elementele (pe care le creaz n memoria liber) sunt n ordinea din vectorul de
numere ntregi primit ca argument.

3. Implementarea modelelor de date

77

IntSList::IntSList(int* p, int n){


first = 0;
for (int i=0;i<n;i++){
IntSListNode* elem = new IntSListNode(p[n-1-i]);
elem->link = first;
first = elem;
if (i == 0)
last = elem;
}
count = n;
}

Se mai poate defini un constructor avnd ca argument o referin la un obiect


din clasa IntArray, care conine un vector de numere ntregi (descris n E2.6):
IntSList::IntSList(const IntArray& array){
int n = array.GetSize();
first = 0;
for (int i=0;i<n;i++){
IntSListNode* elem =
new IntSListNode(array.GetAt(n-1-i));
elem->link = first;
first = elem;
if (i == 0)
last = elem;
}
count = n;
}

Constructorul de copiere al clasei IntSList se definete astfel:


IntSList::IntSList(IntSList& r){
first = 0;
last = 0;
count = 0;
IntSListNode* ref = r.first;
for (int i=0;i<r.count;i++){
int x = ref->v;
AddTail(x);
ref = ref->link;
}
}

Destructorul clasei IntSListNode nu are de executat nici o operaie util;


el afieaz doar un mesaj care s permit ulterior observarea comportrii acestei clase.
Destructorul clasei IntSList parcurge lista nlnuit de noduri i le terge succesiv
din memorie folosind operatorul delete:
~IntSListNode(){
cout << "Destructor nod v = " << v <<endl;
}

Elemente de Programare Orientat pe Obiecte

78

IntSList::~IntSList(){
IntSListNode* p = first;
while (p){
IntSListNode* current = p;
p = p->link;
delete current;
}
}

n Exerciiul 3.2 este propus i un alt mod de distrugere a obiectelor din


clasele IntSListNode i IntSList.
Funcia Display() a clasei IntSList afieaz coninutul unei liste
ncepnd cu primul nod. O operaie asemntoare se poate obine prin suprancrcarea
funciei operator << pentru clasa IntSList (Exerciiul 4.11)
void IntSList::Display(){
IntSListNode* p = first;
while (p){
cout << p->v << " ";
p = p->link;
}
cout << endl;
}

Fie urmtoarea funcie fslist1():


void fslist1(){
int val[] = {1,2,3,4,5};
IntSList list1(val, sizeof(val)/sizeof(int));
list1.Display();
}

Obiectul list1 de clas IntSList care se creeaz la execuia acestei


funcii este reprezentat n Fig. 3.4.
count
count=5
first

last

obiect
IntSList

obiect
IntSListNode

Fig. 3.4 Obiect (list simpl nlnuit de numere ntregi) din clasa IntSetList

3. Implementarea modelelor de date

79

Mesajele afiate la execuia funciei fslist1() sunt:


1 2 3 4 5
Destructor
Destructor
Destructor
Destructor
Destructor

nod
nod
nod
nod
nod

v
v
v
v
v

=
=
=
=
=

1
2
3
4
5

Operaiile de dicionar implementate pentru aceast list permit adugarea


unui nou element la nceputul sau sfritul listei (AddHead() i AddTail()) i
extragerea (citire valoare i eliminare) unui element din capul sau coada listei
(RemoveHead() i RemoveTail()). Aceste operaii de adugare i tergere
element nu trateaz complet cazurile de eroare (eroare de alocare a unui nod nou la
inserare sau eroarea de list vid la extragere). Aceste situaii se vor trata ca excepii n
seciunea 8 (E8.5).
void IntSList::AddHead(int x){
IntSListNode* elem = new IntSListNode(x);
elem->link = first;
first = elem;
if (count == 0)
last = elem;
count++;
}

void IntSList::AddTail(int x){


IntSListNode* elem = new IntSListNode(x);
if (count==0){
first = elem;
last = elem;
}
else{
last->link = elem;
last = elem;
}
count++;
}
int IntSList::RemoveHead(){
if (count == 0){
cout << Eroare lista goala\n;
return -1;
}
else {
int v = first->v;
IntSListNode* p = first;
first = first->link;
delete p;
count--;
if (count == 0) { last = 0; }
return v;
}
}

80

Elemente de Programare Orientat pe Obiecte


int IntSList::RemoveTail(){
int v;
if (count == 0)
cout << Eroare lista goala\n;
return -1;
}
else{
IntSListNode* p = first;
while(1){
if (p->link){
// cel putin 2 elemente
if (!p->link->link){
v = p->link->v;
delete last;
p->link = 0;
last = p;
count--;
return v;
}
else p = p->link;
}
else{
// 1 element
v = p->v;
delete p;
first = 0;
last = 0;
count = 0;
return v;
}
}
}
}

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

//funct. listei ca stiva


//
//
//
//
//
//

push
push
push
pop,
pop,
pop,

1
2
3
afiseaza 3
afiseaza 2
afiseaza 1

3. Implementarea modelelor de date

81

De asemenea, aceast list se poate folosi pentru implementarea unei cozi de


numere ntregi. Funcia AddTail() este echivalent operaiei de inserare, iar funcia
RemoveHead() este echivalent operaiei de extragere. De exemplu:
void fslist3(){
// functionarea ca o coada a listei
IntSList list3;
list3.AddTail(1);
// inserare 1
list3.AddTail(2);
// inserare 2
list3.AddTail(3);
// inserare 3
cout << list3.RemoveHead(); // extragere, afiseaza 1
cout << list3.RemoveHead(); // extragere, afiseaza 2
cout << list3.RemoveHead(); // extragere, afiseaza 3
}

3.1.4.2 List dublu nluit de numere ntregi


Pentru implementarea unei liste dublu nlnuite de numere ntregi se folosesc
dou clase: clasa IntDListNode, care reprezint un nod al listei i clasa
IntDList, care reprezint lista dublu nlnuit. n clasa IntDListNode este
memorat elementul listei (un numr ntreg, variabila int v;), pointerul next la
nodul urmtor i pointerul prev la nodul precedent. n clasa IntDList este
memorat pointerul la primul nod al listei (first), pointerul la ultimul nod al listei
(last) i numrul de elemente ale listei (count). Pentru fiecare clas s-au definit
constructori, un destructor i funcii membre care implementeaz operaii asupra listei.
Clasa IntDList este declarat friend n clasa IntDListNode, pentru ca
aceasta din urm s poat accesa datele protejate ale clasei IntDList.
class IntDListNode{
unsigned int v;
IntDListNode* prev;
IntDListNode* next;
public:
friend class IntDList;
IntDListNode(int x) {
v = x;
prev = NULL;
next = NULL;
}
~IntDListNode();
};
class IntDList{
IntDListNode* first;
IntDListNode* last;
int count;
public:
IntDList(){ first = 0; last = 0; count = 0;}
IntDList(int x);
IntDList(int* p, int n);

Elemente de Programare Orientat pe Obiecte

82

};

IntDList(const IntArray& array);


IntDList(IntDList& r);
~IntDList();
int GetCount(){return count;}
void AddHead(int x);
void AddTail(int x);
int GetHead();
int GetTail();
int RemoveHead();
int RemoveTail();
void Display();
void ReverseDisplay();

Constructorul cu un argument de tip ntreg al clasei IntDList se definete


astfel:
inline IntDList::IntDList(int x){
IntDListNode* elem = new IntDListNode(x);
first = elem;
last = elem;
count = 1;
}

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

3. Implementarea modelelor de date

83

else{
last->next = elem;
elem->prev = last;
last = elem;
}
count++;

Cazul particular al listei vide este tratat separat.


elem
count
first
last
obiect
IntDList
Fig. 3.5 Inserarea unui element naintea primului element al listei (AddHead())

Ceilali constructori, destructorii i funciile acestor clase sunt asemntoare


cu cele ale claselor IntSListNode i IntSList i sunt propuse ca un exerciiu
simplu (E3.3). Fie funcia:
void fdlist1(){
int v[] = {1,2,3,4,5};
IntDList list1(v, sizeof(v)/sizeof(int));
list1.Display();
list1.ReverseDisplay();
}

n aceast funcie se creeaz lista dublu nlnuit de numere ntregi list1,


cu valori ale elementelor luate din vectorul v. Elementele listei sunt afiate n ordine
direct (ncepnd cu primul element al listei) i n ordine invers (ncepnd cu ultimul
element al listei). La ieirea din funcia fdlist1() obiectul list1 este distrus, iar
la distrugerea acestuia sunt apelai pe rnd destructorii nodurilor componente.
Mesajele afiate la execuia acestei funcii sunt urmtoarele:
1 2 3 4 5
5 4 3 2 1
Destructor
Destructor
Destructor
Destructor
Destructor

nod
nod
nod
nod
nod

v
v
v
v
v

=
=
=
=
=

1
2
3
4
5

Elemente de Programare Orientat pe Obiecte

84

Lista dublu nlnuit implementat prin clasele IntDListNode i


IntDList poate fi folosit ca stiv de numere ntregi, (operaiile push i pop
utiliznd funciile AddHead() i respectiv, RemoveHead()), sau ca o coad de
numere ntregi (operaiile de inserare i extragere utiliznd funciile AddTail() i
respectiv, RemoveHead()). n funcia fdlist2() sunt exemplificate ambele
modaliti de funcionare.
void fdlist2(){
/* Functionare ca stiva a listei */
IntDList list2;
list2.AddHead(1);
// push 1
list2.AddHead(2);
// push 2
list2.AddHead(3);
// push 3
cout << list2.RemoveHead(); // pop afiseaza 3
cout << list2.RemoveHead(); // pop afiseaza 2
cout << list2.RemoveHead(); // pop afiseaza 1

/* Functionarea ca o coada a listei */


IntDList list3;
list3.AddTail(1);
// inserare 1
list3.AddTail(2);
// inserare 2
list3.AddTail(3);
// inserare 3
cout << list3.RemoveHead(); // extragere afiseaza 1
cout << list3.RemoveHead(); // extragere afiseaza 2
cout << list3.RemoveHead(); // extragere afiseaza 3

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.

3. Implementarea modelelor de date

85

Utilizarea claselor de colecii. Elementele coleciei sunt obiecte ale unei


clase derivate dintr-o clas de baz unic, pentru care sunt definite
colecii de date (vectori, liste nlnuite, etc), prin clase numite clase de
colecii; un exemplu de utilizare a claselor de colecii este prezentat n
seciunea 5.
Utilizarea claselor abstracte pentru definirea coleciilor (clase container);
clasele container sunt prezentate n seciunea 5.
Utilizarea claselor template pentru definirea coleciilor de obiecte de un
tip oarecare; particularizarea tipului de date (predefinit sau definit de
utilizator) asigur crearea de ctre compilator a coleciei de date de tipul
dorit. n seciunea 7 sunt prezentate mai multe categorii de colecii de
date prin intermediul claselor template.

n coleciile definite n aceast seciune, inspectarea elementelor listelor s-a


executat ntr-un mod simplu, prin definirea unor funcii de afiare (display()) care
afieaz la consol elementele coleciei exact n ordinea n care sunt stocate n list.
Aceste funcii pot fi nlocuite prin suprancrcarea operatorului de extragere <<
(exerciii propuse n seciunea 4).
Un mecanism mai evoluat de inspectare a coleciilor de date ordonate (de
exemplu liste ordonate) l reprezint iteratorii. Un iterator permite parcurgerea n
ordinea dorit a elementelor, fr ca aceast ordine s depind de modul de ordonare
intern a elementelor coleciei. Iteratorii se pot implementa n mai multe moduri. Ei
pot fi constituii dintr-un grup de funcii membre publice ale unei clase a coleciei
(seciunea 3.3) sau pot fi definii printr-o clas diferit de clasele prin care se descrie
colecia nsi, acceptat ca o clas friend de ctre aceastea. n Exemplul 7.3 din
seciunea 7 este prezentat implementarea unui astfel de iterator pentru parcurgerea
unui vector asociativ.

3.2

Arbori

Un arbore (tree) este un model de date care permite reprezentarea structurilor


ierarhice i constituie una dintre cele mai importante structuri neliniare ce intervin n
algoritmii pentru calculatoare.
Un arbore este compus dintr-o mulime finit T de unul sau mai multe noduri
(nodes) i o mulime de muchii (edges), fiecare muchie conectnd dou noduri ntre
ele. Un arbore are urmroarele proprieti:
Un nod este distinct i se numete rdcin (root).
Fiecare nod c, cu excepia rdcinii este conectat printr-o muchie la un alt
nod p, numit nodul printe al lui c; nodul c se numete nod fiu (child) al
lui p.
Un arbore este conectat, n sensul c, pornind de la un nod oarecare n,
diferit de nodul rdcin, i mergnd la printele lui, apoi la printele
printelui lui n, . a. m. d., se ajunge la nodul rdcin.

Elemente de Programare Orientat pe Obiecte

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

Fig. 3.6 Forma general a unui arbore.

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. Implementarea modelelor de date

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 parcurge subarborele stng

Se parcurge subarborele stng

Se parcurge subarborele stng

Se viziteaz rdcina

Se parcurge subarborele drept

Se parcurge subarborele drept

Se parcurge subarborele drept

Se viziteaz rdcina

Elemente de Programare Orientat pe Obiecte

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

Implementarea unui arbore binar ordonat


de numere ntregi

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

3. Implementarea modelelor de date

89

Constructorul clasei IntTreeNode creeaz un nod care conine informaia


primit ca argument, iar cei doi pointeri la nodurile fii (left i right) primesc
valoarea zero. Constructorul clasei IntTree construiete un arbore vid (cu pointerul
la rdcin root de valoare zero).
n clasa IntTree sunt definite trei operaii de dicionar, inserare, cutare i
tergere element (funciile publice insert(), lookup() i remove()) i trei
funcii de parcurgere a arborelui n preordine, inordine i postordine (funciile publice
preorder(), inorder(), postorder()).
Funcia insert() introduce un nou nod n arbore, cu informaia dat printro valoare de tip ntreg. Algoritmul de inserare a unui element x ntr-un arbore T este
un algoritm recursiv definit astfel:
dac T este arbore vid, se nlocuiete T printr-un nod nou creat, care
primete informaia x;
dac T nu este vid i rdcina lui conine informaia x, atunci x exist
deja n dicionar i nu mai trebuie s fie inserat;
dac T nu este vid i rdcina lui nu conine informaia x, atunci se
insereaz x n subarborele din stnga dac x este mai mic dect eticheta
rdcinii, sau se insereaz x n subarborele din dreapta dac x este mai
mare dect eticheta rdcinii.
Funcia public insert() a clasei IntTree implementeaz acest algoritm
de inserare folosind o funcie private a clasei, funcia insert1() astfel:
IntTreeNode* IntTree::insert1(IntTreeNode *root,
IntTreeNode *r, int data){
if(!r) {
r = new IntTreeNode(data);
count++;
if(!root) return r;
if(data < root->d) root->left = r;
else root->right = r;
return r;
}
if (data == r->d) return 0;
if (data < r->d) return insert1(r, r->left, data);
else return insert1(r, r->right, data);
}
void IntTree::insert(int data) {
if(!root) root = insert1(root, root, data);
else insert1(root, root, data);
}

Parcurgerea n inordine a clasei IntTree este implementat prin funcia


membr public inorder(), care apeleaz funcia protejat a clasei,
inorder1():
void IntTree::inorder1(IntTreeNode *root){
if(!root) return;

Elemente de Programare Orientat pe Obiecte

90
inorder1(root->left);
cout << root->d<<" ";
inorder1(root->right);

}
void IntTree::inorder(){
inorder1(root);
}

Dac un arbore a fost creat ordonat, (folosind funcia insert()), atunci


funcia de parcurgere inorder() afieaz etichetele nodurilor n ordine cresctoare.
De exemplu:
void fi1(){
int n = 10;
IntTree tree;
for(int i=0;i<n;i++)
tree.insert(rand()%100));
cout << "Numar noduri: " << tree.getcount()<< endl;
tree.inorder();
cout << endl;
tree.preorder();
cout << endl;
tree.postorder();
cout << endl;
}

La execuia acestei funcii se genereaz n mod aleator 10 numere ntregi care


sunt inserate n arborele binar ordonat tree, folosind funcia insert(). Pentru
fiecare numr nou inserat se creeaz un nod care este introdus ntr-o poziie n arbore
astfel nct s fie pstrat proprietatea de arbore ordonat. Implementarea celorlalte
funcii ale clasei IntTree este lsat ca exerciiu (E3.6).

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).

3. Implementarea modelelor de date

91

n oricare din aceste reprezentri, operaia de inserare (insert) a unui nou


element trebuie s verifice dac mai exist un element identic cu noul element i s nu
se mai insereze elementul dac el exist deja n mulimea dat.
O alt operaie care se definete ntr-o mulime este operaia de cutare a unui
element (lookup). Fiind dat o mulime S, trebuie aflat dac un element oarecare x
aparine sau nu mulimii.
Pentru implementarea eficient a acestor operaii, cea mai bun metod este
de a menine elementele mulimii ntr-o colecie ordonat. Astfel se ajunge la situaia
c, dei o mulime nu presupune ordonarea elementelor sale, pentru accelerarea
operaiilor n mulimi se folosesc colecii ordonate, ca de exemplu liste ordonate
(implementate ca vectori sau liste nlnuite) sau arbori binari ordonai. Pentru astfel
de reprezentri prin colecii ordonate ale mulimilor, elementele mulimii trebuie s
aparin unui domeniu (tip de date, n general) n care s fie definit relaia de
preceden mai mic i relaia de identitate egal.
Operaia de extragere (sau tergere) a unui element dintr-o mulime trebuie s
pstreze ordinea de reprezentare a elementelor mulimii.

3.3.1 Implementarea mulimilor prin tablouri


O mulime, reprezentat ca o list liniar ordonat de elemente, se poate
implementa ca vector (tablou unidimensional) sau ca list nlnuit.
De exemplu, pentru implementarea ca vector a unei mulimi de numere
ntregi, se definete clasa IntSet care pstreaz elementele mulimii ntr-un vector
alocat dinamic n memorie:
class IntSet{
int *set;
int count;
// numar de elemente
int size;
// dimensiune vector
public:
IntSet(int d){
size = d;
set = new int[d];
count = 0;
}
~IntSet(){delete set;}
int insert(int t);
int remove(int t);
int lookup(int t) const;
// functii iterator
void start(int& pos)const {pos = 0;}
int inside(int& pos) const {return pos < count;}
int next(int& pos) const {return set[pos++];}
};

Constructorul clasei IntSet aloc n memoria liber un vector de


dimensiune dat ca argument, iar destructorul dezaloc acest spaiu de memorie.

92

Elemente de Programare Orientat pe Obiecte

Elementele mulimii stocate n vectorul set sunt meninute ordonate


cresctor, ceea ce permite accelerarea operaiilor de inserare i cutare a elementelor
n mulime. Operaiile de baz ntr-o mulime sunt: operaia de inserare a unui nou
element ntr-o mulime dat, implementat prin funcia insert(), operaia de
tergere a unui element din mulime, implementat prin funcia remove() i operaia
de cutare a unui element, implementat prin funcia lookup().
Numerele sunt inserate sau terse din mulime astfel nct ele s fie meninute
n ordine cresctoare n vector:
int IntSet::insert(int t){
int i = count;
if (count >= size){
cout << "Depasire capacitate\n";
return 0;
}
while(i>0 && t<set[i-1]){
set[i] = set[i-1];
// deplasare elemente
i--;
}
set[i] = t;
count++;
return 1;
}
int IntSet::remove(int t){
int low = 0;
int up = count -1;
while(low <= up){
int m = (low+up) /2;
if( t < set[m] ) up = m-1;
else if (t > set[m])low = m+1;
else {
int i = low;
count --;
while(i<count){
//deplasare elemente
set[i] = set[i+1];
i++;
}
return 1;
// gasit si sters
}
}
return 0;
// nu este gasit
}

Operaia de aflare dac un membru aparine mulimii folosete un algoritm de


cutare binar, la fel ca i operaia de tergere:
int IntSet::lookup(int t)const {
int low = 0;
int up = count -1;
while(low <= up){
int m = (low+up) /2;

3. Implementarea modelelor de date

93

if( t < set[m] ) up = m-1;


else if (t > set[m]) low = m+1;
else return 1;
// gasit

}
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

O alt modalitate de implementare a unui iterator pentru mulimi din clasa


IntSet este prin definirea unei clase diferite care memoreaz i actualizeaz poziia
de parcurgere a mulimii. O astfel de clas, clasa SetIter se declar clas friend
n clasa IntSet i se definete n felul urmtor:
class SetIter{
IntSet* s;
int pos;

Elemente de Programare Orientat pe Obiecte

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++];}
};

Un iterator se construiete pentru un obiect din clasa IntSet, al crui pointer


l primete ca argument la construcie i-l memoreaz n variabila protejat s. La
construcie sau la apelul funciei start() se iniializeaz poziia de nceput a
parcurgerii, pos = 0. Funcia membr next() returneaz valoarea elementului
curent i avanseaz cu o poziie n mulime; funcia inside() returneaz 1 dac
poziia de iterare se afl n domeniul vectorului i 0, dac a fost depit domeniul. Se
poate parcurge o mulime folosind un iterator din clasa SetIter astfel:
void fset2(){
IntSet set(100);
int n = 10;
for(int i=0;i<n;i++)
set.insert(rand()%100);
SetIter iter(&set);
// iterator pentru set
while(iter.inside())
cout << iter.next() << " ";
cout<<endl;
//afiseaza 0 24 34 41 58 62 64 67 69 78

set.remove(0);
iter.start();
while(iter.inside())
cout << iter.next() << " ";
cout<<endl;
//afiseaza 24 34 41 58 62 64 67 69 78

Operaiile cu mulimi, cum sunt reuniunea, intersecia, diferena a dou


mulimi, se pot implementa prin funcii membre ale clasei care modeleaz mulimea
dat (Exerciiul 3.16) sau prin suprancrcarea unor operatori (Exerciiul 4.15). Ca i
operaia de cutare, aceste operaii beneficiaz de faptul c mulimea de elemente este
ordonat. De exemplu, operaia de reuniune a dou mulimi se poate implementa
printr-un algoritm de combinare (merge), dac mulimile sunt reprezentate ca liste
ordonate. Singura diferen fa de operaia de combinare este aceea c n mulimea
rezultat se insereaz o singur copie a unui element comun n cele dou mulimi (n
loc de dou copii cum este n cazul combinrii) sau nici o copie, dac elementul
respectiv nu este comun celor dou mulimi.
Exemplul de mai sus, care folosete un vector pentru reprezentarea listei
ordonate a elementelor mulimii, a ilustrat n principal modul de reprezentare a
mulimilor, dar nu este cea mai eficient reprezentare a acestora. Chiar dac operaia
de cutare este rapid, deoarece se poate executa cutare binar, care este n ordinul
lui log n pentru n elemente ale mulimii (n O(log n)), operaiile de inserare i
de tergere sunt foarte ineficiente, deoarece necesit deplasarea unor elemente pentru
meninerea ordonrii.

3. Implementarea modelelor de date

95

Mulimile se pot implementa i prin liste nlnuite, n care elementele se


menin de asemenea ordonate. Operaia de cutare a unui element nu se mai poate
efectua prin cutare binar (deoarece nu exist posibilitatea de a accesa noduri interne
ale listei fr ca aceasta s fie parcurs), de aceea se folosete cutarea secvenial,
care este n ordinul lui n pentru n elemente ale listei (n O(n)). n schimb, operaiile
de inserare i tergere a unui element sunt mult mai eficiente deoarece nu necesit
deplasri pentru meninerea ordonrii listei.

3.3.3 Implementarea mulimilor prin arbori binari ordonai


O mulime de elemente de un anumit tip (predefinit sau definit de utilizator)
poate fi implementat printr-un arbore binar ordonat. ntr-o astfel de implementare,
operaiile de inserare, tergere i cutare a unui element n mulime sunt executate
eficient (n ordinul lui log n pentru n elemente ale mulimii). n schimb, operaiile cu
mulimi (reuniunea, intersecia, diferena) pot s nu fie la fel de eficiente datorit
apelurilor recursive ale funciei de parcurgere n ordine cresctoare a elementelor
(inorder()).
Pentru implementarea mulimilor se mai pot folosi i alte structuri de date,
cum sunt vectorii caracteristici, vectorii asociativi sau tabelele de dispersie (hash
table). Alegerea celei mai adecvate metode de reprezentare a unei mulimi depinde,
evident, de cerinele aplicaiei n care este folosit aceasta.

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

Se poate reprezenta o list nlnuit printr-un pointer la primul nod de tip


IntNode, care le nlnuie pe urmtoarele i se definesc funciile membre
AddHead() i RemoveHead() pentru adugarea i tergerea unui nod n list. Se
cere s se defineasc funciile declarate n clasa IntNode().
O astfel de list poate implementa direct o stiv de numere ntregi:

96

Elemente de Programare Orientat pe Obiecte


void fnode(){
IntNode* list1 = new IntNode(1);
list1 = list1->AddHead(2);
// push 2
list1 = list1->AddHead(3);
// push 3
list1->Display();
// afiseaza
cout << list1->GetHead()<< endl; // afiseaza
IntNode* list2 = list1->RemoveHead(); // pop
delete list1;
// stergere
list2->Display();
// afiseaza
}

3 2 1
3
nod
2 1

Care sunt dezavantajele acestei implementri a listei nlnuite?


E3.2 S se defineasc destructorii claselor IntSListNode i IntSList astfel
nct destructorul clasei IntSListNode s tearg elementul urmtor din list, dac
acesta exist. Cum se explic faptul c se modific mesajele afiate la execuia funciei
fslist1() dup aceast modificare?
E3.3 S se defineasc constructorii, destructorii i celelalte funcii ale claselor
IntDListNode i IntDList. Pentru definirea constructorilor se vor testa dou
modaliti de implementare: folosind funcia AddTail() sau folosind funcia
AddHead().
E3.4 S se defineasc o coad de caractere folosind un vector cu parcurgere
circular. Vectorul se va aloca dinamic n memoria liber, cu o dimensiune dat ca
argument la construcie.
E3.5 Se se implementeze modelul de date mulime de numere ntregi folosind o
list dublu nlnuit ordonat.
E3.6

S se defineasc destructorul i celelate funcii membre ale clasei IntTree.

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:

3. Implementarea modelelor de date

97

typedef void* Pdate;


typedef int (*COMPARE) (Pdate d1, Pdate d2);
typedef void (*EXECUTE) (Pdate d);
class info{
int label;
public:
info() { }
info(int x) { label = x; }
int get() { return label; }
};
class PointTreeNode{
Pdate d;
PointTreeNode *left, *right;
friend class PointTree;
public:
PointTreeNode(Pdate data){
d = data;left =right =NULL;
}
~PointTreeNode() { left = right = NULL; delete d; }
};
class PointTree{
PointTreeNode *root;
int count;
COMPARE f;
EXECUTE g;
PointTreeNode *insert1(PointTreeNode *root,
PointTreeNode *r, Pdate data);
void inorder1(PointTreeNode *root);
void preorder1(PointTreeNode *root);
void postorder1(PointTreeNode *root);
void delTree1(PointTreeNode *root);
public:
PointTree(COMPARE comp, EXECUTE exec) {
root = NULL; f = comp; g = exec; count = 0;}
int getcount() {return count;}
void insert(Pdate data);
int lookup(Pdate data);
void remove(Pdate data);
void inorder();
void preorder();
void postorder();
~PointTree();
};

Constructorul clasei PointTreeNode creeaz un nod care conine pointerul


la etichet primit ca argument i cei doi pointeri la nodurile fii (left i right) de
valoare zero. Constructorul clasei PointTree construiete un arbore vid (cu
pointerul la rdcin root de valoare zero) i memorez n obiectul creat pointerii la
funciile de comparaie i de execuie primii ca argumente.
Se cere s se defineasc funciile callback de comparaie i de execuie ale
clasei info i toate funciile clasei PointTree.

98

Elemente de Programare Orientat pe Obiecte

S se modifice funcia de creare a unui arbore binar ordonat de numere ntregi


(funcia fi1(), definit n seciunea 3.2.4), folosind un obiect din clasa
PointTree. S se verifice echivalena dintre cele dou implementri. Care sunt
dezavantajele acestei implementri?
E3.8 Fie urmtoarea definiie a claselor PointListNode i PointList, care
implementeaz o list dublu nlnuit generalizat:
typedef void* Pdate;
class PointListNode {
friend class PointList;
public:
Pdate data;
PointListNode *next, *prev;
PointListNode(Pdate d) { data = d; next = prev = 0; }
~PointListNode();
};
class PointList {
private:
int count;
PointListNode *first;
PointListNode *last;
public:
PointList() {first = 0; last = 0; count = 0;}
PointList(Pdate p);
~PointList();
};

S se completeze definiia clasei PointList i s se defineasc funciile


membre ale acestei clasei, astfel nct s implementeze urmtoarele structuri de date:
Stiv de: numere ntregi, caractere, obiecte de tip Complex,obiecte de
tip Point.
Coad de: numere ntregi, caractere, obiecte de tip Complex, obiecte
de tip Point.
E3.9 S se definesc o list dublu nlnuit de obiecte de clas Complex, care s
poat fi utilizat ca stiv i ca o coad.
E3.10 S se definesc o list dublu nlnuit de obiecte de clas Point, care s
poat fi utilizat ca stiv i ca o coad.
E3.11 S se defineasc o coad de obiecte de tip Complex, reprezentat printr-un
vector cu parcurgere circular.
E3.12 S se defineasc o coad de obiecte de tip Point, reprezentat printr-un
vector cu parcurgere circular.

3. Implementarea modelelor de date

99

E3.13 S se defineasc o clas iterator pentru o list dublu nlnuit de numere


ntregi (clasa ListIter). Acest iterator trebuie s parcurg elementele listei n
ordinea n care sunt memorate.
E3.14 Se definete un arbore n-ar de iruri de caractere (terminate cu nul) pentru o
valoare oarecare a lui n > 2, n care fiii unui nod sunt memorai ca o list simplu
nlnuit astfel:
class NTree {
char* label;
NTree *first;
// primul nod fiu
NTree *link;
// nodul frate urmator
NTree *parent;
// nodul parinte
public:
NTree::NTree() {
first = NULL;
link = NULL;
parent = NULL;
label = NULL;
}
NTree(char *n);
~NTree();
void AddChild(NTree *pNode);
void RemoveChild();
void RemoveAll();
void PostOrder();
};

S se defineasc constructorii, destructorul i celelate funcii membre. Funcia


RemoveChild() terge subarborele a crui rdcin este primul fiu al nodului
pentru care se execut aceast operaie. Funcia RemoveAll() terge toi subarborii
a cror rdcin sunt fii nodului pentru care se execut aceast operaie. Funcia de
traversare n postordine PostOrder() afieaz etichetele nodurilor n postordine
(mai nti fiii, apoi printele). Se d funcia:
void fnt(){
NTree* root = new NTree("0");
NTree* child1 = new NTree("1");
NTree* child2 = new NTree("2");
NTree* child3 = new NTree("3");
root->AddChild(child3);
root->AddChild(child2);
root->AddChild(child1);
NTree* child11 = new NTree("11");
NTree* child12 = new NTree("12");
child1->AddChild(child11);
child1->AddChild(child12);
cout << "Treaversare postordine\n";
root->PostOrder();
root->RemoveChild();
cout << "Treaversare postordine\n";

Elemente de Programare Orientat pe Obiecte

100

root->PostOrder();
delete root;

Ce mesaje se afieaz la consol la execuia acestei funcii?


E3.15 S se scrie constructorii de copiere ai claselor de tip arbore (arbore binar de
numere ntregi, IntTree i arbore n-ar, NTree.
E3.16 S se defineasc operaiile de reuniune, intersecie i diferen a dou mulimi
de numere ntregi reprezentate prin clasa IntSet:
class IntSet{
//..

public:
//..

};

void reunion(const IntSet &set2, IntSet& result);


void intersection(const IntSet& set2, IntSet& result);
void diference(const IntSet& set2, IntSet& result);

S se arate care sunt rezultatele execuiei urmtoarei funcii:


void fset3(){
int d = 1000;
int n = 20;
IntSet set1(d);
for(int i=0;i<n;i++)
set1.insert(rand()%100);
SetIter iter1(&set1);
// iterator pt. set1
while(iter1.inside())
cout << iter1.next() << " ";
cout << endl;
IntSet set2(d);
for(i=0;i<n;i++)
set2.insert(rand()%200);
SetIter iter2(&set2);
while(iter2.inside())
cout << iter2.next() << "
cout << endl;

IntSet result(d);
set1.reunion(set2, result);
SetIter iter3(&result);
while(iter3.inside())
cout << iter3.next() << "
cout << endl;

";

";

4
Suprancrcarea operatorilor

Suprancrcarea funciilor i a operatorilor (overloading) sunt mecanisme


importante n C++ care ofer flexibilitate i extensibilitate limbajului. Pentru tipurile
fundamentale ale limbajului sunt definii mai muli operatori care permit operaii de
baz executate ntr-un mod convenabil. Dar, dup cum este cunoscut, n limbaj sunt
definite prea puine tipuri de date ca date fundamentale, iar pentru reprezentarea altor
tipuri de date necesare n diferite domenii (cum ar fi aritmetica numerelor complexe,
algebra matricelor, etc.), se definesc clase care conin funcii ce pot opera asupra
acestor tipuri. Definirea operatorilor care s opereze asupra obiectelor unei clase
permite un mod mult mai convenabil de a manipula obiectele dect prin folosirea unor
funcii ale clasei. O funcie care definete pentru o clas o operaie echivalent
operaiei efectuate de un operator asupra unui tip predefinit este numit funcie
operator. Majoritatea operatorilor limbajului C++ pot fi suprancrcai, i anume:
new
+
!
^=
<=

delete
() []
*
/
=
<
>
&=
|=
<<
>=
&&
||

%
+=
>>
++

^
-=
>>=
--

&
*=
<<=
,

|
/=
==
->*

~
%=
!=
->

Operatorul () este apelul unei funcii, iar operatorul [] este operatorul de


indexare. Urmtorii operatori nu se pot suprancrca:
.

.* :: ?: sizeof

Exist cteva reguli care trebuie s fie respectate la suprancrcarea


operatorilor:
Funciile operator=(), operator()(), operator[]() i
operator ->() trebuie s fie membri nestatici ai clasei.
Cu excepia funciei operator=(), toate celelate funcii operator pot fi
motenite.
Nu pot fi suprancrcai operatorii pentru tipurile predefinite ale
limbajului.
Funciile operator nu pot avea argumente implicite.

Elemente de Programare Orientat pe Obiecte

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

Funcii operator membre ale clasei

Forma general a funciilor operator membre ale clasei este urmtoarea:


tip_returnat operator#(lista_argumente){
// operaii
}

n aceast form general semnul # reprezint oricare dintre operatorii care


pot fi suprancrcai.

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
//
//
//
//

suma a doi vectori


diferena a doi vect
produs scalar
multipl. cu o const.

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

Funcia operator+() are un singur argument, chiar dac ea suprancarc


un operator binar (operatorul +), care necesit doi operanzi. Argumentul transmis
funciei este operandul din dreapta operaiei, iar operandul din stnga este chiar
obiectul pentru care se apeleaz funcia operator. Expresia:
pct3 = pct1 + pct2;
semnific, de fapt:
pct3 = pct1.operator+(pct2);

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-().

Elemente de Programare Orientat pe Obiecte

104

Pentru acelai operator se pot defini mai multe funcii suprancrcate, cu


condiia ca selecia uneia dintre ele n funcie de numrul i tipul argumentelor s nu
fie ambigu. n clasa Point s-a suprancrcat operatorul * cu dou funcii: prima
pentru calculul produsului scalar a doi vectori, cealalt pentru multiplicarea vectorului
cu o constant.

n implementarea prezentat, funcia operator+() creeaz un obiect


temporar, care este distrus dup returnare. n acest fel, ea nu modific nici unul dintre
operanzi, aa cum nici operatorul + pentru tipurile predefinite nu modific operanzii.
ntr-o funcie operator se pot efectua orice fel de operaii, dar, n mod obinuit,
se pstreaz semnificaia aciunii operatorului respectiv. Cu excepia funciilor
operator new (), operator delete() i operator->(), valoarea
returnat de o funcie operator poate fi de orice tip, dar, tot pentru pstrarea
contextului utilizrii normale, de obicei se returneaz un obiect din aceeai clas, sau o
referin la aceasta. Acest lucru este important pentru utilizarea operatorului n
expresii, aa cum este cea scris mai sus.
Atunci cnd este returnat o referin, aceast referin este, de regul, chiar
obiectul pentru care a fost apelat funcia i care a fost modificat de funcia operator.
De exemplu, funcia operator de multiplicare cu o constant din clasa Point
modific datele obiectului pentru care se execut i returneaz referina la propriul
obiect (return *this;). O astfel de implementare este admis, dar programatorul
trebuie s rein acest aspect, c aceast funcie operator modific unul din operanzi.
n expresia scris mai sus, pct3 = pct1 + pct2, se execut, pe lng
operaia de adunare, o operaie de asignare pentru o variabil de tip Point. Pentru
clasa Point, aceast operaie se execut corect, chiar dac nu a fost suprancrcat
funcia operator=(), deoarece asignarea implicit se execut printr-o copiere
membru cu membru i n acest caz nu produce erori. Condiiile n care este absolut
necesar suprancrcarea funciei operator=() vor fi discutate mai jos.
n general, un operator binar poate fi suprancrcat printr-o funcie membr
nestatic cu un argument, sau printr-o funcie nemembr cu dou argumente.
Un operator unar poate fi suprancrcat printr-o funcie membr nestatic fr
nici un argument, sau printr-o funcie nemembr cu un argument. La suprancrcarea
operatorilor de incrementare sau decrementare (++, --) se poate diferenia un
operator prefix de un operator postfix folosind dou versiuni ale funciei operator.
Aceste funcii modific operandul asupra cruia se execut, aa cum se ntmpl i n
operaiile de incrementare i decrementare predefinite. n continuare sunt prezentate
cteva funcii operator ale clasei Point pentru operatori unari.
class Point{
//

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

Dac ++ precede operandul, este apelat funcia operator++(); dac ++


urmeaz operandului, atunci este apelat funcia operator++(int a), iar a are
valoarea zero.

4.2

Funcii operator friend

La suprancrcarea unui operator folosind o funcie care nu este membr a


clasei este necesar s fie transmii toi operanzii necesari, deoarece nu mai exist un
obiect al crui pointer (this) s fie transferat implicit funciei. Din aceast cauz,
funciile operator binar necesit dou argumente de tip clas sau referin la clas, iar
funciile operator unar necesit un argument de tip clas sau referin la clas. n cazul
operatorilor binari, primul argument transmis este operandul stnga, iar al doilea
argument este operandul dreapta. n continuare se reiau unele funcii operator ale
clasei Point, implementate ca funcii friend ale clasei.
class Point {
double x;
double y;
public:
//.

Elemente de Programare Orientat pe Obiecte

106
friend
friend
friend
friend
friend
friend

Point
Point
Point
Point
Point
Point

operator+(Point op1, Point op2);


operator-(Point op1, Point op2);
operator++(Point &p);
operator--(Point &p);
operator++(Point &p, int a);
operator--(Point &p, int a);

};
Point operator+(Point op1, Point op2){
Point temp;
temp.x = op1.x + op2.x;
temp.y = op1.y + op2.y;
return temp;
}

Point operator-(Point op1, Point op2){


Point temp;
temp.x = op1.x - op2.x;
temp.y = op1.y - op2.y;
return temp;
}

Point operator++(Point &p){


++p.x;
++p.y;
return p;

// incrementare prefix

Point operator--(Point &p) {


--p.x;
--p.y;
return p;

// decrementare prefix

Point operator++(Point &p, int a){// incrementare postfix


Point temp(p);
p.x++;
p.y++;
return temp;
}

Point operator--(Point &p, int a){// decrementare postfix


Point temp(p);
p.x--;
p.y--;
return temp;
}

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

Referitor la suprancrcarea operatorilor folosind funcii nemembre ale clasei


se mai pot face cteva observaii.
Dac funcia operator nu ar fi declarat funcie friend a clasei, ea nu ar avea
acces la variabilele protejate ale clasei. Problema s-ar putea rezolva prin adugarea
unor funcii publice de citire i scriere ale datelor membre ale clasei respective, care s
fie apelate n funcia operator. Dar o astfel de soluie este incomod, ineficient i nu
aduce nici un avantaj. Se poate evidenia acest aspect dac se ncearc o modificare a
funciei operator+() a clasei Point astfel:
class Point {
public:
//..
double getx() {return x;}
double gety() {return y;}
void setx(double a) {x = a;}
void sety(double b) {y = b;}
};
Point operator+(Point op1, Point op2){ // nu este friend
Point temp;
temp.setx(op1.getx() + op2.getx());
temp.sety(op1.gety() + op2.gety());
return temp;
}

Se poate observa c, dac se folosesc funcii nemembre ale clasei pentru


suprancrcarea operatorilor, atunci este mult mai eficient i mai comod ca acestea s
fie declarate funcii friend ale clasei respective.
O alt observaie referitoare la suprancrcarea operatorilor folosind funcii
friend este aceea c pentru operatorii care trebuie s modifice operandul (cum sunt
operatorii de incrementare, decrementare, etc) este necesar transmiterea operandului
ca parametru prin referin, ceea ce permite modificarea lui n funcia operator. Acest
mod de apel se observ la definirea funciilor operator++() i operator--()
din Exemplul 4.2.

4.3

Suprancrcarea operatorului de asignare

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

Elemente de Programare Orientat pe Obiecte

108

constructorii dect cu ceilali operatori, dar este, totui diferit de operaia de


iniializare pe care o efectueaz constructorii. n lipsa unei funcii operator=()
definit de utilizator pentru o clas X, este utilizat definiia implicit de asignare prin
copierea membru cu membru astfel:
class X {
//.
};
X& X::operator=(const X& op2){
// copiere membru cu membru
}
void f(){
X a,b;
a = b;
// se folosete asignarea implicit
}

Ceilali operatori de asignare (+=, -=,*=, etc.) nu au o semnificaie


predefinit pentru clase, deci, pentru a putea fi folosii, trebuie s fie definii de
utilizator.
n cazul claselor de obiecte care nu conin date alocate dinamic la iniializare
sau prin intermediul altor funcii membre, asignarea prin copiere membru cu membru
funcioneaz corect i n general nu mai este necesar s fie suprancrcat operatorul de
asignare. Acest lucru s-a remarcat i n execuiile din Exemplele 4.1 sau 4.2.
Nu acelai lucru este valabil pentru clasele care conin date alocate dinamic.
Situaia este foarte asemntoare celei prezentate n cazul constructorilor de copiere:
dac o clas conine date alocate dinamic, copierea membru cu membru care se
execut implicit la asignare sau la construcia prin copiere, are ca efect copierea
pointerilor la datele alocate dinamic, deci doi pointeri din dou obiecte vor indica ctre
aceeai zon din memoria liber. Acest situaie conduce la numeroase i subtile erori
care se vor analiza mai sugestiv pe un exemplu de clas care modeleaz un ir de
caractere, clasa String.

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

String& operator=(const String &op2);


String& operator=(const char* p);

Clasa String conine un pointer la un ir de caractere, str, i o variabil de


tip ntreg size care memoreaz dimensiunea vectorului de caractere corespunztor,
deci inclusiv spaiul necesar pentru caracterul 0 de la sfritul irului. Constructorii i
destructorul sunt simplu de implementat. Constructorul implicit creeaz un obiect
String cu un ir de caractere de lungime zero. Constructorul de iniializare
construietc un obiect String avnd ca argument un ir de caractere terminat cu nul.
Dimensiunea vectorului de caractere pe care l aloc n memoria liber este
strlen(p)+1, pentru a se insera caracterul 0 de la sfritul irului. Constructorul de
copiere este absolut necesar, pentru a aloca un ir nou i a evita astfel erorile care ar
aprea prin copierea pointerului, care ar indica ctre acelai ir de caractere n
memorie. De asemenea este absolut necesar s fie definit destructorul, pentru
eliminarea irului de caractere alocat dinamic n memorie.
String::String(){
cout << "Constructor implicit\n";
str = 0; size = 0;
}
String::String(const char *p){
cout << "Constructor initializare\n";
size = strlen(p) + 1;
str = new char[size];
strcpy(str, p);
}
String::String(const String& r){
cout << "Constructor copiere\n";
size = r.size;
str = new char[size];
strcpy(str, r.str);
}
String::~String(){
cout << "Destructor\n";
if (str){
delete []str;
str = 0;
}
}

Funciile operator <<() i operator >>() care definesc operaiile de


inserare i de extragere a unui obiect String dintr-un stream definite n acest punct
vor fi prezentate detaliat n seciunea 6.
ostream& operator <<(ostream &stream, const String &r){
stream << r.str;
return stream;
}
istream& operator >>(istream &stream, String &r){
char buf[256];

Elemente de Programare Orientat pe Obiecte

110

stream.get(buf,256);
r = buf;
return stream;

Operatorul de asignare este asemntor constructorului de copiere: nu


efectueaz copierea membru cu membru a datelor ci aloc un spaiu nou n memoria
liber pentru irul de caractere str i efectueaz copierea coninutului acestuia. n
plus, la asignare, mai este necesar tergerea irului pe care obiectul String pentru
care se execut operaia de asignare ar fi putut s-l aib alocat n memorie. S-au definit
dou funcii operator=(), cu argumente de tip diferit (o referin la clasa
String i un pointer i un ir de caractere), selecia ntre aceste funcii efectunduse pe baza tipului argumentului de apel.
String& String::operator=(const String &op2){
cout << "Operator=(String&)\n";
if (str)
delete []str;
size = op2.size;
str = new char[size];
strcpy(str, op2.str);
return *this;
}
String& String::operator=(const char* p){
cout << "Operator=(char*)\n";
if (str) delete []str;
size = strlen(p) + 1;
str = new char[size];
strcpy(str, p);
return *this;
}

Se pot studia situaiile n care se folosesc diferii constructori sau operatori de


asignare pentru obiecte din clasa String. Fie funcia f3():
void f3 (){
String str1("123456");
cout << str1 << endl;
String str2 = "abcd";
cout << str2 << endl;
String str3 = sir1;
cout << str3 << endl;
str3 = str2;
cout << str3 << endl;
str3 = "mnp";
cout << str3 << endl;
}

// constructor initializare
// constructor initializare
// constructor copiere
// operator=(String&)
// operator=(char*)

La execuia acestei funcii se afieaz urmtoarele mesaje la consol:


Constructor initializare
123456

4. Suprancrcarea operatorilor

111

Constructor initializare
abcd
Constructor copiere
123456
Operator=(String&)
abcd
Operator=(char*)
mnp
Destructor
Destructor
Destructor

Aceste mesaje indic modul n care sunt apelai constructorii, destructorul i


funciile operator de asignare. S-au creat trei obiecte din clasa String, obiectele
str1 i str2 prin constructori de iniializare, iar obiectul str3 prin constructorul
de copiere. Asignrile ctre obiectul str3 utilizeaz acea funcie operator=()
care se potrivete tipului argumentului. La ieirea din funcia f3(), cele trei obiecte
sunt distruse i se apeleaz de fiecare dat destructorul, care elimin din memorie irul
de caractere str corespunztor fiecrui obiect.
Aceasta este execuia corect a programului, asigurat de definirea corect a
constructorilor, destructorului i a funciilor de copiere. Lipsa unora dintre aceste
funcii poate avea urmri dintre cele mai grave.
De exemplu, lipsa funciei operator:
String& operator=(const String &op2);

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

Suprancrcarea operatorului de indexare

O funcie operator[]() poate fi folosit pentru a defini o operaie de


indexare pentru obiecte de tipuri definite de utilizator (clase). Ca i funcia operator de
asignare, funcia operator de indexare nu poate fi dect funcie membr nestatic a
clasei respective. Argumentul funciei reprezint al doilea operand al operaiei de
indexare i este un indice. Acest argument poate fi orice tip de date, spre deosebire de
indicii n tablouri care nu pot avea dect valori ntregi. Primul operand al funciei este
obiectul pentru care se execut operaia de indexare i pointerul la acesta (pointerul

Elemente de Programare Orientat pe Obiecte

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

La execuia acestei funcii se afieaz urmtoarele mesaje la consol:


123456789
ABCDEFGH

Datorit faptului c funcia operator de indexare returneaz o referin la un


element al irului, este posibil folosirea indexrii att pentru un membru dreapt ct i
pentru un membru stng al unei expresii.

4.5

Suprancrcarea operatorilor new i delete

Operatorii new i delete sunt utilizai pentru alocarea dinamic a datelor n


memoria liber (heap). Pentru suprancrcarea acestor operatori se pot folosi numai
funcii membre statice ale clasei. Prototipurile funciilor de suprancrcare ale
operatorilor new i delete sunt:

4. Suprancrcarea operatorilor

113

void* operator new(size_t lungime);


void operator delete(void* p);

n ambele situaii, funciile sunt implicit statice, fr s fie nevoie de utilizarea


specificatorului static.
Tipul size_t este un tip definit n fiierul de bibliotec stdlib.h (ntreg
fr semn), iar lungime este dimensiunea n numr de octei a zonei de memorie
care trebuie s fie alocat. Aceast valoare (lungime) nu trebuie s fie specificat la
apelul unei funcii operator new, deoarece compilatorul calculeaz n mod automat
dimensiunea obiectului pentru care se aloc zona de memorie. Pointerul universal
(void*) returnat are ca valoare adresa de nceput a zonei de memorie alocat.
Funcia operator delete() primete un pointer ctre regiunea de
memorie pe care trebuie s o elibereze.
Operatorii new i delete pot fi suprancrcai global, astfel nct orice
utilizare a lor s foloseasc versiunea suprancrcat, sau pot fi suprancrcai pentru o
anumit clas, i n aceast situaie funciile operator sunt membre statice ale clasei.
La ntlnirea unuia dintre operatorii new sau delete pentru un anumit tip,
compilatorul verific mai nti dac acesta a fost suprancrcat pentru clasa respectiv.
Dac a fost suprancrcat, se folosete versiunea suprancrcat a clasei; dac nu, este
apelat operatorul new sau delete global. Dac acestea au fost suprancrcate, se
utilizeaz aceste versiuni.

Exemplul 4.5

Se consider suprancrcarea operatorilor new i delete pentru clasa


Point. n acest exemplu simplu, operatorul suprancrcat nu modific modul de
alocare sau de eliberare a memoriei, ci doar adaug un mesaj la consol.
class Point{
//.
public:
//.
void* operator new(size_t dim);
void operator delete(void *p);
};
void* Point::operator new(size_t dim){
cout << "Creare punct nou\n";
return ::new Point;
}
void Point::operator delete(void* p){
cout << "Distrugere punct\n";
::delete p;
}
void f5(){
Point *p1 = new Point;
p1->display();
delete p1;

Elemente de Programare Orientat pe Obiecte

114
p1 = new Point(7);
p1->display();
delete p1;
p1=new Point(5,8);
delete p1;
}

Point *p2 = new Point[7];


delete []p2;

Expresiile de forma ::new sau ::delete se refer la operatorii globali de


alocare dinamic. Dac i acetia au fost suprancrcai, atunci este apelat funcia
suprancrcat, dar aceasta este o practic mai puin frecvent. La execuia funciei
f5(), la consol sunt afiate urmtoarele mesaje:
Creare punct nou
0 0
Distrugere punct
Creare punct nou
7 7
Distrugere punct
Creare punct nou
5 8
Distrugere punct

Operatorul new suprancrcat se apeleaz la fel ca operatorul new predefinit:


X* pX = new X(arg1, arg2, );
Dac n clasa X operatorul new a fost suprancrcat, atunci este apelat
versiunea suprancrcat a acestuia, iar argumerntele arg1, arg2, sunt folosite
pentru selecia constructorului clasei X, apelat implicit de funcia operator new().
n funcia f5() s-au apelat trei constructori diferii (constructorul implicit, cu
un argument i cu dou argumente) pentru primele trei alocri a cte unui obiect din
clasa Point i, de fiecare dat, la distrugere a fost apelat funcia operator delete
suprancrcat a clasei Point. Mesajele afiate evideniaz acest lucru.

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

Suprancrcarea operatorului de apel funcie

Operatorul de apel al unei funcii poate fi considerat o expresie binar de


forma:
nume_funcie(lista_argumente)
unde lista_argumente introduce argumentele efective de apel ale funciei cu
numele nume_funcie. n aceast expresie binar operandul stnga este numele
funciei, iar operandul dreapta este lista argumentelor de apel. Operatorul () poate fi
suprancrcat pentru o clas dat folosind o funcie membr nestatic a clasei printr-o
construcie de forma:
tip_returnat operator() (lista_argumente);

Lista de argumente este evaluat i utilizat dup regulile obinuite de transfer


ale argumentelor. Funcia operator de apel poate fi suprancrcat pentru orice clas,
dar ea este util n special pentru acele clase care au fie o singur funcie, fie una
dintre funciile membre este predominant ca utilizare.
De exemplu, suprancrcarea funciei operator de apel pentru clasa Point,
poate arta astfel:
class Point{
//..
public:
//.
Point operator()(double a, double b){
cout << "Operator () " << endl;
x = a;
y = b;
return *this;
}
};

Pentru funcia operator()() s-a ales operaia de atribuire a unor valori


datelor membre ale clasei i utilizarea ei ntr-o funcie oarecare f6() poate arta
astfel:
void f6(){
Point p3(11,22);
p3.display();
p3(4,5);
p3.display();
}

// afiseaza 11 22
// apel funcie afiseaz Operator()
// afiseaza 4 5

Suprancrcarea operatorului de apel este frecvent utilizat n definirea


iteratorilor, care permit parcurgerea n ordinea dorit a elementelor unei colecii, fr
ca aceast ordine s depind de modul de ordonare intern a elementelor coleciei. Un
astfel de exemplu este dat n seciunea 8. De asemenea, funcia operator () ()
mai este utilizat n operaii cu subiruri i ca operator de indexare n tablourile
multidimensionale.

Elemente de Programare Orientat pe Obiecte

116

4.7

Suprancrcarea operatorului pointer

Operatorul pointer -> poate fi considerat un operator unar postfix, care se


aplic operandului care l precede. Suprancrcarea acestui operator se poate face
printr-o funcie membr nestatic a clasei, cu aceeai sintax ca cea prezentat la
nceputul acestei seciuni. Fie o operaie de selecie membru cu forma general:
obiect->expresie

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

Fie dou clase X i Y n care se suprancarc n mod diferit operatorul pointer:


n clasa X funcia operator->() returneaz un pointer la tipul X, iar n clasa Y
funcia operator->()returneaz un pointer la tipul X (deci diferit de tipul Y al
clasei respective).
class X{
public:
int x;
X* operator->() {return this;}
void display(){
cout << x << endl;
}
friend ifstream& operator>>(ifstream& stream, X& obX);
};
ifstream& operator>>(ifstream& stream, X& obX){
stream >> obX.x;
return stream;
}
X* read(const char *p){
ifstream file(p);
X* pX = new X;
file >> (*pX);
return pX;
}
class Y{
X* pX;
const char* name;
public:

4. Suprancrcarea operatorilor

117

Y(const char *p):name(p) {pX=0;}


X* operator->() {
if (pX==0)
pX = read(name);
return pX;
}

};
void f7(){
X obX;
obX->x = 10;
cout << obX->x <<"
Y obY("test.txt");
obY->display();
}

" << obX.x << endl;

//10 10

//afiseaza valoarea citit

n acest exemplu implementarea funciei read() folosind operaii cu


streamuri este mai puin interesant (ea poate fi neleas mai bine dup parcurgerea
seciunii 6), ceea ce intereseaz este faptul c returneaz un pointer la un obiect de
tipul X, obiect creat n memoria liber i iniializat prin citirea unor date dintr-un fiier
de pe disc.
Din acest exemplu se pot observa cele dou modaliti de utilizare a funciei
operator->() suprancrcate n cele dou clase X i Y.
Funcia operator->() din clasa X returneaz un pointer la X (chiar
pointerul this), de aceea poate fi utilizat pentru a accesa o dat membr a clasei X.
De aceea, operaiile obX->x i obX.x sunt echivalente i mesajele afiate la consol
sunt identice, indicnd valoarea datei membre x a obiectului obX de tip X.
Funcia operator->() din clasa Y returneaz un pointer la X, de aceea
poate fi utilizat pentru accesul la o dat membr a clasei X, i anume funcia
display(). La construcia obiectului obY, sunt iniializate datele membre name =
test.txt i pX=0. Apelul funciei operator pointer pentru obiectul obY din
clasa Y apeleaz funcia read() care creeaz un obiect de clas X n memoria heap,
l iniializeaz cu date citite din fiierul cu numele test.txt, returneaz
pointerul la acest obiect creat. Acest pointer este folosit pentru selectarea funciei
display() a obiectului. La consol se afieaz valoarea numrului ntreg citit din
fiierul al crui nume este transferat ca argument constructorului obiectului obY
(test.txt).

4.8

Suprancrcarea operatorilor de conversie

Utilizarea unui constructor pentru a specifica o conversie de tip este posibil


pentru un numr limitat de situaii ,deoarece:
Nu exist o conversie implicit de la un tip definit de utilizator (clas), la
un tip predefinit.
Nu se poate defini o conversie de la un tip nou de date la un tip definit
mai nainte, fr modificarea tipului vechi.

Elemente de Programare Orientat pe Obiecte

118

Aceste probleme se pot rezolva prin definirea unui operator de conversie al


clasei. ntr-o clas X, o funcie membr nestatic X::operator T(), unde T este
un nume de tip (predefinit sau definit de utilizator) realizeaz conversia de la tipul X la
tipul T, n modul descris de funcia operator. Se pot diferenia dou categorii de
operatori de conversie: conversie dintr-un tip definit de utilizator ntr-un tip predefinit
i conversie dintr-un tip definit de utilizator n alt tip definit de utilizator.

4.8.1

Conversia dintr-un tip definit de utilizator


ntr-un tip predefinit

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

Se completeaz clasa Point cu definirea ctorva operatori de conversie, care


apoi sunt apelai n diferite modaliti.
class Point{
//..
public:
operator double();
operator void*();
};
Point::operator double(){
return sqrt(x*x + y*y);
}
Point::operator void*(){
if (sqrt(x*x + y*y)) return this;
else return 0;
}
void f8(){

4. Suprancrcarea operatorilor

119

Point p1(3, 4);


double x = p1;
cout << x << endl;
// afiseaza 5
cout << (double)p1 << endl;
// afiseaza 5
cout << double(p1) << endl;
// afiseaza 5
void* pp1 = p1;
cout << pp1 << endl;
// afiseaza o adres
if (p1)
// eroare, conversie ambigua
// intr-o expresie conditionala
cout << p1;

Se poate evita eroarea de compilare determinat de ambiguitatea la conversia


implicit din instruciunea if(p1), fornd n mod explicit unul dintre operatorii de
conversie ai clasei, de exemplu astfel: if ((void*)p1)

4.8.2

Conversia dintr-un tip definit de utilizator


ntr-un alt tip definit de utilizator

Fie dou clase X i Y. Conversia obiectelor de tip X n obiecte de tip Y se poate


realiza prin utilizarea constructorilor, sau prin suprancrcarea operatorului de
conversie n clasa Y.
Dac n clasa Y se definete un constructor de tipul Y(X ob), atunci se
poate realiza o conversie a unui obiect de tip X ntr-un obiect de tip Y. Pentru accesul
la datele private sau protected ale clasei X, este necesar declaraia
friend class Y n clasa X.

Exemplul 4.8

Se consider clasa Point, definit n aceast seciune i clasa Complex,


definit n seciunea 2. Conversia datelor ntre cele dou tipuri are i o semnificaie
matematic bine precizat, dat fiind c un numr complex poate fi reprezentat printrun punct ntr-un plan bidimensional (imaginea numrului complex). n continuare este
prezentat modul n care se realizeaz conversia datelor de tip Complex n date de tip
Point,(deci calculul imaginii unui numr complex) folosind conversia prin
constructor.
class Complex {
double re;
double im;
public:
Complex(){re = 0; im = 0;}
Complex(double r, double i) {re = r; im = i;}
friend class Point;
};
class Point {
//
public:
Point(Complex c) {
x = c.re;
y = c.im; }

Elemente de Programare Orientat pe Obiecte

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

Se construiete mai nti obiectul p1 folosind constructorul implicit;


instruciunea p1 = c1 apeleaz operatorul de conversie la tipul Point definit n
clasa Complex; dup conversie este apelat funcia operator de asignare a clasei
Point, care atribuie valoarea rezultat din conversie obiectului p1.

Este de menionat faptul c este admis definirea unei singure conversii de la


un tip de date la altul. Dac n Exemplul 4.9 s-ar pstra i constructorul de conversie
definit n Exemplul 4.8, atunci ar aprea o eroare de compilare.
Se poate observa c, n unele cazuri, o valoare de un tip dorit poate fi
construit prin utilizarea repetat a constructorilor i operatorilor de conversie. Dintre
acestea, o singur conversie implicit definit de utilizator este legal. Situaiile n
care un obiect poate fi construit n mai multe feluri sunt ilegale (mesajul de eroare de
compilare se refer la o ambiguitate).

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

Fie urmtoarea funcie:


void f(){
char v[4] = {65, 66, 67, 68};
String string(v);
cout << string;
}

Elemente de Programare Orientat pe Obiecte

122

Cum se explic eroarea care apare la execuia acestei funcii? S se defineasc


un alt constructor al clasei String care s poat fi folosit n astfel de situaii.
E4.6 Pentru clasa String s se defineasc urmtoarele operaii de comparaie
folosind funcii operator friend:
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int

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

String& s1, const String& s2);


String& s1, const char* s2);
char* s1, const String& s2);
String& s1, const String& s2);
String& s1, const char* s2);
char* s1, const String& s2);
String& s1, const String& s2);
String& s1, const char* s2);
char* s1, const String& s2);
String& s1, const String& s2);
String& s1, const char* s2);
char* s1, const String& s2);
String& s1, const String& s2);
String& s1, const char* s2);
char* s1, const String& s2);
String& s1, const String& s2 );
String& s1, const char* s2 );
char* s1, const String& s2 );

Valoarea returnat este diferit de 0 dac cele dou obiecte String


ndeplinesc condiia de comparaie i este 0 n toate celelate cazuri. Pentru
argumentele de tipul (const char* s) se consider pointerii la iruri terminate cu
nul.
E4.7 Pentru clasa String s se defineasc operaia de concatenare a dou iruri
folosind funcii friend operator+(). Valoarea returnat este un obiect nou de
tip String, al crui ir reprezint concatenarea irurilor coninute de cele dou
argumente. Se consider pointerii la iruri terminate cu nul.
String operator +(const String& s1, const String& s2);
String operator +(const char* s1, const String& s2);
String operator +(const String& s1, const char* s2);

E4.8 Pentru clasa String s se defineasc operaia de adugare a unui ir nou la


sfritul unui ir existent folosind funcia membr operator+=(). Se consider
pointerii la iruri terminate cu nul.
const
const

String& operator +=(const String& s );


String& operator +=(const char* s);

E4.9 S se defineasc urmtoarele funcii de cutare a unui caracter sau a unui


subir ale clasei String:
int FindChar(char c) const;

4. Suprancrcarea operatorilor
int
int
int
int
int

123

Find(const char* subsir) const;


Find(const String s) const;
FindReverse(char c) const;
FindReverse(const char* subsir)const;
FindReverse(const String s)const;

Pointerii subir trebuie s fie pointeri la iruri terminate cu nul. Funcia


Find(char c) returneaz indexul primului caracter cu valoarea c dintr-un obiect
String, sau 1 dac obiectul String nu conine acest caracter. Celelalte dou
funcii Find() returneaz indexul primului caracter n irul de caractere al unui
subir dat prin pointer la caracter sau ca un obiect String, dac acest subir este
coninut n irul dat, sau 1, dac nu exist un astfel de subir. Funciile
FindReverse() sunt definite n mod asemntor, cu diferena c se execut o
cutare de la sfritul irului.
E4.10 n clasa String, s se defineasc urmtoarele funcii:
int GetLength();
char
void
void
void

//
//
*GetBuffer(); //
MakeUpper(); //
MakeLower(); //
MakeReverse();//

return nr. caractere ale irului


fara caracterul nul terminal
returneaz bufferul de caractere
converteste n majuscule
converteste n litere mici
inverseaz caracterele n ir

E4.11 S se defineasc un iterator pentru clasa String.


E4.12 Se consider clasa IntArray descris n E2.6. S se defineasc urmtoarele
funcii membre ale clasei:
class IntArray {
//.
public:

IntArray(int* v, int n);


int& operator[](int i){return p[i];}
friend ostream& operator << (ostream& stream,
IntArray& r);
};
Ce se ntmpl la execuia funciei fia2():
void fia2(){
int v[] = {0,1,2,3,4,5,6,7};
int size = sizeof(v)/sizeof (int);
IntArray array(v, size);
cout << array;
for (int i=0;i<size;i++)
array[i] = array.GetAt(size-1-i);
cout << array;
}

124

Elemente de Programare Orientat pe Obiecte

E4.13 S se nlocuiasc funciile Display() ale claselor IntSList i


IntDList (descrise n seciunea 3.1.4) prin suprancrcarea funciei operator <<
pentru aceste clase, pentru scrierea la consol a valorilor memorate n nodurile listei
nlnuite.
E4.14 S se defineasc funcia operator de conversie operator int()const; a
claselor IntSList i IntDList, care s permit testarea strii unei liste (numr de
elemente egal sau diferit de zero) ntr-o expresie condiional.
E4.15 Se consider clasa IntSet (descris n seciunea 3.3.1) pentru reprezentarea
unei mulimi de numere ntregi. S se defineasc operaiile de reuniune, intersecie i
diferen a dou mulimi prin suprancrcarea operatorilor +, & i respectiv - . Cum
pot fi transferate argumentele de apel i cum poate fi returnat valoarea rezultat? S se
compare funciile operator cu funciile membre reunion(), intersection() i
diference() implementate n E3.16.

5
Clase derivate. Moteniri

Derivarea permite definirea ntr-un mod simplu, eficient i flexibil a unor


clase noi prin adugarea unor funcionaliti claselor deja existente, fr s fie
necesar reprogramarea sau recompilarea acestora. Clasele derivate exprim relaii
ierarhice ntre conceptele pe care acestea le reprezint i asigur o interfa comun
pentru mai multe clase diferite. De exemplu, entitile cerc, triunghi, dreptunghi, sunt
corelate ntre ele prin aceea c toate sunt forme geometrice, deci ele au n comun
conceptul de form geometric. Pentru a reprezenta un cerc, un triunghi sau un
dreptunghi, ntr-un program, trebuie ca aceste clase, care reprezint fiecare form
geometric n parte, s aib n comun clasa care reprezint n general o form
geometric. O clas care asigur proprieti comune mai multor clase se definete ca o
clas de baz. O clas derivat motenete de la una sau mai multe clase de baz toate
caracteristicile acestora, crora le adaug alte caracteristici noi, specifice ei.

5.1

Clase derivate

Se consider un program care descrie organizarea personalului unei instituii


fr folosirea claselor derivate. O clas numit Angajat deine date i funcii
referitoare la un angajat al instituiei:
class Angajat{
char *nume;
float salariu;
public:
Angajat();
Angajat(char *n, float sal);
Angajat(Angajat& r);
void display();
};

Angajat::display(){
cout << nume << << salariu << endl;
}

Elemente de Programare Orientat pe Obiecte

126

Diferite categorii de angajai necesit date suplimentare fa de cele definite n


clasa Angajat, corespunztoare postului pe care l dein. De exemplu, un ef de
secie (administator) este un angajat (deci sunt necesare toate datele care descriu
aceast calitate) dar mai sunt necesare i alte informaii, de exemplu precizare seciei
pe care o conduce. De aceea, clasa Administator trebuie s includ un obiect de
tipul Angajat, la care adaug alte date:
class Administrator{
Angajat ang;
int sectie;
public:
void display();
};

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

Aceast operaie care se poate reprezenta schematic prin indicarea derivrii cu


o sgeat (arc direcionat) de la clasa derivat la clasa de baz.
clasa de baz

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

Specificatorul de acces poate fi unul din cuvintele-cheie: public,


private, protected. Cnd o clas este derivat dintr-o clas de baz, clasa

5. Clase derivate. Moteniri

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

n schimb, se poate folosi funcia membr public display() a clasei


Angajat:
void Administrator::display(){
Angajat::display();
cout << sectie << endl;
}

Redefinirea funciei display() n clasa derivat ascunde funcia cu acelai


nume din clasa de baz, de aceea este necesar calificarea funciei cu numele clasei de
baz folosind operatorul de rezoluie: Angajat::display().
Trebuie fcut precizarea c funcia display() din clasa derivat nu
suprancarc ci redefinete (redefines) funcia cu acelai nume din clasa de baz. Un
membru al unei clase derivate care are acelai tip i nume cu un membru al clasei de
baz l redefinete pe cel din clasa de baz. Din clasa de baz (n funcii membre
apelate pentru obiecte din clasa de baz) este accesat membrul din clasa de baz.
Din clasa derivat (n funcii membre ale acesteia sau pentru obiecte din clasa
derivat) este accesat membrul redefinit n clasa derivat. Se spune c membrul din
clasa de baz este ascuns (hidden) de membrul redefinit n clasa derivat. Un membru
ascuns din clasa de baz poate fi totui accesat dac se folosete operatorul de
rezoluie (::) pentru clasa de baz. De exemplu:
class Base {
public:
int a, b;
};

Elemente de Programare Orientat pe Obiecte

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

Este posibil motenirea din mai multe clase de baz. De exemplu:


class X : public Y, public Z, .public W {
// corpul clasei
};

Evident, specificatorii de acces pot s difere (pot fi oricare din public,


private, protected). Motenirea multipl va fi reluat ntr-o subseciune
urmtoare.
O clas de baz se numete baz direct dac ea este menionat n lista de
clase de baz. O clas de baz se numete baz indirect dac nu este baz direct dar
este clas de baz pentru una din clasele menionate n lista claselor de baz. ntr-o
clas derivat se motenesc att membrii bazelor directe ct i membrii bazelor
indirecte i, de asemenea, se pstreaz mecanismul de redefinire i ascundere a
membrilor redefinii. De exemplu:
class A { public: void f();};
class B : public A { };
class C : public B {public: void f(); };
void f() {
C oc; c.f();
// apel f() din clasa C
A oa; a.f();
// apel f() din clasa A
B ob; b.f();
// apel f() din clasa A,
// deoarece nu este redefinit n B
}

Prin motenire multipl i indirect se pot crea ierarhii de clase care se


reprezint prin grafuri aciclice direcionate.
De exemplu, se completeaz programul de descriere a organizrii personalului
unei instituii i pentru alte categorii de personal (personal temporar, consultant,
secretar, director), prin adugarea cte unei clase pentru fiecare categorie de personal.
Modul n care aceste clase se deriveaz din anumite clase de baz evideniaz relaiile
de ierarhie ntre categoriile pe care le reprezint:
class
class
class
class

Personal { /* */ };
Angajat : public Personal{ /* */ };
Administrator : public Angajat { /* */ };
Director : public Administrator { /* */ };

5. Clase derivate. Moteniri

129

class Secretara : public Angajat { /* */ };


class Temporar : public Personal { /* */};
class Consultant : public Temporar, public Administrator
{ /* */ };

Aceast organizare a claselor se poate descrie printr-un graf aciclic direcionat


astfel:
Personal

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.

5.1.1 Constructori i destructori n clasele derivate


Constructorii i destructorii sunt funcii membre care nu se motenesc. La
crearea unei instane a unei clase derivate (obiect) se apeleaz implicit mai nti
constructorii claselor de baz i apoi constructorul clasei derivate. Ordinea n care sunt
apelai constructorii claselor de baz este cea din lista claselor de baz din declaraia
clasei derivate. Constructorii nu se pot redefini pentru c ei, n mod obligatoriu, au
nume diferite (numele clasei respective).
La distrugerea unui obiect al unei clase derivate se apeleaz implicit
destructorii n ordine invers: mai nti destructorul clasei derivate, apoi destructorii
claselor de baz, n ordinea invers celei din lista din declaraie.
La instanierea unui obiect al unei clase derivate, dintre argumentele care se
transmit constructorului acesteia o parte sunt utilizate pentru iniializarea datelor
membre ale clasei derivate, iar alt parte sunt transmise constructorilor claselor de
baz. Argumentele necesare pentru iniializarea claselor de baz sunt plasate n
definiia (nu n declaraia) constructorului clasei derivate.

Elemente de Programare Orientat pe Obiecte

130

Un exemplu simplu, n care constructorul clasei derivate D transfer


constructorului clasei de baz B un numr de k argumente arat astfel:
class B{
//.
public:
B(tip1 arg1,,tipk argk);
};
class D:public B {
//.
public:
D(tip1 arg1, ,tipk argk,,tipn argn);
};
D::D(tip1 arg1, ,tipk argk, .,tipn argn)
:B(arg1, arg2, ,argk);
{
// initialzare date membre clasa derivata
}

Dac nici clasa de baz nici clasa derivat nu au definii constructori,


compilatorul genereaz cte un constructor implicit pentru fiecare din clase i-i
apeleaz n ordinea baz, apoi derivat.
O clas derivat trebuie s aib prevzut cel puin un constructor n cazul n
care clasa de baz are un constructor care nu este implicit. n exemplul urmtor se
completeaz cele dou clase Angajat i Administrator cu constructorii i
destructorii necesari.

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

5. Clase derivate. Moteniri

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

class Administrator : public Angajat {


int sectie;
public:
Administrator(const char *n, float sal, int sec);
Administrator(Administrator& r);
~Administrator() {
cout << endl << "Destructor derivata"<< endl;
}

void display() const;


};

Administrator::Administrator(const char *n,


float sal, int sec):Angajat(n, sal) {
sectie = sec;
cout << "Constructor derivata\n";
}

void Administrator::display() const {


Angajat::display();
cout << "Sef sectie: " << sectie << endl;
}

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

Elemente de Programare Orientat pe Obiecte

Aceste mesaje evideniaz ordinea de apel a constructorilor i destructorilor


clasei derivate i a clasei de baz, precum i selecia funciei display() redefinit
n clasa derivat.
Pentru cele dou clase din Exemplul 5.1 au fost definii i constructorii de
copiere corespunztori. Se poate remarca faptul c argumentul constructorului de
copiere al bazei este o referin la un obiect derivat:
Administrator::Administrator(Administrator& r)
:Angajat(r) {.}

Acest lucru este posibil deoarece o referin la clasa derivat


Administrator poate fi convertit fr ambiguitate n referin la clasa de baz
Angajat. Fie funcia:
void fa2(){
Administratoe m1(Popescu, 3000.0f, 1);
Administrator m2(m1);
m2.display();
}

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.

5. Clase derivate. Moteniri

133

n ceea ce privesc destructorii, se poate observa i din exemplele date c nu se


motenesc destructorii claselor de baz, dar la distrugerea unui obiect derivat este
apelat mai nti destructorul clasei derivate i apoi destructorul clasei de baz. Acest
lucru este valabil att n cazul n care au fost definii destructori n baz i derivat, ct
i pentru destructorii implicii generai de compilator. Dat fiind c destructorii pot fi
declarai funcii virtuale, cteva aspecte relativ la comportarea destructorilor virtuali
vor fi prezentate n seciunea 5.4

5.2

Controlul accesului la membrii clasei de baz

Accesul la membrii clasei de baz motenii n clasa derivat este controlat de


specificatorul de acces (public, protected, private) din declaraia clasei
derivate.
O regul general este c, indiferent de specificatorul de acces declarat la
derivare, datele de tip private n clasa de baz nu pot fi accesate dintr-o clas
derivat. O alt regul general este c, prin derivare, nu se modific tipul datelor n
clasa de baz.
Un membru protected ntr-o clas se comport ca un membru private,
adic poate fi accesat numai de membrii acelei clase i de funciile de tip friend ale
clasei. Diferena ntre tipul private i tipul protected apare n mecanismul de
derivare: un membru protected al unei clase motenit ca public ntr-o clas
derivat devine tot protected n clasa derivat, adic poate fi accesat numai de
funciile membre i friend ale clasei derivate i poate fi transmis mai departe, la o
nou derivare, ca tip protected.

5.2.1

Motenirea de tip public a clasei de baz

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

n acest exemplu sunt prezentate i comentate cteva din situaiile de acces la


membrii clasei de baz din clasa derivat atunci cnd specificatorul de acces este
public.
class Base {
int a;

Elemente de Programare Orientat pe Obiecte

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
}

Dac se comenteaz liniile de program care provoac erori i se execut


funcia fb(), se obin urmtoarele mesaje la consol:
seta
setb
setb
setc
setc

5.2.2

din
din
din
din
din

baza
baza
derivata
baza
derivata

Motenirea de tip protected a clasei de baz

Dac specificatorul de acces din declaraia clasei derivate este protected,


atunci toi membrii de tip public i protected din clasa de baz devin membri
protected n clasa derivat. Bineneles, membrii de tip private n clasa de baz
nu pot fi accesai din clasa derivat.
Se reiau clasele din exemplul precedent cu motenire protected:

5. Clase derivate. Moteniri

135

class Derived : protected Base {


// acelasi corp al clasei
};

n aceast situaie, n funcia fb() sunt anunate ca erori de compilare toate


apelurile de funcii ale clasei de baz pentru un obiect derivat, precum i accesul la
variabila c a clasei de baz:
void fb(){
Derived obd;
obd.a = 1;
// eroare, a este private in baza
obd.seta(2); // eroare, Base::seta()este protected
obd.b = 3;
// eroare, b este protected
obd.Base::setb(5); // eroare, Base::setb este prot.
obd.setb(4); // corect, Derived::setb este public
obd.c = 6;
// eroare, c este protected
obd.Base::setc(7); // eroare, Base::setc este prot.
obd.setc(8); // corect, Derived::setc este public
}

Dac se comenteaz toate liniile din funcia fb() care produc erori, la
execuia acesteia se afieaz urmtoarele rezultate:
setb din derivata
setc din derivata

Din acest exemplu reiese pregnant faptul c n motenirea protected a unei


clase de baz nu mai pot fi accesai din afara clasei derivate nici unul dintre membrii
clasei de baz.

5.2.3

Motenirea de tip private a clasei de baz

Dac specificatorul de acces din declaraia clasei derivate este private,


atunci toi membrii de tip public i protected din clasa de baz devin membri de
tip private n clasa derivat i pot fi accesai numai din funciile membre i
friend ale clasei derivate. Din nou trebuie reamintit c membrii de tip private n
clasa de baz nu pot fi accesai din clasa derivat. Din punct de vedere al clasei
derivate, motenirea de tip private este echivalent cu motenirea de tip
protected. ntr-adevr, dac modificm clasa derivata din Exemplul 5.2 astfel:
class Derived : private Base {
// acelasi corp al clasei
};

mesajele de erori de compilare i de execuie ale funciei fb() sunt aceleai ca i n


motenirea protected.
Ceea ce difereniaz motenirea de tip private fa de motenirea de tip
protected este modul cum vor fi trasmii mai departe, ntr-o nou derivare,
membrii clasei de baz. Toi membrii clasei de baz fiind motenii de tip private,

Elemente de Programare Orientat pe Obiecte

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

Modificarea individual a tipului de acces

Se pot modifica drepturile de acces la membrii motenii din clasa de baz


prin declararea individual a tipului de acces. Aceste declaraii nu pot modifica, ns,
accesul la membrii de tip private ai clasei de baz. Se reiau astfel cele dou clase
din Exemplul 5.2:
class Base {
int a;
protected:
int b;
public:
int c;
};
class Derived : private Base {
int d;
public:
Base::a;
// eroare, a nu poate fi declarat public
Base::b;
// corect
Base::c;
// corect
//
};
void fb(){
Derived obd;
obd.a = 1;
// eroare, a este private
obd.b = 5;
// corect, b este public
obd.c = 6;
// corect, c este public
obd.setb(5); // corect, Derived::setb este public
obd.setc(8); // corect, Derived::setc este public
}

Variabilele b i c din clasa baza au fost transformate n membri publici ai


clasei Derived prin declaraiile: Base::b; Base::c; n zona de declaraii de tip
public a clasei Derived.

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:

5. Clase derivate. Moteniri

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

n funcia fbm() este creat obiectul obd de clas Derived. Constructorul


clasei Derived transfer cte un argument constructorilor fiecrei clase de baz, care
iniializeaz datele membre x i y. La execuia funciei fbm() se afieaz
urmtoarele mesaje, care indic ordinea n care sunt apelai constructorii i destructorii
clasei derivate i ai claselor de baz.
Constructor baza 1
Constructor baza 2
Constructor derivata
3 4
Destructor derivata
Destructor baza 2
Destructor baza 1

Elemente de Programare Orientat pe Obiecte

138

5.3.1

Clase de baz virtuale

ntr-o motenire multipl este posibil ca o clas s fie motenit indirect de


mai multe ori, prin intermediul unor clase care motenesc, fiecare n parte, clasa de
baz. De exemplu:
class
class
class
class

L
A
B
D

{
:
:
:

public: int x;};


public L { /*
*/};
public L { /*
*/};
public A, public B { /*

*/};

Acest motenire se poate reprezenta printr-un graf aciclic direcionat


(directed acyclic graph DAG) care indic relaiile dintre subobiectele unui obiect
din clasa D. Din graful de reprezentare a motenirilor, se poate observa faptul c baza
L este replicat n clasa D.
L

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

n aceast situaie, acesul la un membru al clasei L (de exemplu variabila x),


al unui obiect de tip D este ambiguu i deci interzis (este semnalat ca eroare la
compilare):
D ob;
ob.x = 2;

// eroare D::x este ambiguu; poate fi n


// baza L a clasei A sau n baza L a clasei B

5. Clase derivate. Moteniri

139

Se pot elimina ambiguitile i deci erorile de compilare prin calificarea


variabilei cu domeniul clasei creia i aparine:
ob.A::x = 2;
ob.B::x = 3;

// corect, x din A
// corect, x din B

O alt soluie pentru eliminarea ambiguitilor n motenirile multiple este de


a impune crearea unei singure copii a clasei de baz n clasa derivat. Pentru aceasta
este necesar ca acea clas care ar putea produce copii multiple prin motenire indirect
(clasa L, n exemplul de mai sus) s fie declarat clas de baz de tip virtual n
clasele care o introduc n clasa cu motenire multip. De exemplu:
class
class
class
class

L
A
B
D

{
:
:
:

public: int x; };
virtual public L { /*
virtual public L { /*
public A, public B { /*

*/ };
*/ };
*/ };

O clas de baz virtual este motenit o singur dat i creeaz o singur


copie n clasa derivat. Graful de reprezentare a motenirilor din aceste declaraii
ilustreaz acest comportament:
L

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

Elemente de Programare Orientat pe Obiecte

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

Funcii virtuale i polimorfism

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

Conversia pointerilor ntre clase de baz i derivate

Conversia unui pointer la o clas derivat n pointer la o clas de baz a


acesteia este implicit, dac derivarea este de tip public i nu exist ambiguiti.
Conversia invers, a unui pointer la o clas de baz n pointer la derivat nu
este admis implicit. O astfel de conversie se poate fora explicit prin operatorul de
conversie cast. Rezultatul unei astfel de conversii este ns nedeterminat, de cele mai
multe ori provocnd erori de execuie. Se vor nelege mai uor aceste aspecte prin
urmtorul exemplu.

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

5. Clase derivate. Moteniri

141

S-a definit o clas de baz B i o clas derivat D. Conversia pointerului la


clasa derivat (de tip D*) n pointer la clasa de baz (de tip B*)este legal i
implicit. Conversia invers nu este admis implicit, iar forarea prin operatorul cast
este admis de compilator dar produce rezultate nedeterminate la execuie.

Se mai poate aduga faptul c i referinele pot fi convertite n mod


asemntor: o referin la un obiect dintr-o clas derivat poate fi convertit implicit
ntr-o referin la clasa de baz a acesteia, dac derivarea este de tip public i nu
exist ambiguiti.
Referinele nu pot fi utilizate n mecanismul de virtualitate deoarece ele
trebuie s fie inializate att la declarare ct i la conversie.

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

Se consider o clas de baz B i dou clase derivate D1 i D2. n clasa de


baz sunt definite dou funcii: funcia normal f()i funcia virtual g(). n fiecare
din clasele derivate se redefinesc cele dou funcii f() i g(). n funcia main() se
creeaz trei obiecte: un obiect din clasa de baz B indicat prin pointerul B* pb i
dou obiecte din clasele derivate D1 i D2. Fiecare dintre obiectele derivate poate fi

Elemente de Programare Orientat pe Obiecte

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;
//

f() este functie normala,g() este functie virtuala

// 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()

, pointeri D1*, D2*


din D1
din D2
din D1
din D2

// 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

5. Clase derivate. Moteniri

143

Mesajele care se afiseaz la consol la execuia funciei fv1() sunt cele


nscrise ca i comentarii n program. n primele situaii, cnd pointerul este pointer la
tipul obiectului, nu se manifest nici o deosebire ntre comportarea unei funcii
virtuale fa de comportarea unei funcii normale: se selecteaz funcia
corespunztoare tipului pointerului i obiectului.
Diferena de comportare se manifest n ultima situaie, atunci cnd este
apelat o funcie pentru un obiect de tip clas derivat printr-un pointer la o clas de
baz a acesteia. Pentru funcia normal f() se selecteaz varianta depinznd de tipul
pointerului. Pentru funcia virtual g() se selecteaz varianta n funcie de tipul
obiectului, chiar dac este accesat prin pointer de tip baz.

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

Comentariile arat care dintre funciile redefinite este utilizat, i anume


funcia care corespunde tipului obiectului, indiferent dac este virtual sau nu.
n acest moment se pot preciza mai detaliat asemnrile i diferenele dintre
funciile suprancrcate (overloaded), funciile redefinite (redefined) i funciile
virtuale redefinite (overrided). n toate aceste situaii numele funciilor este acelai.
n cazul funciilor suprancrcate, lista de argumente trebuie s difere astfel
nct selecia unei variante a funciei s poat fi fcut fr ambiguitate dup tipul
argumentelor de apel.
Funciile redefinite i funciile virtuale redefinite trebuie s aib aceleai
argumente de apel (acelai prototip).
Dintre mai multe funcii (o funcie definit virtual n clasa de baz i
redefinit n clasele derivate), selecia variantei funciei se face dup tipul obiectului
pentru care este invocat.
Dintre mai multe funcii nevirtuale (o funcie definit fr specificatorul
virtual n clasa de baz i redefinit n clasele derivate), selecia variantei funciei
se face dup tipul pointerului cu care este invocat.
Nu pot fi declarate funcii virtuale: funcii nemembre, constructori, funcii de
tip static, funcii friend.

Elemente de Programare Orientat pe Obiecte

144

5.4.3

Motenirea funciilor virtuale

Atributul virtual se motenete pe tot parcursul derivrii. Cu alte cuvinte,


dac o funcie este declarat de tip virtual n clasa de baz, funcia redefinit ntr-o
clas derivat este funcie de tip virtual n mod implicit (fr s fie nevoie de
declaraie explicit virtual). Acest lucru nseamn c, dac aceast clas derivat
servete pentru definirea unei noi derivri, funcia va fi motenit de asemenea de tip
virtual. Un exemplu n acest sens poate fi obinut uor din clasele definite n
Exemplul 5.5 (clasa B i clasa D1) la care se mai adaug nc o clas derivat astfel:
class DD1:public D1{
public:
void f() { cout << "f() din DD1\n";}
void g() { cout << "g() din DD1\n";}
};

Mesajele care se obin la execuia funciei urmtoare sunt prezentate ca i


comentarii:
void fdd(){
DD1* pdd1 = new DD1();
D1* pd1 = pdd1;
B* pb = pdd1;
pb->f();
// f()
pd1->f();
// f()
pdd1->f();
// f()
pb->g();
// g()
pd1->g();
// g()
pdd1->g();
// g()
}

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"; }
};

5. Clase derivate. Moteniri

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

Destructorii pot fi declarai de tip virtual, aceast declaraie fiind absolut


necesar n situaia n care se dezaloc un obiect de tip clas derivat folosind pointer
la o clas de baz a acesteia. De exemplu:
class B{
public:
B(){ cout << "Constructor B\n";}
~B(){cout << "Destructor B\n";}
};
class D:public B {
public:
D(){ cout << "Constructor D\n";}
~D(){cout << "Destructor D\n";}
};
void main(){
B* p = new D();
// creaza un obiect D
delete p;
// sterge un obiect B
}

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:

Elemente de Programare Orientat pe Obiecte

146

class B{
public:
B(){ cout << "Constructor B\n";}
virtual ~B(){cout << "Destructor B virtual\n";}
};

Fr s fie modificat clasa D sau funcia main()de mai sus, la execuia


acesteia dup declararea destructorului clasei B ca virtual, se obin mesajele:
Constructor B
Constructor D
Destructor D
Destructor B virtual

care indic faptul c, la execuia instruciunii delete p a fost apelat destructorul


clasei derivate D, dup tipul obiectului (obiectul este de clas D) nu al pointerului
folosit (p este de tip B*). Destructorul D::~D() elibereaz partea din clasa derivat a
obiectului, dup care (n mod implicit, fr s fie nevoie ca programatorul s specifice
acest lucru), apeleaz destructorul clasei de baz B care elibereaz subobiectul de baz
din obiectul dat.

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

5. Clase derivate. Moteniri

147

class Y : public X{};// clasa abstracta


class Z : public Y{ // clasa normala (neabstracta)
public:
void fp(){cout<< "f() din Z\n";} // redefinire fp()
};
void main (){
X obx;
// eroare: nu se poate instantia
// clasa abstracta X
Y oby;
// eroare: nu se poate instantia
// clasa abstracta Y
Z obz;
// corect, clasa Z nu este abstracta
X* pxz = &obz;
// corect, se pot defini pointeri
// la clasa abstracta
Y* pyz = &obz;
// corect, la fel ca mai sus
pxz->fp();
// corect, fp() din Z
pyz->fp();
// corect, fp() din Z
obz.fp();
// corect, fp() din Z
}

Din clasa abstract X a fost derivat clasa, de asemenea abstract, Y. Numai


clasa Z redefinete funcia virtual pur fp(), deci ea poate fi instaniat.
Comentariile scrise sunt suficiente pentru a se nelege comportarea acestor
clase.

Un exemplu mai complet care evideniaz utilitatea funciilor virtuale n


general i a funciilor virtuale pure n special este Exemplu 5.7 de mai jos.

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

Elemente de Programare Orientat pe Obiecte

148

void main (){


Convert* p = 0;
// pointer la baza
cout<<"Introduceti valoarea si tipul conversiei: ";
double v;
char ch;
cin >> v >> ch;
switch (ch){
case 'i':
//conversie inch -> cm (clasa IC)
p = new IC(v);
break;
case 'f':
//conv. Farenheit -> Celsius (clasa FC)
p = new FC(v);
break;
}
if (p){
p->conv();
cout << p->getx() << "---> " << p->gety()<< endl;
delete p;
}
}

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

Dup cum s-a menionat n partea introductiv, una din caracteristicile


importante ale programrii orientate pe obiecte este aceea c permite definirea unei
interfee comune pentru mai multe metode specifice diferitelor funcionaliti.

5. Clase derivate. Moteniri

149

Aceast comportare, care asigur simplificarea i organizarea sistemelor de


programe complexe, este cunoscut sub numele de polimorfism. Polimorfismul
introdus prin mecanismul de virtualitate este polimorfism la nivel de execuie, care
permite legarea trzie (late binding) ntre evenimentele din program, n contrast cu
legarea timpurie (early binding), proprie apelurilor funciilor normale (nevirtuale).
Intercorelarea (legarea) timpurie se refer la evenimentele care se desfoar
n timpul compilrii i anume apeluri de funcii pentru care sunt cunoscute adresele de
apel: funcii normale, funcii suprancrcate, operatori suprancrcai, funcii membre
nevirtuale, funcii friend. Apelurile rezolvate n timpul compilrii beneficiaz de o
eficien ridicat.
Termenul de legare trzie se refer la evenimente din timpul execuiei. n
astfel de apeluri, adresa funciei care urmeaz s fie apelat nu este cunoscut dect n
momentul execuiei programului. Funciile virtuale sunt evenimente cu legare trzie:
accesul la astfel de funcii se face prin pointer la clasa de baz, iar apelarea efectiv a
funciei este determinat n timpul execuiei de tipul obiectului indicat, pentru care
este cunoscut pointerul la clasa sa de baz.
Avantajul principal al legrii trzii (permis de polimorfismul asigurat de
funciile virtuale) l constitue simplitatea i flexibilitatea programelor rezultate.
Bineneles, exist i dezavantaje, cel mai important fiind timpul suplimentar necesar
seleciei funciei apelate efectiv din timpul execuiei, ceea ce conduce la un timp de
execuie mai mare al programelor. Cu alte cuvinte, folosirea funciilor virtuale trebuie
rezervat numai pentru situaii n care sunt n mod real necesare, atunci cnd
beneficiul adus de polimorfism depete costul suplimentar de execuie.

5.5

Mecanismul de motenire n implementarea


coleciilor de date

Aa cum s-a prezentat n seciunea 3, o colecie de date se caracterizeaz prin


modelul de date pe care l reprezint, care d forma coleciei (de exemplu, list,
vector, etc.) i prin tipul datelor (obiecte ale coleciei), care pot fi de tip predefinit sau
definit de utilizator. Definirea individual a claselor de reprezentare a unei colecii de
o anumit form pentru fiecare tip de obiecte conduce la un numr imens de
reprezentri, toate avnd aceeai organizare i funcionalitate i diferind doar prin tipul
de date la care se refer. S-a putut remarca acest lucru din definirea repetat a claselor
pentru reprezentarea tablourilor de diferite tipuri (tablouri de numere ntregi, tablouri
de iruri de caractere, etc) sau a listelor nlnuite. Implementarea mai general a unor
clase de colecii prin folosirea pointerilor generici ca informaii n tablouri, sau liste
implic numeroase restricii n definirea i utilizarea acestora i n general sunt
considerate nesigure ca tip, datorit conversiilor explicite (cast) a tipurilor de
pointeri, i deci necontrolate de ctre compilator.
Folosind motenirea se pot defini colecii de date de diferite tipuri, cu mult
mai mult flexibilitate i concizie. Exist mai multe posibiliti, trei dintre acestea
fiind prezentate n acest capitol.

Elemente de Programare Orientat pe Obiecte

150

5.5.1

Implementarea coleciilor de date prin clase derivate

O modalitate simpl (dar nu general aplicabil) de implementare a coleciilor


de date este de a defini forma coleciei printr-o clas de baz, iar colecia de obiecte de
un tip oarecare (predefinit sau definit de utilizator) se reprezint printr-o clas derivat
din aceasta.
Se va exemplifica aceast modalitate pentru implementarea unei liste dublu
nlnuite generale, pentru orice tip de date. Se definesc mai nti dou clase de baz:
clasa DlistNode, care nu conine nici o informaie, ci doar pointerii de nlnuire i
clasa DList, care memoreaz pointerii la primul i la ultimul nod al listei i numrul
de noduri:
class DListNode{
DListNode* prev;
DListNode* next;
public:
friend class DList;
DListNode() { prev = NULL; next = NULL;}
DListNode* GetNext(){return next;}
DListNode* GetPrev(){return prev;}
virtual ~DListNode(){}
};

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

Funciile acestor clase sunt foarte asemntoare cu ale clasei care


implementeaz o list dublu nlnuit de numere ntregi, clasa IntDList, prezentat
n seciunea 3. Pentru implementarea unei liste de obiecte de un anumit tip (de
exemplu, de numere ntregi), se deriveaz o clas pentru reprezentarea nodurilor din
clasa DlistNode i o clas de reprezentare a listei nsi din clasa DList. Pentru o
list dublu nlnuit de numere ntregi se pot defini clasele astfel:
class IDListNode :public DListNode{
int v;
public:
IDListNode(int x){v = x;}
int& GetV(){return v;}
};

5. Clase derivate. Moteniri

151

class IDList : public DList{


public:
void AddHead(int x);
void AddTail(int x);
IDListNode* GetHead();
IDListNode* GetTail();
int RemoveHead();
int RemoveTail();
friend ostream& operator <<(ostream& stream,
IDList& list);
};

Funciile clasei derivate folosesc funciile clasei de baz pentru operaii de


inserare sau extragere a obiectelor din list. De exemplu, funcia AddHead():
void IDList::AddHead(int x){
IDListNode* p = new IDListNode(x);
DList::AddHead(p);
}

Aceast modalitate de implementare a coleciilor se poate folosi pentru orice


tip de date, predefinit sau definit de utilizator. n exerciiile din aceast seciune sunt
propuse mai multe astfel de reprezentri.
Deficiena acestei metode este aceea c nu se poate defini o interfa unic n
clasa de baz pentru acele funcii care necesit accesul la informaia propriu zis din
nod, cum sunt de exemplu funciile de cutare a unui anumit element sau funciile de
inserare a unui element care s menin elementele ordonate (ntr-o list, vector sau
arbore). Pentru astfel de situaii se pot folosi coleciile care au o clas de baz unic,
coleciile care folosesc clase abstracte, sau colecii reprezentate prin clase template.

5.5.2

Implementarea coleciilor de date


folosind o clas de baz unic

O alt modalitate de a implementa colecii sigure ca tip (type-safe) pentru


diferite tipuri de date definite de utilizator este de a defini o clas de baz unic, din
care se deriveaz toate clasele (tipurile de date) ale coleciilor. Forma coleciei (list,
vector, etc.) se definete pentru clasa de baz; pentru o colecie de aceeai form
pentru un tip specific de date, se poate folosi direct colecia pentru clasa de baz sau se
poate deriva o clas de colecie proprie, prin redefinirea unui numr redus de funcii.
Se poate nelege mai uor acest mecanism pe un exemplu de implementare a
tablourilor de obiecte de tipuri definite de utilizator.
Se definete mai nti o clas de baz unic pentru toate tipurile de obiecte
dorite n colecii, clasa Object:
class Object{
public:
Object() {};
~Object() {};
};

152

Elemente de Programare Orientat pe Obiecte

Forma coleciei (vector, n acest exemplu), se definete printr-o clas de


asemenea derivat din clasa de baz Object (clasa ObArray) ca un vector de
pointeri de tip Object*. Nu este necesar s fie specificat dimensiunea vectorului
deoarece se asigur creterea dimensiunii acestuia atunci cnd este necesar:
class ObArray : public Object {
Object **p;
// vector de pointeri
int count;
// numar de elemente
int grows;
// increment de cretere dimens.
int size;
// dimens. vector
public:
ObArray()
{
count = 0;
grows = 4;
size = grows;
p=(Object**)new BYTE[grows*sizeof(Object*)];
}
void RemoveAll();
int GetCount() { return count; }
int Add(Object* x);
int InsertAt(int i, Object *x);
int RemoveAt(int i);
Object* GetAt(int i) {
if(i >= 0 && i < count)return p[i];
else return 0;
}
};

Constructorul clasei ObArray construiete un vector de pointeri de


dimensiune (count) zero. Elemente noi (pointeri de tip Object*) se pot aduga la
sfritul vectorului, folosind funcia Add(), sau se pot insera ntr-o poziie dorit
(funcia InsertAt()), i de fiecare dat dimensiunea vectorului crete dac este
necesar:
int ObArray::Add(Object *x){
if(count < size)
p[count++]=x;
else {
Object **p1 = p;
size = count + grows;
p=(Object**)new BYTE[size*sizeof(Object*)];
for(int i=0;i<count;i++)
p[i] = p1[i];
delete [] (BYTE *)p1;
p[count++] = x;
}
return count-1;
}
int ObArray::InsertAt(int i, Object *x){
if(i < 0 || i > count) return -1;
if(i==size) return Add(x);

5. Clase derivate. Moteniri

153

if(count < size) {


for(int j = count;j>i;j--)
p[j]=p[j-1];
p[i]=x;
}

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

Pentru o colecie de obiecte de un anumit tip, se folosete clasa Object ca o


clas de baz a acestuia. De exemplu, pentru reprezentarea unui vector de puncte n
plan, se deriveaz clasa Point (definit n seciunea 2) din clasa Object:
class Point : public Object {
double x,y;
public:
Point(double xx, double yy) {x=xx; y=yy;}
Point(double v){ x = v; y = v;}
~Point() {};
friend ostream &operator<<(ostream &stream, Point &p){
return stream<<"X= "<<p.x<<" "<<"Y= "<<p.y;
};

}
//

Elemente de Programare Orientat pe Obiecte

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();

n clasa PointArray se redefinesc un numr redus de funcii, majoritatea


fiind folosite cele motenite de la clasa de baz ObArray. n primul rnd trebuie s
fie definit destructorul (care nu se motenete) i apoi toate funciile care returneaz un
pointer de tipul specific (Point*), deoarece conversia din pointer la baz n pointer
la tipul derivat nu este admis implicit ((Point*)ObArray::GetAt(i); ).
n schimb, dat fiind c exist o conversie implicit din pointer la clasa derivat
(Point*) n pointer la clasa de baz (Object*), nu sunt necesare redefiniri ale
funciilor de forma:
int PointArray::Add(Point *x){
return ObArray::Add((Object*)x);
}

deoarece, dac nu este redefinit funcia Add() n clasa derivat PointArray,


atunci oricum se folosete funcia Add() din clasa de baz ObArray, iar conversia
unui pointer de tipul Point* n pointer la clasa de baz (Object*) este de
asemenea implicit. Exemplu de utilizare a clasei:
void fpa1(){
PointArray array;
for(int i=0;i<5;i++) {
Point *p = new Point(i);
array.Add(p);
}
array.RemoveAt(2);
array.InsertAt(3,new Point(9.0));
int size = array.GetSize();
for(i=0;i<size;i++) {
Point *p = array.GetAt(i);
cout << *p <<endl;
}
}

5. Clase derivate. Moteniri

155

La execuia acestei funcii se afieaz urmtoarele mesaje:


X
X
X
X
X

=
=
=
=
=

0
1
2
9
4

Y
Y
Y
Y
Y

=
=
=
=
=

0
1
2
9
4

A doua modalitate de a avea o colecie de form vector pentru tipul de date


Point, este aceea de a folosi direct clasa coleciei de baz, ObArray, cu specificarea
de fiecare dat a conversiei de tip necesare. De exemplu, dac se rescrie funcia
fpa1(), n care vectorul array se declar de tip ObArray se obine o eroare de
compilare datorit faptului c nu poate fi convertit implicit pointerul de tip Object*
returnat de funcia Add() a clasei ObArray n pointer de tipul Point*. Soluia este
simpl, de a impune o conversie explicit astfel:
void fpa1(){
ObArray array;
for(int i=0;i<5;i++) {
Point *p = new Point(i);
array.Add(p);
}
array.RemoveAt(2);
array.InsertAt(3,new Point(9.0));
int size = array.GetSize();
for(i=0;i<size;i++) {
Point p = (Point*)array.GetAt(i);
cout << *p <<endl;
}
}

La execuia acestei funcii se obin aceleai rezultate ca i la execuia funciei


precedente.
Astfel de implementri ale claselor de colecii sigure se gsesc n bibliotecile
C++. De exemplu n biblioteca Microsoft Foundation Class, care este bibiloteca de
clase a compilatorului Microsoft Visual C++, sunt prevzute mai multe clase de
colecii sigure ca tip. Clasa CObArray reprezint un vector de pointeri la obiecte din
clasa CObject (clasa CObject este folosit ca o clas de baz pentru majoritatea
claselor MFC). Clasa CObList reprezint o list dublu nlnuit de pointeri la
obiecte de clas CObject. Alte variante de clase de colecii din MFC se pot gsi n
manualul de referin.

5.5.3

Implementarea coleciilor de date prin clase container

Implementrile coleciilor de date prezentate n aceast seciune, ca i n


seciunea 3, folosesc clase concrete, n sensul c toate operaiile acestora sunt definite
i ele pot fi folosite pentru instanieri. Clasele concrete (tipurile concrete concrete
data types) permit definirea fiecrui concept care nu are suport predefinit n limbaj,
dar nu exprim prile comune ale mai multor implementri ale unui anumit concept

Elemente de Programare Orientat pe Obiecte

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(){};
};

Absena constructorului i prezena destructorului virtual este tipic n clasele


abstracte. Toate funciile membre (cu excepia destructorului) sunt funcii virtuale
pure. Fie clasele CList, respectiv CArray, clasele care definesc o list, respectiv un
vector de obiecte de tip T. Folosind aceste clase se pot defini mai multe implementri
ale unei mulimi. De exemplu:
class CArraySet : public Set, private CArray {
int index;
public:
void insert(T*);
void remove(T*);
int lookup(T*);
T* first() {index = 0; return next();}
T* next();
};

CArraySet(int size): CArray(size), index(0) {}

Clasa CArraySet folosete tipul concret de implementare a mulimii ca


vector (clasa concret CArray) ca o clas de baz cu tip de acces private i
redefinete funciile virtuale pure ale clasei Set, asigurndu-se astfel o interfa
identic cu a oricrei alte implementri care folosete clasa Set ca i clas de baz.
n mod asemntor se poate defini o clas CListSet, cu dou clase de baz,
clasa abstract Set i clasa concret CList, care implementeaz o mulime
reprezentat printr-o list nlnuit. Clasa CListSet definete o mulime

5. Clase derivate. Moteniri

157

reprezentat ca list nlnuit i, bineneles, redefinete toate funciile virtuale pure


ale clasei container Set.
Cele dou clase CListSet i CArraySet implementeaz fiecare modelul
de date (conceptul) mulime reprezentat ca list, respectiv ca vector, dar avnd
interfa identic, asigurat prin motenirea clasei abstracte Set, care exprim, prin
interfaa pe care o pune la dispoziie, acest concept de mulime.
Mai mult, instane ale claselor CListSet i CArraySet (deci mulimi,
reprezentate ns diferit, ca list sau ca vector) pot fi prelucrate prin aceleai funcii
care folosesc pointeri la clasa container Set:
void fproc(Set* s) {
for (T* p = s.first(); p = s.next()){
// prelucrare
}
}
void fabstr(){
CArraySet set1(1000); // vector de 1000 de elemente
CListSet set2;
fproc(&set1);
fproc(&set2);
}

Aadar, clasele container folosite pentru implementarea coleciilor de date


sunt clase abstracte, care asigur definirea unui concept ntr-un mod ce permite
coexistena ntr-un program a mai multor implementri ale acestuia.
Clasele abstracte se pot utiliza n acelai mod pentru definirea oricrui
concept, nu numai pentru concepte privitoare la coleciile de date, aa cum a fost
prezentat n aceast seciune.

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

care s afiseze unul din mesajele Punctele nu sunt n acelasi


spatiu, Punctele sunt identice sau Punctele nu sunt
identice, in funcie de rezultatul comparaiei dimensiunilor spaiului i al
coordonatelor punctului p cu al punctului curent. Se poate nlocui aceast funcie cu o
funcie operator?

158

Elemente de Programare Orientat pe Obiecte

ntr-o funcie fcp1() se creeaz 2 puncte: punctul p13 n spaiul 3d


(3-dimensional) de coordonate 4,5,6, i punctul p14 n spaiul 4d (4-dimensional) de
coordonate 4,5,6,1. Se cere s se afiseze la consol rezultatul comparaiei celor dou
puncte, folosind funcia Compare(), sau funcia operator < suprancrcat.
S se defineasc funcia operator << pentru clasa CPoint i s se afieze la
consol coordonatele pe care le are fiecare din cele dou puncte folosind acest
operator. Presupunnd c n fiecare funcie constructor i destructor a fost introdus
cte un mesaj care afieaz la consol numele acesteia, s se arate care sunt toate
mesajele afiate la execuia funciei fcp1().
E5.2 S se defineasc funcia operator de indexare a clasei CPoint i s se
foloseasc pentru citirea i modificarea valorilor coordonatelor punctelor.
E5.3 S se completeze sau s se modifice clasa CPoint astfel nct s poat fi
folosit ca o clas de baz pentru mai multe clase derivate corespunztoare spaiilor de
diferite dimensiuni, dar fr s se modifice tipul de acces al datelor membre (acestea
trebuie s rmn private). Dintre clasele derivate posibile, s se defineasc clasa
CPoint2d, care modeleaz un punct n spaiul 2-dimensional i clasa CPoint3d
care modeleaz un punct n spaiul 3-dimensional. Pentru fiecare din aceste dou clase
se cere s se defineasc cte o funcie constructor de iniializare de forma:
CPoint2d(double x, double y);
CPoint3d(double x, double y, double z).

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 = ..

(pentru clasa CPoint2d),


(pentru clasa CPoint3d).

unde .. nseamn valoarea curent a coordonatelor punctului. Fie funcia fpd():


void fpd(){
CPoint *p = new CPoint2d(4,5);
p->Display();
delete p;
cout << endl;
p = new CPoint3d(4,5,1);
p->Display();
delete p;
}

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().

5. Clase derivate. Moteniri

E5.5

159

Cum se poate transforma clasa de baz CPoint ntr-o clas abstract?

E5.6 S se defineasc toate funciile declarate ale claselor DlistNode i DList,


IDListNode i IDList i s se demonstreze funcionalitatea similar a acestei liste
dublu nlnuite de numere ntregi cu cea definit prin intermediul claselor
IntDListNode i IntDList din seciunea 3. De exemplu, se va arta echivalena
n funcionarea ca o stiv i n funcionarea ca o coad a acestei liste.
E5.7 S se creeze o list dublu nlnuit de numere complexe, folosind clasele de
baz DListNode i DList.
E5.8 S se creeze o list dublu nlnuit de obiecte din clasa String (definit n
seciunea 4) folosind clasele de baz DListNode i DList. Ce operatori trebuie s
fie suprancrcai n clasa String, pentru funcionarea corect a listei nluit ca
stiv sau ca i coad?
E5.9 S se definesc funcia operator >> pentru clasa IDlist, care s introduc
un nod nou ntr-o list nlnuit pentru fiecare numr ntreg introdus de la tastatur.
E5.10 S se defineasc funcia operator de indexare a clasei de baz DList, n care
indicele reprezint poziia elementului ncepnd de la capul listei. Dup aceast
completare a clasei DList, cum se execut urmtoarea funcie fd3()?
void fd3(){
IDList list;
list.AddTail(0);
list.AddTail(1);
list.AddTail(2);
list.AddTail(3);
list[1] = 11;
list[2] = 22;
list[3] = 33;
cout << list[1]<< ;
cout << list[2]<< ;
cout << list[3]<< endl;
cout << list;
}

//
//
//
//

inserare
inserare
inserare
inserare

0
1
2
3

E5.11 S se implementeze un iterator pentru parcurgerea n ordinea de la primul spre


ultimul element al listelor dublu nlnuite definite de clasa DList, prin definirea unei
clase iterator DListIter. Se poate folosi acest iterator pentru parcurgerea i afiarea
elementelor unei liste de numere ntregi ?
E5.12 S se adauge funcia int Append(ObArray& r)n clasa ObArray, care
s adauge un vector la sfritul unui alt vector de clas ObArray, cu creterea
corespunztoare a dimensiunii.

160

Elemente de Programare Orientat pe Obiecte

E5.13 S se defineasc funcia operator de indexare pentru clasa ObArray. Cum


poate fi utilizat aceast funcie pentru accesul unui element, dat prin index, ntr-un
obiect din clasa PointArray (descris n seciunea 5.5.2)?
E5.14 S se implementeze un vector de pointeri la obiecte de clasa Complex
(numere complexe), folosind clasa de baz ObArray.
E5.15 S se implementeze un vector de pointeri la obiecte de clasa String (ir de
caractere) folosind clasa de baz ObArray. Ce operatori ai clasei String trebuie s
fie suprancrcai pentru funcionarea corect a vectorului la operaiile de inserare i
de eliminare a unor iruri din vector?

6
Sistemul de intrare/ieire din C++

Pe lng faptul c poate fi folosit sistemul de intrare/ieire (I/O) din C, n C++


mai este definit nc un sistem complet integrat, care permite operarea cu obiecte de
tipuri definite de utilizatori. Ca i sistemul I/O din C, cel din C++ opereaz prin
streamuri (fluxuri). Un stream este o entitate logic n care se introduc (se insereaz)
sau din care se extrag informaii. Toate streamurile se comport n mod asemntor,
chiar dac echipamentele fizice la care sunt conectate pot s difere substanial.
Funciile bibliotecii de streamuri sunt numeroase, dar n continuare vor fi
descrise numai cele fundamentale, iar pentru alte detalii se poate consulta biblioteca
I/O a compilatorului C++. Fiierul antet iostream.h definete interfaa cu
biblioteca de streamuri prin mai multe clase i funcii. Clasa de nivel cel mai mic este
clasa streambuf, care definete operaiile de baz de I/O. Cu excepia situaiilor n
care se doresc propriile clase de I/O, nu se deriveaz direct din clasa streambuf, ci
din clasa din al doilea nivel de ierarhie, numit clasa ios. Clasa ios accept I/O
formatate i din ea sunt derivate clasele istream, ostream, iostream, pentru
streamuri de intrare, ieire, respectiv intrare/ieire, precum i alte clase pentru
utilizarea fiierelor pe disc i formatarea n memorie.
La nceperea execuiei unui program C++, n mod automat se deschid patru
streamuri standard ncorporate. Aceste streamuri sunt:
cin
cout
cerr
clog

Intrare standard
Ieire standard
Ieire standard pentru eroare
Versiune cu buffer de memorie pentru cerr

Tastatur
Ecran
Ecran
Ecran

Streamurile cin, cout i cerr corespund streamurilor stdin, stdout i


stderr din C.

Elemente de Programare Orientat pe Obiecte

162

6.1

Funcii de I/O pentru tipurile predefinite

Operaiile de I/O din C++ se efectueaz folosind funciile operator de inserie


<< i operator de extragere >>. Streamurile pot fi considerate ca recipieni de
caractere aranjate ntr-o anumit ordine n care se introduc i din care se extrag date.
Funciile de operare asupra streamurilor specific modul n care se execut conversia
ntre un ir de caractere din stream i o variabil de un anumit tip. Aceste funcii
operator sunt definite n clasa ostream, respectiv istream, pentru toate tipurile
predefinite ale limbajului, iar pentru tipurile definite de utilizator ele pot fi
suprancrcate.
De exemplu, n clasa ostream sunt definite funcii operator de inserie <<
astfel:
class ostream : public virtual ios {
// ...
public:
ostream& operator << (const char*); // iruri
ostream& operator << (char);
ostream& operator << (short);
ostream& operator << (int);
ostream& operator << (long);
ostream& operator << (double);
ostream& operator << (const void*); // pointeri
//
};

n clasa istream sunt definite funcii operator de extracie >> astfel:


class istream : public virtual ios {
// ...
public:
istream& operator >> (char*);
istream& operator >> (char&);
istream& operator >> (short&);
istream& operator >> (int&);
istream& operator >> (long&);
istream& operator >> (double&);

};

// iruri

Exemplul 6.1

Funcia de citire de la consol a unei secvene de numere ntregi separate prin


spaii albe (whitespace adic unul din caracterele blanc, tab, newline, carriage return,
formfeed) poate arta astfel:
void fint(){
int size = 10;
int array[10];
for(int i=0;i<size;i++){

6. Sistemul de I/O din C++

163

if (cin >> array[i])


cout << array[i] << " ";
else {
cout << "eroare non-int";
break;
}

O intrare diferit de ntreg va cauza eroare n operaia de citire i deci oprirea


buclei for. De exemplu, din intrarea:
1 2 3 4.7 5 6 7 8 9 0 11
funcia fint() va citi primele patru numere, dup care apare eroare n operaia de
intrare, citirea numerelor ntregi se ntrerupe i pe ecran apare mesajul:
1 2 3 4 eroare non-int
Caracterul punct este lsat n streamul de intrare, ca urmtor caracter de citit.

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

Aceste funcii trateaz spaiile albe la fel ca pe toate celelalte caractere.


Funcia get(char& c) citete un singur caracter n argumentul c. De exemplu, o
funcie fg() de copiere caracter cu caracter de la intrare (streamul cin) la ieire
(streamul cout) poate arta astfel:
void fg(){
char c;
while(cin.get(c)) cout << c;
}

n locul funciei operator >> se poate folosi funcia put() pentru


scrierea ntr-un stream de intrare, astfel nct funcia fg() se poate rescrie astfel:
void fg(){
char c;
while(cin.get(c)) cout.put(c);
}

Funcia cu trei argumente istream::get() citete cel mult n1 caractere


ntr-un tablou de caractere care ncepe la adresa p i introduce, ca ultim caracter n
tablou, caracterul 0. Cel de-al treilea argument specific caracterul terminator al citirii,
implicit caracterul newline (\n), care este lsat ca primul caracter necitit n stream.

Elemente de Programare Orientat pe Obiecte

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

Instruciunea cin >> buf este suspect deoarece un ir de intrare cu mai


mult de 99 caractere va produce depire la scrierea n buffer i deci erori de execuie.
Citirea folosind funcia get() introduce maximum n-1 caractere n buffer, urmate
de caracterul 0. Dac n irul de intrare este gsit caracterul terminator '\n',nainte
de terminarea unei secvene de n-1 caractere, acesta este lsat n streamul de intrare,
iar n buffer se introduce, de asemenea, caracterul 0.

6.2

Funcii de I/O pentru tipuri definite de utilizator

Funciile de I/O pentru tipuri definite de utilizator se obin prin


suprancrcarea operatorilor de inserie i de extragere, care au urmtoarea form
general:
ostream& operator<<(ostream& os,tip_clasa nume){
// corpul functiei
return os;
}
istream& operator<<(istream& is,tip_clasa& nume){
// corpul functiei
return is;
}
Primul argument al funciei este o referin la streamul de ieire, respectiv de
intrare. Pentru funcia operator de extragere << al doilea argument este dat printr-o
referin la obiectul care trebuie s fie extras din stream; n aceast referin sunt
nscrise datele extrase din streamul de intrare. Pentru funcia operator de inserie >> al
doilea argument este dat prin tipul i numele obiectului care trebuie s fie inserat, sau
printr-o referin la acesta. Funciile operator de inserie i extracie returneaz o
referin la streamul pentru care au fost apelate, astfel nct o alt operaie de I/O
poate fi adugat acestuia.
Funciile operator << sau >> nu sunt membre ale clasei pentru care au fost
definite, dar pot (i este recomandabil) s fie declarate funcii friend n clasa
respectiv. Motivul pentru care o funcie operator << sau >> nu poate fi membr a
unei clase este simplu de observat: atunci cnd o funcie operator de orice fel este
membru al unei clase, operandul din stnga, care este un obiect din clasa respectiv,
este cel care genereaz apelul i transmite implicit funciei pointerul this. Ori, n

6. Sistemul de I/O din C++

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.

Elemente de Programare Orientat pe Obiecte

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');

for (int i=0;i<size;i++)


cout << buf[i];
cout << endl;

Execuia acestei funcii produce urmtoarele mesaje la consol:


Introduceti x, y: 1.3 4.5
z = (1.3,4.5)
Introduceti un sir:123456
123456

Funcia operator de extragere a clasei String permite citirea sigur a unor


iruri de caractere de o dimensiune maxim impus (255 de caractere n exemplul dat).
Extragerea unor iruri de o dimensiune oarecare este lsat ca un exerciiu simplu
pentru cititor.

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;

6. Sistemul de I/O din C++

};

167

int bad() const;

Funcia rdstate() returneaz 0 dac nu a aprut nici o eroare i nici nu a


fost ntlnit indicatorul de sfrit al streamului (end of file, eof). Fiecare stare n parte
poate fi obinut i prin apelul uneia din funciile good(), eof(), fail() sau
bad().
Dac starea este good() sau eof(), operaia precedent s-a desfurat cu
succes. Aplicarea unei operaii de inserie ntr-un stream care nu se afl n starea
good()este o operaie nul i nu modific streamul. O operaie de extragere a unor
date dintr-un stream care cauzeaz starea fail() las, n general, nemodificat
variabila n care urma s aib loc nscrierea.
Atunci cnd un stream este utilizat ntr-o operaie de testare (ca de exemplu, n
expresiile deja utilizate mai sus: while(cin.get(c))) este testat starea
streamului i testul returneaz valoarea 0 dac este setat unul din biii failbit sau
badbit. Aceast operaie de testare este posibil prin conversia unui stream ntr-un
pointer, executat de funcia suprancarcat operator de conversie a clasei ios:
operator void*() const;

6.4

Formatarea I/O

n exemplele prezentate pn acum au fost folosite operaii de I/O


neformatate, n care transformarea unui obiect ntr-o secven de caractere a fost
executat conform unor reguli implicite. n C++ se pot specifica formate diferite de
cele implicite pentru operaiile de intrare i de ieire, n mod asemntor cu formatele
introduse prin funciile printf() i scanf() din C.
Exist dou posibiliti de formatare a datelor n C++. Prima posibilitate este
cea oferit de accesul la variabilele i funciile membre ale clasei de baz ios care se
refer la formatarea datelor. Cea de-a doua posibilitate o reprezint utilizarea unor
funcii speciale, numite manipulatori, care pot fi incluse n operaii (expresii) de I/O.

6.4.1

Formatarea I/O folosind membrii clasei ios

Clasa ios controleaz conexiunea dintre un stream i bufferul utilizat pentru


operaiile de intrare i de ieire, avnd astfel posibiltatea de a specifica modul n care
sunt introduse sau extrase caracterele. Clasa ios conine date membre care
memoreaz informaii despre baza de numeraie folosit (zecimal, octal, hexazecimal),
despre precizia cu care se scriu sau se citesc numerele n virgul mobil, etc, precum i
funcii care permit setarea sau examinarea acestora pentru fiecare stream. Flagurile
(indicatorii) de formatare din clasa ios i funciile de acces la acetia sunt:
class ios {
public:
// flaguri (indicatori) de formatare

Elemente de Programare Orientat pe Obiecte

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;

//
//
//
//
//
//
//
//
//
//
//
//
//
//

ignor spaii la intrare


completare dup valoare
compl. nainte de val.
compl.format intern
zecimal
octal
hexazecimal
afiseaza baza
af. zerouri din fata nr.
caractere uppercase
explicit +
.dddddd Edd
dddd.dd
dupa fiec. op. ieire

// lrgime cmp
// caracter de umplere

long flags(long f);


long flags() const;
long setf(long);
// setare indicatori
long setf(long setbits, long field);
long unsetf(long);

};

int precision(int);
int precision() const;

// prec. nr. v. mobil

Funcia setf() cu un singur argument este utilizat pentru activarea unuia


sau mai multor indicatori de formatare. Fiind o funcie membr a clasei ios, ea
trebuie s fie apelat pentru un anumit obiect (stream), ceea ce nseamn c nu exist o
stare global de formatare, ci fiecare stream are propria lui stare. Indicatorii de
formatare pot fi modificai individual, prin apeluri separate ale funciei setf() sau
grupai ntr-o expresie logic OR (SAU). De exemplu, instruciunile:
cout.setf(ios::hex);
cout.setf(ios::showbase);

sunt echivalente cu instruciunea:


cout.setf(ios::hex | ios::showbase);

i au ca efect afiarea unui numr n format hexazecimal, cu reprezentarea explicit a


bazei folosite. Dup o astfel de formatare, instruciunea:
cout << 100;

va afia la consol: 0x64.

6. Sistemul de I/O din C++

169

Dat fiind c indicatorii de formatare sunt definii n interiorul clasei ios,


pentru a avea acces la valorile lor, acetia trebuie s fie nsoii de specificarea
domeniului clasei ios (de exemplu, ios::hex).
Funcia setf() cu dou argumente este o form suprancrcat care permite
modificarea acelor stri care sunt reprezentate prin cmpuri compuse din mai muli
bii. De exemplu, baza numeric de afiare implic un cmp format din trei bii (dec,
oct i hex) din care numai unul poate fi setat la un moment dat. Pentru o astfel de
setare se apeleaz funcia setf() avnd ca prim argument (setbits) indicatorul
care trebuie s fie setat, al doilea fiind un pseudoargument (field), care definete
cmpul celui dinti.
Pseudoargumentele definite n clasa ios sunt: ios::basefield (pentru
baza de numeraie, format din biii dec, oct, hex), ios::adjustfield
(pentru cmpul de aliniere format din biii left i right) i ios::floatfield
(pentru cmpul de afiare a numerelor cu virgul mobil, format din biii
scientific i fixed). De exemplu, fiecare din instruciunile:
cout.setf(ios::oct, ios::basefield); // octal
cout.setf(ios::dec, ios::basefield); // zecimal
cout.setf(ios::hex, ios::basefield); // hexazecimal

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

Fie urmtoarea funcie care formateaz streamuri folosind funciile membre


ale clasei ios.
void ff(){
cout.setf(ios::uppercase | ios::scientific);
cout <<100.12 <<\n;
cout.unsetf(ios::uppercase);
cout <<100.12 <<\n;
}

La execuia acestei funcii se afieaz la consol numrul 100.12 n


urmtoarele formate, stabilite folosind funciile setf() i unsetf():
1.0012E+002
1.0012e+002

Funcia flags() permite examinarea strii indicatorilor de format, fr ca


acetia s fie modificai. Funcia flags() returneaz o dat de tipul long, care
conine valoarea fiecrui indicator de format
ntr-un bit aflat pe poziia
corespunztoare definirii din clasa ios.

Elemente de Programare Orientat pe Obiecte

170

Funcia width() specific o mrime de cmp minim pentru reprezentarea


unui numr. Dup apelul funciei width(w), dac valoarea inserat n stream
folosete mai puin de w caractere, atunci se completeaz cmpul pn la aceast
lungime cu caracterul de completare curent (implicit spaiu). Dac pentru
reprezentarea valorii inserate n stream sunt necesare mai mult de w caractere, nu se
trunchiaz numrul, ci se intoduc toate caracterele acestuia, chiar dac se depete
valoarea w.
Funcia fill() specific caracterul de completare a cmpurilor, folosit
atunci cnd numrul de caractere necesare pentru reprezentarea unui numr este mai
mic dect mrimea minim setat.
Funcia precision() este utilizat pentru a determina numrul de cifre
care s fie afiate dup punctul zecimal.

Exemplul 6.4

Se consider urmtorul program care permite afiarea strii indicatorilor de


formatare ai unui stream:
void showflags(){
long f, i, j;
char indic[15][12] = {
"skipws",
"left",
"right",
"internal",
"dec",
"oct",
"hex",
"showbase",
"showpoint",
"uppercase",
"showpos",
"scientific",
"fixed",
"unitbuf",
};
f = cout.flags(); // se citeste starea indic.
if (f)
for (i=1,j=0; i<=0x2000; i = i<<1,j++){
if(i & f)
cout << "Este activat " << indic[j] << "\n";
}
else cout<< "Nu este activat nici un indicator \n";
}
void main(){
cout << 1234.56 <<endl;
showflags();

6. Sistemul de I/O din C++

171

cout.setf(ios::right| ios::showpoint | ios::fixed);


cout.fill('*');
cout.width(20);
cout << 1234.56 << "\n";
showflags();
cout.unsetf(ios::right|ios::showpoint|ios::fixed);
cout << 1234.56 <<endl;
showflags();

La execuia acestui program se obin urmtoarele mesaje la consol:


1234.56
Nu este activat nici un indicator
*********1234.560000
Este activat right
Este activat showpoint
Este activat fixed
1234.56
Nu este activat nici un indicator

Aceste mesaje evideniaz corespondena dintre comanda de formatare


apelat, numele indicatorilor de formatare modificai i efectul acestora asupra
modului de afiare a unor date. De asemenea, folosind aceast funcie se pot
experimenta i alte comenzi de formatare a streamurilor.

6.4.2

Formatarea I/O folosind manipulatori

A doua posibilitate de a modifica modul de formatare a unui stream este aceea


de a folosi funciile speciale numite manipulatori. O parte din manipulatorii care se pot
folosi pentru formatarea streamurilor sunt prezentai n tabelul de mai jos.
Din acest tabel se poate observa c cei mai muli manipulatori dubleaz
funciile de formatare din clasa ios.
Manipulator

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/ieire de date n zecimal


Insereaz un caracter newline
Insereaz un null
Golete un stream
Intrare/ieire n hexazecimal
Intrare/ieire n octal
Dezactiveaz indicatorii f
Seteaz baza numeric
Seteaz caracterul de completare
Activeaz indicatorii f
Stabilete nr. de cifre pt. precizie
Stabilete dim. min. a cmpului
Omite spaii libere de la inceput

Intrare i ieire
Ieire
Ieire
Ieire
Intrare i ieire
Intrare i ieire
Intrare i ieire
Ieire
Ieire
Intrare i ieire
Ieire
Ieire
Intrare

Elemente de Programare Orientat pe Obiecte

172

Exemplul 6.5

Folosind aceeai funcie showflags() din exemplul precedent, se poate


observa modul de formatare a streamurilor folosind manipulatori i corespondena
dintre acetia i funciile de formatare membre ale clasei ios. Fie funcia:
void fm(){
cout<<1234.56<<endl;
showflags();
long flags=ios::right| ios::showpoint | ios::fixed;
cout <<setw(20)<<setfill('*') <<setiosflags(flags);
cout << 1234.56<<endl;
showflags();
cout << resetiosflags(flags);
cout << 1234.56;
showflags();
}

La execuia acestei funcii se afieaz aceleai mesaje la consol ca i la


execuia programului precedent, deci se obin aceleai formate ale streamurilor ca i
prin execuia funciilor membre ale clasei ios.

Pentru accesul la manipulatorii care au argumente (cum sunt setw(),


setfill(), etc.), este necesar includerea fiierului antet iomanip.h.

6.5

Sistemul de I/O cu fiiere n C++

Dei biblioteca de streamuri din C++ reprezint un sistem integrat pentru


operaiile de intrare/ieire, lucrul cu fiiere pe disc prezint unele particulariti care
trebuie s fie studiate separat.
Clasele care definesc streamuri care corespund unor fiiere pe disc sunt
ifstream, ofstream i fstream, care sunt derivate din istream i, respectiv,
ostream, deci, indirect, sunt derivate din clasa ios i au acces la toate datele i
funciile membre ale acesteia. Aceste clase (ifstream, ofstream, fstream)
sunt definite n fiierul antet fstream.h, care trebuie s fie inclus atunci cnd se
lucreaz cu fiiere pe disc.

6.5.1

Deschiderea i nchiderea fiierelor

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:

6. Sistemul de I/O din C++

173

void open(const char *file_name, int mode,


int access=filebuf::openprot);
n acest prototip, file_name este numele fiierului, care poate include calea

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
};
//
};

//
//
//
//
//
//
//

desch. pt. citire


desch. pt. scriere
desch. pt cutare
adaugare (append)
trunchiere
er. daca nu exista fis.
er. daca fis. exista

Argumentul mode este opional pentru streamuri de intrare ifstream,


pentru care valoarea implicit este ios::in, i pentru streamuri de ieire
ofstream, pentru care valoarea implicit este ios::out. Pentru streamuri de
intrare/ieire fstream, argumentul mode trebuie s aib una din valorile definite n
clasa ios. n argumentul mode se pot combina prin operatorul OR (SAU) dou sau
mai multe din aceste valori definite.
Implicit, fiierele se deschid n mod text. Valoarea ios::binary
determin deschiderea n mod binar a fiierului. Cnd fiierul este deshis n mod text,
au loc unele modificri de caractere, de exemplu, caracterul newline este convertit n
secvena newline-carrige return. Astfel de modificri nu apar n cazul fiierelor
deschise n mod binar. Orice fiier poate fi deschis n mod text sau mod binar,
indiferent de felul n care au fost formatate datele.
Valoarea argumentului access determin tipul de acces la fiier. Valoarea
implicit este filebuf::openprot, care specific tipul de fiier normal (clasa
filebuf este derivat din clasa streambuf).
Exemple de apeluri ale funciei open() pentru deschidere de fiiere:
ifstream input;
input.open("intrare.txt");
ofstream output;
output.open("iesire.txt");
fstream inout;
inout.open("fisier", ios::in|ios::out);

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.

Elemente de Programare Orientat pe Obiecte

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

Programul care urmeaz creaz un fiier de inventariere inventar.txt


care conine numele articolului, preul unitar, numrul de buci i valoarea total.
void finv(){
ofstream output("inventar.txt");
if (!output)
cout << "Nu se poate deschide
fisierul inventar.txt\n";
char buffer[80];
double pret,total;
int buc;
while(1){
cout << "\nArticol: ";
cin.get(buffer,80);
if (buffer[0] == 0)
break;
cout << "Pret unitar: ";
cin >> pret;
cout << "Nr. bucati: ";
cin >> buc; cin.get();
total = buc*pret;
output << buffer << endl;
output << pret<< endl;
output << buc << endl;
output << total << endl;
}

6. Sistemul de I/O din C++

175

output.close();

6.5.2

Accesul aleator la fiiere

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();
//
};

Tipurile de date streamoff i streampos sunt definite n fiierul antet


iostream.h, i au capacitatea de a conine cea mai mare valoare valid pe care o
poate avea dimensiunea, respectiv offsetul ntr-un fiier, iar seek_dir este o
enumerare n clasa ios care poate lua urmtoarele valori:
class ios {
// ...
enum seek_dir{
beg=0,
// depl. de la nceputul fiierului
cur=1,
// depl. de la poz. curent n fiier
end=2
// depl. de la sfritul fiierului
};
// ..
};

Funcia seekp() cu un argument poziioneaz pointerul de inserie a


fiierului la valoarea pos, dat ca argument.
Funcia seekp() cu dou argumente deplaseaz pointerul de inserie al
fiierului asociat streamului cu un numr de caractere egal cu valoarea offset fa
de originea orig specificat ca argument.

Elemente de Programare Orientat pe Obiecte

176

Funcia tellp() returneaz poziia curent de scriere n fiier, deci


valoarea pointerului de scriere.
Pentru accesul aleator la date n cursul operaiilor de intrare se pot folosi
urmtoarele funcii membre publice ale clasei istream:
class istream : public virtual ios{
//
public:
int peek();
istream& putback();
istream& seekg(streampos pos);
istream& seekg(streamoff offset, seek_dir orig);
streampos tellg();
// ...
};

Funciile suprancrcate seekg() cu unul i dou argumente se comport n


mod asemntor cu funciile seekp(), dar opereaz asupra pointerului de citire:
funcia seekg() cu un argument seteaz pointerul de citire n poziia pos transmis
ca argument; funcia seekg() cu dou argumente deplaseaz pointerul de citire al
fiierului asociat streamului cu un numr de caractere egal cu valoarea offset fa
de originea orig specificat ca argument.
Funcia tellg() permite obinerea poziiei curente n fiier.
Funcia peek() permite aflarea urmtorului caracter din fiier, fr s-l
extrag din streamul asociat.
Funcia putback() permite introducerea napoi n fiier a unui caracter,
astfel nct acesta s poat fi utilizat ulterior ntr-o alt operaie de intrare.
n exemplele urmtoare se ilustreaz modul de utilizare a acestor funcii de
acces aleator la datele din fiiere.

Exemplul 6.7

n funcia finvers() se utilizeaz operaiile de acces aleator pentru


inversarea primelor n caractere din fiierul cu numele fisier.txt .
int finvers(int n){
long i, j;
char c1, c2;
fstream file1("fis.txt",ios::in|ios::out|ios::binary);
for(i=0;i<n;i++){
file1.get(c1);
cout<< c1;
}
cout << endl;
for (i=0,j=n-1; i<j; i++,j--){
file1.seekg(i, ios::beg);
file1.get(c1);
file1.seekg(j, ios::beg);

6. Sistemul de I/O din C++

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;

Se presupune un fiier care conine urmtoarele caractere:


1234567890abcd... La apelul funciei finvers(10), la consol sunt afiate
coninutul primelor 10 poziii din fiier nainte i dup inversare astfel:
1234567890
0987654321

La un nou apel al funciei finvers(10), mesajele afiate la consol sunt:


0987654321
1234567890

6.6

Streamuri asociate cu iruri de caractere

Aa cum s-a prezentat n seciunea precedent, un stream poate fi asociat cu


un fiier pe disc. n mod asemntor, se poate asocia un stream cu un ir de caractere
n memoria principal. Clasele care construiesc streamuri n memorie (deci asociate cu
iruri de caractere stocate n memoria principal) sunt descrise de clasele
istrstream, ostrstream i strstream, derivate din clasele istream,
ostream, iostream i sunt definite n fiierul antet strstrea.h.
Un stream de I/O asociat unui ir de caractere n memorie se poate construi ca
un obiect din clasa strstream, care are doi constructori:
strstream();
strstream(char* pch, int length, int mode);

Constructorul implicit construiete un stream care folosete un buffer intern


dinamic, care iniial este gol.
Constructorul cu trei argumente construiete un stream asociat unui ir de
caractere n memorie, aflat la adresa pch. Argumentul length specific numrul de
caractere ale irului. Dac acest argument are valoarea 0, atunci irul asociat trebuie s
fie un ir terminat cu un caracter nul. Dac argumentul length are o valoare

Elemente de Programare Orientat pe Obiecte

178

negativ, atunci se consider un ir de dimensiune infinit. Pentru o valoare pozitiv


diferit de zero a argumentului length, numai primele length caractere din ir
sunt asociate streamului, i streamul nu accept caractere peste numrul de length
caractere.
Argumentul mode specific modul de creare a streamului i poate lua una din
valorile ios::in, ios::out, ios::ate, ios::app, ale enumeratorului
open_mode al clasei ios.
Pentru clasele istrstream i ostrstream sunt definii constructori i
funcii membre asemntoare celor din clasele corespunztoare ifstream i
ofstream.
Fie funcia:
void fstr(){
char* p = new char[100];
strstream ost(p, 100);
ost << "123456789";
ost << "abcdef" << ends;
cout << p;
}

n aceast funcie se creaz un stream ost asociat unui tablou de caractere n


memorie n care se introduc mai multe caractere, ultimul fiind un caracter nul.
Caracterele introduse n streamul asociat cu tabloul de caractere p sunt afiate la
consol prin apelul operatorului << , iar mesajul care se obine la execuia acestei
funcii este: 123456789abcdef.
Dup cele preyentate n acest capitol se poate concluziona care este ierarhia de
clase care definesc streamurile C++. (Fig. 6.1). Clasa de baz ios este mostenit
virtual n cele dou clase istream i ostream.
ios
istream

ifstream
istrstream
ostream
ofstream

iostream
fstream
strstream

ostrstream
Fig. 6.1. Ierarhia claselor care definesc streamuri n C++.

6. Sistemul de I/O din 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);

Elemente de Programare Orientat pe Obiecte

180

};

friend ostream& operator<<(ostream& os,


NumeAdresa& na);
friend istream& operator>>(istream& is,
NumeAdresa& na);
void display(char* fisier);

S se completeze clasa NumeAdresa cu definiiile constructorilor, ale funciilor


operator >> i <<, precum i a funciei display(), care s afieze la consol toate
numele i adresele stocate n fiierul cu numele adrese.
E6.7 S se implementeze biblioteca standard de I/O din C (stdio.h) folosind
biblioteca standard de I/O din C++ (iostream.h).
E6.8 S se implementeze o clas pentru care operatorul de indexare [] s fie
suprancrcat astfel nct s permit accesul aleator de citire i de scriere a caracterelor
ntr-un fiier pe disc.
E6.9 S se proiecteze i s se implementeze o operaie de citire a unei secvente de
date care s corespund unui pattern dat. Pentru definirea patternului se poate folosi
stilul de specificare a formatelor din funcia printf.
E6.10 S se defineasc funcia operator << pentru clasa SetIter (descris n
seciunea 3.3.1). Aceast funcie trebuie s afieze la consol elementele n ordine
cresctoare ale unei mulimi reprezentate prin clasa IntSet.
E6.11 S se defineasc funcia operator << pentru clasa ListIter (descris n
seciunea 3.3.1). Aceast funcie trebuie s afieze la consol elementele unei liste n
ordinea n care sunt memorate ntr-un obiect din clasa IntDList (descris n
seciunea 3.1.4).

7
Clase i funcii template

Template-urile (abloanele) permit crearea de clase sau funcii generice, n


care tipurile de date asupra crora se opereaz sunt specificate ca argumente. Un
template definete o familie de tipuri (clase) sau funcii.
Forma general de declarare a unui template este:
template<lista_argumente> declaratie

unde lista_argumente conine unul sau mai multe argumente formale,


separate prin virgul, fiecare definit prin tipul argumentului i numele lui, iar
declaratie trebuie s declare sau s defineasc o funcie sau o clas. Argumentele
formale de tip predefinit din lista de argumente se introduc prin numele tipului urmat
de numele argumentului, iar argumentele formale de tip definit de utilizator (clase) se
introduc prin specificatorul class urmat de numele argumentului. De exemplu, o
declaraie de template poate arta n felul urmtor:
template<class X, class Y, int s> declaratie

7.1

Clase template

O clas template specific modul n care pot fi construite clase individuale,


difereniate prin tipul sau tipurile de date asupra crore se opereaz. O clas template
pentru definirea unei stive de obiecte de un tip oarecare T poate arta n felul urmtor:
template<class T> class TStack{
T *bot;
T *top;
int size;
public:
TStack(int s){
size = s;
bot = top = new T[s];
}
~TStack(){delete [] bot;}

Elemente de Programare Orientat pe Obiecte

182

};

void push(T t){*top++ = t;}


T pop(){return *--top;}

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

Cu excepia diferenei n sintaxa numelui, clasa TStack<int> se comport


exact la fel cum s-ar comporta dac ar fi fost definit astfel:
class IStack{
int *bot;
int *top;
int size;
public:
IStack(int s){
size = s;
bot = top = new int[s];
}
~IStack(){delete [] bot;}
void push(int t){*top++ = t;}
int pop(){return *--top;}
};

Utilizarea template-urilor nu implic mecanisme run-time ci doar generarea de


ctre compilator a fiecarei clase care corespunde tipului (sau tipurilor) de date folosit
la declararea unui obiect.

Exemplul 7.1

Se consider implementarea unui program (programul Template) care


conine descrierea clasei template TStack precum i a claselor Complex i
String, care se pot prelua din seciunile precedente. Fie funcia:
void ft1(){
TStack<int> istack(100);
TStack<double> dstack(200);

7. Clase i funcii template

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();

Declararea variabilelor istack, dstack, cstack i sstack impune crearea


claselor corespunztoare tipurilor integer, double, Complex i, respectiv
String. n fiecare stiv se introduc, se extrag i se afieaz la consol date de tipul
respectiv. Rezultatul execuiei funciei ft1() este urmtorul:
7 5
Al treilea sir
Al doilea sir
Primul sir
(7.6, 3.4)(1.2, 3.4)

O observaie important n legtur cu operaiile definite ntr-o clas template


este aceea c o clas care definete un tip de date transmis ca argument al clasei
template trebuie s aib prevzute funcii de suprancrcare a operatorilor utilizai n
clasa template.
De exemplu, n clasa template TStack<class T> se folosete operaia de
asignare n funcia push(): *top++ = t, unde t este un obiect de tipul T. Dac
n clasa T nu este suprancrcat operatorul de asignare, atunci compilatorul folosete
asignarea implicit, prin copiere membru-cu-membru a obiectelor (de clas T). Pentru
tipurile de date predefinite, ca i pentru clase care nu conin date alocate dinamic,
acest lucru funcioneaz corect. n schimb, dac o clas T conine date alocate
dinamic, atunci copierea membru-cu-membru produce erori de execuie. Rezult c n
clasele folosite ca argumente ale clasei template TStack trebuie s se suprancarce
operatorul de asignare. Un astfel de caz este clasa String care conine un ir de
caractere care se aloc dinamic la crearea unui obiect i pentru care este absolut
necesar suprancrcarea operatorului de asignare.

Elemente de Programare Orientat pe Obiecte

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

Domeniul funciei membre este specificat prin numele template-ului calificat


cu numele parametrului, deci TStack<T>.
Pentru crearea claselor template, este o bun practic de a crea i depana o
clas particular, de exemplu IStack, nainte de a o transforma n clas generic
TStack<T>.

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

La fiecare apel, tipul argumentului determin funcia de sortare care va fi


folosit.

Exemplul 7.2

Se introduce funcia template sort() n programul Template i fie funcia


ft2()definit astfel:

7. Clase i funcii template

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

Implementarea coleciilor de date


folosind clase template

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

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

Elemente de Programare Orientat pe Obiecte


TListNode* prev;
TListNode* next;
public:
friend class TList<E>;
TListNode() { prev = NULL; next = NULL;}
TListNode(E el){elem = el; prev = NULL; next = NULL;}
TListNode* GetNext(){return next;}
TListNode* GetPrev(){return prev;}
virtual ~TListNode(){cout<< "Destructor baza nod \n";}
};
template <class E> class TList{
TListNode<E>* first;
TListNode<E>* last;
int count;
public:
TList(){ first = 0; last = 0; count = 0;}
TList(TList& r);
~TList(){};
int GetCount(){return count;}
void AddHead(E);
void AddTail(E);
E GetHead();
E GetTail();
E RemoveHead();
E RemoveTail();
void Display();
friend ostream& operator<<(ostream& stream,
TList& list);
};

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++;
}

7. Clase i funcii template

187

template <class E> E TList<E>::RemoveHead(){


E v;
if (count == 0){
cout << Eroare lista vida\n
return v;
}
else {
v = first->elem;
TListNode<E>* p = first;
first = first->next;
if (first)
first->prev = 0;
delete p;
count--;
if (count == 0){
last = 0;
}
return v;
}
}

Un exemplu de utilizare a unei liste definite prin clase template:


void ft2(){
/* Functionare ca stiva a listei */
TList<int> list2;
list2.AddHead(1);
// push
list2.AddHead(2);
// push
list2.AddHead(3);
// push
cout << list2.RemoveHead();
// pop,
cout << list2.RemoveHead();
// pop,
cout << list2.RemoveHead();
// pop,

1
2
3
afiseaza 3
afiseaza 2
afiseaza 1

/* Functionarea ca o coada a listei */


TList<double> list3;
list3.AddTail(1);
// inserare 1
list3.AddTail(2);
// inserare 2
list3.AddTail(3);
// inserare 3
cout << list3.RemoveHead();
// extragere afiseaza 1
cout << list3.RemoveHead();
// extragere afiseaza 2
cout << list3.RemoveHead();
// extragere afiseaza 3
cout << endl;
}

Declararea variabilelor list2 i list3 impune crearea claselor


corespunztoare tipurilor integer i double. n fiecare stiv se introduc, se
extrag i se afieaz la consol date de tipul respectiv.

7.3.2

Implementarea unui arbore binar ordonat

Este dificil implementarea coleciilor de date cu elemente ordonate folosind


ierarhii de clase: o clas de baz care s defineasc forma coleciei i clase derivate

Elemente de Programare Orientat pe Obiecte

188

care s particularizeze tipul de date. Acest dificultate se datoreaz faptului c


majoritatea operaiilor (inserare, tergere, cutare) necesit comparaii ale valorilor
elementelor (informaiile din colecie), care nu sunt definite n clasa de baz. Astfel de
colecii se pot, ns, implementa cu uurin folosind clase template.
Se va exemplifica acest lucru prin implementarea unui arbore binar ordonat
ale crui noduri conin orice tip de informaie, tip care se precizeaz ca argument al
clasei template. Un arbore binar ordonat de elemente de un tip oarecare E se definete
prin dou clase template, TBTreeNode i TBTree, astfel:
template <class E> class TBTreeNode{
E d;
// informatia continuta in nod
TBTreeNode *left, *right; // pointeri la fii
friend class TBTree<E>;
public:
TBTreeNode(E x) { d = x; left = right = NULL; }
~TBTreeNode() { left = right = NULL; }
};
template <class E> class TBTree{
TBTreeNode<E> *root;
int count;
TBTreeNode<E> *insert1(TBTreeNode<E> *root,
TBTreeNode<E> *r, E data);
void inorder1(TBTreeNode<E> *root);
void preorder1(TBTreeNode<E> *root);
void postorder1(TBTreeNode<E> *root);
void delTree1(TBTreeNode<E> *root);
public:
TBTree() { root = NULL; count = 0;}
int getcount() {return count;}
void insert(E data);
int search(E data);
void remove(E data);
void inorder();
void preorder();
void postorder();
~TBTree();
};

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

7. Clase i funcii template

189

if (data == r->d) return 0;


if (data < r->d)
return insert1(r, r->left, data);
else return insert1(r, r->right, data);

}
template <class E> void TBTree<E>::insert(E data) {
if(!root) root = insert1(root, root, data);
else insert1(root, root, data);
}

Prin declaraii de forma: TBTree<int> itree; TBTree<double>


dtree, TBTree<Complex> ctree; se definesc obiecte pentru tipurile de date
int, double,respectiv Complex, asupra crora se poate opera n mod obinuit,
folosind funciile membre ale clasei template. De exemplu:
void ftbt2(){
TBTree<Complex> ctree;
for(i=0;i<n;i++){
Complex c(n-i-1);
ctree.insert(c);
}
cout << "Nr noduri: " << ctree.getcount()<< endl;
ctree.inorder();
}

n funcia ftb2() se creeaz un arbore binar ordonat cu 10 noduri,


informaia din fiecare nod fiind un numr complex, definit prin clasa Complex.
Aceste informaii sunt apoi afiate n inordine.
Obiectele de tip definit de utilizator folosit ca argument al claselor template
TBTreeNode i TBTree sunt utilizate n diferite operaii la crearea i prelucrrile n
arbore. De aceea pentru aceste clase a argumntelor trebuie s fie suprancrcai
operatorii pentru toate operaiile folosite. De exemplu, n funcia insert1() se
compar date de tip class E prin operatorii < i ==; aceti operatori trebuie s fie
suprancrcai n clasa Complex, folosit ca argument al clasei template. n funcia
operator de comparaie < s-au comparat modulule celor dou numere complexe:
int Complex::operator<(Complex c){
double r1 = x*x + y*y;
double r2 = c.x*c.x+c.y*c.y;
return r1 < r2;
}

De asemenea este utilizat operatorul de scriere ntr-un stream << , care trebuie
s fie suprancrcat n clasa Complex.

7.3.3

Implementarea unui vector asociativ

Un vector asociativ (associative array, dictionary, map) este o colecie de


elemente, fiecare element fiind compus din dou pri: o parte numit cheie (key),

190

Elemente de Programare Orientat pe Obiecte

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

Declararea celor dou clase friend Map i friend MapIter asigur


accesul acestora la elementele listei nlnuite.
Vectorul asociativ, reprezentat ca o list dublu nlnuit de elemente de tip
MapNode, este descris de clasa template Map, avnd aceleai argumente, tipurile de
date class K i class V. Aceast clas definete lista dublu nlniut printr-un
pointer la primul element al listei (head) i numrul de elemente (noduri) ale listei
(size). Implementarea clasei template Map este urmtoarea:
template<class K, class V> class Map{
friend class MapNode<K,V>;
friend class MapIter<K,V>;
MapNode<K,V>* head;
MapNode<K,V>* current;
V def_v;
K def_k;
int size;
void init(){ size = 0; head = 0; current = 0;}

7. Clase i funcii template

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;

Elemente de Programare Orientat pe Obiecte

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;

Funcia operator[]() se comport n felul urmtor: dac se gsete un


element a crui cheie este identic cu argumentul funciei operator[]()
(considerat ca un indice generalizat), atunci se returneaz referina la valoarea
(value) corespunztoare acestei chei. Dac, parcurgnd lista nlnuit ncepnd cu
primul element (head), la un moment dat, cheia argument este mai mic dect cheia
elementului curent, se deduce c nu exist un element cu aceast cheie i atunci se
creeaz un element cu valoare (value) implicit, care se insereaz n lista ordonat.
La fel, dac lista a fost parcurs n ntregime i cheia de cutare este mai mare dect
cheia ultimului element, se creeaz un element nou cu valoare (value) implicit care
se insereaz la sfritul listei. O tratare separat necesit cazul unei liste vide
(head = 0). n aceast situaie se creeaz de asemenea un element nou, cu valoare
(value) implicit care devine primul element al listei.

Exemplul 7.3

Se implementeaz un program (programul MapIter) n care se definete un


vector asociativ generic folosind clasele MapNode i Map prezentate mai sus. n
funcia fm1() se creeaz un vector asociativ smap ca un obiect din clasa template
Map cu tipul de date ntreg (int) i valoare implicit 1 pentru argumentul key i
tipul definit mai sus String, cu valoare implicit Default pentru argumentul
value. Folosind operatorul de indexare se introduc elemente n vector, se extrag i se
afieaz la consol.
void fm1(){
Map<int, String> smap(-1,Default);
smap[2] = String 2;
smap[1] = String 1;
smap[3] = String 3;
cout << smap[3];
cout << smap[7];
cout << smap[1];
cout << smap[2];
}

7. Clase i funcii template

193

La execuia funciei fm1(), se obin urmtoarele mesaje la consol:


String 3
Default
String 1
String 2

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

Elemente de Programare Orientat pe Obiecte

194

template <class K, class V>


void MapIter<K,V>::operator--(int){
if(node) node = node->left;
}
template <class K, class V>
MapIter<K,V>& MapIter<K,V>::operator++(){
if (node) node = node->right;
return *this;
}
template <class K, class V>
void MapIter<K,V>::operator++(int){
if(node) node = node->right;
}

Clasa MapIter se definete pentru parcurgerea unei liste nlnuite de tipul


Map compus din noduri de tipul MapNode i de aceea conine doi pointeri map i
node la aceste tipuri de obiecte. Un obiect din clasa MapIter se poate construi
implicit, cu pointerii map i node setai la valoarea 0, sau printr-unul din cei doi
constructori de iniializare, care seteaz corespunztor cei doi pointeri, n funcie de
argumentele primite. Pointerul node indic elementul curent din list.
Funciile de parcurgere a vectorului pot fi implementate prin suprancrcarea
operatorilor de incrementare i decrementare, aa cum este prezentat mai sus. Funcia
operator++() permite parcurgerea elementelor n ordine cresctoare a cheilor, iar
funcia operator--() permite parcurgerea elementelor n ordine descresctoare a
cheilor. Aceste funcii sunt analoge operaiilor de incrementare-decrementare a
pointerilor i ele modific valoarea pointerului node astfel nct lista s fie parcurs
n sensul dorit: spre stnga (la decrementare) sau spre dreapta (la incrementare).
Funciile membre key() i value() ale clasei MapIter returneaz cheia,
respectiv valoarea elementului curent din list, indicat de pointerul node.

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

7. Clase i funcii template

195

for (; iter; ++iter)


cout << iter.key() << iter.value() << endl;

La execuia acestei funcii sunt afiate la consol urmtoarele mesaje:


word1
word2
word3

5
8
6

n operaia de testare a limitei buclei for se subnelege testul (iter != 0)


pentru efectuarea cruia are loc o operaie de conversie de la tipul MapIter la pointer la
void folosind funcia operator de conversie operator void*()a clasei
MapIter. Acest funcie returneaz zero dac iteratorul nu se refer la nici un
element; n caz contrar returneaz o valoare diferit de zero.

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

Se modific funcia fm2() astfel nct vectorul asociativ table s fie


inspectat simultan prin intermediul a doi iteratori, iter1 i iter2, din care
iteratorul iter1 parcurge lista din element n element n ordinea cresctoare a cheii,
iar iteratorul iter2 parcurge lista din dou n dou elemente n ordinea cresctoare a
cheii.
void fm3(){
Map<String,int> table("nil", 0);
//readlines(table);
table["word1"] = 5;
table["word3"] = 2;
table["word3"] +=4;
table["word2"] = 8;
table["word4"] = 9;
table["word5"] = 7;
MapIter<String,int> iter1(table);
MapIter<String,int> iter2(table);
for (; iter2; iter1++, iter2+=2) {
cout << iter1.key() << iter2.value() << endl;
cout << iter2.key() << iter2.value() << endl;
}

Elemente de Programare Orientat pe Obiecte

196
}

La execuia funciei fm3() se afieaz la consol urmtoarele mesaje:


word1
word2
word3
word4
word5

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

Derivarea i clasele template reprezint instrumente puternice n programarea


orientat pe obiecte. O clas template exprim modul similar de reprezentare a mai
multor tipuri de date (clase), folosite ca argumente ale template-ului, n timp ce o clas
de baz exprim partea comun de definire, prin date membre i funcii de interfa,
partajate de toate clasele derivate din aceast clas de baz.

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.

7. Clase i funcii template

197

E7.4 S se modifice funcia ft2()n programul Template astfel nct s se


sorteze urmtoarele iruri de caractere: BCD, ABC, GHFJ, MNG. S se
afieze la consol irurile ordonate.
E7.5 Se elimin funcia operator de conversie operator void* () din clasa
MapIter i se compileaz programul MapIter ca s conin apelul funciei
fm2(). Cum se poate explica eroarea de compilare obinut? Ce alt funcie
operator de conversie poate fi definit pentru a obine o compilare i o execuie
corect? Cum se modific mesajele afiate i care este cauza acestor modificri?
E7.6 S se defineasc destructorii claselor template Map i MapNode care s
includ mesaje de identificare ale acestora. Folosind aceste mesaje, s se urmreasc i
s se interpreteze modul de eliminare din memorie (prin tergere, folosind operator
delete, sau la ieirea din blocul n care este definit) a unui obiect de clas Map.
E7.7 n funcia fm2(), dup afiarea coninutului ntregului vector asociativ, se
cere s se mai afieze nc o dat coninutul acestuia. Se poate folosi acelai iterator
iter? Dac nu, ce operaii mai trebuie s fie prevzute pentru a se putea folosi
acelai iterator? Care sunt mesajele afiate la consol?
E7.8 Cum se poate parcurge un vector asociativ n ordine descresctoare a cheii
elementelor?
E7.9 S se implementeze funcia remove() a clasei Map. n funcia fm2()din
programul MapIter, dup crearea vectorului asociativ table i afiarea
coninutului acestuia, s se elimine elementul cu cheia word2, dup care s se afieze
din nou ntregul coninut al vectorului. Care sunt mesajele afiate la consol?
E7.10 S se implementeze constructorul de copiere al clasei template Map i funcia
operator de asignare. n funcia fm2()din programul MapIter, folosind un
constructor de copiere sau o funcie operator de asignare, s se creeze doi noi vectori
asociativi table1 i table2 cu acelai coninut ca i vectorul table. S se
verifice identitatea coninutului acestor vectori prin afiare la consol folosind trei
iteratori diferii de parcurgere a listelor nlnuite.
E7.11 S se suprancarce operatorul de apel pentru clasa MapIter pentru operaia
de avansare la valoarea urmtoare a cheii ntr-un vector asociativ descris de clasa Map.
E7.12 Pentru o variabil de tipul z pentru care au fost suprancrcai operatorii >> i
<<, o bucl de copiere de la intrare la ieire poate fi scris astfel:
while (cin >> z ) cout << z << \n;

Se cere s fie definit o funcie template de copiere pentru un tip de date oarecare
(class T).

198

Elemente de Programare Orientat pe Obiecte

E7.13 S se defineasc operatorul de inserie << i operatorul de extragere >>


pentru clasa template Map.
E7.14 S se defineasc o clas template pentru reprezentarea unui vector de date de
un tip oarecare T, TArray, care s prezinte aceeai funcionalitate ca vectorul de
numere intregi IntArray descris n E2.6. S se reia exemplul de utilizare ale clasei
IntArray din exerciiul E2.6 prin utilizarea clasei TArray<int>.
E7.15 S se definesc un vector de iruri de caractere (obiecte de clas String)
folosind clasa template TArray<String> i s se verifice funcionarea acestuia ca
stiv i coad de iruri de caractere.
E7.16 S se modifice clasa template TArray, astfel nct s poat fi folosit pentru
implementarea unei mulimi de obiecte de un tip oarecare. Pentru aceast clas
template (va fi numit TOrdArray), funciile de inserare i cutare a elementelor
mulimii trebuie fie implementate printr-un algoritm de cutare binar i s menin
vectorul ordonat. Se va particulariza clasa template pentru iruri de caractere (obiecte
de clas String) i pentru numere complexe (obiecte de clas Complex).
E7.17 S se modifice clasa template TList descris n seciunea 7.3.2, astfel nct
s poat fi folosit pentru implementarea unei mulimi de obiecte de un tip oarecare.
Pentru aceast clas template (va fi numit TOrdList) se vor defini funciile de
inserare i tergere a elementelor din list astfel nct s menin lista ordonat. Se va
particulariza clasa template pentru iruri de caractere (obiecte de clas String) i
pentru numere complexe (obiecte de clas Complex).
E7.18 S se defineasc un iterator pentru clasa TOrdList care reprezint o
mulime (list ordonat), care s parcurg lista n ordinea cresctoare a elementelor.
E7.19 S se implementeze operaiile cu mulimi (reuniunea, intersecia, diferena) n
reprezentrile prin clasele template TOrdArray, TOrdList, TBTree i Map a
mulimilor.
E7.20 S se creeze o list dublu nlnuit de obiecte de clas Complex, folosind
clasa template TArray definit n seciunea 7.3.1 i s se verifice funcionarea ca
stiv a acesteia.
E7.21 O mulime de m elemente, fiecare element fiind un tuplu de o aritate oarecare
n, se numete relaie. Fiecare tuplu este la rndul lui o mulime de n atribute, fiecare
atribut fiind definit ntr-un anumit domeniu (tip de date, n general). Valoarea m se
numete cardinalitatea relaiei, iar valoarea n gradul relaiei. De exemplu, relaia
R = {(1, nume1), (2, nume2), (3, nume3)} este o relaie de
cardinalitate 3 i grad 2.
O relaie binar (de grad 2) poate fi reprezentat printr-un vector asociativ
(clasa Map, definit n aceast seciune) sau printr-un arbore binar ordonat. (Nu exist

7. Clase i funcii template

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

E7.24 Operaia de restricie (restriction) se aplic unei relaii i const n crearea


unei noi relaii care conine acele tupluri din relaia original care ndeplinesc o
condiie specificat. S se implementeze operaia de restricie 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.25 Operaia de proiecie (projection) se aplic unei relaii i const n crearea
unei noi relaii care conine tupluri formate dintr-o submulime de atribute din
mulimea atributelor care formeaz tuplurile relaiei originale. S se implementeze

200

Elemente de Programare Orientat pe Obiecte

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

Tratarea excepiilor permite rezolvarea n mod unitar a erorilor de execuie.


Mecanismul de tratare a excepiilor din C++ ofer posibilitatea ca o problem care
apare ntr-o funcie i aceasta nu o poate rezolva, s fie definit ca o excepie, cu
sperana c funcia apelant va trata aceast problem. O funcie care dorete s trateze
astfel de probleme va indica acest lucru prin captarea excepiei respective, iar codul
care se execut la captare se numete rutin de tratare a excepiei (exception handler).
Ca exemplu, se consider modul n care poate fi tratat o depire de
dimensiune n clasa Vector.
class Vector{
int* p;
int sz;
public:
class Range {};
// exceptie la depasire dimens.
int& operator[](int i);
// ...
};
int& Vector::operator[](int i){
if (i < 0 || i > sz)
throw Range();
return p[i];
}
void f(Vector &v){
try {
fv(v);
// apelul unei funcii
}
catch(Vector::Range){
// Rutina de tratare a exceptiei
// Vector::Range
// In acest punct se ajunge numai dac
// in functia fv(v) s-a apelat operatorul []
// cu un indice n afara domeniului
}
}

Elemente de Programare Orientat pe Obiecte

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
}

catch (tip_arg1 arg1) {


// bloc de tratare a excepiei
// de tipul tip_arg1
}

catch (tip_arg2 arg2) {


// bloc catch de tratare a excepiei
// de tipul tip_arg2
}
.

catch (tip_argn argn) {


// bloc de tratare a excepiei
// de tipul tip_argn
}

8. Tratarea Excepiilor

203

O astfel de construcie este asemntoare unei instruciuni switch, n care


selecia unei rutine (un bloc catch) se face n funcie de tipul excepiei lansate n
blocul try, fr s fie necesar o instruciune break. Dac n blocul try nu apare
nici o excepie, toate blocurile catch consecutive acestuia sunt ignorate. La apariia
unei excepii, controlul programului este transferat rutinei de tratare a excepiei
corespunztoare tipului acesteia, toate celelate blocuri catch fiind ignorate. Dup
execuia unei rutine de tratare a excepiei, programul fie continu cu execuia
instruciunilor urmtoare blocurilor catch, fie se termin, dac rutina respectiv a
apelat o funcie exit() sau abort().
Dac se lanseaz o excepie pentru care nu exist nici o rutin de tratare (bloc
catch), atunci este posibil s apar o execuie anormal a programului.

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

Elemente de Programare Orientat pe Obiecte

204

cout << "End bloc try-catch\n";

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];
}

Aa cum s-a artat mai sus, operatorul de indexare [] al clasei Vector va


lansa excepia de tip (clas) Range dac este apelat cu un indice n afara domeniului.
n mod similar, constructorul Vector::Vector va lansa o excepie de tip Size,
dac este apelat cu un argument cu o valoare inacceptabil.
n utilizarea clasei Vector se poate discrimina ntre cele dou excepii prin
adugarea a dou rutine de tratare a excepiilor dup un bloc try:
void fex(Vector &v){
try {
fv(v);
// apelul unei funcii cu arg. v
}
catch(Vector::Range){
// Rutina de tratare a exceptiei
// Vector::Range
// In acest punct se ajunge numai dac
// in functia fv(v) s-a apelat operatorul []
// cu un indice n afara domeniului
}
catch(Vector::Size){
// Rutina de tratare a exceptiei
// Vector::Size
// In acest punct se ajunge daca in functia
// fv(v) s-a apelat un constructor
// cu un argument cu o valoare inacceptabila
}
}

8.2

Modaliti de tratare a excepiilor

n C++ exist mai multe modaliti de tratare a excepiilor, dintre care o parte
vor fi descrise n continuare.

8.2.1 Tratarea tuturor excepiilor


n Exemplul 8.1 a fost prezentat o situaie n care nu era tratat o excepie de
un anumit tip, situaie care provoac execuia anormal a programului, atunci cnd o

206

Elemente de Programare Orientat pe Obiecte

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
}

Instruciunea catch() indic captarea oricror excepii (corespunztoare


tuturor tipurilor de date). Tratarea excepiilor n astfel de situaii poate fi o tratare
general.
Mai este posibil combinarea uneia sau mai multor instruciuni catch de
captare a unor tipuri specifice de excepii cu o instruciune de captare a tuturor
excepiilor, combinaie prin care se urmrete tratarea difereniat a unor anumite
tipuri de excepii, toate celelalte fiind tratate global. Astfel de construcii permit
evitarea terminrii anormale a programului.
Dac n funcia fd() din Exemplul 8.1 se adaug dup blocul try o
instruciune catch() de captare general, atunci nu mai apare eroarea de execuie
la lansarea unei excepii netratate specific:
//..
try{
// acelasi bloc ca in Exemplul 8.1
}
catch(int i){
cout << "Captare exceptie int i = " << i <<endl;
}
catch(double d){
cout << "Captare exceptie double d = " << d <<endl;
}
catch(){
cout << "Captare exceptie oarecare\n;
}
//.

La lansarea excepiei cu argument de tip caracter (throw argc) se transfer


controlul rutinei de captare general a excepiilor (care, n exemplul de mai sus
afieaz doar un mesaj la consol) i nu mai apare execuia anormal a programului.

8.2.2 Transferul de informaii ctre rutina de tratare a excepiei


O excepie poate fi captat prin specificarea tipului ei. Totui, ceea ce se
lanseaz (throw) n momentul apariiei unei erori nu este un tip de date ci un obiect
de tipul respectiv, care este construit la apariia erorii. De aceea, n definirea unui tip
excepie (clas) se pot aduga date membre care pot fi setate la construcia obiectului
lansat i utilizate n rutina de tratare a excepiilor pentru identificarea condiiilor de
apariie a erorii.

8. Tratarea Excepiilor

207

De exemplu, clasa Range de definire a excepiei de depire a dimensiunii


vectorului poate s conin o dat membr index care memoreaz valoarea indicelui
care a produs depirea i o funcie public getindex() care permite citirea acestei
valori, astfel nct valoarea indicelui care a produs depirea poate fi aflat i,
eventual, afiat n rutina de tratare a erorii. La fel se poate proceda i pentru clasa
Size. Exemplul urmtor completeaz clasa Vector i clasele excepie definite de
aceasta i ilustreaz crearea i utilizarea obiectelor de clas excepie.

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];
}

Elemente de Programare Orientat pe Obiecte

208

int& Vector::operator[](int i){


if (i < 0 || i > sz)
throw Range(i);
return p[i];
}
void fv(Vector& v, int d){
Vector v2(d);
for (int i=0;i<d;i++){
v2[i] = v[i] = i;
}
}
void fex(Vector &v, int d){
try{
fv(v, d);
}
catch(Vector::Range r){
cerr << "Indicele " << r.getindex()
<< " in afara domeniului\n";
return;
}
catch(Vector::Size s){
cerr << "Dimensiunea " << s.getdim()
<< " depaseste valoarea admisa\n";
return;
}
cout << "Vectori OK" << endl;
return 0;
}
void main(){
Vector v1(100);
int d;
cout << "Introduceti dimensiunea: ";
cin >> d;

while(fex(v1,d)){
cout << "Introduceti dimensiunea:
cin >> d;
}

";

La execuia acestui program se creeaz mai nti un vector de dimensiune 100,


dup care se citete de la tastatur dimensiunea unui alt vector. n funcie de valoarea
introdus la consol, pot aprea trei situaii distincte.
Dac dimensiunea introdus d este mai mic sau egal cu dimensiunea
primului vector (100), atunci nu apare nici o eroare de execuie, programul afieaz un
mesaj la consol dup care se oprete.
Dac dimensiunea introdus d este mai mare dect dimensiunea primului
vector (100), dar mai mic dect valoarea maxim admisibil pentru construcia
vectorilor (max=1000), atunci apare o eroare de depire a domeniului n funcia
operator de indexare [] i se lanseaz excepia prin construirea unui obiect de clas

8. Tratarea Excepiilor

209

Range, cu argument al constructorului egal cu valoarea primului indice care


depete domeniul. Aceast valoare este regsit n funcia de tratare a excepiei, care
o afieaz la consol. Mesajele care apar la consol n aceast situaie sunt:
Introduceti dimensiunea: 200
Indice 100 in afara domeniului
Introduceti valoarea:

Dac dimensiunea introdus d este mai mare dect valoarea maxim


admisibil (1000), atunci apare o eroare n momentul construciei obiectului v2 de tip
Vector n funcia fex(). n constructorul Vector::Vector, la apariia unei
astfel de erori se lanseaz o excepie prin construirea unui obiect de tip Size, cu
valoare a argumentului d, valoare care este memorat n data membr privat dim a
clasei Size. Aceast valoare este regsit n funcia de tratare a excepiei, care o
afieaz la consol. Mesajele care apar la consol n aceast situaie sunt:
Introduceti dimensiunea: 1001
Dimensiunea 1001 depaseste valoarea admis
Introduceti dimensiunea:

8.2.3

Imbricarea rutinelor de tratare a excepiilor

Limbajul C++ admite construcii imbricate de tratare a execpiilor, ca de


exemplu:
try{
// bloc try
}
catch (ExceptionX){
try {
// o rutin care incearc
// tratarea excepiei
}
catch(ExceptionX){
// o alta rutin de tratare
// care este lansat dac prima rutin
// a provocat o nou excepie
}
}

O excepie este tratat de rutina imediat urmtoare blocului try, dar i n


aceast tratare poate fi lansat o nou excepie (eventual de acelai tip), de care se va
ocupa un nou bloc catch, imbricat n primul bloc try. Dei posibile, astfel de
construcii imbricate sunt rar utilizate sau utile.

Elemente de Programare Orientat pe Obiecte

210

8.2.4 Gruparea excepiilor


De cele mai multe ori, excepiile se pot grupa n familii, n funcie de categoria
la care se refer. De exemplu, erori de depire superioar, depire inferioar de
mprire la zero i altele pot fi grupate n categoria de erori n operaii aritmetice cu
numere flotante; erori de deschidere fiier, nchidere fiier, erori de citire sau erori de
scrire, pot fi grupate ca erori n operaii cu fiiere, .a.md.
n astfel de situaii, rutinele de tratare a excepiilor sunt destul de
asemntoare i se pot implementa ntr-un mod unitar. Implementarea unitar a
grupurilor de excepii se poate face n dou modaliti: prin enumerare i prin
organizarea excepiilor n ierarhii de clase folosind motenirea i funciile virtuale.
n abordarea prin enumerare a grupurilor de excepii se definete un tip
enumerare ale crui posibile valori sunt numele tuturor excepiilor din grup, iar
selecia ntre rutinele specifice se face printr-o instruciune switch:
class Overflow { };
class Underflow { };
class Dividebyzero { };
enum Mathenum {Overflow, Underflow, Dividebyzero};
void fenum(){
try{
// lansare excepii
}
catch (Mathenum m){
switch (m){
case Overflow:
// tratare depasire superioara
break;
case Underflow:
// tratare depasire inferiora
break;
case Dividebyzero:
// tratare impartire la 0
break;
}
}
}

O astfel de organizare a grupurilor de excepii, dei posibil, este voluminoas


i cu mari riscuri de a fi omise unele situaii, care, bineneles, vor provoca terminarea
anormal a programului. n plus, la adugarea unei noi excepii ntr-un grup de
excepii ale unei biblioteci, ar fi necasar recompilarea tuturor modulelor care conin
rutine de captare a excepiilor. Acest lucru este, bineneles, imposibil i de aceea ar fi
preferabil s nu se adauge noi excepii ntr-o bibliotec dac tratarea grupurilor de
excepii este implementat prin enumerarea acestora.
O soluie mai elegant i mai robust o reprezint organizarea excepiilor n
ierarhii de clase. De exemplu:

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

Elemente de Programare Orientat pe Obiecte

E8.4 Pentru clasele IntSList i IntDList (descrise n seciunea 3.1.4) s se


defineasc excepia de extragere, prin intermediul unei clase definite global, astfel
nct s poat fi utilizat att pentru clasa IntSList ct i pentru clasa IntDList.
Excepia de extragere este lansat n funciile RemoveHead() sau
RemoveTail(), atunci cnd numrul de elemente ale listei (count) este 0. ntr-o
funcie care utilizeaz liste nlnuite, s se introduc blocurile de captare i tratare a
acestor excepii.
E8.5 Pentru clasa IntTree (descris n seciunea 3.2.3) s se defineasc dou
tipuri de excepii, excepia de alocare i excepia de extragere, prin intermediul a dou
clase definite local. Excepia de alocare este lansat n funcia insert(), atunci
cnd operatorul new de alocare dinamic returneaz pointer nul. Eroarea de extragere
este lansat n funciile remove(), atunci cnd nu este gsit data care trebuie s
fie extras din arbore. ntr-o funcie care utilizeaz un arbore binar ordonat, s se
introduc blocurile de captare i tratare a acestor excepii.
E8.6 S se defineasc o clas template TVector cu clase locale pentru excepiile
Range i Size.
E8.7 Pentru clasa template TBTree (definit n seciunea 7) care implementeaz o
mulime printr-un un arbore binar ordonat, s se defineasc o clas local pentru
definirea excepiei la cutarea unui element n mulime: dac elementul nu este gsit,
funcia de cutare va lansa o excepie. Se va particulariza pentru un arbore binar sortat
de iruri de caractere (obiecte de clas String).
E8.8 S se implementeze o funcie de nsumare a elementelor unui obiect de tip
Vector (clasa Vector a fost descris n seciunea 8.2) care s lanseze o excepie
dac se depeste capacitatea de reprezentare a numerelor ntregi.
E8.9 Pentru clasa template TArray (definit n E7.14) care implementeaz un
vector de obiecte de diferite tipuri, s se modifice funcia InsertAt() i funcia
operator de indexare astfel nct s lanseze o excepie, definit printr-o clas local
TRange, atunci cnd indexul de apel este n afara domeniului vectorului. S se
implementeze rutina de tratare a acestei excepii.
E8.10 S se suprancarce operatorul de indexare al clasei Date (definit n E2.1 i
E4.1) 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. Pentru valori ale indicelui n afara domeniului 0-2, funcia operator[]()
va lansa o excepie definit printr-o clas declarat n interiorul clasei Date. ntr-o
funcie oarecare f() s se execute o instruciune care provoac depire de indice, s
se capteze aceast excepie i s se trateze ntr-o rutin de tratare a excepiilor. Aceast
rutin trebuie s afieze mesajul: Indicele cu valoarea: depeste
domeniul, unde . reprezint valoarea indicelui care a provocat excepia.