Differences

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

Link to this comparison view

poo-is-ab:laboratoare:05 [2024/10/13 17:04]
razvan.cristea0106 [Moștenirea între două clase]
poo-is-ab:laboratoare:05 [2025/10/11 20:12] (current)
razvan.cristea0106 [Supraîncărcarea operatorilor]
Line 1: Line 1:
-===== Laborator 05 - Moștenire simplă =====+===== Laborator 05 - Supraîncărcarea operatorilor ​=====
  
 **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 moștenire între două clase +  * recunoască și să înțeleagă conceptul de supraîncărcare (overloading) 
-  * construiască legături între clase folosind relația de tip "​is-a"​ (relație de specializare) +  * știe când un operator trebuie supraîncărcat fie ca funcție membră fie ca funcție friend ​ 
-  * folosească membrii marcați cu protected și să înțeleagă diferențele dintre accesul public, privat și protejat în moștenire +  * supraîncarce operatorii aritmetici pentru o clasă 
-  * aplice principiile ​de reutilizare a codului prin extinderea funcționalității clasei ​de bază în clasa derivată+  * supraîncarce operatorii logici ​de comparație 
 +  * supraîncarce operatorii ​de flux pentru citire și afișare
  
 ==== Introducere ==== ==== Introducere ====
  
-În acest laborator ​vom extinde lucrul cu claseintroducând conceptul ​de **relații între clase**. Vom construi și analiza legături între două clasefolosind relațiile de tip **"​is-a"​** și **"has-a"**dar vom pune accentul mai mult pe relația de **"​is-a"​**. Relația de tip **"​is-a"​** reprezintă o formă de moștenireun principiu fundamental al **POO**, prin care o **clasă derivată (subclasă)** preia **proprietățile** și **comportamentele** ​unei **clase de bază (superclasă)**+Î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:​03|laboratorul 3]]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 numedar cu semnături diferite. Acest mecanism se aplică atât funcțiilor libere, cât și metodelor în cadrul ​unei clase.
  
-Acest tip de relație ne permite să definim ​**ierarhii de clase**, să **reutilizăm codul** și să **extindem** ​funcționalitățile claselor, oferind un model de organizare flexibil ​și scalabil**Moștenirea** funcționează similar cu cea din viața reală: ​**clasa derivată** preia proprietățile și comportamentele ​**clasei ​de bază**, dar poate adăuga și **comportamente noi** sau **modifica** pe cele existenteAstfel, **moștenirea** facilitează **extensibilitatea** ​și **întreținerea** coduluireducând duplicarea ​și oferind un mod eficient de a gestiona complexitatea în proiecte de mari dimensiuni.+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 parametrilorCu 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țeipermițând programatorilor să scrie cod mai clar și mai modular.
  
 +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.
  
 +==== Operatorii limbajului C++ ====
  
-==== Moștenirea între două clase ====+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.
  
-Așa cum am menționat anterior, ​**moștenirea** este un principiu fundamental al **POO** care permite unei **clase derivate** să preia atât **proprietățile (atributele)** cât și **comportamentele (funcțiile membre)** unei **clase părinte**Prin acest mecanism, **clasa derivată** poate să extindă funcționalitatea moștenită prin adăugarea de noi atribute șmetode ​sau prin redefinirea celor existenteScopul principal al **moștenirii** este de a promova ​**reutilizarea codului** și de a permite o **extensie** naturală a funcționalităților inițialeastfel încât să se creeze o structură mai **flexibilă** și mai **ușor de întreținut** în cadrul aplicațiilor.+**Operatori Unari**: acești operatori au nevoie de **un singur** operand pentru a-și îndeplini ​funcțiaEi sunt folosiți în mod frecvent pentru operații simple precum negarea, incrementarea ​sau decrementarea valorii operanduluiExemple comune includ ​**++ (incrementare)****%%--%% (decrementare)**, **! (negare logică)** și **- (negare aritmetică)**.
  
-Înainte de a explica moștenirea între două clase vom face o scurtă recapitulare a noțiunilor deja învățate în cadrul laboratoarelor anterioare. Pentru acest laborator propunem clasa **Locuinta** care are ca și membri ​**pret (de tip float)** și **adresa (șir de caractere alocat dinamic)**.+**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 (==, !=, <, >)**.
  
-<code cpp> +**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.
-#pragma once +
-#include <​cstring>​ +
-#include <​iostream>​+
  
