Differences

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

Link to this comparison view

poo-is-ab:laboratoare:04 [2025/01/12 18:48]
razvan.cristea0106 [Supraîncărcarea operatorilor]
poo-is-ab:laboratoare:04 [2025/10/18 22:44] (current)
razvan.cristea0106
Line 1: Line 1:
-===== Laborator 04 - Supraîncărcarea operatorilor ​=====+===== Laborator 04 - Particularitățile clasei ​=====
  
 **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ă:
  
-  * recunoască și să înțeleagă conceptul de supraîncărcare (overloading) +  ​* lucreze cu clase care includ membri de tip pointer, gestionând corect alocarea și eliberarea memoriei 
-  * știe când un operator trebuie supraîncărcat fie ca funcție membră fie ca funcție friend ​ +  ​* recunoască și să utilizeze membri constanți, garantând imutabilitatea acestora 
-  * supraîncarce operatorii aritmetici ​pentru ​o clasă +  * folosească atribute și funcții statice, care aparțin clasei în ansamblu și nu instanțelor individuale 
-  * supraîncarce operatorii logici ​de comparație +  * implementeze destructorul ​pentru ​a asigura eliberarea corectă a resurselor la distrugerea obiectelor 
-  * supraîncarce operatorii de flux pentru citire ​și afișare+  * implementeze constructorul ​de copiere ​și operatorul de asignare, gestionând corect copierea profundă a conținutului obiectelor
  
 ==== Introducere ==== ==== Introducere ====
  
-În acest laborator, ​ne vom concentra ​pe aprofundarea conceptului de **overloading (supraîncărcare)**, un aspect esențial al **POO**. Așa cum am introdus deja în [[poo-is-ab:​laboratoare:​02|laboratorul 2]], atunci când am discutat despre ​**polimorfism**, am înțeles că supraîncărcarea se referă la posibilitatea ​de a defini mai multe funcții cu același nume, dar cu semnături diferite. Acest mecanism se aplică atât funcțiilor libere, cât și metodelor în cadrul unei clase.+Î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 o clasă este scrisă în aș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.
  
-Este important de subliniat că supraîncărcarea nu schimbă comportamentul fundamental al unei **funcții** sau **metode**, ci oferă alternative prin care acestea pot fi apelateîn funcție de tipul și numărul parametrilor. Cu alte cuvinte, ​**funcția** sau **metoda** își păstrează scopul de bază, dar poate trata diverse scenarii sau tipuri de date fără a necesita ​**nume diferite**. Această flexibilitate contribuie la creșterea lizibilității codului și la reducerea redundanței,​ permițând programatorilor să scrie cod mai clar și mai modular.+Pentru a înțelege despre ce vom vorbi pe parcursul laboratorului propunem ca și exemplu clasa **Conifer**, căreia îi vom adăuga noutățile rând pe rând. De asemeneapentru această clasă vom pune la dispoziție **constructori** și accesori de tip **get** și **set** pentru fiecare membru.
  
-Pe parcursul acestui laborator, vom explora în detaliu cum funcționează **supraîncărcarea operatorilor** în C++ și cum poate fi utilizată eficient în cadrul claselor pentru a îmbunătăți funcționalitatea și flexibilitatea aplicațiilor la care lucrăm.+<code cpp> 
 +#include <​iostream>​
  
-==== Operatorii limbajului C++ ====+class Conifer 
 +
 +    double pret; 
 +    float inaltime;
  
-Când discutăm despre operatori în C++, primul lucru de care trebuie să ținem cont este faptul că aceștia operează asupra unor **operanzi**. În funcție de **numărul de operanzi** asupra cărora acționează,​ operatorii pot fi clasificați în trei mari categorii dupa cum urmează în descrierea de mai jos.+public:
  
-**Operatori Unari**: acești operatori au nevoie de **un singur** operand pentru a-și îndeplini funcția. Ei sunt folosiți în mod frecvent pentru operații simple precum negarea, incrementarea sau decrementarea valorii operandului. Exemple comune includ **++ (incrementare)**, **%%--%% ​(decrementare)****! (negare logică)** și **- (negare aritmetică)**.+    Conifer()
 +    Conifer(const double& pretconst float& inaltime);
  
-**Operatori Binari**: aceștia necesită **doi** operanzi și sunt cei mai utilizați operatori în programare. Acești operatori includ **adunarea ​(+)**, **scăderea ​(-)**, **înmulțirea (*)**, **împărțirea (/)**, dar și operatori logici precum **&&​ (și logic)**, **|| (sau logic)** și **operatori de comparație (==, !=, <, >)**.+    double getPret() const; 
 +    float getInaltime() const;
  
-**Operatorul Ternar**: există un **singur** operator ternar în C++, cunoscut sub numele de **operator condițional ​(?:)**. Acesta utilizează **trei** operanzi și este folosit pentru a evalua o condiție și a alege între două valori, în funcție de rezultatul acelei condiții.+    void setPret(const double& pret)
 +    void setInaltime(const float& inaltime);​ 
 +}; 
 +</​code>​
  
-În continuare ​vom prezenta un tabel cu operatorii existenți ​în limbajul C++ pentru a putea vedea atât simbolurile cât și modul de asociere (aplicare) al acestora.+Iar implementările metodelor le vom regăsi ​în fișierul **"​Conifer.cpp"​** după cum urmează.
  
-^        Categoria de operatori ​       ^       ​Simbolurile operatorilor ​            ​^ ​                Mod de asociere (aplicare) ​             ^ +<code cpp
-| **Primari** ​           | ( ), [ ], ., %%->%% |         ​stânga - dreapta ​        | +#include "​Conifer.h"​
-| **Unari** ​             | ++, %%--%%, -, !, ~, (tip), &, *, sizeof |         ​**dreapta - stânga** ​        | +
-| **Multiplicativi** ​    | *, /, %                         ​| ​        ​stânga - dreapta ​        | +
-| **Adunare, scădere** ​  | +, -                            |         ​stânga - dreapta ​        | +
-| **Deplasare (nivel bit)** | %%<<%%, %%>>​%% ​              ​| ​        ​stânga - dreapta ​        | +
-| **Relaționali** ​       | <, >, %%<=%%, %%>​=%% ​           |         ​stânga - dreapta ​        | +
-| **Testare egalitate** ​ | ==, !=                          |         ​stânga - dreapta ​        | +
-| **ȘI (nivel bit)** ​    | &                               ​| ​        ​stânga - dreapta ​        | +
-| **SAU exclusiv (nivel bit)** | %%^%%                     ​| ​        ​stânga - dreapta ​        | +
-| **SAU inclusiv (nivel bit)** | %%|%%                     ​| ​        ​stânga - dreapta ​        | +
-| **ȘI logic** ​          | %%&&​%% ​                         |         ​stânga - dreapta ​        | +
-| **SAU logic** ​         | %%||%% ​                         |         ​stânga - dreapta ​        | +
-| **Condițional (ternar)**| ?:                             ​| ​        ​stânga - dreapta ​        | +
-| **Atribuire** ​         | =, +=, -=, *=, /=, %=, %%<<​=%%,​ %%>>​=%%,​ &=, %%^=%%, %%|=%% |         ​**dreapta - stânga** ​        | +
-| **Virgulă** ​           | ,                               ​| ​        ​stânga - dreapta ​        |+
  
