Differences

This shows you the differences between two versions of the page.

Link to this comparison view

poo-is-ab:laboratoare:03 [2024/10/25 10:56]
razvan.cristea0106 [Introducere]
poo-is-ab:laboratoare:03 [2025/10/17 18:18] (current)
razvan.cristea0106 [Programarea Orientată pe Obiecte (POO)]
Line 1: Line 1:
-===== Laborator 03 - Particularitățile clasei ​=====+===== Laborator 03 - Inițiere în POO =====
  
 **Autor: Răzvan Cristea** **Autor: Răzvan Cristea**
Line 7: Line 7:
 Studentul va fi capabil la finalul acestui laborator să: Studentul va fi capabil la finalul acestui laborator să:
  
-  * lucreze cu clase care includ membri de tip pointer, gestionând corect alocarea ​și eliberarea memoriei +  * înțeleagă diferențele fundamentale dintre o structură (struct) ​și o clasă (class) în C++ 
-  * recunoască și să utilizeze membri constanți, garantând imutabilitatea acestora +  * definească corect o clasă și să explice conceptul acesteia 
-  * folosească ​atribute ​și funcții ​staticecare aparțin clasei ​în ansamblu și nu instanțelor individuale +  * înțeleagă ce este un obiect și să folosească ​obiectele în programe C++ 
-  * implementeze destructorul pentru a asigura eliberarea resurselor la distrugerea obiectelor +  * creeze ​și să implementeze ​funcții ​membre pentru o clasă, înțelegând rolul acestora în manipularea datelor 
-  * implementeze constructorul de copiere și operatorul de atribuire, gestionând corect copierea profundă conținutului obiectelor+  * scrie un program complet în C++ care să conțină o clasă, respectând principiile POO
  
 ==== Introducere ==== ==== Introducere ====
  
-În cadrul laboratorului anterior ne-am axat pe scrierea corectă a unei clase respectând ​**principiul încapsulării datelor**. În acest laborator atenția ne este îndreptată tot asupra modului în care clasă este scrisă în așfel încât să respecte principiile **OOP**dar vom introduce noi tipuri de membri ​și de asemenea vom prezenta câteva din funcțiile membre care sunt **specifice** clasei.+Până acum, erați obișnuiți să vă organizați codul folosind funcții, fiecare subprogram având un rol bine definit. Paradigma ​**Orientată Obiect (OO)** presupune că știți deja cum se scrie funcție, însă diferențconstă în modul în care va fi structurat codul. Pe parcursul acestui laborator, vom observa ​și înțelege cum funcționează această organizare.
  
-Pentru ​înțelege despre ce vom vorbi pe parcursul laboratorului propunem ca șexemplu clasa **Conifer**, căreia îi vom adăuga noutățile rând pe rând. De asemenea, pentru această clasă vom pune la dispoziție **constructori** și accesori ​de tip **get** și **set** pentru fiecare membru.+De-a lungul anului întâi ​ați "​simulat"​ paradigma ​**OO** prin folosirea **structurii**. Vom vedea însă câteva diferențe semnificative între ce știți deja și ce veți învăța în cadrul laboratorului curent. 
 + 
 +==== Cuvântul cheie struct ==== 
 + 
 +**Structura** este un tip de date special ​**definit** de către programator. Prin intermediul ei programatorul poate stoca sub **același nume** mai multe proprietăți de care are nevoie atunci când lucrează la o aplicație. Pentru a crea o structura de obicei se asociază obiecte din viața reală în cod. 
 + 
 +Să urmărim cu atenție exemplul de cod de mai jos unde am alcătuit structura ​**Avion**.
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class Conifer+struct Avion
 { {
- double pret+    int numarLocuri
- float ​inaltime;+    int anFabricatie;​ 
 +    ​float capacitateRezervor;​ 
 +};
  
-public:+void afisareAvion(const Avion& avion) 
 +
 +    std::cout << "​Numarul de locuri din avion este: " << avion.numarLocuri << '​\n';​ 
 +    std::cout << "Anul de fabricatie al avionului este: " << avion.anFabricatie << '​\n';​ 
 +    std::cout << "​Capacitatea rezervorului avionului este: " << avion.capacitateRezervor << " litri\n";​ 
 +}
  
- Conifer(); +int main() 
- Conifer(const double& pret, const float& inaltime);+
 +    Avion avion// in limbajul C era eroare de compilare daca nu foloseam typedef la definirea structurii 
 +    /* struct Avion avion// ar fi fost declaratia corecta pentru o variabila de tip Avion in C */
  
- double getPret() const+    avion.numarLocuri = 230
- float getInaltime() const;+    ​avion.anFabricatie = 2000; 
 +    avion.capacitateRezervor = 755.5f;
  
- void setPret(const double& pret); +    afisareAvion(avion); 
- void setInaltime(const float& inaltime)+ 
-};+    return 0
 +}
 </​code>​ </​code>​
  
-Iar implementările metodelor le vom regăsi în fișierul **"​Conifer.cpp"** după cum urmează.+Am creat o funcție auxiliară, care primește ca parametru o **referință constantă** de tip **Avion**, pentru a afișa datele parametrului **avion**. Am transmis parametrul prin **referință** pentru a evita copierea inutilă a variabilei avion din funcția main. Referința este **constantă**,​ deoarece nu dorim să modificăm câmpurile avionului în funcția de afișare.
  
-<code cpp> +<note important>Se poate observa deja o diferență între modul de utilizare a structurii în limbajele C și C++Compilatorul de C++ recunoaște structura **Avion** ca un tip de date definit de către programator,​ permițând crearea de instanțe ale acesteia fără a fi necesară utilizarea cuvântului cheie **struct** în declarație.</​note>​
-#include "​Conifer.h"+
  
-Conifer::​Conifer() +În limbajul C pentru a putea avea programul identic cu cel de mai sus trebuie să procedăm astfel.
-+
- pret = 0.0; +
- inaltime = 0.0f; +
-}+
  
-Conifer::​Conifer(const double& pret, const float& inaltime) +<code c
-+#include <cstdio>
- this->pret = pret; +
- this->inaltime = inaltime; +
-}+
  
-double Conifer::​getPret() const+typedef struct Avion Avion; 
 + 
 +struct Avion
 { {
- return pret+    int numarLocuri;​ 
-}+    int anFabricatie;​ 
 +    float capacitateRezervor
 +};
  
-float Conifer::​getInaltime(const+void afisareAvion(const ​Avion* const avion)
 { {
- return inaltime;+    printf("​Numarul de locuri din avion este: %d\n", avion->​numarLocuri);​ 
 +    printf("​Anul de fabricatie al avionului este: %d\n", avion->​anFabricatie);​ 
 +    printf("​Capacitatea rezervorului avionului este: %.2f litri\n",​ avion->​capacitateRezervor);
 } }
  
-void Conifer::​setPret(const double& pret)+int main()
 { {
- if (pret <= 0) +    Avion avion;
-+
- return; +
- }+
  
- this->​pret ​pret+    avion.numarLocuri ​230
-}+    ​avion.anFabricatie = 2000; 
 +    avion.capacitateRezervor = 755.5f;
  
-void Conifer::​setInaltime(const floatinaltime) +    afisareAvion(&avion);
-+
- if (inaltime <= 0) +
-+
- return; +
- }+
  
- this->​inaltime = inaltime;+    return 0;
 } }
 </​code>​ </​code>​
  
-Până în acest punct am făcut doar o scurtă recapitulare a ceea ce am învățat ​în laboratorul precedent.+De acum înainte când vom discuta despre structuri ne vom referi **doar** la cele din limbajul **C++**. Variabilele **numarLocuri**,​ **anFabricatie** și **capacitateRezervor** poartă denumirea de **câmpuri** sau **membri**, deoarece se găsesc în interiorul declarației structurii **Avion**. Prin intermediul operatorului **"​."​** ​am avut acces **direct** la acești membri pe care am putut să îi modificăm și mai apoi i-am folosit la afișarea avionului ​în funcția **main**
  