-class Locuinta +Î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.
-+
- float pret; +
- char* adresa;+
  
-public:+^        Categoria de operatori ​       ^       ​Simbolurile operatorilor ​            ​^ ​                Mod de asociere (aplicare) ​             ^ 
 +| **Primari** ​           | ( ), [ ], ., %%->%% |         ​stânga - dreapta ​        | 
 +| **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 ​        |
  
- Locuinta();​ +<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 ei. Astfel cea mai **mică** prioritate o are operatorul **virgulă**în timp ce cea mai **mare** prioritate o au **operatorii primari**.</​note>​
- Locuinta(const float& pretconst charadresa); +
- Locuinta(const Locuinta&​ locuinta);​ +
- Locuinta&​ operator=(const Locuinta&​ locuinta);​ +
- ~Locuinta();​+
  
- float getPret() const; +==== Funcții friend ====
- char* getAdresa() const;+
  
- void setPret(const float& pret); +Î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. ​
- void setAdresa(const charadresa);+
  
- friend ​std::​ostream&​ operator<<​(std::​ostream&​ outconst Locuinta&​ locuinta);​ +<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 personaleacest 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>
-}; +
-</code>+
  
-Iar implementările pentru ​funcțiile membre ​și cea friend ​le putem observa în codul de mai jos.+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. 
 + 
 +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ă.
  
 <code cpp> <code cpp>
-#​include ​"​Locuinta.h"​+#​include ​<​iostream>​ 
 +#include <​cstring>​
  
-Locuinta::​Locuinta()+class Persoana
 { {
- pret = 0.0f+ int varsta
- adresa = nullptr+ char* nume; 
-}+ 
 +public: 
 + 
 + Persoana(const int& varsta, const char* nume); 
 + ~Persoana();​ 
 + 
 + friend void afisarePersoana(const Persoana&​ persoana); // functie friend pentru afisarea datelor unei persoane 
 +};
  
-Locuinta::Locuinta(const ​floatpret, const char* adresa)+Persoana::Persoana(const ​intvarsta, const char* nume)
 { {
- this->pret pret;+ this->varsta ​varsta;
  
- if (adresa ​!= nullptr)+ if (nume != nullptr)
  {  {
- this->adresa ​= new char[strlen(adresa) + 1]; + this->nume = new char[strlen(nume) + 1]; 
- strcpy(this->​adresaadresa);+ strcpy(this->​numenume);
  }  }
  else  else
  {  {
- this->adresa ​= nullptr;+ this->nume = nullptr;
  }  }
 } }
  
-Locuinta::Locuinta(const Locuinta&​ locuinta)+Persoana::~Persoana()
 { {
- pret = locuinta.pret;​ + if (nume != nullptr)
- +
- if (locuinta.adresa ​!= nullptr)+
  {  {
- adresa = new char[strlen(locuinta.adresa) + 1]+ delete[] nume;
- strcpy(adresa,​ locuinta.adresa);​ +
-+
- else +
-+
- adresa = nullptr;+
  }  }
 } }
  
-Locuinta&​ Locuinta::​operator=(const ​Locuintalocuinta)+void afisarePersoana(const ​Persoanapersoana)
 { {
- if (this == &​locuinta) + std::cout << "​Numele persoanei este: " << persoana.nume << '​\n';​ 
- + std::cout << "​Varsta persoanei este: " << persoana.varsta << "​\n\n"​
- return *this+}
- }+
  
- if (adresa != nullptr+int main() 
-+
- delete[] adresa; + Persoana persoana(22,​ "​Andrei"​);
- }+
  
- pret = locuinta.pret;+ afisarePersoana(persoana)// apelarea functiei friend este identica cu a unei functii clasice din C/C++
  
- if (locuinta.adresa != nullptr) + return ​0;
-+
- adresa = new char[strlen(locuinta.adresa) + 1]; +
- strcpy(adresa,​ locuinta.adresa);​ +
-+
- else +
-+
- adresa = nullptr; +
-+
- +
- return ​*this;+
 } }
 +</​code>​
  
