Differences

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

Link to this comparison view

poo-is-ab:laboratoare:06 [2024/10/27 21:44]
razvan.cristea0106 [Concluzii]
poo-is-ab:laboratoare:06 [2025/09/23 21:25] (current)
razvan.cristea0106
Line 1: Line 1:
-===== Laborator 6 - Moștenire ​multiplă și agregare ​=====+===== Laborator 06 - Moștenire ​simplă =====
  
 **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ă:
  
-  ​* definească și să utilizeze namespace-uri +  * recunoască și să înțeleagă conceptul de moștenire ​între două clase 
-  ​* recunoască și să înțeleagă conceptul de moștenire ​multiplă și agregare între mai multe clase +  * construiască legături între clase folosind relația de tip "​is-a" ​(relație de specializare) 
-  * construiască ​ierarhii și legături între clase folosind relațiile de tip "​is-a"​ și "​has-a"​ +  * folosească membrii marcați cu protected ​și să înțeleagă diferențele dintre accesul public, privat și protejat în moștenire 
-  * aplice principiile de reutilizare a codului prin extinderea funcționalităților claselor ​de bază în clasa derivată, combinând diverse clase și folosind agregarea pentru a crea relații complexe între obiecte+  * aplice principiile de reutilizare a codului prin extinderea funcționalității clasei ​de bază în clasa derivată
  
 ==== Introducere ==== ==== Introducere ====
  
-În cadrul acestui ​laborator vom aprofunda conceptele legate ​de **moștenire**,​ extinzând noțiunile discutate în laboratorul anterior și explorând două relații ​esențiale ​între clase**relația de tip "​is-a"​** ​între mai multe clase, cunoscută sub numele de **moștenire multiplă** și **relația de tip "has-a"**, cunoscută și sub denumirea ​de **agregare**. Vom analiza astfel cum poate o clasă ​să moștenească simultan trăsături de la mai multe clase și cum pot fi organizate relațiile între ​clase pentru a modela compoziția unui obiect. Scopul este să înțelegem mai bine felul în care aceste concepte contribuie la crearea unui cod modular, reutilizabil și clar structurat, fiind esențiale în designul eficient al aplicațiilor complexe.+În acest laborator vom extinde lucrul cu clase, introducând conceptul ​de **relații între clase**. Vom construi și analiza legături între două clase, folosind ​relațiile de tip **"​is-a"​** ș**"​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ștenire, un principiu fundamental al **POO**, prin care **clasă ​derivată (subclasă)** preia **proprietățile** ​și **comportamentele** unei **clase de bază (superclasă)**
  
-==== Moștenirea ​multiplă ====+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 existente. Astfel, **moștenirea** facilitează **extensibilitatea** și **întreținerea** codului, reducând duplicarea și oferind un mod eficient de a gestiona complexitatea în proiecte de mari dimensiuni. 
 +==== Moștenirea ​între două clase ====
  
-Limbajul de programare **C++** permite ​**moștenirea ​multiplă**, ceea ce înseamnă că o **clasă derivată** poate avea mai multe **clase ​de bază**, moștenind astfel trături din **cel puțin două clase părinte**. Acest tip de moștenire oferă flexibilitate ​și posibilitatea de a combina ​funcționalități variate din clase distincte într-o singură clasă. Pe lângă ​**C++**, și limbajul ​**Python** permite ​**moștenirea multiplă**, spre deosebire ​de limbajele ​**Java** și **C#**, care **nu** acceptă acest tip de moștenire direct pentru clase, în principal din motive legate de ambiguitate ​și complexitatea managementului de resurse. În aceste limbaje, în schimb, este folosită o abordare bazată pe **interfețe** pentru a atinge un scop similar.+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 și metode sau prin redefinirea celor existente. Scopul 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.
  
