Documente Academic
Documente Profesional
Documente Cultură
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
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.
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
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;
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.
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:
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;
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.
7
• constructorii pot avea (sau nu) parametrii
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&);
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);
}
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
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()
{
// 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
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
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();
};
Date Date::getDefault()
{
zi++; // EROARE: membrii nestatice nu sunt accesibili in
// functii statice deoarecea acestea nu-l primesc pe this
return default_date;
}
11
void f()
{
Date d1(17, 7);
Date d2();
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.
class Date
{
// Alte declaratii
int getDay() const;
};
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.
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.
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;
}
class Matrix
{
Vector rows[4];
public:
friend Vector multiply(const Matrix& m, const Vector& v);
};
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
// ...
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
// ...
15
CAPITOLUL 3. Obiecte
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;
}
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.
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;
}
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
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.
class Student
{
Date ziNastere;
String nume;
public:
Student(const char* n, const Date& data);
}
void f()
{
Student unStudent(„Popescu”, Date(7, 8, 1978);
}
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:
De exemplu constructorul de mai sus poate fi rescris mai elegant si mai optim astfel:
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)
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)
{
}
};
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
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;
}
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);
21
: nume(n), adresa(a), dataNasterii(d),
status(STATUS_IMPLICIT), id(Student::generateID())
{
}
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.
class complex
{
float re, im;
public:
complex(float r=0, float i=0)
: re(r), im(i) {
}
23
// operatorul ++ prefix
complex& operator++();
// operatorul ++ postfix
complex& operator++(int);
24
bool operator>= (complex c1, complex c2)
{
return !(c1<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);
}
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.
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:
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)
class complex
{
// restul declaratiilor
// ...
complex& operator= (complex x)
{
re = c.re;
im = c.im;
return *this;
}
};
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)
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);
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:
struct List
{
void add(Angajat*);
};
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.
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:
Tabelul urmator prezinta sintetizat accesul la membrii unei clase in cazul derivarii:
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;
32
Exemplu: vom suprascrie metoda print a clasei Angajat astfel incat aceasta sa
tipareasca si proprietatile specifice clasei Manager (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>);
public:
Manager(String s1, String s2, List &g)
33
: Angajat(s1, s2), managedGroup(g)
{
}
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:
class Angajat
{
Sting nume, prenume;
Date dataNastere;
public:
Angajat(String n, String p);
34
class Manager : public Angajat
{
List managedGroup;
public:
Manager(String s1, String s2, List &g)
: Angajat(s1, s2), managedGroup(g)
{
}
public:
SefDepartament(String s1, String s2, int id)
: Angajat(s1, s2), departamentID(id)
{
}
void main()
{
List l;
l.add(new Manager(„Popescu”, „Vasile”));
l.add(new SefDepartament sd(„Alexandrescu”, „Mircea”, 1001));
printList(l);
}
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.
Se impune aici observatia ca functiile virtuale pure trebuie definite in clasele derivate,
altfel si acestea vor fi considerate abstracte.
36
unde ListaClaseDeBaza este:
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:
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.
try
{
// cod
throw TipExceptie;
}
throw TipExceptie;
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 {};
unsigned GetX() {
return x;
}
unsigned GetY() {
return y;
}
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;
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:
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:
NumeClasa <NumeParametru>
#include <iostream.h>
41
}
};
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;
}
};
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;
}
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