-Locuinta::​~Locuinta()+Î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++. 
 + 
 +<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 parametri. Cuvântul cheie **friend** se utilizează **doar** la declararea funcției pentru a anunț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/​C++**.</​note>​ 
 + 
 +Vom folosi foarte mult acest tip de funcții după cum vom vedea în cele ce urmează la supraîncărcarea operatorilor limbajului C++. 
 + 
 +==== Supraîncărcarea operatorilor ==== 
 + 
 +Î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. 
 + 
 +Structura clasei **NrComplex** poate fi observată în codul de mai jos. 
 + 
 +<code cpp> 
 +#include <​iostream>​ 
 + 
 +class NrComplex
 { {
- if (adresa !nullptr+ double real; 
- { + double imaginar; 
- delete[] adresa+ 
- }+public: 
 + 
 + NrComplex(const double& real 0.0, const double& imaginar = 0.0); // constructor cu parametri cu valori implicite 
 + 
 + double getReal() const; 
 + double getImaginar() const; 
 + 
 + void setReal(const double& real); 
 + void setImaginar(const double& imaginar)
 +};  
 +</​code>​ 
 + 
 +Iar implementările funcțiilor membre sunt vizibile în codul de mai jos. 
 + 
 +<code cpp> 
 +#include "​Complex.h"​ 
 + 
 +NrComplex::​NrComplex(const double& real, const double& imaginar) 
 +
 + this->​real = real; 
 + this->​imaginar = imaginar;
 } }
  
-float Locuinta::getPret() const+double NrComplex::getReal() const
 { {
- return ​pret;+ return ​real;
 } }
  
-char* Locuinta::getAdresa() const+double NrComplex::getImaginar() const
 { {
- return ​adresa;+ return ​imaginar;
 } }
  
-void Locuinta::setPret(const ​floatpret)+void NrComplex::setReal(const ​doublereal)
 { {
- if (pret <0.0f) + this->​real ​real
-+}
- return+
- }+
  
- this->pret pret;+void NrComplex::​setImaginar(const double& imaginar) 
 +
 + this->imaginar ​imaginar;
 } }
 +</​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>​
 +
 +<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>​
 +
 +=== Operatori supraîncărcați ca funcții membre în clasă ===
 +
 +În general alegem această variantă de supraîncărcare atunci când vrem să avem acces **direct** la membrii unei clase. Astfel, se 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>​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>​
 +
 +== 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>
 +#include <​iostream>​
  
-void Locuinta::​setAdresa(const char* adresa)+class NrComplex
 { {
- if (adresa == nullptr) + double real; 
- + double imaginar;
- return; +
- }+
  
- if (this->​adresa != nullptr) +public:
-+
- delete[] this->​adresa;​ +
- }+
  
- this->​adresa ​new char[strlen(adresa) + 1]+ NrComplex(const double& real 0.0, const double& imaginar = 0.0); 
- strcpy(this->adresa, adresa);+ 
 + double getReal() const; 
 + double getImaginar() const; 
 + 
 + void setReal(const double& real); 
 + void setImaginar(const double& imaginar);​ 
 + 
 + // supraincarcarea operatorilor ca functii membre 
 + 
 + NrComplex&​ operator++()// forma prefixata 
 + NrComplex operator++(int); // forma postfixata 
 +}; 
 +</​code>​ 
 + 
 +Iar implementările corespunzătoare pentru cele două forme ale operatorului de incrementare le putem vedea mai jos. 
 + 
 +<code cpp> 
 +NrComplex&​ NrComplex::​operator++() 
 +
 + this->real++; 
 + this->​imaginar++;​ 
 + 
 + return *this;
 } }
  
-std::ostream& ​operator<<(std::​ostream&​ out, const Locuinta&​ locuinta)+NrComplex NrComplex::operator++(int)
 { {
- out << "​Pretul locuintei este: " << locuinta.pret << " ron\n";+ NrComplex copie = *this;
  
- if (locuinta.adresa != nullptr) + this->​real++
-+ this->​imaginar++;
- out << "​Adresa locuintei este: " << locuinta.adresa << "​\n\n"​+
- +
- else +
-+
- out << "​Adresa locuintei este: inexistenta\n\n"​; +
- }+
  
- return ​out;+ return ​copie;
 } }
 </​code>​ </​code>​
  
