Biblioteca
Tutora
BibliotecaInformatică și TIC › clasa a XI-a

Programare orientată pe obiecte (POO)

Introducere în conceptele fundamentale ale programării orientate pe obiecte în C++.

Bacalaureat

Clase și obiecte: tipul și instanțele lui

O clasă este un tip de date definit de programator care grupează împreună datele (atribute, câmpuri) și funcțiile care le prelucrează (metode). Un obiect este o instanță a clasei — o variabilă concretă de acel tip, cu valori proprii ale atributelor.

Distincția clasă/obiect e prima grilă de teorie: clasa e șablonul (definirea tipului, o singură dată), obiectele sunt exemplarele (oricâte, fiecare cu propriile date). Din clasa Elev se pot crea obiectele e1, e2, e3 — fiecare cu nume și medie proprii, dar cu aceleași metode.

În C++, membrii unei clase au modificatori de acces:

Detaliul de sintaxă care se punctează: la `struct` accesul implicit este public, la `class` este private — singura diferență dintre ele în C++.

Accesul la membri se face cu operatorul punct pentru obiecte (`e1.afisare()`) și cu săgeată pentru pointeri la obiecte (`p->afisare()`). Metodele pot fi definite în interiorul clasei sau în exterior, cu operatorul de rezoluție: `void Elev::afisare() { … }` — sintaxa `Clasa::metoda` spune compilatorului cărui tip îi aparține funcția.

Constructori și destructori: viața unui obiect

Constructorul este metoda specială apelată automat la crearea obiectului, cu rolul de a-i inițializa atributele. Reguli de sintaxă cerute la examen:

Constructorul de copiere se apelează când un obiect nou se creează din alt obiect (inițializare, transmitere prin valoare ca parametru). Implicit copiază membru cu membru — suficient până când clasa deține resurse alocate dinamic, caz în care copia superficială face ca două obiecte să partajeze aceeași memorie.

Destructorul — `~Elev()` — se apelează automat la distrugerea obiectului (ieșirea din domeniul de vizibilitate sau delete). Este unic (nu se supraîncarcă), nu are parametri și nici tip de retur; rolul lui tipic este eliberarea resurselor alocate de constructor.

Ordinea care apare în grile de urmărire a execuției: obiectele locale se distrug în ordinea inversă creării lor; la o clasă derivată se apelează întâi constructorul bazei, apoi al derivatei, iar destructorii — exact invers.

Încapsularea: date private, interfață publică

Încapsularea este principiul POO care leagă datele de metodele care le prelucrează și ascunde reprezentarea internă a obiectului de exterior. Practic:

De ce nu sunt atributele pur și simplu publice — argumentul care se cere la teorie: cu date publice, orice cod poate scrie `elev.medie = 47;` și nimic nu împiedică starea invalidă. Cu atribut privat și setter, clasa validează (media rămâne între 1 și 10) și își poate schimba reprezentarea internă fără să strice codul care o folosește. Încapsularea reduce astfel cuplajul și face programele mari întreținabile.

Excepția controlată de la regulă: o funcție sau o clasă declarată friend primește acces la membrii privați ai clasei. Se folosește rar și justificat (tipic la supraîncărcarea operatorilor << și >> pentru fluxuri); abuzul de friend subminează exact protecția pe care o oferă încapsularea — nuanță care apare în întrebările de tip adevărat/fals.

Confuzia frecventă: încapsularea nu înseamnă doar „a pune datele într-o clasă”, ci a le ascunde (information hiding); o clasă cu toate atributele publice folosește sintaxa claselor, dar încalcă principiul.

Moștenirea: reutilizare prin relația este-un

Moștenirea permite definirea unei clase derivate care preia atributele și metodele unei clase de bază și adaugă sau modifică propriile componente: `class Elev : public Persoana { … }`. Relația modelată este este-un: orice Elev este o Persoana.

Ce se moștenește și ce nu — puncte de teorie:

Ordinea apelurilor, iar și iar cerută în grile: la crearea unui obiect derivat se execută întâi constructorul bazei, apoi al derivatei; la distrugere, întâi destructorul derivatei, apoi al bazei — construcția de la fundație în sus, demolarea de sus în jos.

Derivata poate redefini (suprascrie) o metodă a bazei: aceeași signatură, comportament nou. Nu confunda cu supraîncărcarea: supraîncărcarea = aceeași clasă, același nume, parametri diferiți; redefinirea = clase diferite (bază/derivată), aceeași signatură. Această pereche de definiții e una dintre cele mai frecvente întrebări-capcană de teorie.

Moștenirea poate fi și multiplă în C++ (o derivată cu mai multe baze), menționată la nivel de existență în programă.

Polimorfismul: funcții virtuale și clase abstracte

Polimorfismul înseamnă că același apel de metodă are comportamente diferite în funcție de tipul real al obiectului. În C++, polimorfismul la execuție cere două ingrediente:

