This shows you the differences between two versions of the page.
poo-is-ab:laboratoare:05 [2024/10/13 16:21] razvan.cristea0106 [Moștenirea între două clase] |
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 300: | Line 168: | ||
=== Implementarea metodelor și a funcțiilor friend în clasa derivată === | === 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 realția de **"is-a"** să fie satisfăcută și să reutilizăm codul din clasa **Locuinta**. | + | Î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 == | == Implementarea constructorilor clasei derivate == | ||
Line 339: | Line 207: | ||
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**). | 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 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> | + | <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. |