-<note important>​Pentru operatorii din tabelul de mai sus am ales această ordine pentru a putea stabili cu ușurință **prioritățile** fiecăruia dintre eiAstfel cea mai **mică** prioritate o are operatorul **virgulă**,​ în timp ce cea mai **mare** prioritate o au **operatorii primari**.</​note>​+Conifer::​Conifer() 
 +
 +    pret = 0.0; 
 +    inaltime = 0.0f; 
 +}
  
-==== Funcții friend ====+Conifer::​Conifer(const double& pret, const float& inaltime) 
 +
 +    this->​pret ​pret; 
 +    this->​inaltime ​inaltime; 
 +}
  
-În C++ funcțiile **friend** sunt acele funcții care au aces la zona **privată** a clasei, dar și la cea **protected**. Deși o funcție friend strică **principiul încapsulării datelor**, trebuie menționat că acest lucru este controlat. ​+double Conifer::​getPret() const 
 +
 +    return pret; 
 +}
  
-<note tip>​Pentru a înțelege mai bine rolul funcțiilor **friend**, imaginați-vă următoarea analogiefiecare dintre voi are un set de gânduri și sentimente care sunt **private**,​ **inaccesibile** celorlalți. Totuși, ca ființe sociale, avem prieteni cărora alegem să le împărtășim aceste gânduri. Deși prietenii noștri **au acces** la informații personale, acest lucru **nu** înseamnă că oricine poate avea acces la ele. Voi sunteți cei care **dețineți controlul total** asupra a ceea ce **dezvăluiți** și **cui**.</​note>​+float Conifer::​getInaltime() const 
 +
 +    return inaltime; 
 +}
  
-Pentru a declara o funcție de tip **friend**, folosim cuvântul cheie **friend**. Aceste funcții sunt declarate în interiorul clasei și au cuvântul cheie **friend** plasat **înaintea tipului de return** al funcției.+void Conifer::​setPret(const double& pret) 
 +
 +    if (pret <= 0) 
 +    { 
 +        ​return
 +    }
  
-Să urmărim exemplul de cod de mai jos unde am declarat ​și am implementat o **funcție friend** ​care afișază datele despre o persoană.+    this->​pret = pret; 
 +
 + 
 +void Conifer::​setInaltime(const float& inaltime) 
 +
 +    if (inaltime <= 0) 
 +    { 
 +        return; 
 +    } 
 + 
 +    this->​inaltime = inaltime; 
 +
 +</​code>​ 
 + 
 +Până în acest punct am făcut doar o scurtă recapitulare a ceea ce am învățat în laboratorul precedent. 
 + 
 +==== Membri de tip pointer ==== 
 + 
 +Să presupunem că suntem dezvoltatorii unui site web pentru o firmă 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 developeri ai site-ului ar trebui să concepem un mecanism prin care să putem stoca în baza de date aceste denumiri indiferent de varietatea lor. 
 + 
 +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 diferite. Pentru a 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. 
 + 
 +Să adăugăm în clasa noastră un atribut care ne va ajuta să stocăm denumirea pentru fiecare conifer în parte.
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
-#include <​cstring>​ 
  
-class Persoana+class Conifer
 { {
- int varsta+    double pret
- char* ​nume;+    float inaltime; 
 +    ​char* denumire// membru de tip pointer la char
  
 public: public:
  
- Persoana(const ​intvarsta, const char* nume); +    Conifer();​ 
- ~Persoana();+    Conifer(const ​doublepret, const float& inaltime, const char* denumire); // se aduga un nou parametru la acest constructor
  
- friend ​void afisarePersoana(const ​Persoanapersoana); // functie friend pentru afisarea datelor unei persoane+    double getPret() const; 
 +    float getInaltime() const; 
 +    char* getDenumire() const; 
 + 
 +    ​void setPret(const ​doublepret); 
 +    void setInaltime(const float& inaltime);​ 
 +    void setDenumire(const char* denumire);
 }; };
 +</​code>​
  
-Persoana::​Persoana(const int& varstaconst char* nume) +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.
-+
- this->varsta = varsta;+
  
- if (nume != nullptr) +<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>
-+
- this->nume = new char[strlen(nume) + 1]; +
- strcpy(this->​numenume); +
-+
- else +
-+
- this->nume = nullptr; +
-+
-}+
  
-Persoana::​~Persoana() +=== Shallow Copy ===
-+
- if (nume !nullptr) +
-+
- delete[] nume; +
-+
-}+
  