-<​note>​Pe prima linie a **fișierului header** în care este definită clasa **Locuinta**,​ putem observa ​utilizarea directivei **''#​pragma once''​**. Aceasta este o instrucțiune specifică compilatorului care indică faptul ​că fișierul respectiv trebuie inclus și compilat o **singură dată**, chiar dacă este referit în mod repetat în alte fișiere prin intermediul directivelor ​**''#​include''​**. Astfel, se previn ​**multiplele incluziuni** ale aceluiași fișier header, care ar putea duce la erori de compilare, cum ar fi redefinirea claselor sau funcțiilor. Directiva ​**''#​pragma once''​** este o alternativă modernă și mai simplă la gardienii clasici ai fișierelor headeradică acele secvențde cod cu **''#​ifndef''​**,​ **''#​define''​** și **''#​endif''​** care au același scop.</​note>​+<​note ​important>Se poate observa că la **forma postfixată** avem un parametru de care **nu** ne folosimAcel parametru este **doar** pentru a asigura ​**polimorfismul**, compilatorul făcând distincția între cele două variante ​de operator de incrementare.</​note>​
  
-Dacă voiam să folosim varianta tradițională de scriere ​unui fișier header am fi procedat ​în maniera următoare.+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**. 
 + 
 +== Supraîncărcarea operatorului ! == 
 + 
 +Acest operator are rolul de a **nega** o **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 urmare, utilizarea **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. 
 + 
 +În continuare vom declara în clasa **NrComplex** acest operator pentru a realiza ceea ce ne dorim, și anume conjugarea unui număr complex.
  
 <code cpp> <code cpp>
-#ifndef LOCUINTA_H 
-#define LOCUINTA_H 
- 
-#include <​cstring>​ 
 #include <​iostream>​ #include <​iostream>​
  
-class Locuinta+class NrComplex
 { {
-    float pret+ double real
-    char* adresa;+ double imaginar;
  
 public: public:
  
-    Locuinta();​ + NrComplex(const ​doublereal = 0.0, const doubleimaginar ​0.0);
-    Locuinta(const ​floatpret, const char* adresa); +
-    Locuinta(const Locuintalocuinta);​ +
-    Locuinta&​ operator=(const Locuinta&​ locuinta);​ +
-    ~Locuinta();+
  
-    float getPret() const; + double getReal() const; 
-    char* getAdresa() const;+ double getImaginar() const;
  
-    ​void setPret(const ​floatpret); + void setReal(const ​doublereal); 
-    void setAdresa(const ​char* adresa);+ void setImaginar(const ​double& imaginar);
  
-    friend std::​ostream&​ operator<<​(std::​ostream&​ out, const Locuinta&​ locuinta);​ + // supraincarcarea operatorilor ca functii membre
-};+
  
-#​endif ​// LOCUINTA_H+ NrComplex operator!() const; ​// operatorul de negare logica (in cazul acestei clase va conjuga un numar complex) 
 + 
 + NrComplex&​ operator++();​ 
 + NrComplex operator++(int);​ 
 +};
 </​code>​ </​code>​
  
-=== Relația de "​is-a"​ între două clase ===+Iar implementarea acestui operator se poate observa mai jos.
  
-Acest tip de relație ne permite să implementăm **moștenirea** între clase. În acest context, când discutăm despre moștenire, întâlnim următorii termeni esențiali**clasă părinte ​(denumită și clasă de bază sau superclasă)** și **clasă derivată (denumită și clasă copil sau subclasă)**. **Clasa părinte** reprezintă clasa de la care dorim să **preluăm** atribute și metode, având posibilitatea să **reutilizăm** codul existent, în timp ce **clasa derivată** **extinde** această funcționalitate,​ **adăugând** noi comportamente și caracteristici.+<code cpp> 
 +NrComplex NrComplex::operator!() const 
 +
 + NrComplex conjugat = *this;
  
-<note tip>​Atunci când dorim să implementăm **moștenirea** între două clase, este important să respectăm un set clar de reguliÎn primul rând, trebuie să stabilim care dintre clase va fi **clasa părinte** și care va fi **clasa copil**. Prin procesul de **moștenire**,​ afirmăm că un **obiect** al **clasei derivate** este, implicit, și un obiect al **clasei părinte**, datorită relației de tip **"​is-a"​** dintre cele două clase. Această relație trebuie să aibă o **coerență logică**, adică orice instanță a **clasei derivate** este automat și o instanță a **superclasei**. De exemplu, omul este un mamifer, iar afirmația că toți oamenii sunt mamifere este corectă. Însă reciproca nu este valabilă, deoarece nu toate mamiferele sunt oameni. Prin urmare, relația de tip **"is-a"** se aplică doar de la **clasa copil** către **clasa părinte**, și nu invers. + conjugat.imaginar = -conjugat.imaginar;
-</​note>​+
  