-==== Membri ​de tip pointer ====+Până aici nu este nimic nou față ​de ce știați deja de la disciplinele **PCLP** și respectiv **PA**. Cu toate acestea care este diferența fundamentală între structura din C și cea din C++? Răspunsul îl vom afla mai târziu după ce vom înțelege ce este **Programarea Orientată Obiect**.
  
-Să presupunem că suntem administratorii unui site care aparține unei firme care se ocupă de comercializarea coniferelor. Firma respectivă are mai multe tipuri de conifere care sunt caracterizate prin denumire, spre exemplu: brad, pin, molid, zadă, ienupăr, etc. După cum se poate observa denumirea fiecărui tip de conifer variază. Noi ca și administratori ai site-ului ar trebui să concepem un mecanism prin care să putem stoca în baza de date aceste denumiri indiferent de varietatea lor.+==== Programarea Orientată pe Obiecte (POO) ====
  
-Dacă ne uităm acum mai atent la denumirile enumerate ​anterior ​putem observa că din punct de vedere al programării acestea nu sunt altceva decât ​șiruri de caractere ​care au lungimi diferitePentru ​putea accepta orice fel de denumire de conifer în cod ar trebui ca în clasa noastră să avem un membru în care putem stoca șiruri de caractere.+**Programarea Orientată Obiect**, tradusă din englezescul **Object-Oriented Programming (OOP)**, este un nou mod de a scrie și a organiza codul sursă, practic o nouă paradigmă de programare așa cum am mai menționat ​anterior. **POO** oferă o modalitate mai intuitivă și mai naturală ​de a învăța programarea,​ deoarece reflectă într-o anumită măsură modul în care percepem lumea realăÎn **POO**, conceptele din viața de zi cu zi, cum ar fi obiectele și relațiile dintre ele, sunt transpuse ​în cod prin **clase** și **obiecte**. Astfel, programatorii pot modela entități reale, fiecare având **atribute** (**proprietăți/​câmpuri/​membri**) și **comportamente** (**funcții membre/​metode**),​ aceasta fiind o modalitate prin care programarea a devenit mai ușor de înțeles și de gestionat. Această abordare modulară simplifică dezvoltarea de aplicații complexe și încurajează reutilizarea codului.
  
-Să adăugăm în clasa noastră un atribut care ne va ajuta să stocăm denumirea pentru fiecare conifer în parte.+=== Principiile POO ===
  
-<code cpp> +Ca orice paradigmă de programare aceasta trebuie să aibă și un set de reguli care trebuiesc aplicate în momentul în care se dorește dezvoltarea unei aplicații **OOP**. Aceste principii fundamentale sunt 4 la număr și le vom discuta pe fiecare separat.
-#include <​iostream>​+
  
-class Conifer +Cele 4 principii ale POO sunt:
-+
- double pret; +
- float inaltime; +
- char* denumire; // membru de tip pointer la char+
  
-public:+  - **Abstractizarea** 
 +  - **Încapsularea** 
 +  - **Moștenirea** 
 +  - **Polimorfismul**
  
- Conifer();​ +== Abstractizarea ==
- Conifer(const double& pret, const float& inaltime, const char* denumire); // se aduga un nou parametru la acest constructor+
  
- double getPret() const; +Fiind primul principiu ​**OOP** pe care îl învățăm trebuie să știm că acesta este utilizat cu scopul de a defini **clase** care să imite obiectele din realitate. Practic **abstractizarea** este procesul de transpunere în codul sursă a caracteristicilor unui obiect din realitate.
- float getInaltime() const; +
- chargetDenumire() const;+
  
- void setPret(const double& pret); +<note important>​În **POO** subiectul principal despre care vom discuta este **clasa**, deoarece pe baza ei vom putea aplica **toate principiile** enunțate mai sus și vom putea înțelege care este de fapt scopul pentru care există acest stil de scriere a codului sursă.</​note>​ 
- void setInaltime(const ​floatinaltime)+ 
- void setDenumire(const char* denumire);+În C++ o clasă se declară folosind cuvântul cheie (keyword**class**. Să urmărim exemplul de mai jos. 
 + 
 +<code cpp> 
 +class Brad 
 +
 +    int vechime
 +    float inaltime; 
 +    ​double pret;
 }; };
 </​code>​ </​code>​
  
-Am adăugat un atribut ​**denumire**, de tip **pointer la char**, pentru a putea gestiona denumirile coniferelor într-un mod mai flexibil. Am optat pentru un pointer deoarece intenționăm să **alocăm memoria dinamic**. De ce ar trebui să alegem această abordare? Pentru că lungimea fiecărei denumiri ​**poate varia**, iar folosirea ​unei dimensiuni fixe ar putea fi ori insuficientă, ori ar putea risipi spațiu.+Se poate observa că am declarat o clasă denumită **Brad**, care conține ca și **proprietăți (membri)** 3 variabile și anume: **vechime**, **inaltime** și **pret**. Astfel am aplicat primul principul ​**OOP** prin crearea ​unei clase care imită un obiect din realitate. La prima vedere putem spune că structura și clasa sunt similare diferența fiind doar keyword-ul folosit însă vom vedea mai târziu care este diferența clară între structură și clasă în limbajul C++.
  
-<note important>​Alocarea dinamică ne oferă flexibilitatea de a rezerva **exact** câtă memorie este necesară, **fără** a irosi resurse sau risca să depășim limitele. Acest lucru ne permite să gestionăm eficient memoria și să adaptăm programul la nevoile reale.</​note>​+== Încapsularea ==
  
-=== Shallow Copy ===+Încapsularea se referă la modul în care o clasă își protejează datele, ascunzându-le de mediul exterior. Cu alte cuvinte nu putem modifica membrii unei clase direct, deoarece avem nevoie de acces la aceștia.
  
-Copia superficială (**Shallow Copy**) apare atunci când un obiect este copiat fără ​se crea duplicatul complet al datelor sale. În cazul în care obiectul conține pointeri, o copie superficială va reține **doar** adresa de memorie stocată ​în **pointeri**, nu și datele efective la care aceștia pointează. Astfelatât obiectul original, cât și copia vor partaja aceeași zonă de memorie pentru acele date.+Pentru ​a înțelege ce presupune încapsularea trebuie să menționăm mai întâi ce sunt **specificatorii de acces** ai unei clase în C++. **Specificatorii de acces**, în C++sunt cuvinte cheie prin intermediul cărora se poate decide dacă membrul unei clase poate fi vizibil în exteriorul acesteia sau nu.
  
-Acest tip de copiere poate duce la probleme neașteptate,​ cum ar fi modificarea datelor originale prin intermediul copiei sau dezalocarea incorectă a memoriei, deoarece două obiecte diferite vor încerca să gestioneze aceeași resursă. De aceeaîn astfel de situații este recomandată utilizarea copiei profunde (**Deep Copy**)care creează o duplicare completă a datelor, inclusiv a zonelor de memorie la care pointerii fac referire.+În limbajul C++ există 3 specificatori ​de acces și anume: **private**, **protected**, **public**.
  
-Pentru a înțelege conceptul ​de copie superficială vom urmări exemplul de cod de mai jos.+**Specificatorul ​de acces private**
  
-<code cpp> +Acest specificator presupune ca membrii (câmpurile) unei clase să **nu** poată fi accesați direct din exterior, singura care poate avea acces direct la propriile câmpuri fiind clasa însăși.
-#include <​iostream>​ +
-#include <​cstring>​+
  
-int main() +**Specificatorul de acces protected**
-+
- charsir1 = new char[strlen("​Ionescu"​) + 1]; +
- strcpy(sir1,​ "​Ionescu"​);​+
  