-void afisarePersoana(const Persoana&​ persoana+Copia superficială ​(**Shallow Copy**apare atunci când un obiect este copiat fără a 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ă. Astfel, atât obiectul original, cât și copia vor partaja aceeași zonă de memorie pentru acele date. 
-{ + 
- std::cout << "​Numele persoanei ​este: " << persoana.nume << '​\n';​ +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
- std::cout << "​Varsta persoanei este: " << persoana.varsta ​<< ​"​\n\n";​ + 
-}+Pentru a înțelege conceptul de copie superficială vom urmări exemplul de cod de mai jos. 
 + 
 +<code cpp> 
 +#​include ​<cstring> 
 +#include <​iostream>​
  
 int main() int main()
 { {
- Persoana persoana(22, "Andrei");+    char* sir1 = new char[strlen("​Ionescu"​) + 1]; 
 +    strcpy(sir1, "Ionescu");
  
- afisarePersoana(persoana);+    char* sir2 = sir1; // shallow copy (sir2 partajeaza aceeasi zona de memorie ca sir1) 
 +    sir2[0] = '​J'​;
  
- return 0;+    std::cout << "​Primul sir de caractere are valoarea: " << sir1 << '​\n';​ 
 +    std::cout << "Al doilea sir de caractere are valoarea: " << sir2 << '​\n';​ 
 + 
 +    delete[] sir1; 
 +    /*delete[] sir2; // incorect deoarece deja am eliberat zona de memorie cu o linie mai sus*/ 
 + 
 +    ​return 0;
 } }
 </​code>​ </​code>​
  
-În mod evident puteam declara ​și implementa o metodă simplă de afișare în loc să optăm pentru o funcție ​**friend**. Trebuie însă menționat faptul că este doar un exemplu didactic pentru a putea înțelege cum putem folosi funcțiile **friend** în limbajul C++.+Pentru a fi și mai clar ce s-a întâmplat pe linia ''​charsir2 = sir1;''​ vom ilustra grafic după cum urmează.
  
-<note warning>​Deși sunt declarate în interiorul clasei **funcțiile friend** se numesc **funcții** și **nu metode** datorită faptului că **nu** primesc **pointerul this** în lista de parametriCuvântul cheie **friend** se utilizează **doar** la declararea funcției pentru a înștința compilatorul că este vorba despre o **funcție** și **nu** despre o **metodă**,​ iar implementarea acesteia este **identică** cu a unei **funcții clasice din C++**.</​note>​+{{ :​poo-is-ab:​laboratoare:​shallow_copy.jpg?​direct&​600 |}}
  
-Vom folosi foarte mult acest tip de funcții după cum vom vedea în cele ce urmează la supraîncărcarea operatorilor limbajului C++.+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.
  
-==== Supraîncărcarea operatorilor ====+<​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>​
  
-În această secțiune vom învăța cum vom putea specializa diverși operatori pentru o clasă astfel încât obiectele acesteia să se comporte similar cu variabilele care sunt declarate folosind **tipuri de date primitive (int, float, char, long, double, etc.)**. Clasa cu care vom lucra este **NrComplex** în care ne dorim să punem în evidență diferite variante de operatori supraîncărcați.+=== Deep Copy ===
  
-Structura clasei ​**NrComplex** poate fi observată ​în codul de mai jos.+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**.
  
 <code cpp> <code cpp>
 +#include <​cstring>​
 #include <​iostream>​ #include <​iostream>​
  
-class NrComplex+int main()
 { {
- double real+    char* sir1 = new char[strlen("​Ionescu"​) + 1]
- double imaginar;+    ​strcpy(sir1,​ "​Ionescu"​);
  
-public:+    char* sir2 = new char[strlen(sir1) + 1]; 
 +    strcpy(sir2,​ sir1);
  
- NrComplex(const double& real = 0.0, const double& imaginar ​0.0)// constructor cu parametri cu valori implicite+    sir2[0'​J'​;
  
- double getReal() const+    std::cout << "​Primul sir de caractere are valoarea: " << sir1 << '​\n'​
- double getImaginar() const;+    ​std::​cout << "Al doilea sir de caractere are valoarea: " << sir2 << '​\n'​;
  
- void setReal(const double& real)+    delete[] sir1
- void setImaginar(const double& imaginar)+    ​delete[] sir2// corect deoarece sir1 si sir2 pointeaza catre doua blocuri de memorie diferite 
-}+ 
 +    return 0; 
 +}
 </​code>​ </​code>​
  
-Iar implementările funcțiilor membre sunt vizibile ​în codul de mai jos.+Iar ca și ilustrare grafică lucrurile arată în felul următor.
  
-<code cpp> +{{ :​poo-is-ab:​laboratoare:​deep_copy.jpg?​direct&​600 |}}
-#include "​Complex.h"+
  
-NrComplex::​NrComplex(const double& real, const double& imaginar) +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.
-+
- this->real = real; +
- this->​imaginar = imaginar; +
-}+
  
-double NrComplex::getReal() const+<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> 
 +Conifer::Conifer()
 { {
- return real;+    pret = 0.0; 
 +    inaltime = 0.0f; 
 +    denumire = nullptr;
 } }
  
-double NrComplex::getImaginar(const+Conifer::Conifer(const ​double& pret, const float& inaltime, const char* denumire)
 { {
- return imaginar;+    this->​pret = pret; 
 +    this->​inaltime = inaltime; 
 + 
 +    if (denumire != nullptr) 
 +    { 
 +        this->​denumire = new char[strlen(denumire) + 1]; 
 +        strcpy(this->​denumire,​ denumire);​ 
 +    } 
 +    else 
 +    { 
 +        this->​denumire = nullptr; 
 +    }
 } }
  
-void NrComplex::setReal(const double& real)+char* Conifer::getDenumire() const
 { {
- this->​real = real;+    return denumire;
 } }
  
-void NrComplex::setImaginar(const ​double& imaginar)+void Conifer::setDenumire(const ​char* denumire)
 { {
- this->imaginar ​imaginar;+    if (denumire == nullptr || strlen(denumire) < 3) 
 +    { 
 +        return; 
 +    } 
 + 
 +    if (this->denumire !nullptr) 
 +    { 
 +        delete[] this->​denumire;​ 
 +    } 
 + 
 +    this->​denumire = new char[strlen(denumire) + 1]; 
 +    strcpy(this->​denumire,​ denumire);
 } }
 </​code>​ </​code>​
  
-<​note>​După cum am putut observa ​în fișierul **header** ​pentru ​clasa **NrComplex** am declarat un constructor cu parametri cu **valori implicite**. Acest **constructor special** ține locul de fapt a trei constructori. Dacă **nu** vom specifica niciun parametru câmpurile vor primi valoarea 0.0 care este cea **default**. Dacă specificăm un parametru atunci **doar** partea imaginară va primi valoarea 0.0 care este cea **implicită**.</​note>​+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.
  
-<note warning>​Atunci când definim **funcții** sau **metode** care conțin parametri cu **valori implicite**,​ este esențial ca acești parametri să fie plasați **după** parametrii fără valori implicite în lista de argumente. Dacă această regulă **nu** este respectată,​ compilatorul va genera o **eroare de ambiguitate**,​ deoarece **nu** va putea să determine **corect** care dintre parametri trebuie să primească valoarea implicită și care valoare este furnizată explicit la apelul funcției. Această regulă asigură **claritatea** și **predictibilitatea** modului în care sunt procesate argumentele funcției.</​note>​+==== Destructorul ====
  
-=== Operatori supraîncărcațca funcții membre ​în clasă ​===+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ăsă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
  
-În general alegem această variantă de supraîncărcare atunci când vrem să avem acces **direct** la membrii unei clase. Astfelse pot manipula ​**direct** datele interne ale obiectului fără a fi necesare ​**metode acccesor** de tipul **getter/setter** sau mecanisme suplimentare pentru accesarea datelor private.+<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>
  
-<note important>​Operatorii **unari** sunt supraîncărcați **doar** ca **funcții membre**, deoarece aceștia operează întotdeauna asupra unui singur obiect, respectiv **obiectul curent**. ​În acest caz, obiectul curent este accesat **implicit** prin intermediul **pointerului this**, iar modificările sunt aplicate **direct** asupra sa. Această abordare oferă un control eficient asupra stării interne a obiectului, fără a necesita acces din exterior la membrii clasei.</​note>​ +În continuare vom prezenta ​cum putem declara destructorul ​pentru ​clasa Conifer.
- +
-== Supraîncărcarea operatorului ++ == +
- +
-După cum bine știm există două variante ​pentru ​acest operator și anume **forma prefixată**,​ care presupune modificarea valorii **înainte** de a trece la următoarea operație, și respectiv **forma postfixată**,​ unde valoarea este **mai întâi folosită** și **ulterior modificată**. +
- +
-Prin urmare trebuie să avem **două** metode care reprezintă cele **două** forme ale acestui **operator unar**. Putem face acest lucru folosindu-ne de **polimorfism** după cum urmează în exemplul de cod de mai jos.+
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class NrComplex+class Conifer
 { {
- double ​real+    ​double ​pret
- double imaginar;+    float inaltime; 
 +    char* denumire;
  
 public: public:
  
- NrComplex(const double& ​real = 0.0, const doubleimaginar = 0.0);+    Conifer();​ 
 +    Conifer(const double& ​pret, const floatinaltime, const char* denumire);​ 
 +    ~Conifer(); // destructorul clasei Conifer
  
- double ​getReal() const; +    ​double ​getPret() const; 
- double getImaginar() const;+    float getInaltime() const; 
 +    char* getDenumire() const;
  
- void setReal(const double& ​real); +    ​void setPret(const double& ​pret); 
- void setImaginar(const ​doubleimaginar); +    void setInaltime(const ​floatinaltime); 
- +    void setDenumire(const char* denumire);
- // supraincarcarea operatorilor ca functii membre +
- +
- NrComplex&​ operator++(); // forma prefixata +
- NrComplex operator++(int);​ // forma postfixata+
 }; };
 </​code>​ </​code>​
  
-Iar implementările corespunzătoare pentru cele două forme ale operatorului ​de incrementare le putem vedea mai jos.+<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 și să implementăm destructorul** pentru a **elibera memoria**.</​note>​ 
 + 
 +După cum putem vedea în exemplul de mai jos destructorul clasei **Conifer** se va ocupa de eliberarea memoriei pentru atributul **denumire**.
  
 <code cpp> <code cpp>
-NrComplex&​ NrComplex::operator++()+Conifer::~Conifer()
 { {
- this->​real++;​ +    if (denumire != nullptr
- this->​imaginar++;​ +    
- +        ​delete[] denumire
- return *this; +    }
-+
- +
-NrComplex NrComplex::​operator++(int+
-+
- NrComplex copie = *this+
- +
- this->​real++;​ +
- this->​imaginar++;​ +
- +
- return copie;+
 } }
 </​code>​ </​code>​
  
-<​note ​important>Se poate observa ca la **forma postfixată** avem un parametru ​de care **nu** ​ne folosim. Acel parametru este **doar** pentru a asigura ​**polimorfismul**, compilatorul făcând distincția între cele două variante de operator de incrementare.</​note>​+<​note ​warning>Destructorul,​ fie că vorbim ​de cel generat de compilator sau de cel implementat de programator, ​**nu** ​se apelează ​**explicit** de către programator. Acest lucru va fi realizat de către ​**compilator în mod automat** atunci ​când durata de viață a obiectului se încheie.</​note>​
  
-Pentru **operatorul ​de decrementare** se aplică aceleași **exact** aceeași pași, încercați să îl implementați voi pentru a putea înțelege mai bine cum funcționează conceptul de **overloading**.+==== Constructorul ​de copiere ====
  
-== Supraîncărcarea operatorului ! ==+Constructorul de copiere (**Copy Constructor**) este o metodă specială a clasei, deoarece cu ajutorul lui putem copia conținutul unui obiect existent într-unul nou. Acest 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**.
  
-Acest operator are rolul de a **nega** **expresie logică**. Deși un număr complex **nu** poate fi negat în sensul unei **negații logice**, putem interpreta negația ca **operație de conjugare**. Prin urmareutilizarea **operatorului ​de negare logică (!)** ar putea fi relevantă pentru clasa **NrComplex**,​ în sensul că am putea implementa acest operator pentru a **returna** conjugatul unui numărul complex.+<note warning>**Constructorul de copiere** generat de că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ă **copiere profundă (deep copy)**.</​note>​
  
-În continuare ​vom declara ​în clasa **NrComplex** acest operator pentru a realiza ceea ce ne dorim, și anume conjugarea unui număr complex.+În codul de mai jos vom putea observa modul în care putem declara constructorul de copiere pentru ​clasa **Conifer**.
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class NrComplex+class Conifer
 { {
- double ​real+    ​double ​pret
- double imaginar;+    float inaltime; 
 +    char* denumire;
  
 public: public:
  
- NrComplex(const double& ​real = 0.0, const doubleimaginar = 0.0);+    Conifer();​ 
 +    Conifer(const double& ​pret, const floatinaltime, const char* denumire);​ 
 +    Conifer(const Conifer&​ conifer); // constructorul de copiere pentru clasa Conifer 
 +    ~Conifer();
  
- double ​getReal() const; +    ​double ​getPret() const; 
- double getImaginar() const;+    float getInaltime() const; 
 +    char* getDenumire() const;
  
- void setReal(const double& ​real); +    ​void setPret(const double& ​pret); 
- void setImaginar(const ​doubleimaginar); +    void setInaltime(const ​floatinaltime); 
- +    void setDenumire(const ​char* denumire);
- // supraincarcarea operatorilor ca functii membre +
- +
- NrComplex operator!(const; // operatorul de negare logica (in cazul acestei clase va conjuga un numar complex) +
- +
- NrComplex&​ operator++();​ +
- NrComplex operator++(int);+
 }; };
 </​code>​ </​code>​
  
-Iar implementarea acestui operator se poate observa ​mai jos.+Iar mai jos vom implementa acest constructor în așa fel încât să realizeze **deep copy**.
  
 <code cpp> <code cpp>
-NrComplex NrComplex::operator!(const+Conifer::Conifer(const ​Conifer&​ conifer)
 { {
- NrComplex conjugat = *this; +    ​this->pret = conifer.pret
- +    this->​inaltime = conifer.inaltime;
- conjugat.imaginar = -conjugat.imaginar;+
  
- return conjugat;+    if (conifer.denumire != nullptr) 
 +    { 
 +        this->​denumire = new char[strlen(conifer.denumire) + 1]; 
 +        strcpy(this->​denumire,​ conifer.denumire);​ 
 +    } 
 +    else 
 +    { 
 +        this->​denumire = nullptr; 
 +    }
 } }
 </​code>​ </​code>​
  
-<note warning>​Operatorul de negare logică ​**nu** trebuie să modifice ​**this-ul**, acesta a fost și motivul ​pentru ​care am returnat o **copie modificată** în loc să modificăm ​**direct** obiectul curent.</​note>​+Astfel ​**constructorul de copiere** este acum specializat pentru crearea de **clone reale** pentru ​obiectele de tip **Conifer**.
  
-== Supraîncărcarea operatorilor ​== și != ==+==== Operatorul de asignare ====
  
-**Operatorul ​%%==%%** este folosit pentru a testa egaliatetea dintre doi operanzi, deci prin urmare trebuie să returneze o valoare de adevăr (**true** sau **false**)Îl supraîncârcăm ca funcție membră, deoarece avem deja un parametru existent, ​și anume **pointerul this**, la care mai adăugăm ​un alt parametru care reprezintă **obiectul cu care facem comparația**.+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 similardeoarece ambii sunt responsabili 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.
  
-Același lucru putem spune și despre ​**operatorul %%!=%%**, numai că el face exact **opusul** a ceea ce face operatorul ​de testare a egalității între doi operanziadică verifică dacă valorile celor doi termeni sunt **diferite**.+**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.
  
-Vom declara după cum urmează ​aceste două metode ​în clasa **NrComplex**.+<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>​ 
 + 
 +Ceea ce urmează ​a 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. 
 + 
 + ​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.
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class NrComplex+class Conifer
 { {
- double ​real+    ​double ​pret
- double imaginar;+    float inaltime; 
 +    char* denumire;
  
 public: public:
  
- NrComplex(const double& ​real = 0.0, const doubleimaginar ​0.0);+    Conifer();​ 
 +    Conifer(const double& ​pret, const floatinaltime, const char* denumire);​ 
 +    Conifer(const Conifer&​ conifer); 
 +    Conifer&​ operator=(const Conifer&​ conifer); // operatorul de asignare 
 +    ~Conifer();
  
- double ​getReal() const; +    ​double ​getPret() const; 
- double getImaginar() const;+    float getInaltime() const; 
 +    char* getDenumire() const;
  
- void setReal(const double& ​real); +    ​void setPret(const double& ​pret); 
- void setImaginar(const ​doubleimaginar);+    void setInaltime(const ​floatinaltime); 
 +    void setDenumire(const char* denumire);​ 
 +}; 
 +</​code>​
  
- // supraincarcarea operatorilor functii membre+Iar implementarea acestuia se poate observa mai jos după cum urmează.
  
- NrComplex ​operator!(const;+<code cpp> 
 +Conifer&​ Conifer::operator=(const ​Conifer&​ conifer) 
 +
 +    if (this->​denumire != nullptr) 
 +    { 
 +        delete[] this->​denumire; 
 +    }
  
- NrComplex&​ operator++()+    this->​pret = conifer.pret
- NrComplex operator++(int);+    ​this->​inaltime = conifer.inaltime;
  
- bool operator==(const NrComplex&​ zconst// operatorul de testare a egalitatii intre doua numere complexe +    if (conifer.denumire !nullptr) 
- bool operator!=(const NrComplex&​ zconst// operatorul de testare a diferentei intre doua numere complexe +    { 
-};+        this->​denumire ​new char[strlen(conifer.denumire+ 1]
 +        ​strcpy(this->​denumire,​ conifer.denumire); 
 +    } 
 +    else 
 +    { 
 +        this->​denumire = nullptr; 
 +    } 
 + 
 +    return *this; 
 +}
 </​code>​ </​code>​
  
-Iar implementările pentru cei doi operatori le putem observa ​în codul de mai jos.+<note warning>​Implementarea anterioară a **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 datelor. Pentru 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>​
  
 <code cpp> <code cpp>
-bool NrComplex::operator==(const ​NrComplexzconst+Conifer&​ Conifer::​operator=(const ​Coniferconifer)
 { {
- return ​this->​real ​== z.real && this->​imaginar == z.imaginar+    if (this == &conifer) // verificarea de auto-asignare 
-}+    { 
 +        return *this; 
 +    }
  
-bool NrComplex::​operator!=(const NrComplex&​ zconst +    if (this->​denumire ​!= nullptr
-+    
- return ​this->real !z.real || this->imaginar ​!= z.imaginar;+        ​delete[] ​this->denumire; 
 +    } 
 + 
 +    this->​pret ​conifer.pret; 
 +    ​this->inaltime = conifer.inaltime;​ 
 + 
 +    if (conifer.denumire ​!= nullptr) 
 +    { 
 +        this->​denumire = new char[strlen(conifer.denumire) + 1]; 
 +        strcpy(this->​denumire,​ conifer.denumire);​ 
 +    } 
 +    else 
 +    { 
 +        this->​denumire = nullptr; 
 +    } 
 + 
 +    return *this;
 } }
 </​code>​ </​code>​
  
-== Supraîncărcarea operatorului += ==+<​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 a 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>​
  
-Acest operator este o **variantă prescurtată** pentru adunarea a două variabile de același tip. În cazul a două numere complexe vom prezenta în codul de mai jos cum putem declara acest operator în fișierul **header**.+==== Cuvântul cheie static ====
  
-<code cpp> +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.
-#include <​iostream>​+
  
-class NrComplex +=== Membri statici ===
-+
- double real; +
- double imaginar;+
  
-public:+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.
  
- NrComplex(const double& real = 0.0const double& imaginar = 0.0);+<note important>​Un atribut static **nu** poate fi inițializat în cadrul constructorului,​ deoarece acesta se ocupă de inițializarea membrilor specifici obiecteloradică membrii **non-statici**.</​note>​
  
- double getReal() const; +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ă.
- double getImaginar() const;+
  
- void setReal(const double& real); +<code cpp> 
- void setImaginar(const double& imaginar);+#include <​iostream>​
  
- // supraincarcarea operatorilor ca functii membre+class Conifer 
 +
 +    double pret; // membru non-static 
 +    float inaltime; // membru non-static 
 +    char* denumire; // membru non-static
  
- NrComplex operator!() const;+    static int numarConifere// membru static -> in acest exemplu este folosit pe post de contor de instante pentru clasa Conifer
  
- NrComplex&​ operator+=(const NrComplex&​ z); // operatorul compus pentru adunarea a doua numere complexe+public:
  
- NrComplexoperator++(); +    Conifer();​ 
- NrComplex ​operator++(int);+    Conifer(const doublepret, const float& inaltime, const char* denumire);​ 
 +    Conifer(const Conifer&​ conifer); 
 +    ​Conifer& ​operator=(const Conifer&​ conifer); // operatorul de asignare 
 +    ~Conifer();
  
- bool operator==(const ​NrComplex&​ z) const;  +    double getPret(const
- bool operator!=(const ​NrComplexz) const;+    float getInaltime() const; 
 +    char* getDenumire() const; 
 + 
 +    void setPret(const ​doublepret)
 +    void setInaltime(const float& inaltime);​ 
 +    void setDenumire(const char* denumire);
 }; };
 </​code>​ </​code>​
  
-Iar implementarea acestui operator o vom regăsi în secvența ​de cod următoare.+<note warning>​Membrii statici ai unei clase se inițializează **întotdeauna** ​în afara clasei, ​de preferat în fișierul **"​.cpp"​** unde există și implementările celorlalte metode, și obligatoriu **nu** în constructori.</​note>​ 
 + 
 +Inițializarea și prelucrarea unui membru static se poate face după cum urmează.
  
 <code cpp> <code cpp>
-NrComplex&​ NrComplex::operator+=(const NrComplex&​ z) +int Conifer::numarConifere ​0;
-+
- this->​real += z.real; +
- this->​imaginar += z.imaginar;+
  
- return *this;+Conifer::​Conifer() 
 +
 +    pret = 0.0; 
 +    inaltime = 0.0f; 
 +    denumire = nullptr; 
 +    numarConifere++;
 } }
-</​code>​ 
  
-<note tip>În mod similarputem proceda pentru ​**operatorii ​-=, /=, *= și %=**. Totuși, este important să ne întrebăm dacă aceste operații au o **semnificație logică** în contextul numerelor complexe. Atunci când supraîncărcăm operatori, **trebuie** să ne asigurăm că operațiile respective **au sens logic** în raport cu clasa pentru care le implementăm.</​note>​+Conifer::​Conifer(const double& pret, const float& inaltimeconst chardenumire) 
 +
 +    this->​pret ​pret; 
 +    this->​inaltime ​inaltime;
  
-==== ====+    if (denumire !nullptr) 
 +    { 
 +        this->​denumire ​new char[strlen(denumire) + 1]; 
 +        strcpy(this->​denumire,​ denumire);​ 
 +    } 
 +    else 
 +    { 
 +        this->​denumire ​nullptr; 
 +    }
  
-<note important>​Nu doar **operatorii unari** sunt supraîncărcați ca funcții membre, mai putem supraîncărca și operatori precum cel de **indexare ("​[]"​)**,​ operatorul de **apel de funcție ("​()"​)**,​ operatorul **săgeată ("​%%->​%%"​)** și operatorul de **asignare ("​="​)**. Cei din urmă trebuie implementați **exclusiv** ca funcții membre în clasă. De asemenea, și operatorii de comparație pot fi supraîncărcați ca **metode** ale clasei, dar în contextul numerelor complexe aceștia **nu au sens**, deoarece în matematică **nu** există **relație de ordine** în mulțimea numerelor complexe.</​note>​+    numarConifere++;​ 
 +}
  
-=== Operatori supraîncărcați ca funcții friend ===+Conifer::​Conifer(const Conifer&​ conifer) 
 +
 +    this->​pret ​conifer.pret;​ 
 +    this->​inaltime ​conifer.inaltime;​
  
-În general, alegem să supraîncărcăm operatorii ca **funcții friend** atunci când dorim să permitem ca aceștia să primească ca parametri tipuri de date **diferite** de **clasa** pentru care facem supraîncărcareaAceastă abordare este utilă mai ales când operatorul trebuie să acceseze **membrii privați** ai claseidar implică și **alte** tipuri de date în operații.+    if (conifer.denumire != nullptr) 
 +    { 
 +        this->​denumire = new char[strlen(conifer.denumire) + 1]; 
 +        strcpy(this->​denumireconifer.denumire);​ 
 +    } 
 +    else 
 +    { 
 +        this->​denumire = nullptr; 
 +    }
  
-== Supraîncărcarea operatorului ​==+    numarConifere++; 
 +}
  
-Aici putem avea mai multe situații spre exempluadunarea a două numere complexe, adunarea dintre un număr complex și unul real și adunarea dintre un număr real și unul complex. Prin urmare avem **trei** situații dintre care două necesită un **alt tip de date** primit ca parametru. Prin urmare vom supraîncărca acest operator ca **funcție friend** punând la dispoziție cele **trei** variante despre care am discutat anterior.+Conifer::​~Conifer() 
 +
 +    if (denumire != nullptr) 
 +    { 
 +        delete[] denumire; 
 +    }
  
-Să urmărim codul de mai jos unde am pus la dispoziție cele trei forme pentru **operatorul de adunare**.+    numarConifere--;​ 
 +
 +</​code>​
  
-<code cpp> +Deși membrul static **numarConifere** este privat noi totuși ne propunem să avem cumva acces la el. Soluția cea mai simplă este să implementăm un **getter** pentru acesta, astfel încât să respectăm și **principiul încapsulării datelor**.
-#include <​iostream>​+
  
-class NrComplex +=== Funcții statice ===
-+
- double real; +
- double imaginar;+
  
-public:+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**).
  
- NrComplex(const double& real = 0.0, const double& imaginar = 0.0);+<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>​
  
- double getReal() const; +În exemplul de mai jos am declarat un **getter** pentru a obține numărul de conifere.
- double getImaginar() const;+
  
- void setReal(const double& real); +<code cpp> 
- void setImaginar(const double& imaginar);+#include <​iostream>​
  
- // supraincarcarea operatorilor ca functii membre+class Conifer 
 +
 +    double pret; 
 +    float inaltime; 
 +    char* denumire;
  
- NrComplex operator!() const;+    static int numarConifere;
  
- NrComplex&​ operator+=(const NrComplex&​ z);+public:
  
- NrComplexoperator++(); +    Conifer();​ 
- NrComplex ​operator++(int);+    Conifer(const doublepret, const float& inaltime, const char* denumire);​ 
 +    Conifer(const Conifer&​ conifer); 
 +    ​Conifer& ​operator=(const Conifer&​ conifer); 
 +    ~Conifer();
  
- bool operator==(const NrComplex&​ z) const;  +    double getPret() const; 
- bool operator!=(const ​NrComplex&​ z) const;+    float getInaltime(const
 +    char* getDenumire() const;
  
- // operatori supraincarcati ca functii friend+    void setPret(const double& pret); 
 +    void setInaltime(const float& inaltime);​ 
 +    void setDenumire(const char* denumire);
  
- friend NrComplex operator+(const NrComplex&​ z1, const NrComplex&​ z2); // operator pentru adunarea a doua numere complexe +    static int getNumarConifere(); // functie statica
- friend NrComplex operator+(const NrComplex&​ z1, const double& numar); // operator pentru adunarea unui numar complex cu un numar real +
- friend NrComplex operator+(const double& numar, const NrComplex&​ z1); // operator pentru adunarea unui numar real cu un numar complex+
 }; };
 </​code>​ </​code>​
  
-Iar implementările pentru cei trei operatori le putem observa ​în codul ce urmează.+Iar implementarea o putem observa ​mai jos.
  
 <code cpp> <code cpp>
-NrComplex operator+(const NrComplex&​ z1, const NrComplex&​ z2)+int Conifer::​getNumarConifere()
 { {
- NrComplex z;+    return numarConifere; 
 +
 +</​code>​
  
- z.real = z1.real + z2.real; +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.
- z.imaginar = z1.imaginar + z2.imaginar;​+
  
- return z; +<code cpp> 
-}+#include "​Conifer.h"​
  
-NrComplex operator+(const NrComplex&​ z1, const double& numar)+int main()
 { {
- NrComplex z;+    std::cout << "​Numarul de conifere este: " << Conifer::​getNumarConifere() << '​\n'​;
  
- z.real = z1.real + numar; +    Conifer c1;
- z.imaginar = z1.imaginar;+
  
- return z+    std::cout << "​Numarul de conifere este: " << Conifer::​getNumarConifere() << '​\n'​
-}+    ​std::​cout << "​Numarul de conifere este: " << c1.getNumarConifere() << '​\n';​
  
-NrComplex operator+(const double& numar, const NrComplex&​ z1) +    Conifer c2 = c1;
-+
- NrComplex z;+
  
- z.real = numar + z1.real; +    std::cout << "​Numarul de conifere este: " << c2.getNumarConifere() << '​\n'​;
- z.imaginar = z1.imaginar;+
  
- return ​z;+    ​return ​0;
 } }
 </​code>​ </​code>​
  
-<note tip>În manieră similară se poate proceda ​și pentru ceilalț**operatori aritmetici (-, *, /, %)**. Din nou, **trebuie** să ne punem întrebarea **dacă** operațiile ​respective au **sens logic** pentru clasa unde supraîncărcăm operatorii.</​note>​+==== ==== 
 + 
 +Astfel am putut observa ​și înțelege cum putem folosi ​**membrii statici** și **fucțiile ​statice** la nivel de clasă.
  
-== Supraîncărcarea operatorilor de flux >> ș<< ​==+==== Membri constanț====
  
-**Operatorul %%>>​%%** este folosit în C++ pentru ​citirea datelor fie de la tastatură fie dintr-un fișier, în timp ce **operatorul %%<<​%%** este folosit pentru afișarea datelor fie în consolă fie într-un fișier. În general cei doi operatori sunt supraîncărcați ca **funcții friend**, dar mai pot fi întâlniți și ca **funcții globale obișnuite ​(adică nu sunt marcate ca funcții friend)** unde operațiile de citire ​și afișare se realizează cu ajutorul **metodelor accesor** de tip **get** și **set**.+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 personalacesteia care știm foarte bine că nu mai poate fi modificat sau atribuit altei persoane.
  
-Să urmărim ​cu atenție ​modul în care declarăm acesți operatori ca **funcții friend** în clasa **NrComplex**.+Î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>​ #include <​iostream>​
-using namespace std; 
  
-class NrComplex+class Conifer
 { {
- double ​real+    ​double ​pret
- double imaginar;+    float inaltime; 
 +    char* denumire; 
 + 
 +    const int codUnic; // membru constant 
 +    static int numarConifere;
  
 public: public:
  
- NrComplex(const double& ​real = 0.0, const doubleimaginar ​0.0);+    Conifer();​ 
 +    Conifer(const double& ​pret, const floatinaltime, const char* denumire);​ 
 +    Conifer(const Conifer&​ conifer); 
 +    Conifer&​ operator=(const Conifer&​ conifer); 
 +    ~Conifer();
  
- double ​getReal() const; +    ​double ​getPret() const; 
- double getImaginar() const;+    float getInaltime() const; 
 +    char* getDenumire() const; 
 +    const int getCodUnic() const; ​// accesor pentru membrul constant
  
- void setReal(const double& ​real); +    ​void setPret(const double& ​pret); 
- void setImaginar(const ​doubleimaginar);+    void setInaltime(const ​floatinaltime);​ 
 +    void setDenumire(const char* denumire);
  
- // supraincarcarea operatorilor ca functii membre +    static ​int getNumarConifere();
- +
- NrComplex operator!() const; +
- +
- NrComplex&​ operator+=(const NrComplex&​ z); +
- +
- NrComplex&​ operator++();​ +
- NrComplex operator++(int); +
- +
- bool operator==(const NrComplex&​ z) const;  +
- bool operator!=(const NrComplex&​ z) const; +
- +
- // operatori supraincarcati ca functii friend +
- +
- friend NrComplex operator+(const NrComplex&​ z1, const NrComplex&​ z2); +
- friend NrComplex operator+(const NrComplex&​ z1, const double& numar); +
- friend NrComplex operator+(const double& numar, const NrComplex&​ z1); +
- +
- friend istream&​ operator>>​(istream&​ in, NrComplex&​ z); // operator pentru citirea unui numar complex +
- friend ostream&​ operator<<​(ostream&​ out, const NrComplex&​ z); // operator pentru afisarea unui numar complex+
 }; };
 </​code>​ </​code>​
  
-<​note>​Se poate observa că cele două **funcții friend** primesc câte un parametru special de tip **stream**. Tipurile de date **istream** și **ostream** anunță compilatorul că vrem să lucrăm cu fluxuri de date. **Istream** vine de la **input stream (flux de intrare)** în timp ce **ostream** vine de la **output stream (flux de ieșire)**. +<​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 a 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>​
-</​note>​+
  
-Să urmărim implementarea celor doi operatori de flux în exemplul de cod de mai jos.+În exemplul de cod de mai jos am pus în lumină modul în care este inițializat un membru constant.
  
 <code cpp> <code cpp>
-istream&​ operator>>​(istream&​ in, NrComplex&​ z)+Conifer::​Conifer() : codUnic(++numarConifere)
 { {
- cout << "​Introduceti partea reala a numarului complex: "+    pret = 0.0
- in >> z.real;+    ​inaltime = 0.0f; 
 +    denumire = nullptr; 
 +}
  
- cout << "​Introduceti partea imaginara a numarului complex"; +Conifer::​Conifer(const double& pret, const float& inaltime, const char* denumire) : codUnic(++numarConifere) 
- in >> ​z.imaginar;+
 +    this->pret = pret; 
 +    this->inaltime = inaltime;
  
- return in;+    if (denumire != nullptr) 
 +    { 
 +        this->​denumire = new char[strlen(denumire) + 1]; 
 +        strcpy(this->​denumire,​ denumire);​ 
 +    } 
 +    else 
 +    { 
 +        this->​denumire = nullptr; 
 +    }
 } }
  
