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 13:58]
razvan.cristea0106 [Obiective Specifice]
poo-is-ab:laboratoare:05 [2025/01/19 22:29] (current)
razvan.cristea0106
Line 17: Line 17:
  
 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. 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 ==== ==== Moștenirea între două clase ====
  
Line 52: Line 49:
  friend std::​ostream&​ operator<<​(std::​ostream&​ out, const Locuinta&​ locuinta);  friend std::​ostream&​ operator<<​(std::​ostream&​ out, const Locuinta&​ locuinta);
 }; };
-</​code>​ 
- 
-Iar implementările pentru funcțiile membre și cea friend le putem observa în codul de mai jos. 
- 
-<code cpp> 
-#include "​Locuinta.h"​ 
- 
-Locuinta::​Locuinta() 
-{ 
- pret = 0.0f; 
- adresa = nullptr; 
-} 
- 
-Locuinta::​Locuinta(const float& pret, const char* adresa) 
-{ 
- this->​pret = pret; 
- 
- if (adresa != nullptr) 
- { 
- this->​adresa = new char[strlen(adresa) + 1]; 
- strcpy(this->​adresa,​ adresa); 
- } 
- else 
- { 
- this->​adresa = nullptr; 
- } 
-} 
- 
-Locuinta::​Locuinta(const Locuinta&​ locuinta) 
-{ 
- pret = locuinta.pret;​ 
- 
- if (locuinta.adresa != nullptr) 
- { 
- adresa = new char[strlen(locuinta.adresa) + 1]; 
- strcpy(adresa,​ locuinta.adresa);​ 
- } 
- else 
- { 
- adresa = nullptr; 
- } 
-} 
- 
-Locuinta&​ Locuinta::​operator=(const Locuinta&​ locuinta) 
-{ 
- if (this == &​locuinta) 
- { 
- return *this; 
- } 
- 
- if (adresa != nullptr) 
- { 
- delete[] adresa; 
- } 
- 
- pret = locuinta.pret;​ 
- 
- if (locuinta.adresa != nullptr) 
- { 
- adresa = new char[strlen(locuinta.adresa) + 1]; 
- strcpy(adresa,​ locuinta.adresa);​ 
- } 
- else 
- { 
- adresa = nullptr; 
- } 
- 
- return *this; 
-} 
- 
-Locuinta::​~Locuinta() 
-{ 
- if (adresa != nullptr) 
- { 
- delete[] adresa; 
- } 
-} 
- 
-float Locuinta::​getPret() const 
-{ 
- return pret; 
-} 
- 
-char* Locuinta::​getAdresa() const 
-{ 
- return adresa; 
-} 
- 
-void Locuinta::​setPret(const float& pret) 
-{ 
- if (pret <= 0.0f) 
- { 
- return; 
- } 
- 
- this->​pret = pret; 
-} 
- 
-void Locuinta::​setAdresa(const char* adresa) 
-{ 
- if (adresa == nullptr) 
- { 
- return; 
- } 
- 
- if (this->​adresa != nullptr) 
- { 
- delete[] this->​adresa;​ 
- } 
- 
- this->​adresa = new char[strlen(adresa) + 1]; 
- strcpy(this->​adresa,​ adresa); 
-} 
- 
-std::​ostream&​ operator<<​(std::​ostream&​ out, const Locuinta&​ locuinta) 
-{ 
- out << "​Pretul locuintei este: " << locuinta.pret << " ron\n";​ 
- 
- if (locuinta.adresa != nullptr) 
- { 
- out << "​Adresa locuintei este: " << locuinta.adresa << "​\n\n";​ 
- } 
- else 
- { 
- out << "​Adresa locuintei este: inexistenta\n\n";​ 
- } 
- 
- return out; 
-} 
 </​code>​ </​code>​
  
Line 219: Line 87:
 </​code>​ </​code>​
  