Cu `virtual void afisare();` în Persoana și un pointer `Persoana* p = new Elev;`, apelul `p->afisare()` execută varianta din Elev — decizia se ia la execuție (legare dinamică/târzie), după tipul obiectului real. Fără virtual, se execută varianta din Persoana — decizia se ia la compilare (legare statică), după tipul pointerului. Exact acest contrast, cu și fără virtual, este grila standard de polimorfism.

O funcție virtuală pură se declară cu `= 0`: `virtual double arie() = 0;`. Clasa care conține cel puțin o funcție virtuală pură devine clasă abstractă:

De reținut și distincția de vocabular: supraîncărcarea funcțiilor este uneori numită polimorfism static (la compilare), iar funcțiile virtuale dau polimorfismul dinamic (la execuție) — cel vizat de obicei de întrebări.

Supraîncărcarea operatorilor și template-uri

Supraîncărcarea operatorilor permite ca operatorii limbajului (+, -, ==, <<) să capete sens pentru tipuri definite de utilizator, prin funcții cu numele operator urmat de simbol: `Fractie operator+(Fractie a, Fractie b)`. Reguli care se cer la teorie:

Template-urile (șabloanele) rezolvă altă problemă: același algoritm scris o singură dată pentru orice tip de date. `template <typename T> T maxim(T a, T b)` funcționează pentru int, double, string; compilatorul generează la compilare câte o versiune concretă pentru fiecare tip folosit. Există și clase template — exemplul omniprezent: containerele STL (`vector<int>`, `stack<char>`).

Distincție finală care închide capitolul și prinde multe grile: template-ul este polimorfism la compilare (codul se generează per tip, fără cost la execuție), funcțiile virtuale sunt polimorfism la execuție (o singură funcție, alegerea variantei se face dinamic). Ambele înseamnă „un nume, mai multe comportamente”, dar mecanismele și momentul deciziei diferă fundamental.

De reținut

clasă
tip de date definit de programator care grupează atribute (date) și metode (funcții) care le prelucrează; servește ca șablon pentru crearea obiectelor
obiect
instanță a unei clase — variabilă concretă de tipul clasei, cu valori proprii ale atributelor și acces la metodele clasei
constructor
metodă specială cu același nume ca al clasei, fără tip de retur, apelată automat la crearea obiectului pentru inițializarea atributelor; poate fi supraîncărcat
destructor
metodă specială unică, cu numele clasei precedat de tildă, fără parametri și fără tip de retur, apelată automat la distrugerea obiectului, tipic pentru eliberarea resurselor
încapsulare
principiul ascunderii datelor: atributele sunt private, iar exteriorul le accesează doar prin metode publice care pot valida modificările
moștenire
mecanismul prin care o clasă derivată preia atributele și metodele unei clase de bază, modelând relația este-un; constructorii și destructorul nu se moștenesc
polimorfism dinamic
proprietatea ca același apel, făcut prin pointer sau referință la clasa de bază, să execute metoda corespunzătoare tipului real al obiectului; necesită funcții declarate virtual
funcție virtuală pură
funcție virtuală declarată cu = 0, fără implementare în clasa de bază; prezența ei face clasa abstractă, iar derivatele trebuie să o implementeze
clasă abstractă
clasă cu cel puțin o funcție virtuală pură, din care nu se pot crea obiecte; se folosește ca interfață, prin pointeri și referințe la ea
template (șablon)
mecanism prin care o funcție sau o clasă se scrie o singură dată pentru un tip generic T, iar compilatorul generează la compilare versiuni concrete pentru fiecare tip utilizat

Greșeli frecvente

Greșit: Constructorul ar avea tipul de retur void
Corect: Constructorul nu are niciun tip de retur, nici măcar void — scrierea void Elev() definește o metodă obișnuită cu numele Elev, nu un constructor
Greșit: După definirea unui constructor cu parametri, declarația Elev e; ar rămâne validă
Corect: Constructorul implicit este generat automat doar dacă nu există niciun constructor definit; odată definit unul cu parametri, Elev e; dă eroare de compilare dacă nu scrii explicit și un constructor fără parametri
Greșit: Redefinirea unei metode în clasa derivată ar fi același lucru cu supraîncărcarea
Corect: Supraîncărcarea înseamnă același nume cu parametri diferiți în aceeași clasă; redefinirea înseamnă aceeași signatură în clasa derivată — sunt mecanisme diferite, evaluate separat la examen
Greșit: Apelul p->metoda() printr-un pointer la bază ar executa mereu varianta clasei derivate
Corect: Fără cuvântul-cheie virtual în clasa de bază, se execută varianta bazei (legare statică, după tipul pointerului); doar metodele virtuale se aleg la execuție după tipul real al obiectului
Greșit: Dintr-o clasă abstractă nu s-ar putea folosi nimic, fiind neinstanțiabilă
Corect: Nu se pot crea obiecte, dar se pot declara pointeri și referințe la clasa abstractă — exact prin ele se realizează polimorfismul cu colecții eterogene de obiecte derivate
Greșit: Supraîncărcarea operatorului + ar putea schimba și precedența acestuia
Corect: Supraîncărcarea schimbă doar semnificația operatorului pentru tipul nou; precedența, asociativitatea și aritatea rămân cele definite de limbaj