-ostream&​ operator<<​(ostream&​ out, const NrComplexz)+Conifer::​Conifer(const ​Coniferconifer) : codUnic(++numarConifere)
 { {
- out << "​Partea reala a numarului complex este: " << z.real << '​\n'​+    this->​pret = conifer.pret
- out << "​Partea imaginara a numarului complex este: " << z.imaginar << "​\n\n"​;+    ​this->​inaltime = conifer.inaltime;
  
- return out;+    if (conifer.denumire != nullptr) 
 +    { 
 +        this->​denumire = new char[strlen(conifer.denumire) + 1]; 
 +        strcpy(this->​denumire,​ conifer.denumire);​ 
 +    } 
 +    else 
 +    { 
 +        this->​denumire = nullptr; 
 +    }
 } }
-</​code>​ 
  
-==== ====+Conifer::​~Conifer() 
 +
 +    if (denumire !nullptr) 
 +    { 
 +        delete[] denumire; 
 +    }
  
-Codul complet cu implementările operatorilor prezentați pentru clasa **NrComplex** poate fi descărcat de {{:poo-is-ab:​laboratoare:​complex_overloading.zip|aici}}.+    /numarConifere--; // nu facem acest lucru deoarece putem avea id-uri duplicate ceea ce ar strica principiul unicitatii*/​ 
 +}
  