-<note important>​În general, găsirea unei combinații potrivite ​de clase care să respecte **moștenirea ​multiplă** poate fi o provocare, deoarece aceasta poate duce la situații **complexe** și **ambigue**. **Moștenirea multiplă** poate să genereze probleme atunci când două **clase părinte** au **metode** sau **membri comuni**, ceea ce poate crea **conflicte** sau **confuzie** în interpretarea acestora în **clasa copil**. Astfel, devine esențial ca **moștenirea multiplă** să fie utilizată doar atunci când **clasele părinte** sunt **bine definite**, **fără** suprapuneri de responsabilități sau funcționalități,​ pentru ​**evita** situațiile de ambiguitate și a păstra codul ușor de întreținut și de extins.</​note>​ +Înainte ​de a explica ​moștenirea ​între ​două clase vom face o scurtă recapitulare ​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)**.
- +
-Pentru acest laborator propunem ​ca și clase părinte ​**ProdusComercial** și respectiv ​**PiesaElectronica**, iar ca și clasă copil **CameraWeb**. Dacă am menționat **moștenire** acest lucru este echivalent cu relația ​de **"​is-a"​** ceea ce înseamnă că **orice cameră web** este un **produs comercial** și în același timp **orice cameră web** este o **piesă electronică**. +
- +
-Declarația clasei **ProdusComercial** se poate observa în blocul de cod mai jos.+
  
 <code cpp> <code cpp>
 #pragma once #pragma once
 +#include <​cstring>​
 #include <​iostream>​ #include <​iostream>​
  
-class ProdusComercial+class Locuinta
 { {
  float pret;  float pret;
 + char* adresa;
  
 public: public:
  
- ProdusComercial(const float& pret = 0.0f); + Locuinta();​ 
- ProdusComercial& operator=(const ​ProdusComercialprodusComercial);+ Locuinta(const float& pret, const char* adresa); 
 + Locuinta(const Locuinta&​ locuinta);​ 
 + Locuinta& operator=(const ​Locuintalocuinta);​ 
 + ~Locuinta();
  
- friend std::​ostream&​ operator<<​(std::​ostream&​ out, const ProdusComercialprodusComercial);+ float getPret() const; 
 + char* getAdresa() const; 
 + 
 + void setPret(const float& pret); 
 + void setAdresa(const char* adresa); 
 + 
 + friend std::​ostream&​ operator<<​(std::​ostream&​ out, const Locuintalocuinta);
 }; };
 </​code>​ </​code>​
  
-Iar declarația clasei ​**PiesaElectronica** se află în codul sursă ​de mai jos.+<​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 header, adică acele secvențe de cod cu **''#​ifndef''​**,​ **''#​define''​** și **''#​endif''​** care au același scop.</​note>​ 
 + 
 +Dacă voiam să folosim varianta tradițională de scriere a unui fișier header am fi procedat în maniera următoare.
  
 <code cpp> <code cpp>
-#pragma once +#ifndef LOCUINTA_H 
-#include <string>+#define LOCUINTA_H 
 + 
 +#include <cstring>
 #include <​iostream>​ #include <​iostream>​
  
-class PiesaElectronica+class Locuinta
 { {
- char* brand;+    float pret; 
 +    ​char* adresa;
  
 public: public:
  
- PiesaElectronica(); +    Locuinta(); 
- PiesaElectronica(const char* brand); +    ​Locuinta(const float& pret, const char* adresa); 
- PiesaElectronica(const ​PiesaElectronicapiesaElectronica); +    ​Locuinta(const ​Locuintalocuinta); 
- PiesaElectronica& operator=(const ​PiesaElectronicapiesaElectronica); +    ​Locuinta& operator=(const ​Locuintalocuinta); 
- ~PiesaElectronica();+    ~Locuinta();
  
- friend std::​ostream&​ operator<<​(std::​ostream&​ out, const PiesaElectronicapiesaElectronica);+    float getPret() const; 
 +    char* getAdresa() const; 
 + 
 +    void setPret(const float& pret); 
 +    void setAdresa(const char* adresa); 
 + 
 +    ​friend std::​ostream&​ operator<<​(std::​ostream&​ out, const Locuintalocuinta);
 }; };
 +
 +#endif // LOCUINTA_H
 </​code>​ </​code>​
  
