Încapsularea presupune și ascunderea datelor sensibile ale unui obiect. Acest lucru este necesar din cel puțin două motive:
0
, într-un interval cunoscut – nu putem avea 157
sau 1204578
de ani!Astfel, datele membru ale unui clase sunt declarate private. Acest lucru duce la respectarea regulilor de mai sus, dar conduce totodată și la imposibilitatea de a vedea din afara clasei care este valoare unei date membru private sau de a o schimba. Aceste operații pot fi făcute prin intermediul metodelor GET și SET (getter-e și setter-e).
O metodă GET este o metodă publică a clasei care returnează valoarea ueni date membru private.
O metodă SET este o metodă publică a clasei care atribuie unei date membru private o anumită valoare.
void
sau este obiectul curent, pentru a putea înlănțui apelul metodelor.În exemplul de mai jos implementăm clasa Fractie
, înzestrând-o cu metode GET și SET. Folosim aceste metode la scrierea unei funcții care determină suma a două fracții.
#include <iostream> using namespace std; class Fractie{ private: int numarator, numitor; // proprietăți void Simplifica(); // metodă privată public: Fractie(int _numarator = 0, int _numitor = 1); // constructor Fractie(const Fractie &); // constructor de copiere Fractie & Citeste(); // metodă publică Fractie & Scrie(); // metodă publică // metode GET int Numarator(); int Numitor(); // metode SET Fractie & Numarator(int); Fractie & Numitor(int); }; Fractie::Fractie(const Fractie & F) { // constructor de copiere numarator = F.numarator; numitor = F.numitor; Simplifica(); } Fractie::Fractie(int _numarator /* = 0 */, int _numitor /* = 1 */) { // constructor numitor = _numitor, numarator = _numarator; Simplifica(); } int Fractie::Numarator() { //metoda GET return numarator; } int Fractie::Numitor() { //metoda GET return numitor; } Fractie & Fractie::Numarator(int x) { numarator = x; Simplifica(); return * this; } Fractie & Fractie::Numitor(int x) { // validam valoarea lui x if(x == 0) { cerr << "Numitor nul" << endl; return * this; } numitor = x; if(x < 0) numarator *= -1, numitor *= -1; Simplifica(); return * this; } Fractie & Fractie::Citeste() { // citeste numaratorul si numitorul obiectului curent // returnează obiectul curent int a , b; cin >> a >> b; // validam valorile citite if(b == 0) { cerr << "Numitor nul"<<endl; return * this; } if(b < 0) a = -a , b = -b; numarator = a, numitor = b; Simplifica(); return * this; } Fractie & Fractie::Scrie() { // afisează numaratorul si numitorul obiectului curent // returnează obiectul curent cout << numarator << " " << numitor <<endl; return * this; } void Fractie::Simplifica() { //metoda privata; realizeaza simplificarea fractiei merorate în obiectul curent int a = abs(numarator), b = abs(numitor); while(b) { int r = a % b; a = b; b = r; } numarator /= a; numitor /= a; } Fractie Suma(Fractie F, Fractie G) { // funcție oarecare. Accesam datele private ale obiectelor prin metodele GET si SET int x = F.Numarator() * G.Numitor() + F.Numitor() * G.Numarator(), y = F.Numitor() * G.Numitor(); Fractie R; R.Numarator(x).Numitor(y); // apelam metodele SET return R; // s-a apelat costructorul de copiere } int main() { Fractie a , b; a.Citeste(), b.Citeste(); Suma(a , b).Scrie(); return 0; }
Derivarea claselor (moștenirea) este unul dintre principiile fundamentale ale programării orientate pe obiecte. Ne permite să definim o clasă pornind de la altă clasă, creată anterior, și facilitează crearea și întreținerea aplicațiilor. Tototdată permite reutilizarea codului și micșorează timpul de implementare a programelor.
La crearea unei clase, în loc să scriem date și funcții membre complet noi, putem să precizăm că noua clasă va moșteni membri unei clase existente. Se obțin astfel ierarhii de clase.
Moștenirea are sens numai când obiectele din clasa moștenită fac parte din obiectele clasei de bază – relația dintre ele este de tipul Is A (este un/o). De exemplu:
Paralelogram
poate fi derivată din clasa Patrulater
.Caine
poate fi derivată din clasa Animal
.Fie B
o clasă care există deja, iar D
clasa care moștenește membrii clasei B
. Spunem că:
B
este clasa de bază;D
este clasa derivată;D
s-a obținut prin derivarea clasei B
;B
a fost derivată și s-a obținut clasa D
;D
moștenește clasa B
;În C++, o clasă poate fi derivată pornind de la mai multe clase de bază. Totodată, specificatorii de acces cunoscuți (public
, private
, protected
) sunt folosiți și pentru a preciza modul în care se face derivarea. Efectul lor este legat de vizibilitatea membrilor clasei de bază în clasa derivată.
Cea mai simplă formă de derivarea a unei clase este prezentată mai jos:
class D: public B{ // membri noi ai clasei derivate }
Observații:
B
este clasa de bază, care trebuie să fie deja definită;D
este clasa derivată;public
, face ca:
public
din clasa de bază B
rămân public
și în clasa derivată D
– pot fi accesați din afara clasei D
;protected
din clasa de bază B
rămân protected
și în clasa derivată D
– pot fi accesați din clasa derivata D
și vor putea fi acesați din alte clase vor moșteni clasa D
;private
din clasa de bază B
nu pot fi accesați din clasa derivată D
;public
putem folosi private
sau protected
. Semnificația lor va fi discutată ceva mai târziu!#include<iostream> using namespace std; class Animal{ private: protected: int varsta; public: Animal(){ varsta = 0; cout << "S-a nascut un animal." << endl; } ~Animal(){ cout << "Animalul a murit." << endl; } void creste() { ++ varsta; cout << "Animalul are " << varsta << (varsta == 1 ? " an":" ani") << endl; } }; class Caine: public Animal{ public: Caine(){ cout << "S-a nascut un caine." << endl; } ~Caine(){ cout << "Cainele a murit." << endl; } void latra() { for(int i = 1 ; i <= varsta ; i ++) cout << "Ham " ; cout << endl; } void creste() { ++ varsta; cout << "Cainele are " << varsta << (varsta == 1 ? " an":" ani") << endl; } }; int main(){ Caine X; X.creste(); X.Animal::creste(); X.latra(); X.creste(); X.latra(); }
S-a nascut un animal. S-a nascut un caine. Cainele are 1 an Animalul are 2 ani Ham Ham Cainele are 3 ani Ham Ham Ham Cainele a murit. Animalul a murit.
Animal
este clasa de bază, iar Caine
este clasa derivată;latra()
) am accesat proprietatea varsta
din clasa de bază:
varsta
a fost declarată protected
private
, nu putea fi accesată din clasa derivatămain()
, pentru obiectul X
de tip Caine
am accesat:
latra()
, disponibilă doar în clasa derivată;creste()
, moștenită din clasa de bază;X.creste(); // Cainele are 1 an
X.Animal::creste(); // Animalul are 2 ani
int main(){ Caine X; X.creste(); // Cainele are 1 an X.latra(); // Ham Animal A; A = X; A.creste(); // Animalul are 2 ani // X = A; eroare de sintaxă
}
A
este de tipul Animal
(clasa de bază), iar X
este de tipul Caine
(clasa derivată). Atunci:
A = X;
este corectă. În A
se vor copia doar membri din X
care fac parte din clasa de bază.X = A;
nu este corectă. Clasa derivată conține și membri care nu aparțin clasei de bază, ce nu pot fi inițializați prin această atribuire!Accesul la membrii (date sau funcții) clasei de bază în clasa derivată se face în funcție de regulile de acces la membrii clasei de bază și în funcție de modalitatea în care s-a făcut derivarea.
Sunt trei moduri de declarare a membrilor unei clase:
public
– pot fi accesați din afara clasei, dar și din interiorul eiprotected
– nu pot fi accesați din exteriorul clasei (de bază sau derivate). Pot fi accesați din interiorul clasei de bază și din interiorul claselor derivateprivate
– pot fi accesați numai din interiorul clasei.Modalitățile de derivarea ale unei clase sunt:
class D: public B{ … }
B
rămân publici și în clasa D
;B
rămân protejați în clasa D
;B
sunt inaccesibili în clasa D
;class D: protected B{ … }
B
devin protejați și în clasa D
;B
rămân protejați în clasa D
;B
sunt inaccesibili în clasa D
;class D: private B{ … }
B
devin privați și în clasa D
;B
devin privați în clasa D
;B
sunt inaccesibili în clasa D
;Supraîncărcarea funcțiilor (eng. functions overloading) se referă la posibilitatea ca în același program să fie declarate și definite mai multe funcții cu același nume, care să difere prin parametri. La apelul unei funcții supraîncărcate, compilatorul stabilește care dintre declarații se potrivește cu apelul respectiv, comparând numărul și tipul parametrilor actuali cu cel al parametrilor formali.
Metodele unei clase, fiind funcții, pot fi supraîncărcate. Totodată, în interiorul unei clase pot fi definiți și supraîncărcați operatori (+
, -
, *
, etc.), care permit utilizarea naturală a obiectelor, în raport cu înțelesul lor.
De exemplu, fracțiile pot fi adunate, scăzute, etc. O clasa care implementează fracții poate fi îmbogățită cu operațiile naturale de adunare, scădere, înmulțire, împărțire, dar și cu comparații, incrementări/decrementări, citire, afișare, etc.
În acest fel lucrul cu obiecte devine natural, evitând apelul explicit al unor metode care poate părea forțat, în anumite situații.
De știut:
În exemplul următor, definim pentru clasa Fracție
două metode creste()
:
1
;n
, care va mări valoarea fracției încapsulate în obiect cu n
.#include <iostream> using namespace std; class Fractie{ private: int numarator, numitor; public: void afiseaza() /// metodă pentru afișarea fractiei { cout << numarator << "/" << numitor << endl; } Fractie(int a , int b) /// constructor { if(b < 0) a = -a, b = -b; numarator = a, numitor = b; } Fractie & creste() { numarator += numitor; return *this; } Fractie & creste(int n) { numarator += n * numitor; return *this; } }; int main(){ Fractie x(1 , 4); x.afiseaza(); x.creste(); x.afiseaza(); x.creste(3); x.afiseaza(); return 0; }
Programul de mai sus va afișa:
1/4 5/4 17/4
Putem supraîncărca aproape toți operatorii C++ predefiniți. În acest mod putem folosi operatorii și pentru tipurile definite de noi, nu doar pentru cele predefinite.
Nu putem supraîncărca operatori pentru tipurile de bază: int
, double
, etc.
Nu putem supraîncărca operatori care nu există. De exemplu, nu putem adăuga operatorul #
.
Nu putem schimba prioritatea operatorilor.
Operatorii sunt funcții cu un nume special: cuvântul rezervat operator
, urmat de simbolul operatorului pe care îl definim. La fel ca orice altă funcție, și operatorii au tip al rezultatului și listă de parametri.
Operatorii care pot fi supraîncărcați sunt:
+
, -
, *
, /
, %
^
, &
, |
, ~
,,
=
<
, >
, <=
, >=
, ==
, !=
,!
, &&
, ||
++
, --
<<
, >>
,+=
, -=
, /=
, %=
, ^=
, &=
, |=
, *=
, <<=
, >>=
,[]
, ()
->
, ->*
new
, new []
, delete
, delete []
Următorii operatori nu pot fi supraîncărcați:
::
, .*
, .
, ?:
Sunt două modalități de defini operatori pentru o clasă:
Putem folosi metodele când operatorul este unar, sau când este binar și primul operand este obiect al clasei pentru care definim operatorul. Dacă operatorul este binar și primul operator nu este obiect al clasei, vom defini operatorul prin intermediul funcțiilor prietene.
În programul de mai jos definim o clasă Fractie
în care vom defini și supraîncărca operatorul +
pentru a implementa adunarea fracțiilor și adunarea fracțiilor cu întreg. Distingem următoarele cazuri:
F + G
, unde F
și G
sunt fracții, poate fi implementată atât printr-o metodă cât și ca funcție prietenă;F + n
, unde F
este fracție și n
este număr întreg, poate fi implementată atât printr-o metodă cât și ca funcție prietenă;n + F
, unde F
este fracție și n
este număr întreg, va fi implementată printr-o funcție prietenă.Situația de mai sus se datorează faptului că, la implementarea operatorului ca metodă, obiectul curent, pentru care se implementează metoda, este primul operand.
#include <iostream> using namespace std; class Fractie{ private: int numarator, numitor; public: void afiseaza() /// metodă pentru afișarea fractiei { cout << numarator << "/" << numitor << endl; } Fractie(int a = 0, int b = 1) /// constructor { if(b < 0) a = -a, b = -b; numarator = a, numitor = b; } Fractie operator+ (Fractie F) { Fractie R; R.numarator = numarator * F.numitor + numitor * F.numarator; R.numitor = numitor * F.numitor; return R; } Fractie operator+ (int n) { Fractie R; R.numarator = numarator + n * numitor; R.numitor = numitor; return R; } friend Fractie operator + (int n, Fractie F) { Fractie R; R.numarator = F.numarator + n * F.numitor; R.numitor = F.numitor; return R; } }; int main(){ Fractie X(1 , 4), Y(2, 3); Fractie R = X + Y; R.afiseaza(); R = X + 2; R.afiseaza(); R = 2 + X; R.afiseaza(); return 0; }
Programul de mai sus va afișa:
11/12 9/4 9/4
În programarea procedurală, un program era alcătuit din două componente distincte: date și funcții; funcțiile prelucrează datele la care au acces, prin intermediul parametrilor sau în alt mod.
Programarea orientată obiect – POO (sau Object Oriented Programming – OOP) este o modalitate de proiectare a programelor în care datele prelucrate și operațiile cu acestea sunt încapsulate în aceeași structură, numită obiect. Funcțiile care fac parte dintr-un obiect au acces la datele care caracterizează acel obiect, iar un program este alcătuit din mai multe obiecte, care interacționează.
Programarea orientată pe obiecte se bazează pe următoarele principii fundamentale:
Clasa este un tip de date, similar cu struct
, care conține atât câmpuri de tip dată (numite proprietăți), cât și câmpuri de tip funcție (numite și metode). Câmpurile clasei (proprietăți sau metode) se mai numesc membri ai clasei. Astfel avem, date membre și funcții membre.
Obiectul reprezintă o dată de tipul clasei – de exemplu o variabilă. Declararea unei variabile de acest tip se mai numește instanțiere a clasei. Spunem că obiectul este o instanță a clasei.
Pentru membrii unei clase (date sau metode) se precizează anumite caracteristici, numite modificatori (specificatori) de acces, care stabilesc cum se face accesul la acestea:
private
– interzic accesul la date și metode în afara clasei;public
– permit accesul la date și metode din afara clasei;protected
– interzic accesul din afara clasei, dar îl permit din clasele derivate – are sens în contextul derivării claselor.#include <iostream> using namespace std; class Fractie{ private: int numarator, numitor; public: void afiseaza(){ cout << numarator << "/" << numitor << " "; } void seteaza(int , int); }; void Fractie::seteaza(int a , int b){ numarator = a , numitor = b; } int main(){ Fractie F; F.seteaza(3 , 4); F.afiseaza(); /// F.numarator = 7; //eroare, data este privata return 0; }
În exemplul de mai sus:
Fractie
este numele clasei;F
este numele obiectului – instanță a clasei;numarator
și numitor
. Ele sunt private – nu pot fi accesate din exteriorul clasei;seteaza()
, care dă valori datelor membre și afiseaza()
, care afișează datele membre în forma specifită unei fracții ordinale. Ele sunt publice.class Fractie{ /// …
};
.
(punctul), sau, în cazul pointerilor la clase, prin operatorul de acces indirect ->
.Fractie X;
X.afiseaza();
Fractie x, y; /// două obiecte
Fractie V[ 10 ]; /// un tablou cu 10 elemente de tip obiect
Fracte * p; /// pointer la obiect. Obiectul încă nu există!!
Fractie x , y;
x.seteaza(3 , 4);
y = x;
y.afiseaza();
->
Fractie x , * p;
x.seteaza(3 , 4);
p = & x;
p->afiseaza();
afiseaza()
a fost scrisă în acest modseteaza()
. Pentru a preciza că este vorba despre o metodă a clasei și nu o funcție oarecare se folosește operatorul de rezoluție ::
void Fractie::seteaza(int a , int b){ numarator = a , numitor = b;
}
Constructorul reprezintă un mecanism prin care datele membre ale unui obiect dintr-o clasă primesc valori la instanțierea (declararea/crearea) obiectului.
void
Exemplu
class Fractie{ private: int numarator, numitor; void simplifica(){ int a = numarator, b = numitor , r; while(b) r = a % b, a = b, b = r; numarator /= a, numitor /= a; } public: void afiseaza(){ cout << numarator << "/" << numitor << endl; } void seteaza(int a , int b){ numarator = a , numitor = b; } Fractie(){ numarator = 0, numitor = 1; } Fractie(int a){ numarator = a, numitor = 1; } Fractie(int a , int b){ if(b == 0) b = 1; numarator = a, numitor = b; simplifica(); } }; int main(){ Fractie x; x.afiseaza(); Fractie y(3); y.afiseaza(); Fractie z(3 , 2); z.afiseaza(); return 0; }
Observații
Constructorul de copiere creează un obiect inițilizândul cu un alt obiect, din aceeași clasă, care există deja. El se folosește pentru:
Clasele au un constructor de copiere implicit, care realizează copierea bit-cu-bit a conținutului obiectului. Acest mod de copiere nu este corect atunci când obiectul conține date alocate dinamic, caz în care este mecesară definirea unui constructor de copiere explicit.
Sintaxa este:
NumeClasa (const NumeClasa &);
Exemplu:
Fractie(const Fractie & F) { numarator = F.numarator(); numitor = F.numitor(); simplifica(); }
Destructorul este o metodă publică care se apelează la eliminarea din memorie a unui obiect. O clasă poate avea un singur destructor, iar numele lui este identic cu al metodei, dar precedat de caracterul ~
. Destructorul, similar cu destructorul, este o funcție fără tip. Destructorul nu are parametri.
Exemplu
class Fractie{ private: int numarator, numitor; public: void afiseaza(){ cout << numarator << "/" << numitor << endl; } ... ~Fractie(){ cout << "Fractia " << numarator << "/" << numitor << " a fost eliminata." << endl; } }; int main(){ Fractie x(1 , 4); x.afiseaza(); return 0; }
Uneori este necesar ca din interiorul unor funcții care nu sunt metode ale unui obiect să accesăm membrii privați ai acestuia. Acest lucru poate fi realizat prin intermediul funcțiilor prietene. Ele:
friend
Exemplu:
class Fractie{ private: int numarator, numitor; public: ... friend void Afiseaza(Fractie F); }; void Afiseaza(Fractie F) { cout << F.numarator << "/" << F.numitor << endl; } int main(){ Fractie x(1 , 4); Afiseaza(x); return 0; }
this
În definiția metodelor unei clase, cuvântul cheie this
reprezintă un pointer către obiectul curent.
Este necesar în următoarele situații:
numarator
și o metodă are un parametru cu același nume, atunci parametrul poate fi referit prin identificatorul numarator
, iar câmpul prin expresia this->numarator
sau (*this).numarator
;Exemplu:
class Fractie{ private: int numarator, numitor; public: ... Fractie & creste(int n) { this->numarator += n * this->numitor; // aici this nu este necesar return * this; } friend void Afiseaza(Fractie F); }; void Afiseaza(Fractie F) { cout << F.numarator << "/" << F.numitor << endl; } int main(){ Fractie x(1 , 4); Afiseaza(x); x.creste(2).creste(3); Afiseaza(x); return 0; }
Programul de mai jos implementează clasa Fractie
și reprezintă o soluție pentru problema #spfractii :
this
. Prin intermediul său, metodele vor returna obiectul curent, fapt ce permite înlănțuirea acestora – vezi apelul funcției Produs()
.#include <iostream> using namespace std; class Fractie{ private: int numarator, numitor; // proprietăți void simplifica(); // metodă privată public: Fractie(int _numarator = 0, int _numitor = 1); // constructor Fractie(const Fractie &); // constructor de copiere Fractie & citeste(); // metodă publică Fractie & scrie(); // metodă publică friend Fractie Suma(Fractie , Fractie); // functie prietena friend Fractie Produs(Fractie , Fractie); // functie prietena }; Fractie::Fractie(const Fractie & F) { // constructor de copiere numarator = F.numarator; numitor = F.numitor; simplifica(); } Fractie::Fractie(int _numarator /* = 0 */, int _numitor /* = 1 */) { // constructor numitor = _numitor, numarator = _numarator; simplifica(); } Fractie & Fractie::citeste() { // citeste numaratorul si numitorul obiectului curent // returnează obiectul curent cin >> numarator >> numitor; return * this; } Fractie & Fractie::scrie() { // afisează numaratorul si numitorul obiectului curent // returnează obiectul curent cout << numarator << " " << numitor <<endl; return * this; } void Fractie::simplifica() { //metoda privata; realizeaza simplificarea fractiei merorate în obiectul curent int a = numarator, b = numitor; while(b) { int r = a % b; a = b; b = r; } numarator /= a; numitor /= a; } Fractie Suma(Fractie F, Fractie G) { // funcție prietenă. Putem accesa datele private ale obiectelor F , G int x = F.numarator * G.numitor + F.numitor * G.numarator, y = F.numitor * G.numitor; Fractie Rez(x , y); // s-a apelat constructorul pentru crearea obiectului. Aici s-a făcut simplificarea return Rez; // s-a apelat costructorul de copiere } Fractie Produs(Fractie F, Fractie G) { // funcție prietenă. Putem accesa datele private ale obiectelor F , G int x = F.numarator * G.numarator, y = F.numitor * G.numitor; return Fractie(x , y); // se apelează constructorul de copiere. Aici se face simplificarea } int main() { Fractie A , B; A.citeste(), B.citeste(); Fractie S = Suma(A , B); S.scrie(); Produs(A , B).scrie(); return 0; }