-În continuare vom căuta o clasă care poate să extindă și să respecte relația de **"​is-a"​** cu clasa **Locuinta**. Vom propune clasa **Apartament** care respectă regulile descrise mai sus, deoarece orice apartament este o locuință. Clasa **Apartament** are ca și atribute **numarCamere (de tip întreg)** și **numeProprietar (șir de caractere alocat dinamic)**.+ return conjugat; 
 +
 +</​code>​
  
-În limbajul C++ pentru ca o clasă să moștenească ​altă clasă se folosește următoarea sintaxă: +<note warning>​Operatorul de negare logică **nu** trebuie ​să modifice **this-ul**,​ acesta a fost și motivul pentru care am returnat ​**copie modificată** în loc să modifică**direct** obiectul curent.</​note>​
-**''​class NumeClasaDerivata : specificator de acces pentru moștenire NumeClasaParinte''​** și apoi corpul clasei derivate.+
  
-Să urmărim în cod cum putem face clasa **Apartament** să moștenească clasa **Locuință**.+== Supraîncărcarea operatorilor == și != == 
 + 
 +**Operatorul %%==%%** este folosit pentru a testa egalitatea 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**. 
 + 
 +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 operanzi, adică verifică dacă valorile celor doi termeni sunt **diferite**. 
 + 
 +Vom declara după cum urmează aceste două metode în clasa **NrComplex**.
  
 <code cpp> <code cpp>
-#pragma once +#​include ​<​iostream>​
-#​include ​"​Locuinta.h"​+
  
-class Apartament : public Locuinta // clasa Apartament mosteneste clasa Locuinta+class NrComplex
 { {
- int numarCamere+ double real
- char* numeProprietar;+ double imaginar;
  
 public: public:
  
- Apartament();​ + NrComplex(const ​doublereal = 0.0, const doubleimaginar ​0.0);
- Apartament(const ​floatpret, const char* adresa, const intnumarCamere,​ const char* numeProprietar);​ +
- Apartament(const Apartament&​ apartament);​ +
- Apartament&​ operator=(const Apartament&​ apartament);​ +
- ~Apartament();+
  
- int getNumarCamere() const; + double getReal() const; 
- char* getNumeProprietar() const;+ double getImaginar() const;
  
- void setNumarCamere(const ​intnumarCamere); + void setReal(const ​doublereal); 
- void setNumarCamere(const ​char* numeProprietar);+ void setImaginar(const ​double& imaginar);
  
- friend std::​ostream& operator<<(std::​ostreamout, const Apartamentapartament);+ // supraincarcarea operatorilor functii membre 
 + 
 + NrComplex operator!() const; 
 + 
 + NrComplex& operator++(); 
 + NrComplex operator++(int);​ 
 + 
 + bool operator==(const NrComplexz) const; // operatorul de testare a egalitatii intre doua numere complexe 
 + bool operator!=(const NrComplexzconst// operatorul de testare a diferentei intre doua numere complexe
 }; };
 </​code>​ </​code>​
  
-<note warning>​Deși clasa **Apartament** moștenește clasa **Locuinta** din cauza faptului că membrii clasei **Locuinta** sunt marcați din default cu **private** nu vom avea acces la ei în **clasa derivată**.</​note>​+Iar implementările pentru cei doi operatori le putem observa ​în codul de mai jos.
  
-=== Specificatorul de acces protected ​===+<code cpp> 
 +bool NrComplex::​operator==(const NrComplex&​ z) const 
 +
 + return this->​real ​== z.real && this->​imaginar ​== z.imaginar;​ 
 +}
  
-Pentru a putea face câmpurile clasei **Locuinta** să fie vizibile în clasa copil, dar să nu poată fi în continuare accesate din exterior de oricine le vom marca cu **protected** după cum urmează în codul de mai jos.+bool NrComplex::​operator!=(const NrComplex&​ z) const 
 +
 + return this->​real != z.real || this->​imaginar != z.imaginar;​ 
 +
 +</​code>​ 
 + 
 +== Supraîncărcarea operatorului += == 
 + 
 +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**.
  
 <code cpp> <code cpp>