-Având definite cele două **superclase** putem să ne ocupăm acum de clasa **CameraWeb** pentru a putea implementa ​**moștenirea ​multiplă** în C++La fel ca în cadrul laboratorului precedent ​trebuie să includem ​**fișierul header** aferent fiecărei **clase părinte** ​în **fișierul header** al **clasei ​copil**, iar apoi să informăm compilațorul că intenționăsă extindem cele două clase, **ProdusComercial** și respectiv ​**PiesaElectronica**, în clasa derivată **CameraWeb**.+=== Relația de tip "​is-a"​ între ​două clase === 
 + 
 +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. 
 + 
 +<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** ​tre **clasa părinte**, și nu invers. 
 +</​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 susdeoarece orice apartament este o locuință. Clasa **Apartament** are ca și atribute ​**numarCamere (de tip întreg)** și **numeProprietar (șir de caractere alocat dinamic)**. 
 + 
 +În limbajul C++ pentru ca o clasă să moștenească o altă clasă se folosește următoarea sintaxă: 
 +**''​class NumeClasaDerivata : specificator de acces pentru moștenire NumeClasaParinte''​** și apoi corpul clasei derivate.
  
-Prin urmare declarația clasei ​**CameraWeb** poate fi observată în codul de mai jos.+Să urmărim în cod cum putem face clasa **Apartament** să moștenească clasa **Locuință**.
  
 <code cpp> <code cpp>
 #pragma once #pragma once
-#include "ProdusComercial.h"​ +#include "Locuinta.h"
-#include "​PiesaElectronica.h"+
  
-class CameraWeb ​: public ​ProdusComercial,​ public PiesaElectronica ​// CameraWeb ​mosteneste ​atat ProdusComercial cat si PiesaElectronica+class Apartament ​: public ​Locuinta ​// clasa Apartament ​mosteneste ​clasa Locuinta
 { {
- int rezolutie+ int numarCamere
- char* ​culoare;+ char* ​numeProprietar;
  
 public: public:
  
- CameraWeb(); + Apartament(); 
- CameraWeb(const float& pret, const char* brand, const int& ​rezolutie, const char* culoare); + Apartament(const float& pret, const char* adresa, const int& ​numarCamere, const char* numeProprietar); 
- CameraWeb(const ​CameraWebcameraWeb); + Apartament(const ​Apartamentapartament); 
- CameraWeb& operator=(const ​CameraWebcameraWeb); + Apartament& operator=(const ​Apartamentapartament); 
- ~CameraWeb();+ ~Apartament();
  
- friend std::​ostream&​ operator<<​(std::​ostream&​ out, const CameraWebcameraWeb);+ int getNumarCamere() const; 
 + char* getNumeProprietar() const; 
 + 
 + void setNumarCamere(const int& numarCamere);​ 
 + void setNumarCamere(const char* numeProprietar);​ 
 + 
 + friend std::​ostream&​ operator<<​(std::​ostream&​ out, const Apartamentapartament);
 }; };
 </​code>​ </​code>​
  
-=== Implementarea constructorilor ​în clasa derivată ​===+<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>​
  
-Se procedează în manieră similară moștenirii simple cu adăugările ​de rigoare după cum urmează în exemplele de mai jos.+=== Specificatorul ​de acces protected ===
  
-== Implementarea constructorului fără parametri ==+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.
  
 <code cpp> <code cpp>
-CameraWeb::​CameraWeb() : ProdusComercial(),​ PiesaElectronica()+#pragma once 
 +#include <​cstring>​ 
 +#include <​iostream>​ 
 + 
 +class Locuinta
 { {
- rezolutie = 0+protected: // specificatorul de acces protected 
- culoare ​nullptr+ 
-}+ float pret
 + char* adresa; 
 + 
 +public: 
 + 
 + Locuinta();​ 
 + Locuinta(const float& pret, const char* adresa); 
 + Locuinta(const Locuinta&​ locuinta);​ 
 + Locuinta&​ operator=(const Locuinta&​ locuinta);​ 
 + ~Locuinta();​ 
 + 
 + float getPret() const; 
 + char* getAdresa() const; 
 + 
 + void setPret(const float& pret); 
 + void setAdresa(const char* adresa); 
 + 
 + friend std::​ostream&​ operator<<​(std::​ostream&​ out, const Locuinta&​ locuinta)
 +};
 </​code>​ </​code>​
  
-<note tip>Se poate observa că elementul de noutate îl constituie apelul a **doi** constructori, ​în loc de unulîn **lista de inițializare** a constructorului fără parametri din clasa **CameraWeb**.</​note>​+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**.
  
-== Implementarea ​constructorului cu parametri ​==+=== Implementarea ​metodelor și a funcțiilor friend în clasa derivată ===
  