-=== Relația de "​is-a"​ între două clase ===+=== 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. 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.
Line 298: Line 166:
 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**. 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 metodelor și a funcțiilor friend în clasa derivată ===
 +
 +Î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 ș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**.
 +
 +<code cpp>
 +Apartament::​Apartament() : Locuinta()
 +{
 + numarCamere = 0;
 + numeProprietar = nullptr;
 +}
 +</​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** 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>
 +Apartament::​Apartament(const float& pret, const char* adresa, const int& numarCamere,​ const char* numeProprietar) : Locuinta(pret,​ adresa)
 +{
 + this->​numarCamere = numarCamere;​
 +
 + if (numeProprietar != nullptr)
 + {
 + this->​numeProprietar = new char[strlen(numeProprietar) + 1];
 + strcpy(this->​numeProprietar,​ numeProprietar);​
 + }
 + 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**).
 +
 +<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>​
 +
 +În manieră similară se implementează și constructorul de copiere al clasei derivate.
 +
 +<code cpp>
 +Apartament::​Apartament(const Apartament&​ apartament) : Locuinta(apartament)
 +{
 + numarCamere = apartament.numarCamere;​
 +
 + if (apartament.numeProprietar != nullptr)
 + {
 + numeProprietar = new char[strlen(apartament.numeProprietar) + 1];
 + strcpy(numeProprietar,​ apartament.numeProprietar);​
 + }
 + else
 + {
 + numeProprietar = nullptr;
 + }
 +}
 +</​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.
 +
 +== 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>
 +Apartament::​~Apartament()
 +{
 + if (numeProprietar != nullptr)
 + {
 + 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>​
 +
 +== Implementarea operatorului de asignare în clasa derivată ==
 +
 +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>
 +Apartament&​ Apartament::​operator=(const Apartament&​ apartament)
 +{
 + if (this == &​apartament)
 + {
 + return *this;
 + }
 +
 + this->​Locuinta::​operator=(apartament);​ // se apeleaza operatorul de asignare din clasa parinte
 + /​*(Locuinta&​)(*this) = apartament; // este echivalent cu linia de mai sus doar ca este o alta forma de apel*/
 +
 + numarCamere = apartament.numarCamere;​
 +
 + if (apartament.numeProprietar != nullptr)
 + {
 + numeProprietar = new char[strlen(apartament.numeProprietar) + 1];
 + strcpy(numeProprietar,​ apartament.numeProprietar);​
 + }
 + else
 + {
 + numeProprietar = nullptr;
 + }
 +
 + return *this;
 +}
 +</​code>​
 +
 +== Implementarea operatorului << în clasa derivată ==
 +
 +Vom prezenta în continuare modul de implementare a operatorului de afișare pentru obiectele de tip **Apartament** respectând în continuare relația de **"​is-a"​**.
 +
 +<code cpp>
 +std::​ostream&​ operator<<​(std::​ostream&​ out, const Apartament&​ apartament)
 +{
 + operator<<​(out,​ (Locuinta&​)apartament);​ // chemam operatorul << din clasa parinte
 +
 + out << "​Numarul de camere din apartament este: " << apartament.numarCamere << " ron\n";​
 +
 + if (apartament.numeProprietar != nullptr)
 + {
 + out << "​Numele proprietarului este: " << apartament.numeProprietar << "​\n";​
 + }
 + else
 + {
 + out << "​Numele proprietarului este: N/​A\n";​
 + }
 +
 + return out;
 +}
 +</​code>​
 +
 +<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 analogie: prietenii părinților voștri **nu sunt neapărat** și prietenii voștri. Relaț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>​
 +
 +==== ====
 +
 +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**. Astfel, vom fi capabili să dezvoltăm sisteme mai robuste, eficiente și ușor de întreținut,​ în care fiecare clasă va adăuga comportamente și atribute specifice, pă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.
 +
 +==== Concluzii ====
 +
 +Î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ștenirii,​ o **clasă derivată** poate prelua proprietățile unei **clase părinte**, oferind astfel posibilitatea de a adăuga sau modifica funcționalități specifice.
 +
 +Un aspect important pe care l-am discutat este faptul că **funcțiile friend nu se moștenesc**. Aceste funcții, deș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ă**.
 +
 +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.
 +
 +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 mecanism, deoarece **doar** constructorii pot fi apelați în această manieră.
 +
 +În plus, pentru a accesa metode sau funcții din **clasa părinte** care nu sunt constructori,​ trebuie 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**.
 +
 +Prin toate aceste concepte și tehnici, am făcut un pas important în **utilizarea eficientă** a **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/05.1728817105.txt.gz · Last modified: 2024/10/13 13:58 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