-#pragma once 
-#include <​cstring>​ 
 #include <​iostream>​ #include <​iostream>​
  
-class Locuinta+class NrComplex
 { {
-protected: // specificatorul de acces protected + double real
- + double imaginar;
- float pret+
- char* adresa;+
  
 public: public:
  
- Locuinta();​ + NrComplex(const ​doublereal = 0.0, const doubleimaginar ​0.0);
- Locuinta(const ​floatpret, const char* adresa); +
- Locuinta(const Locuintalocuinta);​ +
- Locuinta&​ operator=(const Locuinta&​ locuinta);​ +
- ~Locuinta();+
  
- float getPret() const; + double getReal() const; 
- char* getAdresa() const;+ double getImaginar() const;
  
- void setPret(const ​floatpret); + void setReal(const ​doublereal); 
- void setAdresa(const ​char* adresa);+ void setImaginar(const ​double& imaginar);
  
- friend std::​ostream& operator<<(std::​ostreamout, const Locuintalocuinta);+ // supraincarcarea operatorilor ca functii membre 
 + 
 + NrComplex operator!() const; 
 + 
 + NrComplex& operator+=(const NrComplexz); // operatorul compus pentru adunarea a doua numere complexe 
 + 
 + NrComplex&​ operator++();​ 
 + NrComplex operator++(int);​ 
 + 
 + bool operator==(const NrComplexzconst;  
 + bool operator!=(const NrComplex&​ z) const;
 }; };
 </​code>​ </​code>​
  
-Astfel acum câmpurile **pret** și **adresa** vor fi vizibile și în clasa derivată, dar vor rămâne **inaccesibile** ​în funcția **main** sau în orice altă clasă care **nu** moștenește clasa **Locuinta**.+Iar implementarea acestui operator o vom regăsi în secvența de cod următoare.
  
-=== Implementarea metodelor și a funcțiilor friend în clasa derivată ===+<code cpp> 
 +NrComplex&​ NrComplex::​operator+=(const NrComplex&​ z) 
 +
 + this->​real +z.real; 
 + this->​imaginar +z.imaginar;
  
-În continuare vom prezenta modul în care trebuiesc implementate toate funcționalitățile clasei ​**Apartament** astfel încât realția de **"​is-a"​** să fie satisfăcută și să reutilizăm codul din clasa **Locuinta**.+ return ​*this; 
 +
 +</​code>​
  
-== Implementarea constructorilor clasei derivate ​==+<note tip>În mod similar, putem 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>​
  
-Atunci când dorim să construim un obiect de tipul **clasei derivate**, trebuie să ținem cont de faptul că, **mai întâi**, trebuie inițializate și gestionate toate datele și resursele **moștenite** din **clasa părinte**. Constructorul **clasei derivate** va apela constructorul **clasei părinte** pentru a asigura corectitudinea inițializării **componentelor moștenite**. Astfel, acest proces garantează că toate proprietățile părintelui sunt gestionate corespunzător înainte de a se trece la inițializarea specifică **clasei derivate**.+==== ====
  
-Să urmărim cu atenție mai jos implemetarea constructorului fără parametri ​pentru ​clasa **Apartament**.+<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>​ 
 + 
 +=== Operatori supraîncărcați ca funcții friend === 
 + 
 +Î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ărcarea. Această abordare este utilă ​mai ales când operatorul trebuie să acceseze **membrii privați** ai clasei, dar implică și **alte** tipuri de date în operații. 
 + 
 +== Supraîncărcarea operatorului + == 
 + 
 +Aici putem avea mai multe situații spre exemplu: adunarea 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. 
 + 
 +Să urmărim codul de mai jos unde am pus la dispoziție cele trei forme pentru **operatorul de adunare**.
  
 <code cpp> <code cpp>
-Apartament::​Apartament() : Locuinta()+#include <​iostream>​ 
 + 
 +class NrComplex
 { {
- numarCamere = 0+ double real
- numeProprietar = nullptr; + double imaginar;
-+
-</​code>​+
  
-<note important>​Se poate observa căînainte de a inițializa câmpurile specifice clasei **Apartament**am apelat **constructorul fără parametri** al clasei **Locuință** în **lista de inițializare** ​constructorului **clasei derivate**. Astfelam **reutilizat** codul din **clasa părinte** printr-un simplu apelasigurând inițializarea corectă a membrilor moșteniți. Este important de menționat că această **listă de inițializare** poate fi utilizată **exclusiv** în cazul **constructorilor**,​ fiind o metodă eficientă de a gestiona inițializarea **clasei părinte**.</note>+public: 
 + 
 + NrComplex(const double& real = 0.0const double& imaginar = 0.0); 
 + 
 + double getReal() const; 
 + double getImaginar() const; 
 + 
 + void setReal(const double& real); 
 + void setImaginar(const double& imaginar);​ 
 + 
 + // supraincarcarea operatorilor ca functii membre 
 + 
 + 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&​ z1const NrComplex&​ z2); // operator pentru adunarea ​doua numere complexe 
 + friend NrComplex operator+(const NrComplex&​ z1const double& numar); // operator pentru adunarea unui numar complex cu un numar real 
 + friend NrComplex operator+(const double& numarconst NrComplex&​ z1); // operator pentru adunarea unui numar real cu un numar complex 
 +}; 
 +</code>
  
-În continuare vom implementa constructorul cu parametri pentru clasa copil urmând același principiu ca la constructorul fără parametri.+Iar implementările pentru cei trei operatori le putem observa în codul ce urmează.
  
 <code cpp> <code cpp>
-Apartament::​Apartament(const ​floatpret, const char* adresa, const intnumarCamere,​ const char* numeProprietar) : Locuinta(pret,​ adresa)+NrComplex operator+(const ​NrComplexz1, const NrComplexz2)
 { {
- this->​numarCamere = numarCamere;+ NrComplex z;
  
- if (numeProprietar !nullptr) + z.real ​z1.real + z2.real; 
- + z.imaginar ​z1.imaginar ​z2.imaginar
- this->​numeProprietar ​new char[strlen(numeProprietar) ​1]+ 
- strcpy(this->​numeProprietar,​ numeProprietar);​ + return z;
- +
- else +
-+
- this->​numeProprietar = nullptr; +
- }+
 } }