-În mod asemănător vom proceda ​și în cazul constructorului cu toți parametrii ​din clasa **CameraWeb** după cum urmează în implementarea ​de mai jos.+În continuare vom prezenta modul în care trebuiesc implementate toate funcționalitățile clasei **Apartament** astfel încât relația de **"​is-a"​** să fie satisfăcută și să reutilizăm codul din clasa **Locuinta**. 
 + 
 +== Implementarea constructorilor clasei derivate == 
 + 
 +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 ș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**.
  
 <code cpp> <code cpp>
-CameraWeb::CameraWeb(const float& pret, const char* brand, const int& rezolutie, const char* culoare) : ProdusComercial(pret), PiesaElectronica(brand)+Apartament::Apartament() : Locuinta()
 { {
- if (rezolutie <= 0) + numarCamere ​= 0; 
-+ numeProprietar ​= nullptr;
- this->​rezolutie ​= 0; +
- +
- else +
-+
- this->​rezolutie = rezolutie;​ +
-+
- +
- if (culoare != nullptr+
-+
- this->​culoare = new char[strlen(culoare) + 1]; +
- strcpy(this->​culoare,​ culoare); +
-+
- else +
-+
- this->​culoare = nullptr; +
- }+
 } }
 </​code>​ </​code>​
  
-== Implementarea ​constructorului de copiere ==+<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** a constructorului ​**clasei derivate**. Astfel, am **reutilizat** codul din **clasa părinte** printr-un simplu apel, asigurâ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>​ 
 + 
 +În continuare vom implementa constructorul cu parametri pentru clasa copil urmând același principiu ca la constructorul fără parametri.
  
 <code cpp> <code cpp>
-CameraWeb::CameraWeb(const ​CameraWebcameraWeb) : ProdusComercial(cameraWeb)PiesaElectronica(cameraWeb)+Apartament::Apartament(const ​floatpret, const char* adresa, const int& numarCamere,​ const char* numeProprietar) : Locuinta(pretadresa)
 { {
- if (cameraWeb.rezolutie <0) + this->​numarCamere ​numarCamere;
-+
- rezolutie = 0; +
-+
- else +
-+
- rezolutie = cameraWeb.rezolutie; +
- }+
  
- if (cameraWeb.culoare ​!= nullptr)+ if (numeProprietar ​!= nullptr)
  {  {
- culoare ​= new char[strlen(cameraWeb.culoare) + 1]; + this->​numeProprietar ​= new char[strlen(numeProprietar) + 1]; 
- strcpy(culoarecameraWeb.culoare);+ strcpy(this->​numeProprietarnumeProprietar);
  }  }
  else  else
  {  {
- culoare ​= nullptr;+ this->​numeProprietar ​= nullptr;
  }  }
 } }
 </​code>​ </​code>​
  
-<​note>​Datorită relației de tip **"​is-a"​**,​ putem trimite un obiect de tip **CameraWeb** ca parametru în constructorii de copiere ai **claselor părinte**. Acest proces, cunoscut sub numele ​de **upcasting**,​ permite tratarea unui **obiect derivat** (de tip **CameraWeb**) ca fiind de tipul **clasei părinte**. **Upcasting-ul** este realizat **automat** de către **compilator** și este esențial în **moștenire** ​pentru ​a permite **utilizarea obiectelor derivate** în locul** obiectelor de tipul clasei de bază**. Prin acest mecanism, putem să **extindem** funcționalitățile **clasei ​de bază** și să reutilizăm codul, ​**fără** a rescrie funcționalitățile în **clasa derivată**,​ ceea ce face codul mai flexibil și mai ușor de întreținut.</​note>​+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**).
  
-=== Implementarea operatorului ​de asignare ​în clasa derivată ===+<note warning>​Constructorul cu parametri din **clasa derivată** 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>​
  
-Operatorul de asignare nefiind un constructor **nu** are **listă de inițializare**,​ prin urmare suntem **nevoiți** să apelăm explicit operatorii ​de asignare din **superclase** înainte de a ne ocupa de zona de memorie a **clasei ​copil**, după cum urmează mai jos.+În manieră similară se implementează și constructorul ​de copiere al clasei ​derivate.
  
 <code cpp> <code cpp>