- charsir2 = sir1; // shallow copy (sir2 partajeaza aceeasi zona de memorie ca sir1) +Similar cu specificatorul ​**private**, datele marcate cu **protected** nu pot fi accesate direct din exteriorul clasei. Cu toate acestea, spre deosebire ​de specificatorul **private**,​ clasele derivate (moștenitoareau acces la aceste date.
- sir2[0] = '​J';​+
  
- std::cout << "​Primul sir de caractere are valoarea: " << sir1 << '​\n';​ +**Specificatorul ​de acces public**
- std::cout << "Al doilea sir de caractere are valoarea: " << sir2 << '​\n';​+
  
-        delete[] sir1; +Acest specificator anunță faptul că datele membre ale unei clase sunt publice și oricine din exterior le poate accesa și modifica fără probleme.
-        /*delete[] sir2; // incorect deoarece deja am eliberat zona de memorie cu o linie mai sus*/+
  
- return 0; +<note warning>​Membrii unei clase sunt **by default** privați. Acest lucru înseamnă că **nu** avem acces la ei direct pentru a-i putea modifica.</note>
-+
-</code>+
  
-Pentru a fi și mai clar ce s-a întâmplat pe linia ''​charsir2 = sir1;''​ vom ilustra grafic după cum urmează.+Vom rescrie declarația clasei ​**Brad** punând explicit specificatorul private chiar dacă știm deja că cei 3 membri sunt privați din start conform a ceea ce am menționat anterior.
  
-{{ :​poo-is-ab:​laboratoare:shallow_copy.jpg?​direct&​600 |}}+<code cpp> 
 +class Brad 
 +{ 
 +private// specificatorul de acces private, nu era necesar sa il punem deoarece membrii deja erau privati
  
-După cum se poate observa în imaginea de mai sus variabila **sir1** conține adresa **primului** element din vectorul de caractere (adresa caracterului '​I'​). Când am facut atribuirea ''​char* sir2 = sir1;''​ am făcut ca pointerul **sir2** să pointeze către adresa de pointare a lui **sir1**. Astfel orice modificare pe care o facem asupra lui **sir1** se va răsfrânge asupra lui **sir2**, valabil și invers după cum se poate observa pe linia unde am schimbat valoarea primului caracter prin intermediul lui **sir2**. Cu alte cuvinte pointerii **sir1** și respectiv **sir2** partajează aceeași zonă de memorie. +    int vechime
- +    float inaltime; 
-<​note>​Exemplul de mai sus ilustrează conceptul de shallow copy. Este important de reținut faptul că acest tip de copiere apare în principal atunci când lucrăm cu **pointeri**. În cazul tipurilor de date care **nu** sunt pointeri, copierea se face **bit cu bit**, ceea ce înseamnă că se realizează automat o copie profundă. Compilatorul se ocupă de acest proces fără a fi nevoie de intervenția noastră.</note> +    double pret; 
- +}; 
-=== Deep Copy === +</code>
- +
-Spre deosebire de **shallow copy**, unde **doar adresele de memorie** sunt copiate, în **deep copy** se creează o **replică completă** a datelor. Deși pare mai costisitor și mai greu de realizat, acest tip de copiere este soluția cea mai bună atunci când ne dorim ca originalul și copia sa să nu partajeze aceeași zonă de memorie. Practic prin **copiere în profunzime** realizăm o clonă pentru original în adevăratul sens al cuvântului.+
  
-Pentru a înțelege cum putem realiza ​**deep copy** vom modifica exemplul anterior în așa fel încât variabilele **sir1** și **sir2** să fie independente,​ adică să pointeze către adrese diferite în **memoria heap**.+Dacă dorim să instanțiem clasa **Brad** va trebui ​să declarăm un **obiect** de tipul acesteia după cum vom putea observa în codul de mai jos.
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
-#include <​cstring>​ 
  
-int main()+class Brad
 { {
- char* sir1 = new char[strlen("​Ionescu"​) + 1]; +private:
- strcpy(sir1,​ "​Ionescu"​);​+
  
- char* sir2 = new char[strlen(sir1) + 1]+    int vechime
- strcpy(sir2,​ sir1);+    float inaltime; 
 +    double pret; 
 +};
  
- sir2[0] = '​J';​ +int main() 
- +{ 
- std::cout << "​Primul sir de caractere are valoarea: " << sir1 << '​\n';​ +    Brad brad; // obiect ​de tipul clasei Brad (instanta a clasei Brad)
- std::cout << "Al doilea sir de caractere are valoarea: " << sir2 << '​\n';​ +
- +
- delete[] sir1; +
- delete[] sir2; // corect deoarece sir1 si sir2 pointeaza catre doua blocuri ​de memorie diferite+
  
- return 0;+    ​return 0;
 } }
 </​code>​ </​code>​
  
-Iar ca și ilustrare grafică lucrurile arată în felul următor.+<note important>​Un obiect nu este altceva decât un reprezentant al clasei, care respectă structura acesteia, adică deține toate proprietățile pe care clasa le pune la dispoziție. Pentru a înțelege puteți face analogia cu oamenii. Toți oamenii au plămâni, aceasta fiind o caracteristică comună speciei umane. Diferențele pot să apară la starea de sănătate a plămânilor,​ deoarece, în general, persoanele mai tinere au plămâni mai sănătoși comparativ cu cei ai persoanelor vârstnice. Starea de sănătate a plămânilor poate varia și în funcție de factori precum stilul de viață, expunerea la poluare sau fumat.</​note>​
  
-{{ :​poo-is-ab:​laboratoare:​deep_copy.jpg?​direct&​600 |}} +Dacă vom dori să accesăm membrii clasei ​**Brad** direct din funcția **main** vom primi o eroare ​de compilare, deoarece ​membrii sunt **inaccesibili**.
- +
-Pașii pe care i-am aplicat pentru a realiza **deep copy** au fost alocarea unui nou spațiu de memorie pentru **sir2** și copierea caracterelor lui **sir1** în **sir2** cu ajutorul funcției **strcpy**. Astfel atunci când am schimbat valoarea primului caracter din **sir2** modificarea nu s-a propagat și pe **sir1**, deoarece acum cei doi pointeri **nu** mai partajează aceeași zonă de memorie. +
- +
-<note important>​În ​**POO** se inteționează ca atunci când se fac copieri acestea să fie în **profunzime (deep copy)**. Copierea superficială poate fi folosită doar dacă suntem siguri că **nu** avem nevoie ​de **clone reale** ci doar de unele **false** pentru a realiza diverse modificări care să se răsfrângă asupra originalului.</​note>​ +
- +
-==== ==== +
- +
-Având acum cele două noțiuni clarificate putem reveni la clasa **Conifer** pentru a putea implementa constructorii și metodele accesor. Vom face **deep copy**, deoarece ​vrem ca fiecare obiect să aibă pentru membrul său **denumire** câte o zonă separată în memorie pe care să nu o partajeze cu nimeni altcineva. +
- +
-Să urmărim în cod implementarea metodelor știind că avem un membru de tip pointer în clasă. Vom prezenta doar implementarile metodelor care conțin atributul **denumire**,​ deoarece restul vor rămâne neschimbate.+
  
 <code cpp> <code cpp>
-Conifer::​Conifer() +#include <​iostream>​
-+
- pret = 0.0; +
- inaltime = 0.0f; +
- denumire = nullptr; +
-}+
  
