Sunteți pe pagina 1din 45

CAPITOLUL 1. Introducere ...................................................................................................

2
Scurt istoric a programarii orientate pe obiecte. ..................................................................2
Evolutia conceptului de programare....................................................................................3
CAPITOLUL 2. Clase ............................................................................................................5
Concepte fundamentale: clasa si obiectul ............................................................................5
Declararea claselor. Instantierea obiectelor. ........................................................................5
Membrii unei clase .............................................................................................................6
Controlul accesului la membrii unei clase ...........................................................................7
Constructorii unei clase ......................................................................................................7
Destructorul clasei ..............................................................................................................9
Membrii statici ai unei clase .............................................................................................10
Cuvantul cheie this ...........................................................................................................12
Functii membre constante .................................................................................................12
Functii si clase prietene (friends) ......................................................................................13
Functii membre inline.......................................................................................................15
CAPITOLUL 3. Obiecte.......................................................................................................16
Construirea si distrugerea obiectelor .................................................................................16
Initializarea variabilelor membre ale unei clase.................................................................18
CAPITOLUL 4. Redefinirea operatorilor..............................................................................23
Operatori unari si binari....................................................................................................26
Redefinirea operatorului de asignare (=) ...........................................................................27
Redefinirea operatorilor new si delete...............................................................................27
Constructori si conversii de tip..........................................................................................27
CAPITOLUL 5. Mostenire ...................................................................................................29
Controlul accesului ...........................................................................................................31
Supraincarcarea functiilor membre ...................................................................................32
Constructorii si destructorul clasei derivate.......................................................................33
Polimorfism......................................................................................................................34
Functii virtuale pure. Clase abstracte ................................................................................36
Ierarhii de clase. Mosternire multipla. Clase virtuale.........................................................36
CAPITOLUL 6. Tratarea exceptiilor ....................................................................................38
CAPITOLUL 7. Template-uri...............................................................................................41

1
CAPITOLUL 1. Introducere

Scurt istoric a programarii orientate pe obiecte.


Prima atestare a unui limbaj de P.O.O. a fost in 1965 intr-o lucrare a Ole-Johan Dahl si
Kristen Nygaard numita “A Language For Programming and Description of Discrete Event
Systems. Introduction and User’s Manual”. Aceasta lucrare a marcat nasterea limbajului
SIMULA I dezvoltat intre 1962-1965 la Institutul Norvegian de Calcul (Norvegian
Computing Centre). Caracteristicile limbajului Simula I cuprind:
- notiuni de clase si obiecte
- notiuni de subclase (mostenire)
- referentierea sigura a obiectelor
- functii virtuale
In 1965 apare si primul compilator pentru limbajul de programare SIMULA I, pentru
sistem de operare UNIX iar in anul 1967 apare limbajul de programare SIMULA 67 dezvoltat
de acelasi grup. Pentru SIMULA 67 primele compilatoare au aparut in 1969
In anii ’70 un grup de cercetatori de la centrul Xerox PARC (USA), sub conducere lui
Alan Kay, au folosit Simula ca platofrma pentru limbajul Smalltalk. Acest limbaj extindea
Simula cu interfete grafice si executia interactiva a programului. Tot anii ’70 aduc si primele
implementari orientate obiect in limbajul Lisp.
Dezvoltarea celui mai popular limbaj orientat obiect, limbajul C++, a inceput in anii ’80
la Bell Laboratories sub conducerea lui Bjarne Stroustrup prin includerea conceptelor cheie
din Simula. Istoria C++ incepe de fapt odata cu istoria limbajului C – limbaj dezvoltat ca
limbaj de sistem pentru sistemul de operare UNIX. Datorita popularitatii UNIX limbajul C a
fost recunoscut intre programatori ca un limbaj a programarii de sistema, care poate fi folosit
pentru programarea compilatoarelor si sistemelor de operare. Sistemul de operare UNIX a fost
creat in 1969 in compania Bell Laboratories, SUA, folosind calculatoare DEC PDP-7 (care in
comparatie cu cele moderne erau destul de slabe). In 1983, pe linga Institutul National de
Standartizare American (American National Standarts Institute - ANSI), a fost creat un
comitet special cu scopul de a standartiza limbajul C, si ca rezultat a fost elaborat standartul
ANSI C. Cresterea complexitatii programelor a dus la necesitatea elaborarii unor alte tipuri de
limbaje. In anul 1980, Bjarne Stroustrup (Bell Labs) a conceput limbajul "C-with-Classes".
Acest limbaj a dus la imbunatatirea C-ului prin adaugarea unor noi facilitati, printre care:
- abstractizarea datelor;
- introducerea paradigmele programarii orientate obiect in C;
- paradigmele programarii generice.
In vara 1983, C-with-classes a patruns si in lumea academica a institutiilor de cercetare,
denumirea finala a acestui limbaj fiind C++. Succesul extraordinar pe care il are limbajul C++
a fost asigurat de faptul ca a extins cel mai popular limbaj al momentului, C, iar programele
scrise in C functioneaza si in C++, si ele pot fi transformate in C++ cu eforturi minime.
Succesul limbajului C++ este evidentiat de urmatoare afirmatie a lui B.Stroupstrup:
"Utilizatorii au inceput sa foloseasca C++ inainte ca specialistii sa aiba timpul necesar sa-i
instruiasca pentru a-l folosi cu randament maxim".
„Moda” anilor 80 (si inceputul anilor 90) era adaptarea paradigmelor programarii
orientate obiect in limbajele deja existente. Astfel se cunosc implementari orientate obiect
pentru ADA, PROLOG, Lips (CLOS: Common Lips Object System) etc.
Un moment deosebit de important in istoria programarii orientate obiect il constitue
nasterea limbajului Java, in ianuarie 1995 la Sun Microsystems sub conducerea lui James
Gosling. La inceputurile sale, scopul principal al limbajului Java era programarea Internet.
Ulterior a evoluat intr-un limbaj general pentru dezvotlarea atat a aplicatiilor Web cat si
pentru dezvoltarea aplicatiilor de sine-statatoare. Java Development Kit (JDK) este pachetul

2
oferit de catre firma Sun in sprijinul dezvoltatorilor de aplicatii in acest limbaj. Acesta se
prezinta sub forma unei colectii impresionante de clase ce satsifac cele mai diverse cerinte ale
programatorilor din zilele noastre. Acest pachet contine clase pentru dezvoltarea interfetelor
grafice cu utilizatorul, clase pentru programarea Internet, pentru dezvoltarea aplicatiilor cu
baze de date sau pentru dezvoltarea aplicatiilor client-server distribuite de mari dimensiuni.

Evolutia conceptului de programare


De-a lungul timpului, programarea a cunoscut mai multe tendinte, si anume:
1. Programarea procedurala este cel mai vechi mod de programare si are la baza notiunea
de procedura (functie). Ideea generala a acestui tip de programare este urmatoarea:
decideti de ce proceduri aveti nevoie in prog. si folositi cei mai buni algoritmi pentru
implementarea lor. Ex: programarea in limbajul C. Principala deficienta a programarii
structurate consta in tratarea separata a algoritmilor si a structurilor de date ce se
prelucreaza.
2. Programarea modulara: cresterea dimensiunii programelor a condus la organizarea
acestora in module. Prin urmare paradigma a devenit: decideti de ce module aveti nevoie
in program si partitionati programul astfel incat datele sa fie ascunse in interiorul
modulelor. Aceasta paradigma mai este cunoscuta si sub denumirea de „principiul
ascunderii datelor”.
3. Abstractizarea datelor. Prin abstractizarea datelor se urmareste selectarea tipurilor de
date necesare in program si oferirea unui set de operatii asupra fiecaruia. Un astfel de tip
mai este denumit si tip de date abstracte. Ex: utilizarea claselor si a functiilor membru ale
acestora. Ex: limbajul C++ este un limbaj ce ofera astfel de facilitati.
4. Programarea orientata pe obiecte. Aceasta paradigma are la baza urmatorul principiu:
decideti de ce clase aveti nevoie; oferiti un set complet de operatii asupra fiecarei clase;
folositi ierarhiile de clase pentru a exprima comportamente comune mai multor clase
(abstractizarea prin ierarhii de clase).
5. Programarea generica. In cadrul programarii generice paradigma devine: decideti de ce
algoritmi aveti nevoie, parametrizati acesti algoritmi astfel incat sa poata fi aplicati mai
multor tipuri sau structuri de data. Ex: functii si clase sablon, containere, algoritmi
generici.

Limbajul C++ suporta toate paradigmele prezentatea mai sus. In ultimii ani, programarea
orientata pe obiecte a devenit foarte populara, mai ales datorita avantajelor sale care ajuta
dezvoltarii proiectelor actuale, ce devin din ce in ce mai complexe. Acest stil de programare
duce la impartirea aplicatiilor in mai multe module, astfel incat cel ce dezvolta un modul nu
trebuie sa cunoasca detaliile de implementare a altor module.
Programarea orientata pe obiecte este un concept foarte natural. In lumea inconjuratoare,
zi de zi, in orice moment, avem de-a face cu Obiecte. Imprejurul nostru sunt o multitudine de
obiecte, interconectate intre ele, comunicand unele cu altele intr-un fel sau altul. Un obiect
este caracterizat atat printr-o structura, cat si printr-un anume comportament. In mod normal,
obiectele evolueaza in timp, adeseori modificandu-si structura si functionalitatea.

Bibliografie:
• Bjarne Stroustrup - The C++ Programming Language (3rd edition), 1997
• Bjarne Stroustrup - The C++ Programming Language (Special 3rd Edition), 2000
• Bjarne Stroustrup - The design and evolution of C++ (Addison-Wesley), 2000
• Bjarne Stroustrup – Limbajul de programarea C++, Teora, 2003
• Ionut Muslea - C++ pentru avansati, microInformatica, 1994

3
• Ionut Muslea - Introducere in C++,microInformatica, 1994
• Dragos Acostachioaie – Note de curs online, http://www.biosfarm.ro/~dragos

4
CAPITOLUL 2. Clase

Concepte fundamentale: clasa si obiectul