-CameraWeb&​ CameraWeb::operator=(const ​CameraWebcameraWeb)+Apartament::Apartament(const ​Apartamentapartament) : Locuinta(apartament)
 { {
- if (this == &​cameraWeb) + numarCamere ​apartament.numarCamere;
-+
- return *this; +
-+
- +
- ProdusComercial::​operator=(cameraWeb);​ +
- PiesaElectronica::​operator=(cameraWeb);+
  
- if (culoare ​!= nullptr)+ if (apartament.numeProprietar ​!= nullptr)
  {  {
- delete[] culoare+ numeProprietar = new char[strlen(apartament.numeProprietar) + 1]; 
- + strcpy(numeProprietar,​ apartament.numeProprietar);
- +
- if (cameraWeb.rezolutie <= 0) +
-+
- rezolutie = 0;+
  }  }
  else  else
  {  {
- rezolutie ​cameraWeb.rezolutie;+ numeProprietar ​nullptr;
  }  }
 +}
 +</​code>​
  
- if (cameraWeb.culoare != nullptr) +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.
-+
- culoare = new char[strlen(cameraWeb.culoare) + 1]; +
- strcpy(culoarecameraWeb.culoare); +
-+
- else +
-+
- culoare = nullptr; +
- }+
  
- return *this; +== Implementarea destructorului în clasa derivată ==
-+
-</​code>​+
  
-=== Implementarea destructorului în clasa derivată ===+Destructorul clasei derivate se implementează la fel ca un destructor obișnuit, adică vom elibera memoria alocată dinamic pentru membrii care sunt de tip pointer.
  
 <code cpp> <code cpp>
-CameraWeb::~CameraWeb()+Apartament::~Apartament()
 { {
- if (culoare ​!= nullptr)+ if (numeProprietar ​!= nullptr)
  {  {
- delete[] ​culoare;+ delete[] ​numeProprietar;
  }  }
 } }
 </​code>​ </​code>​
  
-<note warning>Reamintim faptul că în destructorul clasei derivate **nu vom apela explicit** destructorii claselor ​părinte, acest lucru fiind realizat **automat** de către **compilator** în momentul în care durata ​de viață a obiectului de tipul **clasei derivate** se încheie.</​note>​+<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>​
  
-=== Implementarea operatorului ​<< ​în clasa derivată ​===+== Implementarea operatorului ​de asignare ​în clasa derivată ==
  
-Fiind declarat ​ca funcție ​**friend** în superclase, ​**operatorul %%<<​%%** nu este moștenit implicit în **clasa derivată**. Prin urmare, vom apela explicit ​**operatorul %%<<​%%** din fiecare clasă părinte în parte după cum urmează.+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.
  
 <code cpp> <code cpp>
-std::ostream& ​operator<<(std::​ostream&​ out, const CameraWebcameraWeb)+Apartament&​ Apartament::operator=(const ​Apartamentapartament)
 { {
- operator<<​(out, (ProdusComercial&)cameraWeb)+ if (this == &apartament) 
- operator<<​(out,​ (PiesaElectronica&​)cameraWeb);​+
 + return *this
 + }
  
- out << "​Rezolutia camerei web este" << cameraWeb.rezolutie << " pixeli\n"​+ this->​Locuinta::​operator=(apartament)// se apeleaza operatorul de asignare din clasa parinte 
- out << "​Culoarea camerei web este: ";+ /​*(Locuinta&​)(*this) = apartament// este echivalent cu linia de mai sus doar ca este o alta forma de apel*/
  
- if (cameraWeb.culoare ​!= nullptr)+ numarCamere = apartament.numarCamere;​ 
 + 
 + if (apartament.numeProprietar ​!= nullptr)
  {  {
- out << cameraWeb.culoare << '​\n'​;+ numeProprietar = new char[strlen(apartament.numeProprietar) + 1]; 
 + strcpy(numeProprietar,​ apartament.numeProprietar);
  }  }
  else  else
  {  {
- out << "​N/​A\n"​;+ numeProprietar = nullptr;
  }  }
  
- return ​out;+ return ​*this;
 } }
 </​code>​ </​code>​
  
-==== Agregare ====+== Implementarea operatorului << în clasa derivată ​==
  
