Generalități
Î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:
- încapsulare = mecanismul prin care atât datele, cât și funcțiile sunt plasate în aceeași structură, numită clasă și stabilirea nivelului de acces la conținutul acesteia;
- abstractizare = identificarea datelor și funcțiilor relevante pentru o anumită clasă;
- moștenire = proprietatea claselor de a prelua date și metode ale unor clase definite anterior. Clasa inițială se numește clasă de bază, iar cea nouă se numește clasă derivate;
- polimorfism = posibilitatea ca atât clasa de bază, cât și clasa derivată să conțină metode cu același nume, dar diferite ca funcționalitate.
Noțiunile de clasă și obiect
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.
Exemplu
#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;- clasa are două date membre:
numarator
șinumitor
. Ele sunt private – nu pot fi accesate din exteriorul clasei; - clasa are două funcții membre:
seteaza()
, care dă valori datelor membre șiafiseaza()
, care afișează datele membre în forma specifită unei fracții ordinale. Ele sunt publice.
Observații
- clasele se definesc similar cu structurile
class Fractie{ /// …
};
De fapt, diferențele dintre clase și structuri sunt reduse. În acest moment putem observa că accesul la câmpurile unei structuri este implicit public, iar accesul la membrii unei clase este implicit privat.
- accesul la membri clasei, indiferent dacă sunt date sau funcții, se face asemănător cu cel al câmpurilor unei structuri, prin operatorul de acces direct
.
(punctul), sau, în cazul pointerilor la clase, prin operatorul de acces indirect->
.
Fractie X;
X.afiseaza();
- obiectele unei clase se declara la fel cum se declară variabilele de orice tip
notextile.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ă!!
Crearea obiectului se numește instanțiere a clasei, iar obiectl este o instanță a clasei.
- la fel ca în cazul structurilor, având două obiecte ale aceleiași clase, se pot realiza atribuiri. Trebuie însă știut că este posibil ca rezultatul atribuirii să nu fie cel așteptat!
Fractie x , y;
x.seteaza(3 , 4);
y = x;
y.afiseaza();
- putem declara pointeri la obiecte. Obiectele adresate de pointeri pot fi statice sau dinamice, iar accesul la datele și funcțiile membre se face prin intermediul operatorului de acces indirect
->
Fractie x , * p;
x.seteaza(3 , 4);
p = & x;
p->afiseaza();
- metodele unei clase pot fi descrise în modul următor:
- plasăm în interiorul clasei definiția metodei, obținând o metodă inline – în exemplul de mai sus metoda
afiseaza()
a fost scrisă în acest mod - plasăm în interiorul clasei numai declarația (prototipul) metodei, iar definiția o plasăm în exteriorul clasei. În exemplul de mai sus am scris în acest mod metoda
seteaza()
. 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;
} - un avantaj al metodelor inline este că orice apel al ei este înlocuit cu secvanța de instrucțiuni corespunzătoare. Această abordare conduce la economie de timp, dar și la un necesar mai mare de memorie.
- plasăm în interiorul clasei definiția metodei, obținând o metodă inline – în exemplul de mai sus metoda
Constructori
Constructorul reprezintă un mecanism prin care datele membre ale unui obiect dintr-o clasă primesc valori la instanțierea (declararea/crearea) obiectului.
- constructorii sunt metode (funcții membre) ale clasei și se apelează la instanțierea obiectului
- constructorul:
- este o metodă a clasei
- are același nume cu clasa
- este o funcție fără tip; la declarare/definire în locul tipului funcție nu se scrie nimic, nici măcar
void
- o clasă poate avea mai mulți constructori, care diferă prin numărul și tipul parametrilor
- constructorii sunt funcții; blocul lor poate conține orice fel de instrucțiuni care reflectă logica clasei pe care o definim!
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
- în lipsa unui constructor definit de programator, se va apela constructorul implicit – el permite declararea obiectelor, alocând memorie pentru ele;
- dacă o clasă are constructor cu parametri, constructorul implict nu mai există. În consecință, nu se pot declara obiecte fără parametri, ceea ce este de multe ori necesar. De aceea, dacă o clasă are constructor, este necesar să aibă și constructor fără parametri, prin care un datele membre ale obiectului declarat să primească valorile pe care le consideram implicite.
Constructor de copiere
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:
- inițializarea unui obiect cu un alt obiect
- transmiterea prin valoare a unui obiect ca parametru pentru o funcție
- copierea unui obiect returnat de o funcție
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(); }
Destructor
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; }
Regula celor Trei
- un constructor de copiere
- operator de atribuire
- destructor
Funcții prietene
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:
- sunt declarate în interiorul clasei; prototipul lor este precedat de cuvântul cheie
friend
- funcția prieten are un parametru de tipul clasei; în funcție se pot accesa membrii privați ai acesteia
- funcțiile prietene nu sunt metode ale clasei; ele nu pot accesa direct membrii clasei, ci numai membrii parametrului de tipul clasei
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; }
Cuvântul cheie 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:
- parametrii unor metode au același nume cu datele membre. Exemplu: clasa fracție conține câmpul
numarator
și o metodă are un parametru cu același nume, atunci parametrul poate fi referit prin identificatorulnumarator
, iar câmpul prin expresiathis->numarator
sau(*this).numarator
; - uneori este necesar ca o metodă să returneze obiectul curent, sau o referință la obiectul curent;
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; }
Un exemplu complet
Programul de mai jos implementează clasa Fractie
și reprezintă o soluție pentru problema #spfractii :
- respectă principiul încapsulării;
- conține:
- proprietăți (private);
- metode private;
- metode publice;
- constructor;
- constructor de copiere;
- funcții prietene;
this
. Prin intermediul său, metodele vor returna obiectul curent, fapt ce permite înlănțuirea acestora – vezi apelul funcțieiProdus()
.
#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; }