-Conifer::​Conifer(const double& pret, const float& inaltime, const char* denumire)+class Brad
 { {
- this->​pret = pret+    int vechime
- this->​inaltime = inaltime;+    ​float ​inaltime
 +    double pret; 
 +};
  
- if (denumire != nullptr) +int main()
-+
- this->​denumire = new char[strlen(denumire) + 1]; +
- strcpy(this->​denumire,​ denumire);​ +
-+
- else +
-+
- this->​denumire = nullptr; +
-+
-+
- +
-char* Conifer::​getDenumire() const+
 { {
- return denumire; +    Brad brad;
-}+
  
-void Conifer::​setDenumire(const char* denumire) +    brad.vechime ​2; // eroare de compilare, membrul vechime este privat 
-+    ​brad.inaltime = 5.25f// gresit, membrul inaltime este inaccesibil ​ 
- if (denumire ​== nullptr || strlen(denumire) < 3) +    ​brad.pret = 299.99; // membrul pret este marcat ca private
- +
- return+
- }+
  
- if (this->​denumire != nullptr) +    return 0;
-+
- delete[] this->​denumire;​ +
-+
- +
- this->​denumire = new char[strlen(denumire) + 1]; +
- strcpy(this->​denumire,​ denumire);+
 } }
 </​code>​ </​code>​
  
-Se poate observa că atât în constructorul cu toți parametrii cât și în setter-ul pentru atributul **denumire** am făcut extra validări ​pentru a preveni atribuirea de denumiri eronate care în realitate nu ar avea sens. +O primă soluție pentru a putea face programul ​să nu mai aibă eroare de compilare ar fi ca membrii clasei ​**Brad** să fie publici.
- +
-==== Destructorul ==== +
- +
-Așa cum îi spune și numele această **metodă** se ocupă de distrugerea obiectelor atunci când aceastora li se încheie durata de viață în program. La fel ca și **constructorii**,​ **destructorul** are o serie de trături ce îl fac ușor de recunoscut într-o clasă după cum urmează: +
-  - nu are **tip returnat** nici măcar **void** +
-  - denumirea destructorului este **aceeeași** cu a clasei din care face parte, dar pentru a putea fi diferențiat de constructori se pune înaintea lui operatorul **"​~"​** +
-  - este **unic** în clasă +
-  - **nu** are parametri +
- +
-<note important>​La fel ca și **constructorul default (cel fără parametri)**, **destructorul** dacă nu este declarat și implementat,​ compilatorul va genera el un **destructor default**.</​note>​ +
- +
-În continuare vom prezenta cum putem declara destructorul pentru clasa Conifer.+
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class Conifer+class Brad
 { {
- double pret; +public: // in acest moment cei 3 membri ai clasei sunt vizibili in exteriorul acesteia
- float inaltime; +
- char* denumire;+
  
-public:+    int vechime; 
 +    float inaltime; 
 +    double pret; 
 +};
  
- Conifer(); +int main() 
- Conifer(const double& pret, const float& inaltime, const char* denumire); +{ 
- ~Conifer()// destructorul clasei Conifer+    Brad brad;
  
- double getPret() const+    brad.vechime = 2
- float getInaltime() const+    ​brad.inaltime = 5.25f
- char* getDenumire() const;+    ​brad.pret = 299.99;
  
- void setPret(const double& pret); +    return 0
- void setInaltime(const float& inaltime);​ +}
- void setDenumire(const char* denumire)+
-};+
 </​code>​ </​code>​
  
-<note warning>Dacă o clasă conține ​**cel puțin** un membru de tip **pointer** care este **alocat dinamic**, varianta de destructor generată de compilator **nu** va funcționa corect, deoarece **nu** se va ocupa de **dezalocarea memoriei**, aceasta fiind datoria noastră. Prin urmare atunci când avem pointeri ​ca și membri ai clasei pe care i-am alocat dinamic avem datoria ​**să declarăm șsă implementăm destructorul** pentru ​**elibera memoria**.</​note>​+<note warning>Acest lucru însă **nu** este o variantă indicată, deoarece **am încălcat principiul încapsulării datelor** și în plus, acum oricine poate modifica din exterior membrii clasei. După cum se poate observa, clasa **Brad** scrisă în această manieră se comportă exact ca o structură. Diferența între cele 2 (struct ​și class) constă în faptul că într-o structură totul este marcat cu **public** by default în timp ce o clasă are membrii by default marcațcu **private**. Vom menționa mai târziu cum putem respecta principiul încaspulării datelor ​pentru ​clasa **Brad** când vom discuta despre funcții membre (metode).</​note>​
  
-După cum putem vedea în exemplul de mai jos destructorul clasei **Conifer** se va ocupa de eliberarea memoriei pentru atributul **denumire**.+== Moștenirea ==
  
-<code cpp> +Moștenirea ​(**Inheritance**permite unei clase să **preia** proprietățile și comportamentele unei alte clase, oferind astfel posibilitatea reutilizării codului. În același timp, **clasa derivată ​(clasa moștenitoare)** poate adăuga funcționalități specifice pe care clasa de bază nu le are, extinzând astfel comportamentul acesteia și adaptându-l la nevoile proprii. ​
-Conifer::​~Conifer() +
-+
- if (denumire != nullptr) +
-+
- delete[] denumire; +
-+
-+
-</​code>​+
  
-<​note ​warning>Destructorul,​ fie că vorbim de cel generat de compilator sau de cel implementat de programator**nu** se apelează **explicit** ​de către programatorAcest lucru va fi realizat ​de către ​**compilator în mod automat** atunci când durata de viață a obiectului se încheie.</​note>​+<​note>​Ca și exemplu putem lua omul care este un mamifer. Omul la fel ca toate mamiferele are plămânidar spre deosebire ​de alte animale din categoria mamiferelor acesta are o postură bipedă. Vom detalia mai multe despre moștenire peste câteva laboratoare și tot atunci vom vorbi mai mult despre specificatorul ​de acces **protected**.</​note>​
  
-==== Constructorul de copiere ====+== Polimorfismul ​==
  
-Constructorul ​de copiere ​(**Copy Constructor**) este o metodă specială a clasei, deoarece cu ajutorul lui putem copia conținutul unui obiect existent într-unul nouAcest constructor este **unic** în clasă și respectă toate caracteristicile unui constructor obișnuit. Dacă **nu** este declarat ​și implementat ​de către programator,​ compilatorul se va ocupa el de generarea unui **copy constructor default**.+Cuvântul **polimorfism** provine din limba greacă, ​de la **polys ​(multe)** și **morphos (formă)**Polimorfismul în **POO** este întâlnit ​în două forme și anume: **early polymorphism** sau **compile time polymorphism** care apare atunci când realizăm un procedeu numit **supraîncărcare (overloading)** și **run time polymorphism** care apare când este realizat procedeul ​de **suprascriere (overriding)**. Despre conceptul de overriding vom discuta peste câteva laboratoare.
  
-<note warning>**Constructorul de copiere** generat de tre compilator face **shallow copy**, iar dacă în clasă avem **cel puțin** un membru de tip **pointer alocat dinamic**, programatorul va trebui să ofere o implementare pentru acest tip de constructor care să facă o **copiere profundă (deep copy)**.</​note>​+În C++ conceptul de **overloading** este aplicat pe funcții. Acest lucru presupune faptul ​că putem avea mai multe funcții cu **același nume**, dar acestea să **difere** prin **numărul și tipul parametrilor**.
  
-În codul de mai jos vom putea observa modul în care putem declara constructorul de copiere pentru clasa **Conifer**.+Să urmărim exemplul de cod de mai jos.
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class Conifer+void interschimbare(int* a, int* b)
 { {
- double pret; +    if (a == nullptr || b == nullptr) 
- float inaltime+    { 
- char* denumire;+        return
 +    }
  
-public:+    int auxiliar = *a; 
 +    *a = *b; 
 +    *b = auxiliar; 
 +}
  
- Conifer(); +void interschimbare(intaintb) 
- Conifer(const doublepretconst floatinaltime, const char* denumire); +
- Conifer(const Conifer&​ conifer)// constructorul de copiere pentru clasa Conifer +    int auxiliar = a
- ~Conifer();+    a = b
 +    b = auxiliar; 
 +}
  
- double getPret() const; +int main() 
- float getInaltime() const+
- char* getDenumire() const;+    int x = 22
 +    int y = 8;
  
- void setPret(const doublepret); +    interschimbare(&x, &y);
- void setInaltime(const floatinaltime); +
- void setDenumire(const char* denumire);​ +
-}; +
-</​code>​+
  