-**Agregarea** presupune ca într-o clasă să avem unul sau mai multe **atribute** de tipul **altei clase**. Când spunem **agregare** acest lucru este echivalent cu relația de "​has-a"​ sau "​has-many" ​în funcție ​de context. +Vom prezenta ​în continuare modul de implementare ​operatorului ​de afișare pentru obiectele ​de tip **Apartament** respectând ​în continuare ​relația de **"is-a"**.
- +
-<​note>​Agregarea este de două tipuri după cum urmează: +
- +
-  * **Weak (slabă)**, ceea ce înseamnă că **existența** obiectului container **nu este condiționată ​de existența atributelor agregate**, putem lua spre exemplu un dulap care poate exista ​și fără să conțină haine. +
- +
-  * **Strong (puternică)**,​ ceea ce înseamnă că obiectul container **nu poate exista în absența obiectelor agregate**, spre exemplu o firmă nu poate exista fără angajați. +
-</​note>​ +
- +
-Ca și exemplu didactic vom crea o clasă nouă denumită **Laptop** care va conține un atribut ​de tip **CameraWeb** punând astfel ​în evidență ​relația de **"has-a"​** ​dintre două clase. Acestă agregare este de tip **weak**, deoarece un laptop poate să nu aibă cameră web.+
  
 <code cpp> <code cpp>