Test — 6 întrebări ca la examen

1. Relația corectă dintre clasă și obiect este:
  1. obiectul este șablonul, clasa este exemplarul creat după el
  2. clasa este tipul de date, obiectul este o instanță a acestui tip
  3. clasa și obiectul sunt sinonime în C++
  4. obiectul conține definiția metodelor, clasa doar datele
Vezi răspunsul
clasa este tipul de date, obiectul este o instanță a acestui tip. Clasa definește tipul (o singură dată), iar obiectele sunt variabile concrete de acel tip, fiecare cu valorile proprii ale atributelor. Prima variantă inversează exact rolurile — cea mai comodă greșeală când termenii sunt învățați mecanic.
2. O clasă C++ are definit doar constructorul C(int x). Care declarație produce eroare de compilare?
  1. C a(5);
  2. C b = C(7);
  3. C c;
  4. C* p = new C(3);
Vezi răspunsul
C c;. Definirea unui constructor cu parametri suprimă constructorul implicit generat automat, deci C c; nu mai are ce constructor să apeleze. Celelalte trei forme transmit toate un argument int, deci folosesc constructorul existent — indiferent că apar ca inițializare directă, prin copiere sau prin new.
3. Atributele unei clase sunt declarate private pentru că:
  1. programul rulează mai repede cu date private
  2. modificarea lor se poate face doar controlat, prin metode publice care validează valorile
  3. membrii privați ocupă mai puțină memorie
  4. altfel clasa nu poate avea constructori
Vezi răspunsul
modificarea lor se poate face doar controlat, prin metode publice care validează valorile. Acesta este miezul încapsulării: starea obiectului rămâne validă pentru că orice modificare trece printr-un setter care poate refuza valori greșite. Variantele cu viteză și memorie sună tehnic, dar modificatorii de acces sunt verificați la compilare și nu influențează nici performanța, nici dimensiunea obiectului.
4. La crearea unui obiect al clasei Elev, derivată din Persoana, ordinea de execuție a constructorilor este:
  1. doar constructorul clasei Elev, cei ai bazei nu se execută
  2. constructorul clasei Elev, apoi constructorul clasei Persoana
  3. constructorul clasei Persoana, apoi constructorul clasei Elev
  4. ordinea depinde de modificatorul de acces al moștenirii
Vezi răspunsul
constructorul clasei Persoana, apoi constructorul clasei Elev. Obiectul se construiește de la fundație: întâi partea moștenită (constructorul bazei Persoana), apoi partea proprie (constructorul derivatei Elev); destructorii se execută exact invers. Varianta a doua inversează ordinea — capcana standard; modificatorul moștenirii (public/private) nu schimbă niciodată această ordine.
5. Fie clasa Persoana cu metoda virtual void afisare() și clasa Elev care o redefinește. După Persoana* p = new Elev; apelul p->afisare() execută:
  1. varianta din Persoana, pentru că pointerul are tipul Persoana*
  2. varianta din Elev, tipul real al obiectului fiind stabilit la execuție
  3. ambele variante, întâi cea din bază, apoi cea din derivată
  4. niciuna — codul nu se compilează
Vezi răspunsul
varianta din Elev, tipul real al obiectului fiind stabilit la execuție. Metoda fiind virtuală, legarea este dinamică: se execută varianta corespunzătoare obiectului real, adică Elev. Prima variantă ar fi corectă doar FĂRĂ virtual, când decizia se ia static după tipul pointerului — distincția cu/fără virtual este exact ce testează întrebarea; metodele nu se apelează niciodată în lanț automat.
6. Despre o clasă care conține declarația virtual double arie() = 0; este adevărat că:
  1. se pot crea obiecte din ea, dar metoda arie nu poate fi apelată
  2. este clasă abstractă: nu se pot crea obiecte, dar se pot declara pointeri la ea
  3. derivatele ei devin automat și ele abstracte, indiferent ce implementează
  4. declarația = 0 înseamnă că metoda returnează întotdeauna zero
Vezi răspunsul
este clasă abstractă: nu se pot crea obiecte, dar se pot declara pointeri la ea. Funcția virtuală pură face clasa abstractă: instanțierea este interzisă, dar pointerii și referințele la ea sunt permise și stau la baza polimorfismului. Derivatele scapă de abstractizare implementând funcția pură, iar = 0 este doar sintaxa purității — nu are nicio legătură cu valoarea returnată.
Deschide varianta interactivă — cu AI care îți explică
← FișiereBaze de date – noțiuni fundamentale →
BiologieChimieEconomieFilosofieFizicăGeografieInformatică și TICIstorieLogică și argumentareMatematicăPsihologieLimba și literatura română