-</​code>​ 
  
-Se poate observa din implementarea anterioară că am deschis lista de inițializare pentru acest constructor unde am chemat constructorul cu parametri al clasei părinte ​(clasa **Locuinta**).+NrComplex operator+(const NrComplex&​ z1, const double& numar) 
 +
 + NrComplex z;
  
-<note warning>​Constructorul cu parametri al **clasei derivate** include în lista sa de argumente și parametrii necesari pentru a apela **constructorul corespunzător din clasa părinte**Acești parametri sunt transmiși în lista de inițializare a constructorului **clasei copil** atunci când este apelat constructorul din **superclasă**,​ facilitând astfel **inițializarea corectă** a **membrilor moșteniți** din **clasa părinte**Acest mecanism permite transmiterea valorilor necesare direct către **clasa părinte**, asigurând o **organizare clară** și o **reutilizare eficientă** a codului.</​note>​+ z.real = z1.real + numar; 
 + z.imaginar = z1.imaginar;​
  
-În manieră simlară se implementează și constructorul de copiere al clasei derivate.+ return z; 
 +}
  
-<code cpp> +NrComplex operator+(const ​doublenumar, const NrComplex&​ z1)
-Apartament::​Apartament(const ​Apartamentapartament) : Locuinta(apartament)+
 { {
- numarCamere = apartament.numarCamere;+ NrComplex z;
  
- if (apartament.numeProprietar !nullptr) + z.real numar + z1.real
-+ z.imaginar = z1.imaginar
- numeProprietar = new char[strlen(apartament.numeProprietar) + 1]+ 
- strcpy(numeProprietar,​ apartament.numeProprietar)+ return z;
- } +
- else +
-+
- numeProprietar = nullptr; +
- }+
 } }
 </​code>​ </​code>​
  
-Astfel, am evidențiat **reutilizarea codului** prin faptul căîn momentul în care construim un obiect de tipul **clasei derivate**, nu este necesar ​să redefinim sau să duplicăm funcționalitățile și datele din **clasa părinte**. Prin simplul apel al constructorului **clasei părinte** în lista de inițializare a constructorului ​**clasei derivate**, putem păstra claritatea și concizia codului sursă. Aceasta reprezintă un avantaj major al **moștenirii** în **POO**, permițând extinderea funcționalității fără a compromite principiile de reutilizare și organizare.+<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>​
  