Ideea de baza de la care pleaca programarea orientata obiect este de a grupa structurile
de date cu operatiile care prelucreaza respectivele date. Un asemenea ansamblu poarta
denumirea de clasa. O clasa reprezinta un tip de data definit de utilizator care are rolul de a
abstractiza (reprezenta) un concept din lumea reala. Privind limbajele orientate obiect ca o
evolutie a limbajelor structurate, constatam ca notiunea de clasa este o generalizare a notiunii
de structura de date. O clasa descrie un ansamblu de obiecte similare. Un obiect este asadar o
variabila de un anumit tip clasa. In mod uzual, se foloseste exprimarea ca un obiect este
instantierea unei clase. Proiectarea de programe utilizand clase se numeste programare
orientata pe obiecte (OOP).
In mod frecvent, pentru structurile de date se utilizeaza denumirea de date membre sau
campuri, iar pentru procedurile ce prelucreaza aceste date, termenul de functii membre sau
metode.
Clasa = Date + Metode
Acest ansamblu este bazat pe principiul fundamental al incapsularii datelor, conform
caruia accesul la datele membre se poate face numai prin intermediul setului de metode
asociat. Acest principiu determina o abstractizare a datelor in sensul ca un obiect este
caracterizat complet de specificatiile metodelor sale, detaliile de implementare fiind
transparente pentru utilizator. Acest aspect este hotarator in cazul proiectelor complexe, de
dimensiuni mari, care nu pot fi realizate decat cu ajutorul unor echipe de programatori.
Aplicatiile pot fi impartite cu usurinta in module, astfel ca cel ce dezvolta un modul nu trebuie
sa cunoasca detaliile de implementare a celorlalte module. Consecintele imediate sunt
scaderea timpului de dezvoltare a aplicatiilor, simplificarea activitatii de intretinere a
modulelor, si cresterea calitatii programelor.

Declararea claselor. Instantierea obiectelor.


Pentru declararea claselor in limbajul C++ se pot folosi doua cuvinte cheie: class si/sau
struct. Sintaxa simplificata pentru definirea/declararea unei clase este urmatoarea:

class NumeClasa
{
// declaratii variabile membre
//
// declaratii functii membre
};

struct NumeClasa
{
// declaratii variabile membre
//
// declaratii functii membre
};

