>> Inapoi <<
--------
Curs 2
--------
-----------------------------------------------------
Definirea claselor si a tipurilor abstracte de date
-----------------------------------------------------
O clasa este un tip de date a carei variabile sunt obiecte. Un obiect este o variabila care are
membri functii la fel ca si posibilitatea de memorare a valorilor de date.
O structura este un tip definit ce permite declararea de membri variabile. Deci, pentru a
obtine o clasa dintr-o structura, trebuie sa adaugam functii membru.
--------------
Incapsularea
--------------
Combinarea unui numar de articole, cum ar fi variabilele si functiile, intr-un singur pachet,
cum ar fi obiectul unei clase, se numeste incapsulare.Cand se defineste o functie membru,
definitia trebuie sa includa numele clasei, pentru ca pot exista doua sau mai multe clase care
au functii membru cu acelasi nume. Operatorul :: se numeste operatorul rezolutiei de domeniu,
si serverste la un scop similar cu cel al operatorului punct. Atat operatorul punct, cat si al
rezolutiei de domeniu precizeaza din care clasa face parte functia membru respectiva.
ATENTIE ! Operatorul rezolutiei de domeniu :: este folosit cu numele clasei, pe cand operatorul
punct este folosit cu obiecte (adica variabile ale unei clase).Numele clasei care precede
operatorul rezolutiei de domeniu se numeste calificator de tip, deoarece acesta califica numele
functiei la unul particular.Sintaxa generala a definitiei unei functii membru a clasei C
foloseste operatorul rezolutiei de domeniu :
Tip_Returnat C :: nume_functie (lista_parametri)
Exemplu:
Functia afisare() din programul C++ de mai jos apartine clasei zi_din_an. In concluzie,
retinem ca operatorul punct este folosit pentru apelarea unei date sau functii membru ale
unui obiect, iar operatorul rezolutiei de domeniu este folosit pentru definirea unei functii
membru a unei clase.
Programul urmator este un exemplu simplu de clasa.
#include
class zi_din_an
{
public:
int zi;
int luna;
void afisare();
};
int main()
{
zi_din_an azi, zi_nastere;
cout << "Dati data de azi (zi luna, ex: 22 03):\n";
cin >> azi.zi >> azi.luna;
cout << "Dati ziua de nastere:\n";
cin >> zi_nastere.zi >> zi_nastere.luna;
cout << "Azi suntem in ";
azi.afisare();
cout << "Ziua dvs de nastere este ";
zi_nastere.afisare();
if (azi.luna==zi_nastere.luna
&& azi.zi==zi_nastere.zi)
cout << "La multi ani !\n";
else
cout << "O zi buna !\n";
return 0;
}
void zi_din_an::afisare()
{
cout << "luna=" << luna
<< ",ziua" << zi << endl;
}
---------------------------
Membrii private si public
---------------------------
Am vazut in exemplul precedent ca datele si functiile membru din clasa zi_din_an erau public.
Deci acestea erau accesibile din orice functie a programului respectiv. Sunt situatii cand nu
ne intereseaza modul de implementare (codificare, memorare) a datelor sau functiilor sau nu
dorim sa fie accesibile. Atunci le vom declara private. Acest lucru se face folosind cuvantul
rezervat private, lista membrilor care apar dupa acesta se numesc membri privati (date sau
functii private).
Programul urmator ilustreaza aceasta tehnica pentru date private.
#include
class zi_din_an
{
public:
void citire();
void afisare();
void set(int noua_zi, int noua_luna);
//Preconditie: noua_zi si noua_luna formeaza o data posibila
//Postconditie: data este resetata conform cu argumentele
int obtine_zi();
//Returneaza ziua din luna
int obtine_luna();
//Returneaza luna(1-ianuarie, 2-februarie, etc. )
private:
int zi;
int luna;
};
int main()
{
zi_din_an azi, zi_nastere;
cout << "Dati data de azi "
<<"(zi luna, ex:22 03):\n";
azi.citire();
cout << "Azi suntem in ";
azi.afisare();
zi_nastere.set(24,3);
cout << "Ziua de nastere este ";
zi_nastere.afisare();
if (azi.obtine_zi()==zi_nastere.obtine_zi()
&& azi.obtine_luna()==zi_nastere.obtine_luna())
cout << "La multi ani !\n";
else
cout << "O zi buna !\n";
return 0;
}
void zi_din_an::afisare()
{
cout << "luna=" << luna
<< ",ziua=" << zi << endl;
}
void zi_din_an::citire()
{
//membrii privati pot fi folositi in definitia functiilor
cin >> zi >> luna;
}
void zi_din_an::set(int noua_zi, int noua_luna)
{
zi=noua_zi;
luna=noua_luna;
}
int zi_din_an::obtine_zi()
{
return zi;
}
int zi_din_an::obtine_luna()
{
return luna;
}
Cand se defineste o clasa, de obicei toti membrii variabila sunt privati. Asta inseamna ca
membrii variabila pot doar sa fie accesati sau modificati prin functii membru.
Sintaxa generala este:
class nume_clasa
{
public:
declarare membru_1;
declarare membru_2;
...
declarare membru_n;
private:
declarare membru_n+1;
...
};
ATENTIE ! Nu uitati la sfarsitul declararii unei clase semnul ; Functiile membru care permit
accesarea variabilelor membru private se numesc functii accessor (de acces). In exemplul
precedent functiile obtine_zi si obtine_luna sunt functii accessor. Este legala folosirea
intructiunii de atribuire pentru obiecte. De exemplu in programul precedent daca scriem:
zi_nastere = azi;
aceasta este echivalenta cu
zi_nastere.zi = azi.zi;
zi_nastere.luna = azi.luna;
Mai mult, atribuirea este legala chiar daca varibilele membru sunt membri privati.
-----------------------------------------
Comparatie intre class, struct si union
-----------------------------------------
Structurile sunt folosite de obicei cu toate variabilele membru publice si fara functii mebru.
Cu toate acestea, o structura C++ poate avea variabile membru private si functii membru
private si publice. Totusi se recomanda, folosirea structurilor in varianta clasica.
Un union este similar cu un struct, cu exceptia faptului ca permite definirea de varibile
care impar spatiul de memorie.
Exemplu:
union int_sau_long
{
int i;
long l;
} un_numar;
Compilatorul va aloca memorie suficienta pentru memorarea variabilei un_numar pentru a incape
in cel mai mare element din union.
Elementele unui union sunt accesata in aceeasi maniera ca la struct.
--------------
Constructori
--------------
Deseori, dorim sa initializam toate variabilele membru pentru un obiect cand il declaram.
Constructorul este o functie membru care este automat apelata cand un obiect al acestei clase
este declarat. Un constructor este folosit pentru initializarea valorilor unor sau tuturor
variabilelor membru precum si pentru a face alte tipuri de initializari pe care le dorim.
Exista doua exceptii fata de functiile membru obisnuite:
1. Un constructor trebuie sa aiba acelasi nume ca al clasei.
2. Definitia constructorului nu poate intoarce o valoare. Mai mult, nu se precizeaza tipul
intors, nici macar void.
Exemplu: continuam exemplul precedent
class zi_din_an
{
public:
void citire();
void afisare();
void set(int noua_zi, int noua_luna);
int obtine_zi();
int obtine_luna();
zi_din_an(int noua_zi, int noua_luna);
private:
int zi;
int luna;
};
Putem intelege obiectele azi, respectiv zi_de_nastere astfel:
zi_din_an azi(22, 3), zi_nastere(16, 4);
Este eronat sa facem astfel (ca la functii membru obisnuite):
zi_din_an azi, zi_nastere;
azi.zi_din_an(22, 3);
zi_nastere.zi_din_an(16, 4);
Un constructor nu poate fi apelat la fel ca functiile membru obisnuite. Definitia unui
constructor se da in acelasi mod ca orice alta functie membru.
Exemplu:
zi_din_an::zi_din_an(int noua_zi, int noua_luna)
{
zi=noua_zi;
luna=noua_luna;
}
Observam ca definitia constructorului este similara cu cea a functiei membru set (din programul
precedent). Diferenta este ca nu intoarce nimic (nici macar void).In general, constructorii sunt
supraincarcati, deci obiectele se pot initializa in mai multe moduri.Un constructor se poate
apela de mai multe ori pentru un obiect. Sintaxa apelului explicit a unui constructor este
diferita de sintaxa folosita pentru apelul altor functii membru. Un constructor se apeleaza cu
operatorul de asignare = in loc de operatorul punct.
Exemplu: Am vazut ca putem apela constructorul in momentul declararii, astfel:
zi_din_an azi(22,3);
Putem acum reapela contructorul, astfel:
azi = zi_din_an(22,3);
De fapt un constructor este o functie ce returneaza un obiect de tipul clasei respective.
ATENTIE ! Constructorul este privit ca o functie membru care initializeaza un obiect si poate fi
apelat folosind o sintaxa neobisnuita.
-----------------------------
Constructori fara argumente
-----------------------------
Daca in definitia unei clase, exista macar o definitie de constructor, atunci C++ va alege spre
folosinta definitia potrivita.
Exemplu:
class C1
{
public:
C1(int a, double b);
void ceva();
private:
int a;
double b;
};
Putem apela constructorul astfel:
C1 obiect(7,2.5);
O declaratie de genul
C1 obiect1;
este gresita deoarece exista in definitia clasei doar un singur constructor cu doua argumente.
Declaratia de mai sus ar fi corecta daca ori declaram obiect1 de doua argumente, ori mai
declaram in clasa C1 un constructor fara argumente.Un constructor fara argumente se numeste
constructor implicit, deoarece se aplica in cazul implicit cand declaram un obiect fara
specificarea vreunui argument.
Exemplu: Redefinim clasa C1 astfel:
class C1
{
public:
C1(int a, double b);
C1();
void ceva();
private:
int a;
double b;
};
Acum declaratia C1 obiect1; este corecta.
ATENTIE ! Declaratia C1 obiect1; poate cauza probleme. Compilatorul ar putea crede ca este vorba
de un prototip pentru o functie numita obiect1 fara argument si care intoarce o valoare de tip
C1.Daca am declarat un obiect al unei clase folosind constructorul implicit, atunci il putem
initializa apoi folsind alt constructor.
Exemplu: pentru clasa de mai sus putem avea:
C1 obiect;
obiect = C1(4,6.7);
In schimb nu putem avea declaratiile (motivul este explicat mai sus):
C1 obiect();
obiect = C1(4,6.7);
Tipuri de date abstracte
Un tip de data este o multime de valori impreuna cu o multime de operatii de baza definite
pentru aceste valori.
Exemplu:
int este un tip de data ce foloseste valorile
{-32768,...,-1,0,1,...,32767} si operatiile +,-,*,/,% (si altele)
Un tip de data se numeste tip de data abstract (ADT-Abstract Data Type) daca programatorii ce
folosesc acel tip nu au acces la detaliile implementarii valorilor si operatiilor.
Exemplu: tipurile predefinite (cum ar fi int) sunt tipuri de date abstracte.
Tipurile definite de programator, cum ar fi struct si class nu sunt implicit tipuri abstracte de
date.
Daca nu sunt definite si folosite cu atentie, tipurile definite de programator pot fi folosite
in mod neintuitiv pentru a face un program greu de inteles si de modificat. Modul cel mai bun
de a evita aceste probleme este sa fim siguri ca toate tipurile de date declarate de
programator sunt tipuri abstracte de date.
Pentru a defini o clasa care sa fie un tip abstract de date, trebuie sa separam specificarile
modului de folosire a tipului de catre programator de detaliile modului de implementare a
tipului.
Separarea trebuie sa fie completa astfel incat la o schimbare in implementarea clasei,
orice program care foloseste clasa abstracta de date sa nu necesite schimbari suplimentare.
Un mod de a asigura aceasta separare este:
1.Facem toate variabilele membru ale clasei membri private.
2.Facem toate operatiile de baza de care programatorul are nevoie functii membru public ale
clasei (specificand modul de folosire a fiecarei functii membru specifice).
3.Facem orice functie help a clasei membru private.
Interfata unui ADT (definit ca o clasa in C++) consta in functiile membru publice impreuna cu
comentariile care specifica modul de folosire a acestor functii membru publice. Implementarea
unui ADT consta in membrii privati ai clasei si definitiile functiilor membre publice si
private.
Deci clasa poate fi impartita in interfata si implementare.
-------------------------------------
Exercitii propuse spre implementare
-------------------------------------
1.Scrieti un program C++ in care definiti o clasa contbancar ce contine marea majoritate a
operatiilor bancare.
De exemplu, o schita pentru definitia acestei clase poate fi:
class contbancar
{
public:
void seteaza(long lei, double dobanda);
//Postconditie: Soldul din contul bancar este lei
void actualizeaza();
//Postconditie: actualizeaza soldul dupa un an
double obtine_soldul()
//Returneaza soldul curent
double obtine_dobanda();
//Returneaza dobanda din contul bancar
void afisare(ostream& iesire);
//Preconditie: iesire este un flux fisier de iesire sau cout
//Postconditie: Soldul si dobanda sunt afisate catre fluxul de iesire
private:
double sold;
double dobanda;
};
2.Scrieti un program C++ in care se definiti o clasa Arbore ce contine operatiile fundamentale
asupra arborilor binari (creare, parcurgere, stergere, numarare).
ATENTIE ! Radacina si numarul de noduri se recomanda a fi declarate private.