-Iar mai jos am implementat acest constructor în așa fel încât să realizeze **deep copy**.+    std::cout << "​Valoarea lui x este: " << x << '​\n';​ 
 +    std::cout << "​Valoarea lui y este: " << y << '​\n';​
  
-<code cpp> +    interschimbare(x, y); 
-Conifer::​Conifer(const Conifer&​ conifer+ 
-{ +    ​std::​cout << "​Valoarea lui x este: " << x << '​\n'​
- this->​pret = conifer.pret+    ​std::​cout << "​Valoarea lui y este: " << y << '​\n'​;
- this->​inaltime = conifer.inaltime;+
  
- if (conifer.denumire != nullptr) +    return 0;
-+
- this->​denumire = new char[strlen(conifer.denumire) + 1]; +
- strcpy(this->​denumire,​ conifer.denumire);​ +
-+
- else +
-+
- this->​denumire = nullptr; +
- }+
 } }
 </​code>​ </​code>​
  
-Astfel ​**constructorul de copiere** este acum specializat pentru crearea de **clone reale** pentru obiectele ​de tip **Conifer**.+Observăm că există două funcții numite ​**interschimbare**, care diferă doar prin tipul parametrilor. Acesta ​este un exemplu ​de **supraîncărcare a funcțiilor (function overloading)**, un concept ce demonstrează polimorfismul în C++.
  
-==== Operatorul ​de atribuire ====+<note important>​Supraîncărcarea **nu** modifică comportamentul general al funcției, ci doar permite funcțiilor cu același nume să gestioneze diferite tipuri ​de date, așa cum se vede în exemplul anterior.</​note>​
  
-Operatorul de atribuire (**Operatorul ​=** sau **Operatorul de asignare**) este folosit pentru a copia conținutul unui obiect existent într-un alt **obiect existent**. În esență, operatorul de **atribuire (asignare)** și **constructorul de copiere** au un comportament similar, deoarece ambele sunt responsabile de copierea valorilor dintr-un obiect în altul. Principala diferență între aceste două **funcții membre** constă în **momentul** și **contextul** în care sunt utilizate.+=== Lucrul cu clase ===
  
-**Constructorul de copiere** este folosit atunci când un **nou** obiect este creat pe baza unui alt **obiect existent**. Pe de altă parte**operatorul ​de atribuire** este utilizat atunci când un obiect **deja existent** primește valori din alt obiect, adică obiectul în care se copiază conținutul trebuie să aibă deja alocată memorie și să fi fost inițializat anterior.+În această secțiune vom descoperi modul în care putem controla accesul la membrii unei clase. Pentru a permite accesul la membrii unei clase din exterior, este necesar să folosim funcții speciale definite în **zona publică** a clasei, cunoscute sub denumirea de **metode** sau **funcții membre**. În continuarevom prezenta câteva categorii ​de funcții membre ​care facilitează interacțiunea controlată cu membrii clasei din exteriorul acesteia.
  
-<note important>​La fel ca și în cazul **constructorului de copiere**, compilatorul va genera el o variantă de **operator de asignare** dacă programatorul nu îl declară și nu îl implementează. La fel ca în cazul **copy constructor-ului**,​ varianta generată de complilator pentru **operatorul ​=** face **shallow copy**, deci va trebui să fie implementat de către programator pentru a face **deep copy**.</​note>​+== Constructorii ==
  
-Ceea ce urmează ​fi prezentat constituie o variantă de **supraîncarcare ​(overloading)** a operatorului de atribuire. Vom discuta mai pe larg ce presupune **supraîncărcarea** operatorilor în laboratorul următor.+cum le spune și numele această categorie de metode se ocupă de **construirea ​(inițializarea) obiectelor (instanțelor clasei)**.
  
- Pentru a putea realiza ​**supraîncărcarea operatorului de asignare** trebuie să folosim cuvântul cheie **operator** urmat de simbolul operatorului (adică **"​="​**) după cum urmează în codul de mai jos.+Constructorii au o serie de trăsături care îi fac pe aceștia foarte ușor de recunoscut după cum urmează: 
 + 
 +  **Nu** au tip returnat nici măcar **void** 
 +  - Au **același nume** cu clasa din care fac parte 
 +  - Se apelează **doar** în momentul declarării unui obiect 
 +  
 +Constructorii pot fi de mai multe tipuri și anume: fără parametri și cu parametri. Să urmărim exemplul de cod de mai jos pentru clasa **Brad**.
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class Conifer+class Brad
 { {
- double pret+    int vechime
- float inaltime; +    float inaltime; 
- char* denumire;+    ​double pret;
  
 public: public:
  
- Conifer(); +    Brad() 
- Conifer(const double& pret, const float& inaltime, const char* denumire); +    { 
- Conifer(const Conifer&​ conifer)+        ​vechime = 0
- Conifer&​ operator=(const Conifer&​ conifer)// operatorul de asignare +        ​inaltime ​0.0f
- ~Conifer();​ +        pret = 0.0
- +    }
- double getPret() const; +
- float getInaltime() const; +
- char* getDenumire() const; +
- +
- void setPret(const double& ​pret)+
- void setInaltime(const float& inaltime);​ +
- void setDenumire(const char* denumire);+
 }; };
-</​code>​ 
  
-Iar implementarea acestuia se poate observa mai jos după cum urmează. +int main()
- +
-<code cpp> +
-Conifer&​ Conifer::​operator=(const Conifer&​ conifer)+
 { {
- if (this->​denumire != nullptr) +    Brad brad; // echivalent cu a scrie direct Brad brad = Brad();
-+
- delete[] this->​denumire;​ +
-+
- +
- this->​pret = conifer.pret;​ +
- this->​inaltime = conifer.inaltime;+
  
- if (conifer.denumire != nullptr) +    ​return ​0;
-+
- this->​denumire = new char[strlen(conifer.denumire) + 1]; +
- strcpy(this->​denumire,​ conifer.denumire);​ +
-+
- else +
-+
- this->​denumire = nullptr; +
-+
- +
- return ​*this;+
 } }
 </​code>​ </​code>​
  
-<note warning>​Implementarea anterioară **operatorului de asignare** are o vulnerabilitate ​și anume **nu** tratează situația ​în care obiectul este atribuit ​**sieși**. Astfel pot apărea probleme ​cu privire la eliberarea memoriei sau coruperea datelorPentru a putea avea o asignare sigură trebuie ​să facem verificarea ​de **auto-asignare** care garantează că **operatorul =** va funcționa corect ​în toate situațiile.</​note>​+Se poate observa că am definit și implementat constructorul fără parametri pentru clasa **Brad**. Constructorul fără parametri se mai numește ​și **constructor default** și dacă nu îl implementăm explicit **în cazul exemplului de mai sus** compilatorul se va ocupa el de generarea unui constructor default ​în care va popula membrii clasei ​**Brad** cu valori aleatoare. 
 + 
 +Ca să nu creștem foarte mult numărul ​de linii din clasă în practică se dorește implementarea metodelor ​în afara clasei după cum putem observa mai jos.
  
 <code cpp> <code cpp>
-Conifer&​ Conifer::​operator=(const Conifer&​ conifer)+#include <​iostream>​ 
 + 
 +class Brad // declaratia clasei se va scrie intr-un fisier cu extensia .h
 { {
- if (this == &​conifer) // verificarea de auto-asignare +    int vechime; 
- +    float inaltime
- return *this+    ​double pret;
- }+
  
- if (this->​denumire != nullptr) +public:
-+
- delete[] this->​denumire;​ +
- }+
  
- this->​pret = conifer.pret+    Brad()
- this->​inaltime = conifer.inaltime;+};
  