Deosebirea intre cele doua declaratii consta in modul de acces la membrii celor doua
clase. In cazul primei declaratii toti membrii sunt privati in timp ce in al doilea toti sunt
publici. Asadar: struct s{…}; <=> class s{ public: …;. Pentru detalii vezi sectiunea”Controlul
accesului la membrii unei clase”. Folosirea unuia sau altuia dintre cuvintele cheie este la
latitudinea programatorului. De obicei cuvantul cheie struct este folosit pentru a defini tipuri
de date ce nu contin membrii de tip functii.

5
Exemplu: Definirea tipului de data Date pentru reprezentarea unei date calendaristice

struct Date
{
int zi, luna, an;

// Initializarea unei date


void init(int z, int l, int a);
};

Membrii unei clase sunt de doua tipuri: membrii de tip data (ex: int d) si membrii de tip
functie (ex: init). Pentru a putea utiliza efectiv un tip de date (in cazul de fata clasaDate),
trebuie sa definim o variabila de acel tip conform sintaxei C pentru declararea unei variabile:
NumeClasa variabila.
De exemplu, pentru a declara o variabila de tpul Date vom scrie: Date astazi;
Vom considera de acum incolo ca variabila poarta numele de obiect. Exprimarea uzuala
este ca un obiect este instantierea unei clase.

Membrii unei clase


Accesarea membrilor unei clase se face ca in cazul structurilor din limbajul C:
obiect.VariabilaMembra = valoare
pentru accesul la o variabila membra, si
obiect.FunctieMembra()
pentru apelarea unei functii membre.
De exemplu, pentru a initializa obiectul astazi cu 17 iulie 2003 vom apela functia init
pentru acest obiect astfel:

astazi.init(17, 7, 2003);

Functiile membre sunt folosite pentru a defini operatiile care sunt valide pentru tipul
respectiv de date. Pentru accesul membrilor unei clase prin intermediul unui pointer de tipul
respectiv se foloseste operatorul „->” in locul lui „.”.
Exemplu:

Date *po = &astazi;


printf(“Astazi este %d/%d/%d.”, po->zi, po->luna, astazi.an);

Pentru implementarea/definirea functiei vom folosi operatorul „::”numit operator de


rezolutie (scope resolution opertaor), (sau echivalent operator de acces sau de domeniu). El
permite accesul la un identificator, dintr-un bloc in care acesta nu este vizibil datorita unei alte
declaratii locale prin calificarea sa completa cu numele spatiului in care este declarat. Orice
clasa introduce un nou spatiu de nume, egal cu numele clasei. Asadar, pentru definitiile
functiilor membre aflate in afara declaratiei clasei este necesara specificarea numelui clasei
urmat de acest operator, indicand faptul ca functia are acelasi domeniu cu declaratia clasei
respective si este membra a ei, desi este definita in afara declaratiei.

void Date::init(int z, int l, int a)


{
zi = z; // zi = membru zi al clasei Date
luna = l;
an = a;
}

6
Controlul accesului la membrii unei clase
Limbajul C++ permite controlul accesului la membrii claselor. In acest scop, s-au creat
trei specificatori de cotrol al accesului:
• public: membrul poate fi accesat de orice functie din domeniul declaratiei clasei
• private: membrul este accesibil numai functiilor membre si prietene ale clasei
• protected: similar cu private, insa accesul se extinde si la functiile membre si prietene ale
claselor derivate.
De remarcat este faptul ca o functie membra a unei clase are acces la toti membrii clasei,
indiferent de specificatorul de acces. Specificatorii se aplica la fel atat membrilor de tip data
cat si functiilor membre. Membrii publici ai unei clase definesc interfata publica a clasei.
Exemplu:

class ClasaExemplu
{
int x; //membru private in mod implicit
public:
int y, z; // membrii public
int f(); // functie membru publica
private:
int w; // membru private
protected:
int f(int, int); // functie protected
};

void main()
{
ClasaExemplu obiect;

obiect.x = 10; // ERROR:Membrii privati sunt inaccesibili


// din functia main
obiect.f(); // OK!
obiect.f(1,1); // ERROR: Membrii protected sunt inaccesibili
// din functia main
}

Toti membrii unei clase ce apar dupa o linie de tipul public:, private: sau protected:
vor avea acelasi modificator de acces si anume cel specificat in linia respectiva. Daca nu se
specifica nici un modifcator, acesta este considerat private in cazul definirii unei clase
utilizand cuvantul cheie class si public pentru definirea clasei folosind struct.

Constructorii unei clase


Multe erori din programarea structurata sunt cauzate de o initializare gresita a datelor,
sau mai rau datorita absentei initializarii datelor. Pentru a evita aceste tipuri de erori, limbajele
orientate obiect au introuds conceptul de constructor. Constructorul este o functie membru
speciala a unei clase cu rolul explicit de a initializa obiectele acelei clase. Constructorul este
apelat automat la instantierea unei clase, dupa alocarea memoriei necesare obiectului, deci in
faza finala a crearii obiectului. Constructorii unei clase se declara si se definesc similar cu
celelalte functii membre, dar prezinta o serie de caracteristici specifice, si anume:
• numele lor coincide cu numele clasei careia ii apartin
• nu pot returna nici un rezultat
• nu se pot utiliza pointeri catre constructori sau destructori

7
• constructorii pot avea (sau nu) parametrii

Un constructor fara parametri poarta denumirea de constructor implicit (default


constructor). De remarcat este faptul ca in cazul in care o clasa nu dispune de constructori sau
destructori, compilatorul de C++ genereaza automat un constructor respectiv destructor
implicit.
Exemplu: in continuare vom adauga clasei Date trei constructori pentru initializarea
corecta a obiectelor de acest tip;

class Date
{
int zi, luna, an;
public:
// Constructorii disponibili
Date(int z, int l, int a);
Date(int z, int l);
Date(int z);
};
void f()
{
Date d0 = Date(6, 10, 2003); // initializat corect
Date d1(1, 1); // initializat corect
Date d2(15); // initializat corect
Date d3; // EROARE: Constructorul implicit nu este declarat
// pentru clasa Date
}

Pentru a evita proliferarea numarului de constructori ai unei clase, se pot utiliza valori
implicite pentru parametrii acestora: De exmplu clasa de mai sus poate fi rescrisa in felul
urmator:

class Date
{
int zi, luna, an;
public:
// Constructor(i)
Date(int z=0, int l=0, int a=0);
};

Observatie: Declartiile obiectelor d0, d1, d2 de mai sus raman valabile si in plus acum
este posibila si declaratia si initializarea corecta si pentru obiectul d4.

O situatie care poate aparea deseori este initializarea unui obiect cu datele membre ale
unui obiect de acelasi tip. Pentru a rezolva aceste situatii, C++ a introdus un constructor
special, numit constructorul de copiere (copy constructor). Prototipul constructorului de
copiere este urmatorul::
NumeClasa (const NumeClasa&);

Constructorul de copiere este apelat automat in urmatoarele situatii:


• la o declaratie de forma NumeClasa x1 = x, pentru initializarea corecta a obiectului x1
utilizand obiectul deja existent x
• transferul unui obiect ca argument unei functii
• la crearea unui instante temporare a unei clase.

8
In cazul in care lipseste constructorul de copiere pentru o clasa, compilatorul va genera
automat un constructor de copiere implicit care va copia bit cu bit continutul obiectului sursa
in obiectul destinatie.
Exemplu:

class Date
{
int zi, luna, an;
public:
// Constructori
Date(int z=0, int l=0, int a=0);
Date(const Date& d); // constructorul de copiere!
};

Date::Date(const Date& d)
{
zi = d.zi;
luna = d.luna;
an = d.an;
}

Un constructor de forma
X(TipDeData);
se mai numeste constructor de conversie si este utilizat in cadrul unei declaratii de
tipul X obiect=valoare. Utilizand aceasta metoda se pot realiza conversii dintr-un tip de baza
sau un tip clasa intr-un tip clasa. Trebuie mentionat faptul ca, in cazul conversiei dintr-un tip
clasa intr-un alt tip clasa, daca acest constructor trebuie sa aiba acces la datele membre ale
clasei de la care se face conversia, deci trebuie declarata prietena a clasei respective.
Exemplu:

class Double
{
double val;
public:
Double(double v) : val(v) { }
} ;

vod f()
{
Double obiect = 2.5;
Double obiect = Double(2.5);
Double obiect(2.5);
}

Observatie: Cele trei declaratii de mai sus sunt echivalente!

Destructorul clasei
Destructorul este o functie membra a clasei care este apelata automat la distrugerea
unui obiect (eliminarea obiectului din memorie, sau la apelul operatorului delete in cazul
obiectelor alocate dinamic (vezi sectiunea ”Construirea si distrugerea obiectelor”)).
Destructorul este utilizat pentru a elibera resursele (memoria, fisierele etc.) alocate la crearea
obiectului. O clasa poate avea cel mult un destructor. Daca nu se specifica destructorul pentru
clasa noastra, compilatorul va genera in mod implicit un destructor vid. Din punct de vedere

9
cronologic, destructorul este apelat inaintea eliberarii memoriei aferente, deci in faza initiala a
distrugerii unui obiect. Caracteristicile unui destructor sunt:
• numele lui coincide cu numele clasei precedat de caracterul ~ (adica ~NumeClasa)
• nu poate returna nici un rezultat
• nu se poate utiliza un pointer catre destructorul unei clase
• destructorul nu poate avea parametrii

Exemplu: In continuare vom introduce clasa String pentru manipularea sirurilor de


caractere.

class String
{
char *s;
int sz;
public:
// Constructorul utilizator si implicit
String(int size = 15);
//Constructorul de copiere
String(const String& str);

// Destructorul
~String();
};

String::String(int size = 15)


{
s = new char [sz = size];
}

String::String(const String& str)


{
s = new char [sz = str.sz];
strcpy(s, str.s);
}

String::~String()
{
// eliberarea memoriei (resurse) alocate in constructor
delete [] s;
}

void f()
{
String a(10); // se apeleaza constructorul utilizator
String b=a; // se apeleaza constr. de copiere
} // => se apeleaza destructorii pentru obiectele a, b

Observatie: Pentru initializarea corecta a obiectului b utilizand obiectul a, a fost


necesara definirea constructorului de copiere pentru clasa String care aloca memoria necesara
si initializeaza corespunzator obiectul destinatie!

Membrii statici ai unei clase


In mod normal, datele membre ale unei clase sunt alocate in cadrul fiecarui obiect. In
C++, se pot defini date membre cu o comportare speciala, numite date statice. Acestea sunt

10
alocate o singura data, existand sub forma unei singuri copii, comuna tuturor obiectelor de
tipul clasa respectiv, iar crearea, initializarea si accesul la aceste date sunt independente de
obiectele clasei. Sintaxa este:
static DeclarareMembru

Functiile membre statice efectueaza de asemenea operatii care nu sunt asociate


obiectelor individuale, ci intregii clase. Functiile exterioare clasei pot accesa membrii statici
ale acesteia astfel:
NumeClasa::NumeMembru
Obiect::NumeMembru

Pentru a face o distinctie clasa intre membrii statici si cei nestatici ai unei clase, se
recomanda accesarea membrilor statici utiliand prima forma din cele doua de mai sus.
Functiile membre statice nu primesc ca parametru implicit adresa unui obiect, asadar in cadrul
lor cuvantul cheie this nu poate fi utilizat. De asemenea, membrii normali ai clasei nu pot fi
referiti in cadrul unei functii statice decat specificand numele unui obiect. Variabilele
membrie statice ale unei clase trebuie definite/initializate in cadrul programului (in cadrul
clasei ele sunt doar declarate).
Exemplu: Vom inzestra clasa Date cu o functie si o variabila statica pentru a folosi o
data implicita atunci cand nu sunt suficiente informatii pentru constructia unei instante.

class Date
{
int zi, luna, an;

// Variabila statica
static Date default_date;
public:
// Constructori
Date(int z=0, int l=0, int a=0);
Date(const Date& d); // constructorul de copiere!

// Functie statica
static Date getDefault();
};

// Definirea variabilelor statice


Date Date::default_date(17, 12, 1989);

Date Date::getDefault()
{
zi++; // EROARE: membrii nestatice nu sunt accesibili in
// functii statice deoarecea acestea nu-l primesc pe this
return default_date;
}

// Daca nu e specificata ziua, luna sau anul la constructia unui


// obiect Date se vor folosi valorile din default_date
Date::Date(int z, int l, int a)
{
zi = z==0 ? default_date.zi : z;
luna = l==0 ? default_date.luna : l;
an = a==0 ? default_date.an : a;
}

11
void f()
{
Date d1(17, 7);
Date d2();

cout << d2.getDefault().zi; // afiseaza 27


cout << Date::getDefault().zi; // acelasi lucru ca mai sus
}

Cuvantul cheie this


Toate functiile membre nestatice ale unei clase primesc un parametru ascuns, pointer-ul
this, care reprezinta adresa obiectului in cauza. Acesta poate fi utilizat in cadrul functiilor
membre, insa NU poate fi modificat deoarece tipul acestuia este:
NumeClasa* const this;
Exemplu:

Date::Date(int z, int l, int a)


{
this->zi = z==0 ? default_date.zi: z;
this->luna = l==0 ? default_date.luna : l;
this->an = a==0 ? default_date.an : a;
this = NULL; // EROARE: this nu poate fi modificat!
}

Observatie: Utilizarea lui this in exemplul de mai sus este superflua. Exista totusi cazuri
in care utilizarea lui this este singura solutie acceptabila dupa cum vom vedea la redefinirea
operatorului de asignare.

Functii membre constante


Functiile membre constante nu pot modifica reprezentarea in memorie a obiectului in
cauza. Functiile constante se declara utilizand cuvantul cheie const dupa lista de parametrii a
functiei. Se recomanda declararea explicita ca functi constante a functiilor care nu necesita
modificarea obiectului. Acest lucru mareste claritatea programului si previne modificarea
accidentala a reprezentarii obiectului in cadrul acestor functii. Exemplu:

class Date
{
// Alte declaratii
int getDay() const;
};

int Date::getDay() const


{
zi++; // EROARE: membrii unei clase nu pot fi modificati intr-o
// functie membru constanta
return zi;
}

Observatii:
1. De fiecare data cand este posibil utilizati functiile membru constante pentru a mari
claritatea programului.

12
2. Incercati sa minimizati pe cat posibil numarul functiilor membre ale unei clase (adica
functie care au acces direct la reprezentarea interna a clasei) si folositi pe cat posibil
functii ne-membre clasei pentru operatii care nu necesita acces direct la reprezentarea
interna a clasei.

Functii si clase prietene (friends)


Principiul incapsularii datelor este bine sa fie respectat in cadrul elaborarii claselor si a
ierarhiei de clase. Cu toate acestea, exista situatii in care este greu sa se respecte acest
principiu. De aceea, Bjarne Stroustrup a introdus un concept menit sa rezolve si aceste situatii
particulare, pentru a oferi solutii elegante in vederea rezolvarii tuturor situatiilor posibile.
Acest concept este cel de friend, care permite practic abateri controlate de la ideea protectiei
datelor prin incapsulare. Mecanismul de friend este bine sa fie folosit numai in cazul in care
nu exista alta solutie, conform proverbului „Fereste-ma Doamne de prieteni ca de dusmani ma
pazesc si singur.”! Mecanismul de friend (sau prietenie) a aparut datorita imposibilitatii ca o
functie sa fie membru a mai multor clase.
Functiile prietene (friend) sunt functii care nu sunt functii ale unei clase, dar care au
totusi acces la membrii privati ai acesteia. Orice functie poate fi prietena a unei clase,
indiferent de natura acesteia. In tabelul de mai jos este prezentat sintetizat proprietatile
diverselor tipuri de functii:

Proprietate Functii membre Functii statice Functii friends


Are acces la membrii privati ai clasei X X X
Este in spatiul de nume al clasei X X -
(se defineste utilizand notatia X::X)
Poate fi apelata prin intermediul obiectelor
(primeste transparent pointerul this) X - -

Sintaxa declararii unei functii prietene in cadrul declaratiei unei clase este urmatoarea:
friend PrototipFunctie

Nu are nici o importanta in cadrul carei sectiuni este declarata functia prietena. O
functie friend nu este „membra cu drepturi depline” a clasei. Functiile friend sunt functii
externe clasei (functii globale sau membre ale altei clase) care insa au acces la membrii privati
ai clasei in care sunt declarate friend.
Clasele prietene sunt clase care au acces la membrii privati ai altei clase. Sintaxa
declararii unei clase prietene este:
friend class NumeClasaPrietena
Iata si un exemplu:

class Clasa1
{
// Membrii clasei
};

class Clasa2
{
friend class PrimaClasa;
};

13
In exemplul de mai sus, clasa Clasa1 (adica toate functiile sale membru, statice sau
nestatice) are acces la membrii privati ai clasei Clasa2. Important este sa remarcam ca relatia
de prietenie nu este tranzitiva. Daca o clasa A este prietena a clasei B, si clasa B este prietena
a unei clase C, aceasta nu inseamna ca A este prietena a clasei C. De asemenea, proprietatea
de prietenie nu se mosteneste in clasele derivate.

Exemplu: Clasele Vector si Matrix implementeaza un vector de 4 numere reale (o


matrice de 4x4 cu elemente numere reale). Functia de inmultire a unui vector cu o matrice
este declarata ca functie prietena in ambelor clase.

class Matrix;

class Vector;
{
float v[4];
public:
Vector(float v0, float v1, float v2, float v3)
{
v[0]=v0;
v[1]=v1;
v[2]=v2;
v[3]=v3;
}

friend Vector multiply(const Matrix& m, const Vector& m);


};

class Matrix
{
Vector rows[4];
public:
friend Vector multiply(const Matrix& m, const Vector& v);
};

Vector multiply(const Matrix& m, const Vector& v)


{
Vector result;
for(int i = 0; i < 4; i++)
{
result.v[i] = 0;
for(int j = 0; j < 4; j++)
result.v[i] += m.rows[i].v[j]*v.v[j];
}
return result;
}

void main()
{
Matrix m;
Vector v(1.0, 2.5, 5.0, 6.0);
v = multiply(m, v);
}

14
Functii membre inline
Functiile inline (membre sau nemembre ale unei clase) sunt functii care in loc sa fie
apelate la executia lor, sunt expandate direct de catre compilator in codul obiect general de
catre compilator. Functiile inline se folosesc in cazul functiilor de mici dimensiuni apelate
frecvent. Sintaxa pentru declararea functiei inline se face astfel:
inline PrototipFuntie;
Exemplu: vom declara inline functia getDay a clasei Date

class Date
{
// Restul declaratiilor
// ...

int getDay() const;


};

inline int Date::getDay() const


{
return zi;
}

O alta modalitate de a defini functii membre inline este cea a implementarii directe a
functiei chiar in interiorul clasei (adica la declaratia functiei). Toate functiile definite in cadrul
unei clase vor fi tratate ca functii inline Exemplul de mai sus poate fi rescris astfel:

class Date
{
// Restul declaratiilor
// ...

int getDay() const


{
return zi;
}
};

15
CAPITOLUL 3. Obiecte

Construirea si distrugerea obiectelor


Obiectele sunt instante ale unei clase, adica o variabila de tipul respectiv. Pentru
exemplificare sa consideram urmatorul program C++:

void main()
{
Date data;
Date oData(25, 12), *azi = new Date(01, 03, 2004);
cout << "Astazi este " << azi->getDay() << „/”
<< azi->getMonth() „/” << azi->getYear();
delete azi;
}

In momentul definirii variabilelor locale oData si data, va fi alocat automat spatiul de


memorie necesar, acesta fiind eliberat la terminarea programului. Dupa care se va apela
constructorul corespunzator. Pentru variabila locala data va fi apelat constructorul implicit al
clasei Date iar pentru oData va fi apelat constructorul cu doua argumente. In exemplul de mai
sus azi este o variabila dinamica (pointer).
Ati observat utilizarea unor operatori noi (care nu exisstau in limbajul C): new si delete
pentru crearea variabilelor dinamice. Limbajul C++ introduce o metoda noua pentru gestiunea
dinamica a memoriei, similara celei utilizate in C, dar superioara si special construita pentru
programarea orientata pe obiecte. Operatorul new este folosit pentru alocarea memoriei, iar
sintaxa acestuia este:
variabila = new tip;
variabila = new tip(valoare_initiala);
variabila = new tip[n];

Prima varianta aloca spatiu pentru variabila si apeleaza constructorul implicit, a doua
varianta ii aloca spatiu si o initializeaza apeland constructorul specific, iar a treia aloca un
tablou de dimensiune n (si apeleaza constructorul implicit pentru fiecare element al tabloului).
Acest operator furnizeaza ca rezultat un pointer continand adresa zonei de memorie alocate, in
caz de succes, sau un pointer cu valoarea NULL (practic 0) atunci cand alocarea nu a reusit.
Eliminarea unei variabile dinamice si eliberarea zonei de memorie aferente se realizeaza
cu ajutorul operatorului delete. Sintaxa acestuia este:
delete variabila;

Exista mai multe posibilitati de creare a obiectelor in cadrul unui program C++. Tabelul
de mai jos sumarizeaza toate situatiile in care sunt create/distruse obiectele.

Nr. Tip Cand este construit Cand este distrus Exemple


1 Variabile locale De fiecare data La parasirea void f() {
cand executia blocului in care a Clasa1 obiect;
programului fost declarat. }
intalneste linia de
declaratie
2 Alocarea La apelul La apelul void f() {
dinamica operatorului new operatorului delete Clasa1 *po = new Clasa1;
Clasa1 *pa = new Clasa1 [10];
delete po;
delete [] pa

16
}
3 Variabile locale Prima data cand La parasirea void f() {
statice executia programului static Clasa1 obiect;
programului }
intalneste linia de
declaratie
4 Tablou de La declararea La parasireavoid f() {
obiecte tabloului blocului in carea a Clasa1 paragraf[5];
fost declarat }
5 Variabile Inainte de apelul La parasireaClasa1 obGlobal1;
globale functiei main; se functiei main, in void f() {
vor crea in ordinea ordinea inversa}
declararii construirii Clasa1 obGlobal2;
6 Obiecte In timpul evaluarii La distrugerea void f(String s1, Strings2) {
temporare unei expresii expresiei evaluate cout << s1 + s2;
}
7 Alocarea La apelul variantei La apelul void* operator new
dinamica intr-o redefinite a operatorului delete (size_t, void*p) {
zona definita operatorului new return p; //zona de def
de utilizator }
8 Membrii unei Uniunile nu pot class X{
uniuni (union) avea membrii de tip };
static
Uniunile nu pot union Ouniune {
avea membrii care int x;
au constructori sau char name[12];
destructori X obiect;
};
9 Membrii unei Membrii unei clase La distrugerea class Student {
clase sunt creati la instantei Date ziNastere;
crearea unei String nume;
instante a clasei };
(obiect) void f() {
Student s;
}

La cele prezentate mai sus se impun cateva observatii:


1. In exemplul de mai jos obiectul s0 este creat la fiecare apel al functiei f, pe cand obiectul
s1 este creat doar in cazul in care functia f este apelata cu un parametru diferit de 0.

void f(int x)
{
String s0(10);
if(x)
{
String s1(20);
cout << s1;
} // -> aici se distruge obiectul s1
cout << s0;
} // -> aici se distruge obiectul s0

2. Deoarece obiectele statice se evalueaza doar la prima executie a liniei de declaratie,


programul de mai jos va afisa urmatoarele valori: 100, 200, 200, 200.

17
void f()
{
static int x = 100;
cout << x;
x = 200;
cout << x;
}

void main()
{
f();
f();
}

3. Pentru a putea declara un tablou de obiecte (pct. 4), tipul obiectelor trebuie sa posede un
constructor implicit (default constructor). Nu se pot specifica parametrii la initializarea
unui tablou.

4. Constructia obiectelor temporare (pct. 6) implica urmatorii pasi:

String stemp = s1 + s2;


cout << stemp;
delete stemp;

Initializarea membrilor unei clase


Deoarece cazul prezentat la punctul 9 – constructia membrilor unei clase – este cel mai
complex, important si interesant caz, ii vom dedica o intreaga sectiune pentru detaliere. La
crearea instantei unei clase, dupa alocarea memoriei necesare se va apela un constructor
potrivit pentru initializarea instantei. Sa consideram clasa Student declarata ca mai jos:

class Student
{
Date ziNastere;
String nume;
public:
Student(const char* n, const Date& data);
}

Student::Student(const char* n, const Date& data)


{
ziNastere = data; // 1
nume = String(n); // 2
}

void f()
{
Student unStudent(„Popescu”, Date(7, 8, 1978);
}

Pentru initializarea membrului ziNastere al clasei Student la declararea obietului


unStudent se vor executa urmatoarele operatii:
1. se aloca memorie (odata cu alocarea memoriei pentru obiectul unStudent)
2. se apeleaza constructorul implicit pentru initializarea sa

18
3. in lina etichetata cu 1 se va apela operatorul de asignare (vezi capitolul „Redefinirea
operatorilor” pentru detalii) pentru atribuirea unei noi valori.

Dupa cum se observa sunt excutati mai multi pasi decat ne-am fi asteptat iar
constructorul de copiere (care ne-am fi asteptat sa fie apelat) nu este utilizat pentru
initializarea acestui membru. Pentru a reduce numarul de operatii care se efectueaza si pentru
a initializa corect membrul ziNastere vom folosi lista de initializare pentru clasa Student.
Aceasta se foloseste in constructorii clasei si sintaxa este urmatoarea:

NumeClasa::NumeClasa(lista de parametrii ai constructorului)


: NumeMembru(Valoare), ... // lista de initializare
{
// alte initializari!
}

De exemplu constructorul de mai sus poate fi rescris mai elegant si mai optim astfel:

Student::Student(const char* n, const Date& data)


: nume(n), ziNastere(data)
{
}

In acest caz, pentru initializarea membrului ziNastere al clasei Student se vor executa
urmatoarele operatii:
1. se aloca memorie (odata cu alocarea memoriei pentru obiectul unStudent)
2. se apeleaza constructorul de copiere pentru initializarea sa corecta! (de data aceasta)

In mod similar, pentru initializarea membrului nume se va apela un constructor de tipul


String(const char*).
Este recomandata folosirea listelor de initializare deoarece se specifica foarte clar modul
in care sunt initializati membrii clasei respective si se evita apelarea inutila a constructorului
implicit urmat de operatorul de asignare. Constructorii membrilor unei clase specificati (sau
nu) in lista de initializare sunt apelati in ordinea in care sunt declarati membrii in clasa
respectiva si inaintea apelului constructorului clasei propriu-zise. Ordinea in care sunt
precizati in lista de initializare nu este importanta! Destructorii membrilor unei clase sunt
apelati dupa apelul destructorului clasei propriu-zise.
Membrii unei clase
• de tip referinta (NumeClasa& membruA;)
• constanti (const int membruB;)
• de tipuri care nu au constructori default (Y membruC;)
trebuie sa fie initializati obligatoriu in lista de initializare si nu pot fi initializati in alt fel.

Exemplu:

class Club
{
// Constructorul implicit este inaccesibil in exterior
// fiind declarat private
Club();
public:
Club(const char* s);
};

19
class X
{
const int i;
Date& date;
Club club;
public:
X(int ii, Date& dd, const char* clubName)
: i(ii), date(dd), club(clubName)
{
}
};

In continuare vom prezenta un exemplu pentru modelarea concepului Student. Exemplul


nu este unul complet, ci isi propune sa ilustreze notiunile prezentate in acest capitol. Atat
clasa String cat si clasa Student neceista si alte functii membre pentru a deveni operationale
intr-un proiect real. Deasmenea, pentru claritatea codului, a fost omisa tratarea diverselor erori
care pot aparea in timpul executiei programului.

class String
{
char* str;
int lungime;
void init(const char *s);
public:
String(const char*s = NULL); // constructor implicit
String(const String& s); // constructor de copiere
~String(); // destructor

const char* get() const


{
return str;
}

void set(const char* s);


};

String::String(const char* s)
{
init (s);
}

String::String(const String& s)
{
str = new char [lungime = strlen(s.str)];
strcpy(str, s.str);
}

String::~String()
{
if(str != NULL)
delete [] str;
}

void String::set(const char* s)

20
{
this -> ~String(); // apel explict al destructorului
init(s);
}
void String::init(const char * s)
{
if(s==NULL)
{
str = NULL;
lungime = 0;
}
else
{
str = new char [lungime = strlen(s)];
strcpy(str, s);
}
}

class Student
{
String nume, adresa;
Date dataNasterii;
int status;
const int id;
static const int STATUS_IMPLICIT;
static int generateID()
{
static int x = 0;
return x++;
}

public:
// constructorul implicit
Student(const char* n, const char* a,
const Date&d = Date::default_date);

// contructorul de copiere
Student(const Student& stud);

enum { ACTIV = 1, GREVIST = 2, NEHOTARAT = 3};

const char* getName() const


{
return nume.get();
}

void setName(const char * n)


{
nume.set(n);
}
};

// initializarea variabilelor statice ale clasei Student


const int Student::STATUS_IMPLICIT = Student::ACTIV;

Student::Student(const char* n, const char* a, const Date& d)

21
: nume(n), adresa(a), dataNasterii(d),
status(STATUS_IMPLICIT), id(Student::generateID())
{
}

Student::Student(const Student & stud)


: nume(stud.nume), adresa(stud.adresa),
dataNasterii(stud.dataNasterii), status(stud.status),
id(Student::generate ID())
{
}

void main()
{
Student Ion(”Ion”, ”Timisoara”, Date(24, 5, 1980));
Student Vasile = Ion; // constructor de copiere

Vasile.setName(”Vasile”);
}

22
CAPITOLUL 4. Redefinirea operatorilor
Limabjul C/C++ are definite mai multe tipuri de date: int, char, etc. Pentru utilizarea
acestor tipuri de date exista definiti mai multi operatori: adunare(+), inmultire (*), etc.
Limbajul C++ permite programatorilor sa isi defineasca acesti operatori (operator
overloading) pentru a lucru cu propriile clase (noile tipuri de date definite de catre acestia).
Pentru a redefini operatorul se va defini o functie (functia operator) cu numele:
operator

Functia operator se comporta ca oricare alta functie membru (sau nemembru) a unei
clase. Valoarea returnata si lista de argumente a functiei operator depind de operatorul care
este redefinit. Supradefinirea operatorilor este supusa in C++ unui set de restrictii:
• nu este permisa introducerea de noi simboluri de operatori;
• patru operatori nu pot fi redefiniti:
o :: (scope resolution)
o . (member acces)
o .* (member acces through pointer to function)
o ? : (conditional operator)
• caracteristicile operatorilor nu pot fi schimbate: pluralitatea (nu se poate supradefini un
operator unar ca operator binar sau invers), precedenta si asociativitatea;
• functia operator trebuie sa aiba cel putin un parametru de tipul clasa caruia ii este asociat
operatorul supradefinit
• operatorii = (asignare), [] (subscripting), () (apel de functie) si -> (dereferentiere) trebuie
implementati ca functii membre ale clasei pentru care dorim sa-i redefinim.

Programatorul are libertatea de a alege natura operatiei realizate de un operator, insa


este recomandat ca noua operatie sa fie apropiata de semnificatia initiala. Dupa cum vom
vedea in continuare, exista doua variante de definire a operatorilor:
• ca functie membra a clasei
• ca functie ne-membra a clasei.

Exemplu: definirea tipului de data complex inzestrat cu operatiile aritmetice si logice de


baza. In exemplu vor fi folosite atat functii membre (pentru operatorii +=, -=, *= etc) cat si
functii nemembre (pentru operatorii +, -, * etc.) pentru redefinirea operatorilor.

class complex
{
float re, im;
public:
complex(float r=0, float i=0)
: re(r), im(i) {
}

complex& operator+=(complex c);


complex& operator-=(complex c);

// friend – deoarece necesita acces la membrii


// privati ai clasei
friend bool operator==(complex c1, complex c2);
friend bool operator<=(complex c1, complex c2);
friend bool operator<(complex c1, complex c2);

23
// operatorul ++ prefix
complex& operator++();
// operatorul ++ postfix
complex& operator++(int);

// EROARE: op. binar == nu poate fi redefinit ca


// operator unar!complex
// int complex== ();

// EROARE: nu pot fi introdusi noi operatori in limbaj!


// complex operator**(const complex& c);
};

inline complex& complex::operator+= (complex c)


{
re += c.re;
im += c.im;
return *this;
}

inline complex& complex::operator-= (complex c)


{
re -= c.re;
im -= c.im;
return *this;
}

inline complex& complex::operator++()


{
re++;
return *this;
}

inline complex& complex::operator++(int)


{
return this->operator++();
}

// Operatori redefiniti prin functii ne-membre prietene ale clasei


bool operator== (complex c1, complex c2)
{
return c1.re == c2.re && c1.im == c2.im;
}

bool operator<= (complex c1, complex c2)


{
return c1.re <= c2.re && c1.im <= c2.im;
}

bool operator< (complex c1, complex c2)


{
return c1.re < c2.re && c1.im < c2.im;
}

// Operatori redefiniti prin functii ne-membre ale clasei

24
bool operator>= (complex c1, complex c2)
{
return !(c1<c2);
}

bool operator> (complex c1, complex c2)


{
return !(c1<=c2);
}

bool operator!= (complex c1, complex c2)


{
return !(c1 == c2);
}

complex operator+(complex c1, complex c2)


{
complex c=c1;
return c+=c2;
}

complex operator-(complex c1, complex c2)


{
complex c=c1;
return c-=c2;
}

void main()
{
complex a(2, 1), b=3, c;
c = a + b; // apel op. +
if (a == b) // apel op. ==
cout << ”a si b sunt egale”;
else
cout << „a si b sunt diferite”;

c = a + 1; // echivalent cu c = a.operator+(complex(1));
c = a++; // EROARE: Operatorul ++ nu este redefinit pentru
// clasa complex!
c = 1 + a; // c = operator+(complex(1), a);
}

Se impun cateva obsevatii:


1. Apelul c = a + b este doar o prescurtare pentru urmatorul apel:
c.operator=(operator+(a, b));
2. Definirea operatorilor ca functii membre a unei clase prezinta o restrictie majora: primul
operand este obligatoriu de tipul clasa respectiv. Din aceasta cauza am preferat redefinirea
operatorului + printr-o functie ne-membra; astfel sunt acceptate ambele forme:c=a+1 cat
si c=1+a; vezi functia main de mai sus. Prin urmare, pentru implementarea de operatori
simetrici (de exemplu operatorul + de mai sus) utilizati functii ne-membre.
3. Daca in cazul numerelor intregi operatorii urmatoarele operatii sunt echivalente

n = n+1;
n++;
n += 1;

25
in cazul numerelor complexe acestea sunt echivalente daca si numai daca au fost
redefiniti operatorii +, ++ si += iar semnificatia operatiilor redefiniti este aceea care ne
asteptam noi sa fie (adica de a aduna constanta 1 la partea reala). In cazul nostru, operatorul
++ nu a fost redefinit pentru clasa complex, asadar a doua linie de mai sus va genera o eroare
de compilare!

Tabelul care urmeaza prezinta operatorii care pot fi redefiniti in C++ in ordinea
descrescatoare a precedentei, impreuna cu pluralitatea si asociativitatea fiecaruia.

Tipul operatorului Simbolul operatorului Asociativitate


Binar () [] -> ->
Unar + - ~ * & (tip) <-
Unar ++ -- <-
Unar new delete <-
Binar -> * / % + - & | && || ->
Binar << >> < <= > >= == != ->
Binar = += -= *= /= %= &= ^= |= <<= >>= <-
Binar , ->

Operatori unari si binari


Fie un operator binar (de exemplu +, *, /, == etc.). Pentru a redefini operatorul binar
pentru clasa X trebuie sa implementam functia operator . Acest lucru il putem face
(dupa cum am mentionat mai sus) in doua moduri:

Tip functie Prototip Utilizarea Apel echivalent Exemple


operator
Membra TipFunctie operator a b a.operator (b) operatorii +=,
a clasei (TipParametru1 parametru1); -= pentru
clasa
complex

Ne-membra TipFunctie operator a b operator (a, b) operatorii +, -


(eventual (TipParametru1 parametru1, , <, <=, ==
friend) TipParametru2 parametru2); pentru clasa
complex

Fie un operator unar (de exemplu ++, --, etc). Pentru a redefini sa pentru clasa X
trebuie sa implementam functia operator . Operatorii unari sunt de doua tipuri: prefix si
postfix. Redefinirea unui operator unar op utem face in doua moduri:

Tip functie Prototip Utilizarea Apel echivalent Exemple


operator
Prefix
Membra TipFunctie operator (); a a.operator () operatorul ++
a clasei
Ne-membra TipFunctie operator (X&); a operator (a)
(eventual

26
friend)
Postfix
Membra TipFunctie operator (int); a a.operator (int) operatorul ++
a clasei
Ne-membra TipFunctie operator (X&, int); a operator (a,int)
(eventual
friend)

Observatie: Tipul functiei si tipul parametrilor depind de operatorul redefinit.

Redefinirea operatorului de asignare (=)


Operatorul de asignare (=) este deja predefinit in C++, pentru operanzi de tip clasa.
Daca nu este redefinit, atribuirea se face membru cu membru, in mod similar cu initializarea
obiectului efectuata de catre compilator. Pot exista situatii in care se doreste o atribuire
specifica clasei, ca atare trebuie redefinit comportamentul implicit.
Exemplu:vom redefini operatorul de asignare pentru clasa complex:

class complex
{
// restul declaratiilor
// ...
complex& operator= (complex x)
{
re = c.re;
im = c.im;
return *this;
}
};

Redefinirea operatorilor new si delete


Acesti doi operatori pot fi supradefiniti pentru a realiza operatii specializate de
alocare/eliberare dinamica a memoriei. Functia operator new trebuie sa primeasca un
argument de tipul size_t care sa precizeze dimensiunea in octeti a obiectului alocat si sa
returneze un pointer de tip void continand adresa zonei alocate:
void *operator new(size_t)

Tipul size_t este definit in stdlib.h. Chiar daca parametrul de tip size_t este obligatoriu,
calculul dimensiunii obiectului in cauza si generarea sa se face de catre compilator.

Functia operator delete trebuie sa primeasca ca prim parametru un pointer de tipul clasei
in cauza sau void, continand adresa obiectului de distrus, si un al doilea parametru, optional,
de tip size_t. Functia nu intoarce nici un rezultat.
void operator delete(void *, size_t)

Trebuie sa mentionam aici ca operatorii new si delete supradefiniti pastreaza toate


proprietatile operatorilor new si delete standard.

Constructori si conversii de tip


Limbajul C++ permite definirea de reguli de conversie pentru clasele create de
programator. Regulile astfel definite sunt utilizate numai dupa ce se verifica existenta altor
solutii (de exemplu, pentru o atribuire, se verifica mai intai supraincarcarea operatorului de

27
atribuire si in lipsa acestuia se face conversia). Exista doua metode de a realiza conversii de
tip: folosind constructori si redefinirea operatorului unar „cast”.
Conversia de tip folosind constructori consta in definirea unui constructor ce primeste ca
parametru tipul de la care se face conversia. Constructorul intoarce intotdeauna ca rezultat un
obiect de tipul clasei de care apartine, ca urmare folosind aceasta metoda se pot realiza numai
conversii dintr-un tip de baza sau un tip clasa intr-un tip clasa.
Conversia de tip folosind redefinirea operatorului unar cast presupune definirea unui
operator de conversie cu urmatoarea sintaxa:
operator TipData()
respectiv:
operator (TipData)

Operatorul "cast" este unar, asadar are un singur parametru, adresa obiectului in cauza,
si intoarce un rezultat de tipul operatorului. Ca urmare, prin aceasta metoda se pot defini
numai conversii dintr-un tip clasa intr-un tip de baza sau un alt tip clasa. De remarcat este
faptul ca, in cazul conversiei dintr-un tip clasa intr-un alt tip clasa, functia operator trebuie sa
aiba acces la datele membre ale clasei de la care se face conversia, deci trebuie declarata
prietena a clasei respective.

28
CAPITOLUL 5. Mostenire
Notiunile de clase si ierarhii de clase sunt notiuni comune limbajelor de programare
orientate obiect si au fost introduse pentru prima data in limbajul Simula. Conceptele din
lumea reala nu exista de sine statatoare si de obicei acestea sunt relationate unele de altele,
impart proprietati comune etc. Daca intrebam un zoolog ce este un caine, ne va raspunde ca
este un reprezentant al speciei canine domesticus. Un caine este un tip de carnivor, un
carnivor este un tip de mamifer, si asa mai departe. Zoologul imparte animalele in regn, clasa,
ordin, familie, gen si specie. Aceasta ierarhie stabileste o relatie de genul "este un/este o".
Putem remarca acest tip de relatie oriunde in aceasta lume: Mercedes este un tip de masina,
care la randul sau este un tip de autovehicul, si exemplele pot continua. Astfel, cand spunem
ca ceva este un tip de altceva diferit, spunem ca este o specializare a acelui lucru, asa cum o
masina este un tip mai special de autovehicul.
Conceptul de caine mosteneste, deci primeste in mod automat, toate caracteristicile unui
mamifer. Deoarece este un mamifer, cunoastem faptul ca se misca si respira aer - toate
mamiferele se misca si respira aer prin definitie. Conceptul de caine aduce in plus ideea de a
latra, de a misca coada, si asa mai departe. Putem clasifica mai departe cainii in caini de paza
si caine de vanatoare, iar cainii de vanatoare in cocker spanioli si dobermani, etc.
Asa cum am vazut mai sus, conceptul de mostenire este o notiune foarte naturala si pe
care o intalnim in viata de zi cu zi. In C++ intalnim notiunea de derivare, care este in fapt o
abstractizare a notiunii de mostenire. O clasa care adauga proprietati noi (date si metode) la o
clasa deja existenta vom spune ca este derivata din clasa originala. Clasa originala poarta
denumirea de clasa de baza. Clasa derivata mosteneste toate datele si functiile membre ale
clasei de baza; ea poate adauga noi date la cele existente si poate suprascrie sau adauga functii
membre. Clasa de baza nu este afectata in nici un fel in urma acestui proces de derivare si ca
urmare nu trebuie recompilata. Declaratia si codul obiect sunt suficiente pentru crearea clasei
derivate, ceea ce permite reutilizarea si adaptarea usoara a codului deja existent, chiar daca
fisierul sursa nu este disponibil. Astfel, nu este necesar ca programatorul unei clase derivate
sa cunoasca modul de implementare a functiilor membre din componenta clasei de baza.
In continuarea acestui capitol vom rafina ierarhia de clase pentru organigrama unei
institutii, pornind de la clasa Angajat, Manager samd.

class Angajat
{
Sting nume, prenume;
Date dataNastere;
public:
Angajat(String n, String p);

void print() const


{
cout << „Angajat: ” << nume << prenume << dataNastere;
}
};

class Manager
{
Angajat ang; // proprietatile sale ca angajat al institutiei
List managedGroup; // lista de persoane
};

29
Este clar ca un manager este la randul sau angajat al institutiei, deci mosteneste toate
proprietatile unui angajat. Pe langa aceste proprietati am adaugat clasei Manager lista de
angajati de care acesta raspunde (managedGroup). Ca atare ar fi mai potrivit sa declaram
clasa Manager ca fiind derivata din clasa Angajat in modul urmator:

class Manager : Angajat


{
List managedGroup;
};

struct List
{
void add(Angajat*);
};

Sintaxa simplificata a derivarii este urmatoarea:


class NumeClasaDerivata : NumeClasaDeBaza {... };

Urmatoarele afirmatii sunt echivalente:


1. Clasa Manager este derivata din clasa Angajat.
2. Clasa Angajat este o clasa de baza pentru clasa Manager.
3. Clasa Manager mosteneste toate proprietatile clasei Angajat.
4. Manager este un subtip al lui Angajat.

Putem reprezenta grafic relatia de mostenire de mai sus in felul urmator:

Angajat

Manager

In baza reprezentarii grafice, clasa Angajat se numeste superclasa iar clasa Manager
poarta denumirea de subclasa.
Deoarece Manager este subtip al clasei Angajat, putem folosi un manager oriunde un
obiect de tipul Angajat este acceptat, adica o variabila de tipul Manager* poate fi folosit
oriunde un Employee* este asteptat.

void f()
{
Manager manager;
Angajat* pa = &manager; // corect!
Angajat& ra = manager; // corect!
}

void generateAngajati(List& l)
{
l.add(new Angajat(“Ionescu”, “Florin”));
l.add(new.Manager(“Popescu”, “Gheoghe”));
}

30
Insa, pentru a „converti” un Angajat* la un Manager* trebuie utilizat explicit
operatorul de cast (operatie care insa va genera o exceptie daca pa nu pointeaza catre un
obiect de tip Manager!):

Manager* pm = static_cast<Manager*>(pa);

Pentru a putea declara o clasa A ca fiind derivata dintr-o clasa B, clasa B trebuie sa fie
declarata inaintea clasei A.

class B; // declaratia clasei B


class A : B { };
class B { };

Controlul accesului
Functiile membre ale clasei derivate, au acces la membrii protected si public ai clasei
de baza, insa nu au acces la membrii private ai clasei de baza. Pentru respectarea principiului
incapsularii datelor, datele membre pentru care se ofera acces claselor derivate se declara in
clasa de baza cu atributul protected.
Pentru a controla accesul la membrii mosteniti din clasa de baza, sintaxa declaratiei unei
clase derivate este extinsa incluzand specficatorul de acces:

class NumeClasaDerivata : SpecificatorAcces NumeClasaDeBaza { };

unde SpecificatorAcces poate fi public, protected sau private. Daca specificatorul


lipseste, private este considerat in mod implicit. De exemplu:

class Manager : public Angajat


{
// restul declaratiilor
};

// private poate sa lipseasca fiind considerat implicit


class Manager : private Angajat
{
// restul declaratiilor
};

Tabelul urmator prezinta sintetizat accesul la membrii unei clase in cazul derivarii:

Atributul din Modificator Accesul mostenit Accesul din


clasa de baza de acces de clasa derivata exterior
private private inaccesibil inaccesibil
protected private private inaccesibil
public private private inaccesibil
private protected inaccesibil inaccesibil
protected protected protected inaccesibil
public protected protected inaccesibil
private public inaccesibil inaccesibil
protected public protected inaccesibil
public public public accesibil

31
Se observa ca pentru a conserva dreptul de acces in urma derivarii, trebuie utilizata
derivarea public. Accesul poate fi stopat pe orice nivel ar ierarhiei de clase printr-o derivare
private sau protected. Stabilirea atributelor de acces ale membrilor unei clase, precum si ale
derivarilor, trebuie sa se faca astfel incat dezvoltarea ierarhiei de clase fara a afecta
incapsularea datelor.
Exemplu:

class B
{
int x;
protected:
int y;
public:
int z;
};

class A : B
{
void f()
{
x = 10; // EROARE: private
y = 20; // CORECT!
z = 30; // CORECT!
}
};

class AA : public B
{
};

void f()
{
B b;
A a;
AA aa;

b.x = 10; // EROARE: private


b.y = 20; // EROARE: protected
b.z = 30; // CORECT!
a.x = 10; // EROARE: private
a.y = 20; // EROARE: private
a.z = 30; // EROARE: private
aa.x = 10; // EROARE: private
aa.y = 20; // EROARE: protected
aa.z = 30; // CORECT!
}

Supraincarcarea functiilor membre


O notiune noua legata de derivare este cea de supraincarcare (suprascriere,
redefinirea) functiilor membre. Aceasta se refera la redefinirea unor functii a clasei de baza in
clasa derivata. De notat este faptul ca functiile originale din clasa parinte sunt in continuare
accesibile in clasa derivata (utilizand operatorul de rezolutie, ::), deci caracteristicile clasei de
baza nu sunt pierdute.

32
Exemplu: vom suprascrie metoda print a clasei Angajat astfel incat aceasta sa
tipareasca si proprietatile specifice clasei Manager (managedGroup).

class Manager : public Angajat


{
List managedGroup;

public:
void print() const
{
// print(); // EROARE: apel recursiv!
Angajat::print(); // OK: apel functie din clasa de baza
cout << managedGroup;
}
};

void f()
{
Manager m(„Popescu”, „Vasile”);
m.print(); // Manager::print
m.Angajat::print(); // Angajat::print
}

Pentru a apela o functie dintr-o clasa de baza a unei clase, vom prefixa numele functiei
cu numele clasei de baza urmat de acest operator

ClasaDeBaza::FunctieMembra(<lista de argumente>);

Prototipul functiei supraincarcate in clasa derivata trebuie sa fie acelasi cu al functiei


care se supraincarca din clasa de baza (poate sa difere eventual valoarea returnata).

Constructorii si destructorul clasei derivate


Pentru a initializa corect membrii clasei de baza trebuie apelat unul din constructorii
pusi la dispozitie de catre clasa de baza si nu prin initializarea lor directa. Acest lucru se face
utilizand lista de initializare in contructorul clasei derivate, o clasa de baza comportandu-se la
fel ca membrii oridnari ai clasei ai unei clase.
Regulile de functionare ale constructorilor si destructorilor, descrise in paragraful
precedent, raman valabile si in cazul claselor derivate, cu doua observatii privind ordinea de
apelare a acestora:
• la instantierea clasei derivate, se apeleaza mai intai constructorul clasei de baza, apoi se
apeleaza propriul constructor
• la distrugerea unui obiect al unei clase derivate, este apelat mai intai propriul constructor,
si apoi destructorul clasei de baza (deci in ordine inversa crearii obiectului).

Exemplu: vom adauga clasei Manager un constructor

class Manager : public Angajat


{
List managedGroup;

public:
Manager(String s1, String s2, List &g)

33
: Angajat(s1, s2), managedGroup(g)
{
}

void print() const


{
Angajat::print(); // OK: apel functie din clasa de baza
cout << managedGroup;
}
};

Observatii:
1. In constructorul clasei Manager, am apelat constructorul clasei de baza in lista de
initializare a acestuia.
2. Slicing („felierea”): problema care apare atunci cand se initializeaza un obiect al clasei de
baza folosind o instanta a clasei deirvate. In exemplul de mai jos, obiectul c va fi
initializat utilizand doar membrii clasei de baza Angajat ai obiectului m.

void f()
{
Manager m(„Popescu”, „Vasile”);
Angajat c = m; // atribuie doar partea Angajat al lui Manager
Angajat* pa = &m;
pa->print(); // se apeleaza Angajat::print
}

Polimorfism
In exemplul de mai sus, apelul pa->print() va apela functia print a clasei de baza
(Angajat) cu toate ca pa pointeaza catre un obiect de tipul Manager. Asadar, ar fi mai potrivit
sa se apeleze metoda Manager::print. Pentru a realiza acest lucru este nevoie sa declaram
functia print ca functie virtuala in clasa de baza. Functiile virtuale mai sunt numite si
metode. Sintaxa este urmatoarea:

virtual TipData NumeFunctieMembra();

Prin suprascrierea unei functii virtuale in clasa derivata, compilatorul va face


corespondenta corecta intre tipul obiectului si functia care trebuie sa-i fie aplicata.
In exemplul de mai jos vom transforma functia print a clasei Angajat intr-o functie
virtuala si vom deriva o noua clasa (SefDepartament) din clasa Anagajat in care vom
suprascrie functia print.

class Angajat
{
Sting nume, prenume;
Date dataNastere;
public:
Angajat(String n, String p);

virtual void print() const


{
cout << „Angajat: ” << nume << prenume << dataNastere;
}
};

34
class Manager : public Angajat
{
List managedGroup;

public:
Manager(String s1, String s2, List &g)
: Angajat(s1, s2), managedGroup(g)
{
}

void print() const


{
Angajat::print(); // OK: apel functie din clasa de baza
cout << „Managed group: ” << managedGroup.id;
}
};

class SefDepartament : public Angajat


{
int departamentID;

public:
SefDepartament(String s1, String s2, int id)
: Angajat(s1, s2), departamentID(id)
{
}

void print() const


{
Angajat::print(); // OK: apel functie din clasa de baza
cout << „Departament: ” << departamentID;
}
};

void printList(List& lista)


{
for(int i=0; i<lista.size(); i++)
lista.get(i)->print(); // se apeleaza versiunea
// corecta a metodei print
}

void main()
{
List l;
l.add(new Manager(„Popescu”, „Vasile”));
l.add(new SefDepartament sd(„Alexandrescu”, „Mircea”, 1001));
printList(l);
}

Programul de mai sus va afisa:

Angajat: Popescu Vasile Managed group: 10


Angajat: Alexandrescu Mircea Departament: 1001

35
Se observa ca au fost apelate functiile print din clasele Manager, respectiv
SefDepartament, in functie de tipul obiectului din lista. Un astfel de comportament se
numeste polimorfism, adica posibilitatea de a opera cu mai multe variante ale unei functii,
care efectueaza o anumita operatie in mod specific pentru tipuri diferite de obiecte. O clasa ce
contine functii virtuale se numeste clasa (tip) polimorfic. Pentru a obtine un comportament
polimorfic, obiectele trebuie manipulate prin intermediul pointerilor sau al referintelor.
La suprascrierea functiilor virtuale, functiile definite in clasele derivate trebuie sa aibe
acelasi prototip (se admit mici modificari la valoarea returnata) ca functia din clasa de baza.
Pentru implementarea functiilor virtuale, compilatorele implementeaza o tabela de
functii virtuale pentru fiecarea ce contine functii virtuale sau este derivata dintr-o functie
virtuala (vftbl = virtual function table). Instantele unei clase ce contine functii virtuale contin
un pointer ascuns catre tabela de functii virtuale corespunzatoare. In acest mod, numele unei
functii virtuale va fi considerat ca intrare in tabela de functii virtuale de unde se va apela in
continuare functia corecta. Acest mecanism nu introduce nici o „degradare” din punct de
vedere al vitezei de executie, iar extra-spatiul necesar pointerului asuns inlocuieste spatiul
care ar fi trebuit alocat „de mana” de catre programator prin includerea in clasa de baza a unui
mod de a distinge tipul obiectului (ex: o variabila intreaga).
Apelarea unei functii utilizand operatorul de rezolutie (ex:p->Manager::print()) nu
va utiliza mecanismul descris mai sus.

Functii virtuale pure. Clase abstracte


In C++ exista posibilitatea de a defini clase generale, care sunt destinate crearii de noi
clase prin derivare, ele neputand fi instantiate si utilizate ca atare. Acest gen de clase se
numesc clase abstracte. Ele se constituie ca baza in cadrul elaborarii de ierarhii de clase,
putand fi folosite, spre exemplu, pentru a impune anumite restrictii in realizarea claselor
derivate. In vederea construirii unor astfel de clase, s-a introdus conceptul de functie virtuala
pura. O astfel de functie este declarata in cadrul clasei, dar nu este definita. O clasa care
contine o functie virtuala pura este considerata abstracta. Sintaxa definirii acestor functii este:

virtual TipData NumeFunctieMembra() = 0;

Se impune aici observatia ca functiile virtuale pure trebuie definite in clasele derivate,
altfel si acestea vor fi considerate abstracte.

Ierarhii de clase. Mosternire multipla. Clase de baz virtuale


Dintr-o clasa de baza pot fi derivate mai multe clase si fiecare clasa derivata poate servi
mai departe ca baza pentru alte clase derivate. Se poate astfel realiza o ierarhie de clase, care
sa modeleze adecvat sisteme complexe. Pornind de la clase simple si generale, fiecare nivel al
ierarhiei acumuleaza caracteristicile claselor "parinte" si le adauga un anumit grad de
specializare. Mai mult decat atat, in C++ este posibil ca o clasa sa mosteneasca simultan
proprietatile mai multor clase, procedura numita mostenire multipla. Construirea ierarhiei de
clase reprezinta activitatea fundamentala de realizare a unei aplicatii orientate obiect,
reprezentand in fapt faza de proiectare a respectivului sistem.
Limbajul C++ (spre deosebire de alte limbaje orientate obiect, cum ar fi Java) permite
crearea de clase care mostenesc proprietatile mai multor clase de baza. Mostenirea multipla
creste astfel flexibilitatea dezvoltarii ierarhiei de clase. Daca derivarea normala duce la
construirea unei ierarhii de tip arbore, derivarea multipla va genera ierarhii de tip graf.
Sintaxa completa pentru operatia de derivare este urmatoarea:

class NumeClasaDerivata : ListaClaseDeBaza

36
unde ListaClaseDeBaza este:

SpecificatorAcces NumeClasaDeBaza, ...

Utilizarea mostenirii multiple se poate complica odata cu cresterea dimensiunii ierarhiei


de clase. O situatie care poate apare este derivarea din doua clase de baza, Clasa1 si Clasa2,
care la randul lor sunt derivate dintr-o clasa comuna, ClasaDeBaza (derivare diamant).

ClasaDeBaza

Clasa1 Clasa2

ClasaNoua

In acest caz, noua clasa, ClasaNoua, va contine datele membre ale clasei ClasaDeBaza
duplicate. Daca prezenta acestor date duplicate este utila, ele pot fi distinse evident cu ajutorul
operatorului de rezolutie, ::. Totusi, in cele mai multe cazuri, aceasta duplicare nu este
necesara si duce la consum inutil de memorie. De aceea, in C++ a fost creat un mecanism care
sa evite aceasta situatie, prin intermediul conceptului de clasa virtuala. Sintaxa este:

class NumeClasaDerivata : SpecificatorAcces virtual NumeClasaDeBaza { };

Aceasta declaratie nu afecteaza clasa in cauza, ci numai clasele derivate din aceasta.
Astfel, clasele Clasa1 si Clasa2 considerate vor fi declarate virtuale. Trebuie mentionat faptul
ca declararea virtual a acestor clase va afecta definirea constructorului clasei ClasaNoua,
deoarece compilatorul nu poate hotari care date vor fi transferate catre constructorul
ClasaDeBaza, specificate de constructorii Clasa1 si Clasa2. Constructorul ClasaNoua va
trebui modificat astfel incat sa trimita datele pentru constructorul ClasaDeBaza. De asemenea,
trebuie precizat ca intr-o ierarhie de clase derivate, constructorul clasei virtuale este
intotdeauna apelat primul.

37
CAPITOLUL 6. Tratarea exceptiilor
Este un fenomen "natural" ca in programe sa se strecoare erori, de diverse naturi.
Activitatea de programare implica si actiuni mai putini placute, adica testarea, depanarea si
corectarea erorilor. Costurile de indepartare a erorilor creste de obicei direct proportional cu
intarzierea din cadrul procesului de dezvoltare cand sunt descoperite. Trebuie insa inteleasa
diferenta dintre erori (bug-uri) si exceptii. Exceptiile sunt situatiile neasteptate aparute in
timpul rularii unui program (exemple: memorie insuficienta, spatiu harddisk insuficient,
adresa email invalida etc.). Programele trebuie sa fie pregatite pentru a trata aceste situatii
exceptionale.
In C++ s-a realizat un mecanism facil de tratare a exceptiilor. Astfel, o exceptie este un
obiect a carui adresa este trimisa dinspre zona de cod unde a aparut problema catre o zona de
cod care trebuie sa o rezolve. Pasii care trebuiesc in general urmati in vederea tratarii
exceptiilor in cadrul programelor C++ sunt urmatorii:
1. se identifica acele zone din program in care se efectueaza o operatie despre care se
cunoaste ca ar putea genera o exceptie, si se „izoleaza” in cadrul unui bloc de tip try. In
cadrul acestui bloc, se testeaza conditia de aparitie a exceptiei, si in caz pozitiv se
semnaleaza aparitia exceptiei prin intermediul cuvantului cheie throw;
2. se realizeaza blocuri de tip catch pentru a capta exceptiile atunci cand acestea sunt
intalnite. Blocurile catch urmeaza un bloc try, in cadrul carora sunt tratate exceptiile.

Sintaxa pentru try:

try
{
// cod
throw TipExceptie;
}

Sintaxa pentru throw:

throw TipExceptie;

Sintaxa pentru catch:

catch(TipExceptie)
{
// cod tratare exceptie
}

Observatii:
1. Daca TipExceptie este "...", este captata orice exceptie aparuta.
2. Dupa un bloc try, pot urma unul sau mai multe blocuri catch. Daca exceptia corespunde
cu una din declaratiile de tratare a exceptiilor, aceasta este apelata. Daca nu exista definita
nici o rutina de tratare a exceptiei, este apelata rutina predefinita, care incheie executia
programului in curs. Dupa ce rutina este executata, programul continua cu instructiunea
imediat urmatoare blocului try.
3. TipExceptie nu este altceva decat instantierea unei clase vide (care determina tipul
exceptiei), putand fi declarat ca: class TipExceptie {};

38
In continuare prezentam un exemplu de program care utilizeaza tratarea exceptiilor.

#include <iostream.h>

#define MAXX 80
#define MAXY 25

class Point
{
public:
class xZero {};
class xOutOfScreenBounds {};

Point(unsigned __x, unsigned __y) {


x = __x;
y = __y;
}

unsigned GetX() {
return x;
}

unsigned GetY() {
return y;
}

void SetX(unsigned __x) {


if(__x > 0)
if(__x < = MAXX)
x = __x;
else
throw xOutOfScreenBounds();
else
throw xZero();
}

void SetY(unsigned __y) {


if(__y > 0)
if(__y < = MAXY)
y = __y;
else
throw xOutOfScreenBounds();
else
throw xZero();
}

protected:
int x, y;
};

main()
{
Point p(1, 1);
try
{
p.SetX(5); // CORECT!

39
// p.SetX(0); // va genera o exceptie de tipul xZero
cout << "p.x successfully set to " << p.GetX() <<
"." << endl;

// va genera o exceptie de tipul xOutOfScreenBounds


p.SetX(100);
}
catch(Point::xZero)
{
cout << "Zero value!\n";
}
catch(Point::xOutOfScreenBounds)
{
cout << "Out of screen bounds!\n";
}
catch(...)
{
cout << Unknown exception!\n";
}
}

Datorita faptului ca exceptia este instantierea unei clase, prin derivare pot fi realizate
adevarate ierarhii de tratare a exceptiilor.
Trebuie avuta insa in vedere posibilitatea de aparitie a unor exceptii chiar in cadrul
codului de tratare a unei exceptii, situatii care trebuie evitate.

40
CAPITOLUL 7. Template-uri
Template-ul implementeaza asa-zisul concept de "tip parametrizat" ("parametrized
type"). Un template reprezinta o familie de tipuri sau functii, cu alte cuvinte, un sablon sau
model. Acest concept a fost introdus in primul rand pentru a creste gradul de reutilizabilitate a
codului. De exemplu, pentru a implementa o lista de numere intregi este necesara in mod
normal realizarea unei clase speciale (sa spunem ListOfIntegers), iar pentru o lista de siruri
alta clasa (sa spunem ListOfStrings). Conceptul de template permite realizarea unei clase
generale (sa spunem List), care sa accepte orice tip de element, inclusiv tipuri necunoscute la
momentul implementarii acesteia. Tipul template-ului este stabilit in momentul instantierii
sale. Template-urile sunt foarte utile pentru realizarea de biblioteci care trebuie sa ofere
metode generice de prelucrare a datelor.
Sintaxa generala de declarare a unui template este urmatoarea:

template < ListaDeParametri > Declaratie

unde Declaratie reprezinta declararea sau definirea unei clase sau functii, definirea
unui membru static al unei clase template, definirea unei clase sau functii membre al unei
clase template, sau definirea unui membru template al unei clase.
Clasele parametrizate (sau clasele template) se declara astfel:

template<class NumeParametru> class NumeClasa


{
// ...
// definirea clasei
}

Particularizarea (= stabilirea tipului) clasei template se face prin intermediul unei


constructii de genul:

NumeClasa <NumeParametru>

unde NumeParametru reprezinta tipul obiectului.

Functiile template se declara astfel:

template <class NumeParametru> declaratia functiei

Sa consideram in continuare ca exemplu implementarea unei stive generice folosind


template-uri.

#include <iostream.h>

template <class T> class StackItem {


public:
StackItem *Next;
T *Data;
StackItem(T __Data, StackItem <T> *__Next)
{
Data = new T(__Data);
Next = __Next;

41
}
};

template <class T> class Stack {


public:
T pop()
{
T result = *(Data->Data);
StackItem <T> *temp = Data;
Data = Data->Next;
delete temp;
return result;
}
T top()
{
return *(Data->Data);
}
void push(T __Data)
{
Data = new StackItem <T>(__Data, Data);
}
int isEmpty()
{
return Data == 0;
}
Stack()
{
Data = 0;
}
private:
StackItem <T> *Data;
};

main()
{
Stack <int> anIntegerStack;
anIntegerStack.push(5);
anIntegerStack.push(7);
if(anIntegerStack.isEmpty())
cout << "Stiva goala" << endl;
else
cout << anIntegerStack.pop() << endl;
cout << anIntegerStack.top() << endl;
}
In exemplul urmator este implementata o lista generica (List). Ca elemente a listei s-au
folosit obiecte de tip Point (clasa definita in Cap. 4). Pentru parcurgerea usoara a listei a fost
implementata o clasa de tip "iterator", care poate fi considerata ca fiind un "cursor" care
strabate lista. Functia List.begin() returneaza un iterator pozitionat pe primul element al listei,
List.end() pe ultimul element al listei. Saltul la urmatorul element al listei se face cu ajutorul
operatorului ++ din clasa Iterator.

#include <iostream.h>

class Point
{

42
friend ostream& operator << (ostream& output, Point p);
protected:
unsigned x, y;
public:
Point()
{
x = 0;
y = 0;
}
Point(unsigned X, unsigned Y)
{
x = X;
y = Y;
}
~Point() {}
unsigned GetX()
{
return x;
}
unsigned GetY()
{
return y;
}
void SetX(unsigned X)
{
x = X;
}
void SetY(unsigned Y)
{
y = Y;
}
};

ostream& operator << (ostream& output, Point p)


{
output << "(" << p.x << ", " << p.y << ")";
return output;
}

template <class T> class Item {


public:
Item *Next;
T *Data;
Item(T __Data, Item <T> *__Next)
{
Data = new T(__Data);
Next = __Next;
}
};

template <class T> class List {


public:
T pop_front()
{
T result = *(Data->Data);
Item <T> *temp = Data;

43
Data = Data->Next;
delete temp;
return result;
}
T front()
{
return *(Data->Data);
}
void push_front(T __Data)
{
Data = new Item <T>(__Data, Data);
}
int empty()
{
return Data == 0;
}
List()
{
Data = 0;
}
class Iterator {
friend class List <T>;
protected:
Item <T> *Current;
Iterator(Item <T> *x)
{
Current = x;
}
public:
Iterator() {}
int operator == (Iterator& x)
{
return Current == x.Current;
}
int operator != (Iterator& x)
{
return Current != x.Current;
}
T operator *()
{
return *(Current->Data);
}
Iterator& operator ++(int)
{
Current = Current->Next;
return *this;
}
};
Iterator begin()
{
return Iterator(Data);
}
Iterator end()
{

44
Item <T> *temp;
for(temp = Data; temp; temp = temp->Next);
return Iterator(temp);
}
private:
Item <T> *Data;
};

main()
{
List <Point> anPointList;
List <Point>::Iterator index, end;
anPointList.push_front(Point(1, 1));
anPointList.push_front(Point(3, 14));
index = anPointList.begin();
end = anPointList.end();
if(anPointList.empty())
cout << "Lista vida" << endl;
else
for(; index != end; index++)
cout << *index << " ";
cout << endl;
}

Clasele template pot avea trei tipuri de prieteni (friends):


• clasa sau functie care nu este de tip template
• clasa sau functie template
• clasa sau functie template avand tipul specificat.

Daca este necesara particularizarea unei functii template sau a unei functii membre a
unei clase template pentru un anumit tip, functia respectiva poate fi supraincarcata pentru
tipul dorit.
Trebuie remarcat de asemenea ca in cazul in care o clasa template contine membri
statici, fiecare instanta a template-ului in cauza va contine propriile date statice.

45

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