-#pragma once +std::​ostream&​ operator<<​(std::​ostream&​ out, const Apartament&​ apartament)
-#include "​CameraWeb.h"​ +
- +
-class Laptop+
 { {
- double pret; + operator<<​(out,​ (Locuinta&​)apartament); // chemam operatorul << din clasa parinte
- CameraWeb cameraWeb; // relatia de has-a+
  
-public:+ out << "​Numarul de camere din apartament este" << apartament.numarCamere << " ron\n";​
  
- Laptop(); + if (apartament.numeProprietar !nullptr)
- Laptop(const double& pret, const CameraWeb&​ cameraWeb);​ +
- +
- friend std::​ostream&​ operator<<​(std::​ostream&​ out, const Laptop& laptop); +
-}; +
-</​code>​ +
- +
-Iar implementările metodelor și a operatorului de afișare pentru clasa **Laptop** le putem observa în codul de mai jos. +
- +
-<code cpp> +
-Laptop::​Laptop() +
-+
- pret 0.0; +
- cameraWeb = CameraWeb();​ +
-+
- +
-Laptop::​Laptop(const double& pret, const CameraWeb&​ cameraWeb) +
-+
- if (pret <= 0.0)+
  {  {
- this->​pret = 0.0;+ out << "​Numele proprietarului este: " << apartament.numeProprietar << "​\n"​;
  }  }
  else  else
  {  {
- this->​pret = pret;+ out << "​Numele proprietarului este: N/A\n";
  }  }
- 
- this->​cameraWeb = cameraWeb; 
-} 
- 
-std::​ostream&​ operator<<​(std::​ostream&​ out, const Laptop& laptop) 
-{ 
- out << "​Pretul laptopului este: " << laptop.pret << " ron\n";​ 
- out << "​\nDetalii despre camera web a laptopului\n\n";​ 
- out << laptop.cameraWeb;​ 
  
  return out;  return out;
Line 311: Line 301:
 </​code>​ </​code>​
  
-Codul cu implementările complete ale tuturor claselor prezentate ​în acest laborator poate fi descărcat de {{:poo-is-ab:​laboratoare:​mostenire_multipla_agregare.zip|aici}}.+<note warning>​**Funcțiile friend** dintr-o clasă **nu** se moștenesc automat de către **clasa derivată**,​ motiv pentru care trebuie să apelăm explicit **operatorul %%<<​%%** definit ​în **clasa de bază**. Pentru a înțelege mai bine acest comportament,​ putem face următoarea analogieprietenii părinților voștri **nu sunt neapărat** și prietenii voștriRelația de prietenie este specifică **doar** între părinții voștri și acele persoane, iar aceasta **nu se extinde automat** asupra voastră. La fel, funcțiile **friend** sunt prietene ale **clasei părinte**, dar **nu** devin prietene implicit și pentru **clasa derivată**.</​note>​
  
-==== Namespace-uri definite de către programator ​====+==== ====
  
-**Namespace-urile (spațiile de nume)** în C++ reprezintă un mecanism fundamental care permite organizarea ​și gestionarea codului sursă. ​**Spațiile de nume** ajută la **prevenirea** coliziunilor de nume și **facilitează organizarea codului în module logice**+Acum că am înțeles conceptul ​de **moștenire** între două clase, vom putea avansa către implementarea unor **ierarhii** mai complexe începând cu următorul laborator. ​**Moștenirea** ne permite să construim structuri ​**ierarhice**în care clasele pot extinde ​și reutiliza ​funcționalități din **clasele părinte**. Astfelvom fi capabili să dezvoltăm sisteme mai robusteeficiente și ușor de întreținut, în care fiecare clasă va adăuga comportamente ​și atribute specificepăstrând în același timp funcționalitatea de bază moștenită. Aceste ierarhii de clase vor facilita gestionarea ​mai bună a codului ​și îmbunătățirea scalabilității aplicațiilor noastre.
- +
-Pentru a defini un namespace în limbajul C++ se folosește cuvântul cheie **namespace** urmat de o denumire a acestuia care **poate să lipsească**. În cazul în care **namespace-ul** nu are o denumire acesta se numește **namespace anonim** ​și poate fi utilizat doar în fișierul în care este declarat. +
- +
-<code cpp> +
-#pragma once +
- +
-namespace MathHelper  +
-+
-    int rest(const int& a, const int& b); +
-    int adunare(const int& a, const int& b); +
-    int diferenta(const int& a, const int& b); +
-    int inmultire(const int& a, const int& b); +
-    int impartire(const int& a, const int& b); +
-+
-</​code>​ +
- +
-Iar implementarea ​funcțiilor din cadrul namespace-ului se poate observa mai jos. +
- +
-<code cpp> +
-#include "​MathUtils.h"​ +
- +
-int MathHelper::​rest(const int& a, const int& b) +
-+
- return (b == 0) ? -1 : a % b; +
-+
- +
-int MathHelper::​adunare(const int& a, const int& b) +
-+
- return a + b; +
-+
- +
-int MathHelper::​diferenta(const int& a, const int& b) +
-+
- return a - b; +
-+
- +
-int MathHelper::​inmultire(const int& a, const int& b) +
-+
- return a * b; +
-+
- +
-int MathHelper::​impartire(const int& a, const int& b) +
-+
- return (b == 0) ? 0 : a / b; +
-+
-</​code>​ +
- +
-Testarea acestui namespace este facută în **funcția main** după cum urmează în codul de mai jos. +
- +
-<code cpp> +
-#include "​MathUtils.h"​ +
- +
-int main() +
-+
- int x = 10; +
- int y = 5; +
- +
- std::cout << "Suma numerelor este: " << MathHelper::​adunare(x,​ y) << '​\n';​ +
- std::cout << "​Diferenta numerelor este: " << MathHelper::​diferenta(x,​ y) << '​\n';​ +
- std::cout << "​Produsul numerelor este: " << MathHelper::​inmultire(x,​ y) << '​\n';​ +
- std::cout << "​Rezultatul impartirii numerelor este: " << MathHelper::​impartire(x,​ y) << '​\n';​ +
- std::cout << "​Restul impartirii numerelor este: " << MathHelper::​rest(x,​ y) << '​\n';​ +
- +
- return 0; +
-+
-</​code>​ +
- +
-Unul dintre avantajele majore ale **namespace-urilor** este faptul că permit definirea unor funcții cu **aceeașsemnătură**, dar cu **implementări diferite**, fără a crea ambiguitate pentru compilator. Astfelputem evita conflictele ​de numeavând libertatea de a implementa funcții similare ​în **namespace-uri diferite**. Când apelăm o funcție **specificând ​și namespace-ul**compilatorul va identifica **automat** ​funcția corectă, asigurând o structură mai clară și ușor de întreținut, mai ales în proiectele mari sau în situațiile în care folosim cu biblioteci externe.+
  
 ==== Concluzii ==== ==== Concluzii ====
  
-Închidem prin a menționa faptul că acest laborator ne-a oferit o înțelegere practică a conceptelor de **moștenire multiplă** și de **relație de agregare ("​has-a"​)** între claseevidențiind astfel ​modurile prin care putem structura relații logice șeficiente între clase +În cadrul acestui laborator, am învățat și aprofundat conceptul de **moștenire** și am văzut cum poate fi implementată între **două clase**. Am înțeles că **moștenirea** este o metodă esențială pentru a **reutiliza** și **extinde** codul existent, oferind un cadru flexibil și scalabil pentru dezvoltarea aplicațiilor **OOP**. Prin utilizarea moșteniriio **clasă derivată** poate prelua proprietățile unei **clase părinte**, oferind ​astfel ​posibilitatea de a adăuga sau modifica funcționalități specifice.
  
-Am înțeles cum putem defini clase derivate ​care extind mai multe clase de bază, oferind posibilitatea reutilizării codului din mai multe surse și creând ierarhii complexedar flexibileAm discutat despre avantajele ​și riscurile moștenirii multiple, precum posibilele conflicte de nume și gestionarea lor prin tehnici specificecum ar fi operatorii din clasele ​părinte ​și apelul lor explicit ​pentru a evita conflictele.+Un aspect important pe care l-am discutat este faptul că **funcțiile friend nu se moștenesc**. Aceste funcțiideși pot accesa membri privați sau protejați ai unei clase**nu** sunt automat apelate în **clasa derivată**Pentru a înțelege acest comportament,​ am făcut o analogie simplă: prietenii părinților voștri nu sunt în mod automat ​și prietenii voștri direcți. Astfelîn cazul în care dorim să accesăm funcționalitățile unei funcții friend dintr-o **clasă ​părinte**, va trebui **să o apelăm ​explicit** în **clasa derivată**.
  
-Relația ​de **"​has-a"​** ne-a permis ​să creăm structuri de tip containerunde o clasă conține una sau mai multe instanțe ale altei clase fără a se afla într-o ierarhie de moștenire. Astfelam putut organiza eficient ​datele ​și responsabilitățile între clasemenținând o separare clară a rolurilor.+De asemenea, am explorat rolul specificatorului ​de acces **protected**, care permite membrilor clasei ​să fie accesibili în cadrul **claselor derivate**dar să rămână inaccesibili din exterior. Această abordare oferă un echilibru între **încapsulare** și **moștenire**protejând ​datele ​interne ale **clasei părinte**dar permițând totuși **clasei copil** să le utilizeze.
  
-Am văzut cum **namespace-urile** pot ajuta la **evitarea conflictelor de nume** și ne oferă control asupra ​**identificării funcțiilor și variabilelor specifice** din cadrul unui anumit contextNamespace-urile sunt o soluție eficientă pentru organizarea coduluimai ales când avem funcții și clase cu nume **identice ca și denumiri** în proiecte mari.+Un alt concept esențial a fost utilizarea ​**listei de inițializare a constructorului** în clasele derivate. În momentul în care instanțiem un obiect din clasa derivată, trebuie să avem grijă să inițializăm corect și **membrii clasei părinte**. Aceasta se realizează prin apelarea ​**explicită** a **constructorului părinte** în **lista de inițializare a constructorului clasei derivate**. Am subliniat importanța acestui mecanismdeoarece ​**doar** constructorii pot fi apelați ​în această manieră.
  
-Am observat că funcțiile friend ​**nu** sunt moștenite în mod implicit în clasele derivatefiind necesar ​să le **apelăm explicit** ​pentru fiecare ​**clasă părinte** ​atunci când le folosim. Acest lucru ne permite să menținem controlul asupra accesului la datele ​**private** între clase, ​**fără** a extinde accesul la întreaga ierarhie.+În plus, pentru a accesa metode sau funcții din **clasa părinte** care nu sunt constructoritrebuie ​să apelăm ​**explicit** ​funcția dorită folosind sintaxa: ​**''​numeClasăPărinte::​numeMetodă()''​**. Acest apel este necesar pentru a ne asigura că executăm ​**corect** comportamentul definit în **clasa părinte** asupra obiectelor din **clasa fiu**.
  
-Aceste ​concepte ​fundamentale de moștenire multiplăagregare și gestionare a spațiului de nume ne oferă instrumente puternice pentru ​structura ​și organiza codul eficient. Aceste tehnici vor fi extrem de utile în viitoarele proiectefacilitând modularea codului ​și scalarea aplicațiilor, reducând ​în același timp redundanța.+Prin toate aceste ​concepte și tehniciam făcut un pas important în **utilizarea eficientă** ​**moștenirii** ​în limbajul C++, și suntem pregătiți să explorăm ierarhii mai complexe de clase în laboratoarele viitoare.
poo-is-ab/laboratoare/06.1730058298.txt.gz · Last modified: 2024/10/27 21:44 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