- if (conifer.denumire != nullptr+Brad::Brad() // aceasta bucata de cod va exista intr-un fisier cu extensia .cpp 
-+
- this->​denumire ​new char[strlen(conifer.denumire) + 1]+    ​vechime ​0; 
- strcpy(this->​denumire,​ conifer.denumire)+    inaltime = 0.0f
-+    pret = 0.0
- else +
-+ 
- this->​denumire ​nullptr; +int main() 
- }+
 +    Brad brad; // echivalent cu a scrie direct Brad brad Brad();
  
- return ​*this;+    ​return ​0;
 } }
 </​code>​ </​code>​
  
-<​note>​Este **rară** situația în care obiectul va fi atribuit ​**sieși**, dar este mai bine ca **operatorul =** să aibă această verificare de **auto-asignare** pentru ​putea evita problemele ce pot apărea la eliberarea și alocarea memoriei ​în cazul în care clasa conține **cel puțin** un membru care este **pointer**.</​note>​+<​note>​Noi în cadrul laboratoarelor vom organiza codul în **fișiere header** (fișiere care conțin extensia **"​.h"​**) și **fișiere cu extensia "​.cpp"​**. În fișierele header vom scrie declarații pentru funcțiiclase și metode din cadrul clasei ​dar **fără** a implementa nimic, deoarece acest lucru îl vom face în fisierele cu extensia ​**"​.cpp"​**.</​note>​
  
-==== Cuvântul cheie static ==== +Să parcurgem ​după cum urmează ​exemplul de cod de mai jos.
- +
-Cuvântul cheie **static** provine din limbajul C și și-a păstrat funcționalitatea în C++, dar a dobândit și noi utilizări datorită paradigmei **Orientate Obiect**. În ambele limbaje de programare, **static** are rolul de a controla durata de viață și vizibilitatea (accesibilitatea) unor variabile sau funcții. +
- +
-=== Membri statici === +
- +
-O variabilă statică are un comportament similar cu cel al unei variabile globale, în sensul că trăiește pe toată durata de execuție a programului. În C++, cuvântul cheie **static** a fost extins pentru a permite declararea membrilor statici într-o clasă. Un **membru static** aparține clasei în sine, și nu unei instanțe a acesteia, fiind partajat între toate obiectele clasei. +
- +
-<note important>​Un atribut static **nu** poate fi inițializat în cadrul constructorului,​ deoarece acesta se ocupă de inițializarea membrilor specifici obiectelor, adică membrii **non-statici**.</​note>​ +
- +
-Un câmp static în clasă poate fi util de exemplu atunci când ne dorim să numărăm câte instanțe ale clasei respective avem la un anumit moment de timp în program. Pentru a evidenția utilitatea unui membru static vom crea un contor de instanțe pentru clasa **Conifer** ​după cum urmează.+
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class Conifer+class Brad
 { {
- double pret// membru non-static +    int vechime
- float inaltime; ​// membru non-static +    float inaltime; 
- char* denumire; // membru non-static +    ​double pret;
- +
- static int numarConifere// membru static -> in acest exemplu este folosit pe post de contor de instante pentru clasa Conifer+
  
 public: public:
  
- Conifer();​ +    ​// Brad();
- Conifer(const double& pret, const float& inaltime, const char* denumire);​ +
- Conifer(const Conifer&​ conifer); +
- Conifer&​ operator=(const Conifer&​ conifer); ​// operatorul de asignare +
- ~Conifer();+
  
- double getPret(const+    Brad(const float& inaltime); // constructor cu un parametru
- float getInaltime(const; +
- char* getDenumire() const;+
  
- void setPret(const ​doublepret); +    Brad(const ​intvechime, ​const float& inaltimeconst double& pret); // constructor cu trei parametri
- void setInaltime(const float& inaltime); +
- void setDenumire(const char* denumire);+
 }; };
 +
 +int main()
 +{
 +    Brad brad; // eroare de compilare, nu exista constructor default
 +
 +    return 0;
 +}
 </​code>​ </​code>​
  
-<note warning>​Membrii statici ai unei clase se inițializează ​**întotdeauna** în afara claseide preferat ​în fișierul ​**"​.cpp"​** unde există și implementările celorlalte metode, și obligatoriu ​**nu** în constructori.</​note>​+Se poate observa că am comentat constructorul fără parametri și am definit alți doi constructori pentru clasa **Brad**, lucru care pune în lumină ​**polimorfismul**, însă ne alegem cu o eroare de compilare atunci când declarăm un obiect de tip **Brad**.
  
-Inițializarea ​și prelucrarea unui membru static se poate face după cum urmează.+<note warning>​Dacă programatorul definește alți constructori compilatorul **nu** îl va mai genera pe cel fără parametri, deci programatorul va avea datoria să declare ​și să implemeteze constructorul default pentru a putea instanția obiecte cu ajutorul lui, sau mai are alternativa să se folosească **doar** de versiunile de constructori care au cel puțin un parametru.</​note>​
  
-<code cpp> +Prin urmare ori de câte ori definim constructori cu parametri trebuie să avem în vedere că tot noi va trebui să definim și să implementăm constructorul default. În continuare vom implementa constructorul cu un parametru după cum urmează.
-int Conifer::​numarConifere = 0;+
  
-Conifer::Conifer()+<code cpp> 
 +Brad::Brad(const float& inaltime)
 { {
- pret 0.0; +    vechime ​= 0; 
- inaltime = 0.0f+    inaltime = inaltime// eroare de compilare 
- denumire ​nullptr; +    ​pret ​0.0;
- numarConifere++;+
 } }
 +</​code>​
  
-Conifer::​Conifer(const double& pretconst float& ​inaltime, const chardenumire) +La prima vedere suntem mirați de faptul că avem o eroare de compilaredeoarece în mod normal ne-am aștepta ca variabila **inaltime** din partea stângă a egalului să fie de fapt membrul **inaltime** din zona privată a clasei **Brad**, însă din păcate nu este vorba despre membrul din zona privată ci despre însuși parametrul constructorului. Practic noi am încercat să îi atribuim parametrului propria valoare, dar compilatorul ne-a salvat prin atenționarea că nu putem modifica valoarea constantă a unui parametru.
-+
- this->​pret = pret; +
- this->inaltime ​= inaltime;+
  
- if (denumire != nullptr) +În cazul de față este vorba despre o situație de ambiguitate în care compilatorul nu știe că de fapt noi vrem să stocăm valoarea parametrului constructorului în membrul clasei **Brad**. Cum putem totuși să rezolvăm această problemă pentru a ne putea îndeplini obiectivul și anume acela de a stoca valoarea trimisă ca parametru în câmpul clasei?
-+
- this->​denumire = new char[strlen(denumire) + 1]; +
- strcpy(this->​denumire,​ denumire);​ +
-+
- else +
-+
- this->​denumire = nullptr; +
- }+
  
- numarConifere++;​ +**Cuvântul cheie this**
-}+
  
-Conifer::​Conifer(const Conifer&​ conifer) +Pentru a putea soluționa problema de mai sus trebuie să introducem un nou keyword specific **POO** pe care îl vom întâlni în aproape toate limbajele de programare care suportă paradigma **OO** și anume cuvântul cheie **this**.
-+
- this->pret = conifer.pret; +
- this->​inaltime = conifer.inaltime;​+
  
- if (conifer.denumire != nullptr) +În C++ **this** este un **pointer** pe care **toate funcțiile membre** îl primesc ca parametru pe **prima poziție**Deși poate părea neclar în C++ acest pointer **nu** este vizibil**dar asta nu înseamnă că nu există**, în lista de parametri a metodei, dar în limbajul Python acesta este prezent și poartă denumirea de **self** și obligatoriu trebuie pus de către **programator** pe **prima poziție** în lista de parametri a fiecărei **funcții membre** dintr-o clasă.
-+
- this->​denumire = new char[strlen(conifer.denumire) ​1]; +
- strcpy(this->​denumireconifer.denumire);​ +
-+
- else +
-+
- this->​denumire = nullptr; +
- }+
  