-<note warning>În limbajul C++ **nu** este permisă supraîncărcarea următorilor operatori:​ +const int Conifer::​getCodUnic() const 
-  * de rezoluție **"::"** +
-  * de acces la membrii unei clase/​structuri **"."** +    return codUnic; 
-  * condițional/​ternar **"?:"​** +
-  * **sizeof** (returnează dimensiunea în bytes a unui tip de date) +</code> 
-  * **typeid** (folosit pentru a obține informații despre tipul dinamic al unui obiect la run time) + 
-  * **alignof** (similar cu operatorul **sizeof**) +Implementarea completă a clasei ​**Conifer** poate fi descărcată de {{:poo-is-ab:laboratoare:​implementare.zip|aici}}.
-</​note>​+
  
 ==== Concluzii ==== ==== Concluzii ====
  
-În cadrul acestui laborator, am descoperit **importanța supraîncărcării operatorilor** într-o clasă și modul în care acest proces ne permite să efectuăm diverse operații într-un mod **intuitiv**, la fel cum procedăm ​și cu **tipurile de date standard** (**int**, **float**, **char**,​...). Prin **supraîncărcarea operatorilor**,​ am reușit să **îmbunătățim lizibilitatea ​și ușurința** în utilizarea claselor personalizate,​ oferind posibilitatea de a efectua **operații** cum ar fi **adunarea**, **scăderea** sau **compararea** obiectelor de tipul definit de noi.+Î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 independente,​ care **pot** exista în afara unei clase, în timp ce metodele sunt funcții membre asociate direct cu o clasă și care operează asupra instanțelor acesteia. 
 + 
 +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 claseiar membrii ​**constanți** oferă garanția că anumite valori rămân neschimbate pe toată durata de viață a obiectelor. Aceste mecanisme sunt esențiale pentru gestionarea eficientă a resurselor ​și pentru menținerea consistenței ​și integrității datelor într-o aplicație **OOP**.
  
-Am înțeles, de asemenea, când este necesar să **supraîncărcăm** un operator ​ca **funcție membră** a unei clase și când este mai potrivit să îl supraîncărcăm ca **funcție friend**. Operatorii care au nevoie de **acces direct** la membrii clasei, cum ar fi **operatorii unari** sau **operatorul de asignare**, sunt adesea implementațca **funcții membre**. În schimb, operatorii care implică obiecte de **diferite tipuri** (de exemplu, un obiect ​al clasei noastre și un tip fundamental precum **int** sau **double**) pot fi implementați mai eficient ca **funcții friend**, pentru a permite accesul din exterior la membri privați **fără** a compromite **încapsularea datelor clasei**.+De asemenea am putut vedea în linii mici ce înseamnă **supraîncărcarea** unui operator și ce presupune aceasta, ​mai 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/04.1736700491.txt.gz · Last modified: 2025/01/12 18:48 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