-== Implementarea destructorului în clasa derivată ==+== Supraîncărcarea operatorilor de flux >> și << ​==
  
-Destructorul clasei derivate se implementează ​la fel ca un destructor obișnuitadică vom elibera memoria alocată dinamic pentru membrii care sunt de tip pointer.+**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**. 
 + 
 +Să urmărim cu atenție modul în care declarăm acesți operatori ca **funcții friend** în clasa **NrComplex**.
  
 <code cpp> <code cpp>
-Apartament::​~Apartament()+#include <​iostream>​ 
 +using namespace std; 
 + 
 +class NrComplex
 { {
- if (numeProprietar != nullptr) + double real; 
- + double imaginar;
- delete[] numeProprietar; +
-+
-+
-</​code>​+
  
-<note warning>​În destructorul clasei derivate **nu** apelăm destructorul clasei părinte. Acest lucru va fi realizat **automat** de către **compilator** în mod corect fără a fi nevoie de intervenția noastră.</​note>​+public:
  
-== Implementarea operatorului de asignare în clasa derivată ​==+ NrComplex(const double& real 0.0, const double& imaginar ​0.0); 
 + 
 + double getReal() const; 
 + double getImaginar() const; 
 + 
 + void setReal(const double& real); 
 + void setImaginar(const double& imaginar);​ 
 + 
 + // supraincarcarea operatorilor ca functii membre 
 + 
 + 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>​ 
 + 
 +<​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>​
  
-La fel ca în cazul constructorilor va trebui să găsim o modalitate prin care **mai întâi** ne ocupăm de atributele **clasei părinte** și pe urmă prelucram datele **clasei copil**. Operatorul ​de asignare **nefiind** un constructor **nu** are listă ​de inițializare și va trebui să îl apelăm explicit pe cel din **clasa părinte** pentru a respecta ordinea pașilor exact la fel ca în cazul constructorilor.+Să urmărim implementarea celor doi operatori ​de flux în exemplul ​de cod de mai jos.
  
 <code cpp> <code cpp>
-ApartamentApartament::​operator=(const Apartamentapartament)+istream& operator>>(istreamin, NrComplex&​ z)
 { {
- if (this == &​apartament) + std::cout << "​Introduceti partea reala a numarului complex: "; 
- + in >> z.real;
- return *this; +
- }+
  
- this->​Locuinta::operator=(apartament)// se apeleaza operatorul de asignare din clasa parinte + std::cout << "​Introduceti partea imaginara a numarului complex: "
- /​*(Locuinta&​)(*this) = apartament// este echivalent cu linia de mai sus doar ca este o alta forma de apel*/+ in >> z.imaginar;
  
- numarCamere = apartament.numarCamere;+ return in; 
 +}
  
- if (apartament.numeProprietar != nullptr+ostream&​ operator<<​(ostream&​ out, const NrComplex&​ z
-+
- numeProprietar = new char[strlen(apartament.numeProprietar) + 1]+ out << "​Partea reala a numarului complex este: " << z.real << '​\n'​
- strcpy(numeProprietar,​ apartament.numeProprietar); + out << "​Partea imaginara a numarului complex este: " << z.imaginar << "​\n\n"​;
-+
- else +
-+
- numeProprietar = nullptr; +
- }+
  
- return ​*this;+ return ​out;
 } }
 </​code>​ </​code>​
  
-== Implementarea operatorului << în clasa derivată ​==+==== ==== 
 + 
 +Codul complet cu implementările operatorilor prezentați pentru clasa **NrComplex** poate fi descărcat de {{:​poo-is-ab:​laboratoare:​complex_overloading.zip|aici}}. 
 + 
 +<note warning>​În limbajul C++ **nu** este permisă supraîncărcarea următorilor operatori:​ 
 +  * de rezoluție **"::"​** 
 +  * de acces la membrii unei clase/​structuri **"​."​** 
 +  * condițional/​ternar **"?:"​** 
 +  * **sizeof** (returnează dimensiunea în bytes a unui tip de date) 
 +  * **typeid** (folosit pentru a obține informații despre tipul dinamic al unui obiect la run time) 
 +  * **alignof** (similar cu operatorul **sizeof**) 
 +</​note>​
  
 +==== 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.
  
 +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ți 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**.
poo-is-ab/laboratoare/05.1728828254.txt.gz · Last modified: 2024/10/13 17:04 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