- numarConifere++;​ +Pointerul **this** indică **adresa obiectului curent** și are rolul de a **rezolva** problemele de ambiguitate între **membrii clasei** și **parametrii metodelor**. Acesta este utilizat pentru a **diferenția** în mod clar variabilele locale de câmpurile obiectului, permițând atribuirea **corectă** a valorilor parametrilor metodelor **membrilor interni** ai obiectului.
-}+
  
-Conifer::​~Conifer() +Să soluționăm problema de mai sus utilizând pointerul **this**.
-+
- if (denumire != nullptr) +
-+
- delete[] denumire; +
- }+
  
- numarConifere--;+<code cpp> 
 +// Brad::​Brad(Brad* this, const float& inaltime) ​-> asa arata de fapt aceasta metoda (functie membra) la compilare 
 +Brad::​Brad(const float& inaltime) 
 +
 +    vechime = 0; // nu este nevoie de this deoarece compilatorul stie ca este vorba despre campul vechime al obiectului curent 
 +    this->​inaltime = inaltime; 
 +    pret = 0.0// aceeasi explicatie ca la vechime
 } }
 </​code>​ </​code>​
  
-Deși membrul static **numarConifere** este privat noi ne propunem cumva să avem accest la el. Soluția este să implementăm un **getter** pentru acesta.+În acest moment compilatorul știe foarte clar ce vrem să facem și anume că ne dorim să stocăm în membrul ​**inaltime** valoarea parametrului.
  
-=== Funcții statice === +Așadar acum putem să implementăm și al treilea constructor al clasei ​**Brad** și să îl utilizăm fără probleme după cum urmează.
- +
-La fel ca variabila statică, **funcția statică** există pe întreaga durată de viață a programului. În **POO** o **funcție statică** la nivel de clasă este foarte asemănătoare cu o **metodă**. Totuși o metodă primește ca parametru pe prima poziție în lista de parametri **pointerul this**. În C++ chiar dacă acest parametru **nu** este vizibil în lista de parametri, la compilare există (acest procedeu ne este ascuns, dar în spate compilatorul adaugă acest parametru la toate **funcțiile membre**). +
- +
-<note important>​**O **funcție statică** la nivel de clasă **nu** primește **pointerul this** ca parametru. Îi spunem **funcție statică** și **nu** metodă statică din acest motiv.**</​note>​ +
- +
-În exemplul de mai jos am declarat un **getter** pentru a obține numărul de conifere.+
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class Conifer+class Brad
 { {
- double pret+    int vechime
- float inaltime; +    float inaltime; 
- char* denumire; +    ​double pret;
- +
- static int numarConifere;+
  
 public: public:
  
- Conifer();​ +    Brad(); // constructor default
- Conifer(const double& pret, const float& inaltime, const char* denumire);​ +
- Conifer(const Conifer&​ conifer); +
- Conifer&​ operator=(const Conifer&​ conifer); +
- ~Conifer();+
  
- double getPret(const+    Brad(const float& inaltime); // constructor cu un parametru
- float getInaltime(const; +
- char* getDenumire() const;+
  
- void setPret(const ​doublepret); +    Brad(const ​intvechime, ​const float& inaltimeconst double& pret); // constructor cu 3 parametri
- void setInaltime(const float& inaltime); +
- void setDenumire(const char* denumire);​ +
- +
- static int getNumarConifere(); // functie statica+
 }; };
-</​code>​ 
  
-Iar implementarea o putem observa mai jos.+// Brad::​Brad(Brad* this) -> asa arata la compilare 
 +Brad::​Brad() 
 +
 +    vechime = 0; // nu este nevoie de this, dar nu este gresit nici daca scriem this->​vechime = 0; 
 +    inaltime = 0.0f; 
 +    pret = 0.0; 
 +}
  
-<code cpp+// Brad::​Brad(Brad* this, const float& inaltime) -asa arata la compilare 
-int Conifer::getNumarConifere()+Brad::Brad(const float& inaltime)
 { {
- return numarConifere;+    vechime = 0; 
 +    this->​inaltime = inaltime; 
 +    pret = 0.0;
 } }
-</​code>​ 
  
-Această ​**funcție statică** poate fi apelată fie prin **numele clasei** fie prin intermediul unui **obiect** după cum urmează în codul sursă de mai jos. +// Brad::​Brad(Bradthis, const int& vechime, const float& inaltime, const double& pret) -> asa arata la compilare 
- +Brad::​Brad(const int& vechime, const float& inaltime, const double& pret) 
-<code cpp+
-#include "​Conifer.h"​+    this->​pret = pret; 
 +    this->​vechime = vechime; 
 +    this->inaltime = inaltime; 
 +}
  
 int main() int main()
 { {
- std::cout << "​Numarul de conifere este: " << Conifer::​getNumarConifere() << '​\n'​; +    Brad brad; // se apeleaza constructorul default 
- +    Brad bradut(4.75f); // se apeleaza constructorul cu un parametru ​(echivalent cu a scrie Brad bradut = Brad(4.75f);) 
- Conifer c1; +    Brad b(2, 2.25f, 399.99); // se apeleaza constructorul cu un parametru (echivalent cu a scrie Brad b Brad(2, 2.25f, 399.99);)
- +
- std::cout << "​Numarul de conifere este: " << Conifer::​getNumarConifere() << '​\n'​+
- std::cout << "​Numarul de conifere este: " << c1.getNumarConifere() << '​\n'​; +
- +
- Conifer c2 c1; +
- +
- std::cout << "​Numarul de conifere este: " << c2.getNumarConifere() << '​\n'​;+
  
- return 0;+    ​return 0;
 } }
 </​code>​ </​code>​
  
-==== ====+<note important>​Este bine să utilizați pointerul **this** ori de câte ori vreți să vă referiți la câmpurile obiectului curent. În cazul constructorului default nu este necesar să folosiți **this** deoarece compilatorul știe că este vorba despre obiectul curent. Problema apare atunci când în interiorul metodelor definim variabile sau parametri care **coincid ca și denumire cu câmpurile clasei**, deoarece folosirea lui **this** devine obligatorie pentru a evita conflictele de nume.</​note>​
  
-Astfel am putut observa și înțelege cum putem folosi **membrii statici** ​și **fucțiile statice** la nivel de clasă.+== Funcțiile membre de tip get și set ==
  
-==== Membri constanți ====+Aceste metode sunt speciale, deoarece ele oferă un mod controlat de acces la membrii clasei. Menționăm că **nu** este obligatoriu să existe pentru fiecare membru al clasei o pereche de **accesori** de tip **get-set**,​ acest lucru depinzând de cerințele aplicației șde modul de implementare. **Getterii** permit accesul la valorile câmpurilor unui obiect, în timp ce **setterii** oferă posibilitatea de a modifica valorile acestora, asigurând astfel un control mai strict și securizat asupra proprietăților unui obiect.
  
-Evident într-o clasă pot exista și **membri constanți** pentru a permite ​**distincția** între obiecte. De obicei un membru constant are un scop bine definit spre exemplu poate fi un cod unic de identificare a obiectului. În lumea reală o persoană este identificată unic după CNP-ul (codul numeric personal) acesteia care știm foarte bine că nu mai poate fi modificat sau atribuit altei persoane. +Denumirea acestor metode trebuie să înceapă cu prefixul ​**get** sau **set** și să conțină numele câmpului asupra căruia acționează. Să ne îndreptăm atenția spre următoarele exemple ​de declarări pentru getteri și pentru setteri ​în clasa **Brad**.
- +
-În cazul exemplului nostru putem să asigurăm **unicitatea** membrilor constanți folosindu-ne ​de **membrul static**. Să urmărim modul în care am declarat ​în clasa **Conifer** un **atribut constant** denumit **codUnic**.+
  
 <code cpp> <code cpp>
-#include <​iostream>​ +class Brad
- +
-class Conifer+
 { {
- double pret+    int vechime
- float inaltime; +    float inaltime; 
- char* denumire; +    ​double pret;
- +
- const int codUnic; // membru constant +
- static int numarConifere;+
  
 public: public:
  
- Conifer(); +    Brad(); 
- Conifer(const ​doublepret, const float& inaltime, const char* denumire);​ +    Brad(const ​intvechime, const float& inaltime, const doublepret);
- Conifer(const Coniferconifer); +
- Conifer&​ operator=(const Conifer&​ conifer); +
- ~Conifer();+
  
- double getPret() const; +    int getVechime() const; ​// metoda la compilare arata asa -> int getVechime(const Brad* this); 
- float getInaltime() const+    float getInaltime() const; 
- char* getDenumire() const; +    ​double getPret() const;
- const int getCodUnic() const; ​// accesor pentru membrul constant+
  
- void setPret(const ​doublepret); +    ​void setVechime(const ​intvechime); // metoda la compilare arata asa -> void setVechime(Brad* this, const intvechime); 
- void setInaltime(const ​floatinaltime); +    void setInaltime(const ​float& inaltime); 
- void setDenumire(const ​char* denumire); +    void setPret(const double& pret);
- +
- static int getNumarConifere();+
 }; };
 </​code>​ </​code>​
  
-<note warning>​Un atribut constant **nu** poate fi inițializat ​în **interiorul constructorilor**,​ deoarece **obiectul deja există** ​și în plus, compilatorul a alocat deja o valoare random ​pentru ​**membrul constant** ce **nu** mai poate fi modificată. Prin urmare **inițializarea membrilor constanți** se face **obligatoriu** în **lista de inițializare ​constructorului**. Această listă de inițializare îi spune compilatorului că înainte de crearea efectivă a obiectului are de atribuit valori pentru anumiți membri.</​note>​ +Iar în exemplul de mai jos am implementat aceste metode ​și le-am testat ​pentru a putea evidenția funcționalitatea lor.
- +
-În exemplul de cod de mai jos am pus în lumină modul în care este inițializat un membru constant.+
  
 <code cpp> <code cpp>
-Conifer::Conifer() : codUnic(++numarConifere)+Brad::Brad()
 { {
- pret 0.0; +    vechime ​= 0; 
- inaltime = 0.0f; +    inaltime = 0.0f; 
- denumire ​nullptr;+    ​pret ​0.0;
 } }
  
-Conifer::Conifer(const ​doublepret, const float& inaltime, const char* denumire) : codUnic(++numarConifere)+Brad::Brad(const ​intvechime, const float& inaltime, const double& pret)
 { {
- this->​pret = pret; +    ​this->​pret = pret; 
- this->​inaltime = inaltime;+    ​this->​vechime = vechime; 
 +    ​this->​inaltime = inaltime; 
 +}
  
- if (denumire != nullptr+int Brad::​getVechime() const // acest const este adresat pointerului this (continutul de la adresa spre care pointeaza nu poate fi modificat
-+
- this->denumire ​new char[strlen(denumire) + 1]; +    // ​this->vechime ​5// imposibil din cauza const-ului din semnatura functiei 
- strcpy(this->​denumire,​ denumire); + 
- } +    ​return vechime; // era corect si daca scriam return ​this->vechime;
- else +
-+
- this->denumire = nullptr; +
- }+
 } }
  
-Conifer::Conifer(const ​Conifer&​ conifer) : codUnic(++numarConifere)+float Brad::getInaltime(const
 { {
- this->​pret = conifer.pret+    return inaltime
- this->​inaltime ​conifer.inaltime;+
 + 
 +void Brad::​setVechime(const int& vechime) 
 +
 +    if (vechime <0) // verificam daca valoarea primita ca parametru este una valida 
 +    { 
 +        return; 
 +    }
  
- if (conifer.denumire != nullptr) +    ​this->vechime ​vechime// trebuie folosit ​this deoarece parametrul are acelasi nume ca si campul clasei
-+
- this->denumire ​new char[strlen(conifer.denumire) + 1]; +
- strcpy(this->​denumire,​ conifer.denumire);​ +
-+
- else +
-+
- this->​denumire = nullptr; +
- }+
 } }
  
-Conifer::~Conifer()+void Brad::setInaltime(const float& inaltime)
 { {
- if (denumire !nullptr+    ​if (inaltime <0
-+    
- delete[] denumire+        ​return
- }+    }
  
- /* numarConifere--; // nu facem acest lucru deoarece putem avea id-uri duplicate ceea ce ar strica principiul unicitatii*/​+    this->​inaltime = inaltime;
 } }
  
-const int Conifer::​getCodUnic() const+int main()
 { {
- return ​codUnic;+    Brad brad; // se apeleaza constructorul default 
 + 
 +    brad.setVechime(3);​ 
 +    brad.setInaltime(5.5f);​ 
 + 
 +    std::cout << "​Vechimea bradului este: " << brad.getVechime() << " ani\n";​ 
 +    std::cout << "​Inaltimea bradului este: " << brad.getInaltime() << " metri\n";​ 
 + 
 +    ​return ​0;
 } }
 </​code>​ </​code>​
  
-Implementarea completă a clasei ​**Conifer** poate fi descărcată de {{:​poo-is-ab:​laboratoare:​implementare.zip|aici}}.+==== ==== 
 + 
 +Pentru a vă familiariza cu noul mod de scrie codul, declarați și implementați un constructor cu doi parametri la alegere și în plus, implementați metodele accesor de tip **get** și **set** pentru câmpul **pret** și apoi testați ce ați implementat în funcția **main**.
  
 ==== Concluzii ==== ==== Concluzii ====
  
-În cadrul acestui ​laborator, am aprofundat conceptul de metode specifice unei clase (constructor de copiere, operator de asignare și destructor) și am clarificat ​diferențele fundamentale dintre ​funcții ​și metode. Funcțiile sunt entități independentecare **pot** exista în afara unei clase, în timp ce metodele ​sunt funcții membre asociate direct cu o clasă șcare operează asupra instanțelor acesteia. +În acest laborator, am explorat în detaliu ​diferențele fundamentale dintre ​clase și structuri, subliniind faptul că singura deosebire constă în specificatorul de acces implicit: la structurimembrii sunt publici, iar la clase sunt privați. Totodată, am învățat cum să creăm obiecte ​și cum să le gestionăm proprietățile folosind constructori,​ precum și accesori prin intermediul getter-ilor și al setter-ilorAcest lucru ne oferă un control mai fin asupra datelor ​și ne permite o manipulare clară ​și sigură a obiectelor ​într-un program care folosește conceptele ​**OOP**. ​Astfel, ​am dobândit o înțelegere mai bună a principiilor fundamentale ale **Programării Orientate Obiect**, principii pe care le vom aplica pe întregul parcurs ​al semestrului.
- +
-Un alt aspect important pe care l-am explorat este utilizarea membrilor **statici** ​și **constanți** pentru a implementa mecanisme de generare automată a codurilor unice. Am demonstrat ​cum membrii **statici** permit partajarea unei valori comune între toate instanțele unei clase, iar membrii **constanți** oferă garanția că anumite valori rămân neschimbate pe toată durata de viață a obiectelorAceste mecanisme sunt esențiale pentru gestionarea eficientă a resurselor ​și pentru menținerea consistenței ​și integrității datelor ​într-o aplicație ​**OOP**. +
- +
-De asemenea ​am putut vedea în linii mici ce înseamnă **supraîncărcarea** unui operator și ce presupune aceastamai multe detalii ​vom da în laboratorul următor când vom studia și alți operatori nu doar pe cel de asignare.+
poo-is-ab/laboratoare/03.1729842963.txt.gz · Last modified: 2024/10/25 10:56 by razvan.cristea0106
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0