Differences

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

Link to this comparison view

poo-is-ab:laboratoare:09 [2024/12/04 11:28]
razvan.cristea0106 [Vector de obiecte neomogene]
poo-is-ab:laboratoare:09 [2025/09/23 20:14] (current)
razvan.cristea0106
Line 1: Line 1:
-===== Laborator ​10 Vectori de obiecte neomogene ​=====+===== Laborator ​09 Clase abstracte și interfețe ​=====
  
 **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ă ​un vector de obiecte neomogene +  * recunoască ​și să definească o funcție virtuală 
-  * știe cum să aloce și să dezaloce memoria pentru un astfel de vector +  * recunoască și să definească o funcție virtual pură 
-  * înțeleagă importanța utilizării ​acestui vector corelat cu noțiunea de suprascriere +  * înțeleagă importanța utilizării ​claselor abstracte și a interfețelor în diferite contexte ale POO 
-  * prelucreze o colecție neomogenă de date +  * înțeleagă conceptul ​de "late binding"​ corelat cu run time polymorphism 
-  * supraîncarce diverși operatori pentru clasa de bază+  * înțeleagă diferența dintre supraîncărcare ​și suprascriere
  
 ==== Introducere ==== ==== Introducere ====
  
-În acest laborator vom aprofunda utilizarea ​**claselor abstracte** și a **interfețelor**, concentrându-ne pe un aspect important al **POO** și anume gestionarea colecțiilor de date **neomogene**. Dacă până acum am lucrat cu **pointeri** pentru ​manipula obiecte derivate din **clase abstracte** sau **interfețe**, acum vom explora cum putem organiza ​și gestiona aceste obiecte utilizând colecții de datecum ar fi **vectorii**. Pentru o mai bună înțelegere a noțiunilor legate ​de **clase abstracte** și **interfețe** se recomandă citirea [[poo-is-ab:​laboratoare:​08|laboratorului 9]].+Până în prezent, am explorat conceptul de **polimorfism timpuriu (early polymorphism sau compile-time polymorphism)**, care se manifestă atunci când **funcții/metode** cu **același nume** sunt **diferențiate prin numărul sau tipul parametrilor**. Acest lucru stă la baza conceptului de **supraîncărcare ​funcțiilor (overloading)**, care permite definirea mai multor variante ale unei funcții în cadrul ​**aceleiași clase**fiecare având un **comportament specific** în funcție de **semnătura sa**. Acest tip de polimorfism este decis la **compilare**, ceea ce înseamnă că alegerea funcției care va fi apelată ​se face de către **compilator** pe baza tipului argumentelor oferite.
  
-==== Vector ​de obiecte neomogene ====+De exemplu, dacă avem o funcție **''​adunare''​** supraîncărcată pentru a lucra cu numere întregi și cu numere reale, **compilatorul** determină **automat** care versiune a funcției urmează să fie apelată, în funcție ​de **tipul** datelor primite ca argumente. Avantajul acestui tip de polimorfism este **viteza de execuție**,​ deoarece decizia a fost deja luată **înainte** ca programul să ruleze.
  
-Un **vector de obiecte neomogene** este o structură de date utilizată pentru a stoca și gestiona obiecte de tipuri diferitedar care împărtășesc o relație comună definită printr-o ​**clasă abstractă** sau o **interfață**. Această abordare este posibilă datorită conceptului ​de **late binding**, care permite apelarea metodelor potrivite pentru fiecare pointer în funcție de tipul său concret, stabilit la momentul execuției +Cu toate acestea, există cazuri în care decizia pentru funcția/​metoda ce trebuie apelată ​**nu** poate fi luată de către compilatorci doar în timpul execuției. Acest procedeu este cunoscut sub denumirea de **polimorfism întârziat (late binding sau run-time polymorphism)**. Acest concept este strâns legat de **suprascrierea funcțiilor (overriding)** și de utilizarea mecanismelor ​de **moștenire** și **funcții virtuale** în C++.
  
-Acest tip de vector **nu** stochează **direct** obiectele, ci **pointeri la obiecte**. Acest lucru asigură flexibilitatea de a lucra cu tipuri diferite de obiecte, cu condiția ca acestea să implementeze toate **metodele virtuale** sau **virtual pure** definite în **clasa abstractă** sau **interfața** comună.  ​+==== Overloading vs Overriding ====
  
-Avantajele utilizării unui vector de obiecte neomogene sunt: +În continuare vom prezenta un tabel care pune în evidență diferențele clare între cele două forme de polimorfism.
-  +
-   * **Polimorfism**:​ Gestionarea comportamentelor variate ale obiectelor printr-o singură interfață+
  
-   * **Scalabilitate**: Adăugarea de noi tipuri de obiecte devine ușoară, fără ​a modifica codul existent+^        ​**Overloading (Compile-Time Polymorphism)**      ​^ ​       ​**Overriding (Run-Time Polymorphism)** ​          ^ 
 +| Se aplică funcțiilor/​metodelor din **aceeași clasă**       | Apare în ierarhiile de clase (**moștenire**) ​           | 
 +| Funcțiile/​metodele au **același nume**dar **diferă** prin **numărul sau tipul parametrilor** | O funcție/​metodă dintr-o clasă derivată **suprascrie** comportamentul unei **funcții virtuale** din clasa de bază | 
 +| Alegerea funcției este cută de către **compilato**pe baza semnăturii acesteia | Alegerea funcției care va fi apelată este făcută la **momentul execuției**,​ în funcție de **tipul dinamic** al obiectului | 
 +| **Nu** necesită funcții virtuale/​virtual pure.                               | **Necesită** utilizarea funcțiilor virtuale/​virtual pure în clasa de bază. | 
 +| Este o formă de polimorfism timpuriu (**early binding**) ​   | Este o formă de polimorfism întârziat (**late binding**) ​ | 
 +| Are un impact **redus** asupra performanței,​ deoarece decizia se ia la compilare | Are un impact **ușor mai mare** asupra performanței,​ deoarece decizia se ia în timpul execuției |
  
-Astfel, vectorii de obiecte neomogene reprezintă un instrument esențial în gestionarea colecțiilor de date diverse, utilizând avantajele oferite de **polimorfism** și de principiile ​**POO**. Această tehnică facilitează crearea ​de sisteme **modulare** ​și **extensibile**, esențiale pentru ​aplicații complexe.+**Suprascrierea** (**overriding**) implică păstrarea aceluiași antent al funcției – adică același tip de return ​și aceeași semnătura (numele și lista de parametri) – dar permite redefinirea completă a comportamentului acesteia într-o ​**clasă derivată**. Prin această abordareo funcție virtuală din clasa de bază poate fi adaptată pentru a răspunde nevoilor specifice ale clasei derivate. Acest proces este esențial pentru ​**polimorfismul la timp de execuție**, oferind flexibilitate și posibilitatea de a extinde funcționalitățile într-un mod dinamic.
  
-Pentru a putea construi un vector ​de obiecte neomogene mai întâi avem nevoie de o ierarhie de clase iar exemplul din acest laboartor este realizat cu ajutorul claselor ​**ProdusElectronic** care este o interfață și respectiv ​**Laptop** și **SmartPhone** ​care sunt clase concrete ce implementează interfața anterior menționată.+Pe de altă parte, ​**supraîncărcarea** (**overloading**) presupune existența mai multor funcții cu același nume în cadrul aceleiași clase, dar care diferă prin numărul sau tipul parametrilor. Deși semnăturile ​sunt distincte, logica generală a funcțiilor rămâne similară, acestea fiind utilizate pentru ​oferi funcționalități variate în contexte diferite. Alegerea variantei corespunzătoare este realizată la timpul de compilare, ceea ce asigură o execuție rapidă.
  
-Interfața **ProdusElectronic** conține ​metodele ​virtual ​pure ''​**getPret**'' ​și respectiv ''​**getProducator**'' ​și un destructor ​virtual pur.+<note important>​Astfel **suprascrierea** permite modificarea profundă a comportamentului unei funcții în cadrul unei **ierarhii de clase**, în timp ce **supraîncărcarea** oferă posibilitatea reutilizării aceluiași nume de funcție pentru ​gestiona scenarii variate, păstrând însă consistența logicii.</​note>​ 
 + 
 +==== Funcții virtuale ==== 
 + 
 +Funcțiile ​**virtuale** reprezintă principalul mecanism prin care putem evidenția conceptul de **suprascriere (override)** în programarea orientată pe obiecte. Atunci când o clasă ​conține ​**cel puțin o metodă ​virtuală**, pentru acea clasă este generată o structură denumită ​**tabelă de pointeri la funcții virtuale (vtable)**. Această tabelă stochează adresele funcțiilor virtuale definite în clasa de bază, respectiv în clasele derivate.  
 + 
 +Fiecare obiect al unei clase care derivă din clasa de bază cu metode virtuale va primi un **pointer la vtable**, ceea ce permite **legarea dinamică** a funcțiilor. Prin urmare, în momentul în care se apelează o **funcție virtuală**,​ sistemul determină în momentul execuției care implementare specifică (din clasa de bază sau din clasa derivată) trebuie utilizată, în funcție de **tipul dinamic al obiectului**. 
 + 
 +Acest mecanism stă la baza conceptului de **late binding** sau **legare dinamică**,​ care presupune că alegerea funcției ce urmează să fie executată **nu** este stabilită în timpul compilării,​ ci **în timpul execuției**,​ oferind astfel o flexibilitate sporită ​și suport pentru polimorfismul la timp de execuție. 
 + 
 +<note warning>**Stabilirea tipului de date** în cazul **funcțiilor virtuale** se aplică exclusiv **pointerilor la obiecte**, **nu** și obiectelor utilizate direct (prin valoare). Acest lucru se datorează faptului că **legarea dinamică** (**late binding**) funcționează doar atunci când accesul la funcțiile virtuale se face prin intermediul unui pointer.</​note>​ 
 + 
 +<​note>​În C++ există **două** tipuri de funcții virtuale și anume: **funcții virtuale** și **funcții virtual pure**. Diferența între cele două tipuri constă în faptul că o funcție ​virtual pură **nu** are implementare în clasa de bază.</​note>​ 
 + 
 +În limbajul C++ pentru a declara o funcție virtuală într-o clasă se utilizeaza cuvântul cheie **virtual**. Dacă am spus funcție virtuală asta înseamnă că în clasa de bază acea funcție va avea implementare. Ca și exemplu vom propune clasele **Animal** și **Caine**.
  
 <code cpp> <code cpp>
-class ProdusElectronic+class Animal
 { {
 public: public:
  
- virtual ~ProdusElectronic() = 0; // destructor virtual pur + void afisare() const;
- +
- virtual float getPret() const = 0; +
- virtual char* getProducator() const = 0;+
 }; };
 +
 +void Animal::​afisare() const
 +{
 + std::cout << "Sunt un animal!\n";​
 +}
 </​code>​ </​code>​
  
-<note important>​În C++, unui **destructor virtual pur** trebuie să îi oferim o **implementare** deoarece va fi **întotdeauna apelat** atunci când un **obiect derivat** este **distrus**. Această cerință se bazează pe mecanismul de distrugere a obiectelor, care implică apelarea destructorilor **în ordine inversă** a **constructorilor**,​ inclusiv pentru ​clasa de bază.</​note>​ +Și respectiv ​clasa **Caine**.
- +
-Prin urmare vom furniza o implementare pentru destructorul clasei ​**ProdusElectronic** după cum urmează în secțiunea de cod de mai jos.+
  
 <code cpp> <code cpp>
-ProdusElectronic::​~ProdusElectronic()+class Caine public Animal
 { {
- // chiar daca nu scriem nimic este necesar sa existe pentru a putea functiona corect dezalocarea memoriei+public: 
 + 
 + void afisare() const; 
 +}; 
 + 
 +void Caine::​afisare() const 
 +
 + std::cout << "Sunt un caine!\n";​
 } }
 </​code>​ </​code>​
  
-Clasele ​**Laptop** și **SmartPhone** pot fi observate mai jos.+În funcția ​**main** vom demostra faptul că deși câinele este un animal se vor apela metodele specifice tipurilor de date din cauza faptului că în clasa Animal metoda ​**''​afisare''​** nu este declarată ca fiind virtuală.
  
 <code cpp> <code cpp>
-class Laptop : public ProdusElectronic+int main()
 { {
- float pret+ Animal animal
- char* producator;+ animal.afisare()// se apeleaza afisarea din Animal
  
-public:+ Caine caine; 
 + caine.afisare();​ // se apeleaza afisarea din Caine
  
- Laptop(); + animal = caine
- Laptop(const float& pret, const char* producator)+ animal.afisare(); // se apeleaza afisarea din Animal
- ~Laptop();+
  
- float getPret() const override+ Animal* pAnimal = new Animal(); 
- chargetProducator() const override+ pAnimal->​afisare();​ // se apeleaza afisarea din Animal 
-};+ 
 + CainepCaine = new Caine(); 
 + pCaine->​afisare()// se apeleaza afisarea din Caine 
 + 
 + Animal* pa = pCaine; 
 + pa->​afisare();​ // se apeleaza afisarea din Animal desi ar fi trebui sa se apeleze cea din Caine 
 + 
 + delete pAnimal; 
 + delete pCaine; 
 + 
 + return 0; 
 +}
 </​code>​ </​code>​
 +
 +Soluția este să marcăm metoda de **''​afisare''​** ca fiind virtuală pentru a putea permite **legături întârziate**.
  
 <code cpp> <code cpp>
-class SmartPhone : public ProdusElectronic+class Animal
 { {
- float pret; 
- char* producator; 
- 
 public: public:
  
- SmartPhone();​ + virtual void afisare() const; ​// metoda afisare este acum virtuala ceea ce inseamna ca vom putea avea legari dinamice
- SmartPhone(const float& pret, const char* producator);​ +
- ~SmartPhone();​ +
- +
- float getPret() const override; +
- char* getProducator() const override;+
 }; };
 </​code>​ </​code>​
  
-Iar implementările metodelor celor două clase se pot observa în blocurile de mai jos.+Iar dacă vom testa acum codul din **funcția main** vom vedea că se va produce o **legare dinamică** atunci când vom chema metoda **''​afisare''​** prin intermediul pointerului **''​pa''​**.
  
 <code cpp> <code cpp>
-Laptop::​Laptop()+int main()
 { {
- pret = 0.0f+ Animal animal
- producator = nullptr; + animal.afisare()// se apeleaza afisarea din Animal
-}+
  
-Laptop::​Laptop(const float& pret, const char* producator+ Caine caine; 
-{ + caine.afisare(); // se apeleaza afisarea din Caine 
- this->​pret ​pret;+ 
 + animal ​caine; 
 + animal.afisare()// se apeleaza afisarea din Animal deoarece animal nu este pointer
  
- if (producator !nullptr+ Animal* pAnimal ​new Animal(); 
- + pAnimal->afisare(); // se apeleaza afisarea din Animal 
- this->producator ​= new char[strlen(producator+ 1]+ 
- strcpy(this->producator, producator); + Caine* pCaine ​= new Caine(); 
- } + pCaine->afisare(); // se apeleaza afisarea din Caine 
- else + 
- + Animal* pa = pCaine; 
- this->producator = nullptr+ pa->afisare()// se apeleaza afisarea din Caine 
- }+ 
 + delete pAnimal; 
 + delete pCaine; 
 + 
 + return 0;
 } }
 +</​code>​
 +
 +Pentru a fi și mai riguroși putem marca în **clasa derivată** metoda de **''​afisare''​** cu **override** pentru a anunța compilatorul că metoda care provine din **superclasă** urmează să fie **suprascrisă**.
  
-Laptop::~Laptop()+<code cpp> 
 +class Caine public Animal
 { {
- if (producator != nullptr) +public:
-+
- delete[] producator;​ +
-+
-}+
  
-float Laptop::​getPret() const+ void afisare() const override; // acum este mentionat explicit faptul ca metoda din Caine o va suprascrie pe cea din Animal 
 +}; 
 +</​code>​ 
 + 
 +<note important>​Cuvântul cheie **override** în **C++** este folosit pentru a specifica în mod **explicit** că o funcție membră dintr-o **clasă derivată** suprascrie o metodă **virtuală** din **clasa de bază**. Acest mecanism oferă mai multă siguranță în ceea ce privește **suprascrierea** funcțiilor și ajută la prevenirea **erorilor de programare**.</​note>​ 
 + 
 +==== Clase abstracte ==== 
 + 
 +În limbajul C++, o **clasă abstractă** este o clasă care conține **cel puțin** o **metodă virtuală pură**. **Metoda virtuală pură** este o funcție declarată în clasa de bază, dar care **nu are o implementare** în această clasă, **obligând** astfel **clasele derivate** să o **suprascrie**. O **clasă abstractă** este utilizată pentru a defini un comportament **general** care trebuie să fie specificat în **mod detaliat** în **clasele derivate**. 
 + 
 +<note warning>​O clasă abstractă **nu** poate fi folosită pentru a crea **obiecte**. Este concepută să fie doar o bază pentru alte clase care o vor moșteni. În schimb se pot instanția **pointeri** de tipul acestei clase care să fie inițializati cu ajutorul **claselor derivate**. Un alt aspect ce trebuie menționat este faptul că **orice** clasă derivată dintr-o clasă abstractă **trebuie** să implementeze **toate** metodele virtual pure, altfel va deveni ea însăși o **clasă abstractă**.</​note>​ 
 + 
 +O clasă abstractă poate avea membri și metode precum constructori,​ getteri, setteri și destructor care vor fi apelate în clasele derivate. În continuare vom prezenta modul în care putem pune în evidență conceptul de **late binding** transformând clasa **Animal** într-o clasă abstractă. 
 + 
 +<code cpp> 
 +class Animal
 { {
- return pret+ char* nume
-}+ int varsta;
  
-char* Laptop::​getProducator() const+public: 
 + 
 + Animal();​ 
 + Animal(const ​char* nume, const int& varsta); 
 + Animal(const Animal& animal); 
 + ~Animal();​ 
 + 
 + char* getNume() const
 + int getVarsta() const; 
 + 
 + virtual void afisare() const = 0; // metoda virtual pura 
 +}; 
 +</​code>​ 
 + 
 +Iar clasa **Caine** moștenește clasa **Animal** și implementează metoda virtual pură din clasa de bază. 
 + 
 +<code cpp> 
 +class Caine : public Animal
 { {
- return producator+ char* rasa; 
-}+ 
 +public: 
 + 
 + Caine(); 
 + Caine(const char* nume, const int& varsta, const char* rasa); 
 + Caine(const Caine& animal); 
 + ~Caine();​ 
 + 
 + void afisare() const override; // implementam metoda din clasa de baza 
 +};
 </​code>​ </​code>​
 +
 +Iar implementările metodelor din clasa **Caine** se pot observa mai jos.
  
 <code cpp> <code cpp>
-SmartPhone::SmartPhone()+Caine::Caine() : Animal()
 { {
- pret = 0.0f; + rasa = nullptr;
- producator ​= nullptr;+
 } }
  
-SmartPhone::SmartPhone(const ​floatpret, const char* producator)+Caine::Caine(const ​char* nume, const intvarsta, const char* rasa) : Animal(nume,​ varsta)
 { {
- this->​pret = pret; + if (rasa != nullptr)
- +
- if (producator ​!= nullptr)+
  {  {
- this->producator ​= new char[strlen(producator) + 1]; + this->rasa = new char[strlen(rasa) + 1]; 
- strcpy(this->​producatorproducator);+ strcpy(this->​rasarasa);
  }  }
  else  else
  {  {
- this->producator ​= nullptr;+ this->rasa = nullptr;
  }  }
 } }
  
-SmartPhone::~SmartPhone()+Caine::Caine(const Caine& caine) : Animal(caine)
 { {
- if (producator ​!= nullptr)+ if (caine.rasa ​!= nullptr) ​ 
 +
 + rasa = new char[strlen(caine.rasa) + 1]; 
 + strcpy(rasa,​ caine.rasa);​ 
 +
 + else 
  {  {
- delete[] producator;+ rasa = nullptr;
  }  }
 } }
  
-float SmartPhone::getPret() const+Caine::~Caine()
 { {
- return pret;+ if (rasa != nullptr) 
 +
 + delete[] rasa; 
 + }
 } }
  
-char* SmartPhone::getProducator() const+void Caine::afisare() const 
 { {
- return producator;+ std::cout << "Nume: " << (getNume() ? getNume() : "​Anonim"​) << '​\n';​ 
 + std::cout << "​Varsta:​ " << getVarsta() << " ani\n";​ 
 + std::cout << "Rasa: " << (rasa ? rasa : "​Necunoscuta"​) << "​\n\n"​;
 } }
 </​code>​ </​code>​
  
-Pentru a putea declara ​un **vector ​de obiecte neomogene** vom folosi un **dublu pointer** la tipul interfeței **ProdusElectronic** pe care îl vom aloca dinamic folosind **operatorul new** după cum urmează.+Iar mai jos propunem ​un exemplu ​de testare a acestor clase.
  
 <code cpp> <code cpp>
-#include "​Laptop.h"​ 
-#include "​SmartPhone.h"​ 
- 
 int main() int main()
 { {
- int nrProduse = 5+ Caine caine("​Zeus",​ 3, "​labrador"​)
- ProdusElectronic** produse = new ProdusElectronic * [nrProduse]; // vector de obiecte neomogene+ caine.afisare(); // valid
  
- produse[0] ​= new Laptop(5499.99f, "​HP"​); // late binding + Animal* pAnimal ​= new Caine(); 
- produse[1] = new SmartPhone(2499.99f, "​Motorola"​);​ + pAnimal->​afisare(); // valid deoarece metoda afisare este virtuala
- produse[2] = new Laptop(3000.0f,​ "​Asus"​);​ +
- produse[3] = new Laptop(7999.99f,​ "​Lenovo"​);​ +
- produse[4] = new SmartPhone(3999.99f,​ "​Apple"​);+
  
- for (int i = 0i < nrProduse; i++) + delete pAnimal// incorect nu se cheama si destructorul din Caine, memory leak 
- { + 
- std::cout << ​"Pretul produsului este: " ​<< produse[i]->​getPret() << '​\n';​ + pAnimal = &caine; // valid datorita relatiei de tip "is-a
- std::cout << "​Numele producatorului este: " << produse[i]->getProducator() << "​\n\n"​; + pAnimal->afisare(); // valid
- }+
  
  return 0;  return 0;
Line 205: Line 283:
 </​code>​ </​code>​
  
-Tot ceea ce mai trebuie să facem acum este să dezalocăm memoria corespunzător. Dacă mai întâi ​am alocat vectorul ​și apoi am alocat câte un slot de memorie pentru fiecare pointer ​din vector ​la eliberarea memoriei vom porni în ordinea inversă după cum se poate observa în codul de mai jos.+<note warning>​În momentul de față, codul de test din **funcția main** produce un **memory leak** deoarece, în mod normal, la eliberarea memoriei, destructorii ar trebui ​să se apeleze în ordinea inversă apelării constructorilor. Dacă, în cazul nostru, ​mai întâi ​s-a apelat constructorul fără parametri pentru clasa **Animal** ​și apoi cel din clasa **Caine**, atunci, ​la **distrugerea** obiectului, se va apela destructorul **Animal** fără a-l apela și pe cel al clasei **Caine**.
  
-<code cpp> +Acest comportament apare deoarece destructorul din clasa de bază **nu** este declarat **virtual**În cazul ierarhiilor de clase care folosesc **metode virtuale** sau **metode virtual pure**, este absolut necesar ca **destructorul clasei de bază** să fie declarat **virtual**,​ astfel încât să asigure eliberarea corectă a resurselorCând destructorul este declarat **virtual**,​ el va garanta apelarea în mod **corect** a destructorilor pentru toate **clasele derivate** implicate.</​note>​
-#include "​Laptop.h" +
-#include "​SmartPhone.h"+
  
-int main()+Așadar pentru a elibera memoria **corect** vom declara destructorul clasei **Animal** ca **funcție virtuală**. 
 + 
 +<code cpp> 
 +class Animal
 { {
- int nrProduse = 5+ char* nume
- ProdusElectronic** produse = new ProdusElectronic * [nrProduse];+ int varsta;
  
- produse[0] = new Laptop(5499.99f,​ "​HP"​);​ // late binding +public:
- produse[1] = new SmartPhone(2499.99f,​ "​Motorola"​);​ +
- produse[2] = new Laptop(3000.0f,​ "​Asus"​);​ +
- produse[3] = new Laptop(7999.99f,​ "​Lenovo"​);​ +
- produse[4] = new SmartPhone(3999.99f,​ "​Apple"​);​+
  
- for (int i = 0; i < nrProduse; i+++ Animal(); 
- + Animal(const char* nume, const int& varsta); 
- std::cout << "​Pretul produsului este: " << produse[i]->​getPret() << '​\n'​+ Animal(const Animal& animal); 
- std::cout << "​Numele producatorului este: " << produse[i]->​getProducator() << "​\n\n"​+ virtual ~Animal(); // destructor virtual
- }+
  
- for (int i = 0; i < nrProduse; i++) + char* getNume() const; 
-+ int getVarsta(const;
- delete produse[i]// eliberam fiecare slot din vector +
- }+
  
- delete[] produse; // stergem vectorul + virtual void afisare() const = 0; // metoda virtual pura 
- +};
- return 0; +
-}+
 </​code>​ </​code>​
  
-Pentru a înțelege mai bine structura unui **vector de obiecte neomogene**, este esențial ​**să vizualizăm** modul în care acesta este organizat în memorie.+Iar acum codul de test din funcția main **nu** va mai genera ​**scurgeri de memorie** fiind apelați destructorii ​în ordinea inversă apelării constructorilor. Codul complet cu implementările celor două clase poate fi descărcat de {{:​poo-is-ab:​laboratoare:​clasa_abstracta.zip|aici}}.
  
-{{ :​poo-is-ab:​laboratoare:​vector_neomogen.jpg?​direct&​600 |}}+==== Interfețe ====
  
-==== Supraîncărcarea operatorului << pentru ​o clasă abstractă ​====+În limbajul C++, o **interfață** este formă specializată de **clasă abstractă** care conține **exclusiv** metode **virtual pure** și, de regulă, un destructor **virtual pur**. Fiind o clasă destinată exclusiv definirii de funcționalități,​ o interfață **nu** conține **membri** și **nici constructori**,​ deoarece scopul său **nu** este să stocheze starea unui obiect, ci să specifice un **set de comportamente** pe care **clasele derivate** le vor implementa.
  
-În contextul exemplului prezentat anterior, ar fi foarte elegant să putem afișa elementele vectorului folosind **operatorul %%<<​%%**,​ ceea ce ar simplifica ​și uniformiza procesul de afișare. Totuși, o problemă fundamentală apare din faptul că acest operator poate fi **supraîncărcat**,​ dar nu și **suprascris**+În continuare vom prezenta un tabel cu caracteristicile interfețelor ​și ale claselor abstracte pentru ​putea identifica eventuale asemănări ​și deosebiri ​și pentru a putea înțelege când avem nevoie de fiecare în parte în practică.
  
-Prin urmare ​**operatorul %%<<​%%** nu poate fi declarat ​**virtual**, astfel încât să permită apelul unei implementări specifice ​**clasei derivate** atunci când este utilizat printr-un ​**pointer** sau o **referință** ​la **clasa de bază**. Soluția implică de obicei definirea unei metode virtuale în **clasa abstractă** și utilizarea acesteia ​în supraîncărcarea ​**operatorului %%<<​%%**+^     ​Caracteristică ​         ^     ​Interfață ​                     ^     ​Clasă abstractă ​                ^ 
 +**Metode**                  | Conține doar metode ​virtual ​pure   | Poate conține metode virtual pure și metode concrete | 
 +**Membri de date**          | Nu poate avea membri ​      | Poate avea membri ​           | 
 +**Constructori**            | Nu poate avea constructori ​        | Poate avea constructori ​             | 
 +**Moștenire multiplă**      | Este utilizată pentru moștenirea multiplă, mai ales pentru a defini contracte comune | Este utilizată în ierarhii simple sau complexe, dar poate genera ambiguități în moștenirea multiplă 
 +**Scop**                    | Definește un contract strict pentru clasele derivate | Definește un comportament parțial și oferă reutilizarea codului | 
 +**Destructor**              | Necesită destructor virtual pur atunci când în clasele derivate există membri de tip pointer | Destructorul virtual care trebuie implementat când există pointeri în clasă | 
 +**Instanțiere**             | Nu poate fi instanțiată,​ dar se pot utiliza pointeri la tipul acesteia | Nu poate fi instanțiată,​ dar poate avea constructori pentru clase derivate |
  
-Această abordare oferă ​separare clară între logica specifică de afișare ​și mecanismul ​**operatorului %%<<​%%**, respectând în același timp principiile polimorfismului. +Ca și exemplu propunem clasa **FiguraGeometrica** care va fi interfață ce conține metodele **''​getArie''​** ​și **''​getPerimetru''​**, iar ca și clase derivate ​vom lucra cu **Cerc** și **Patrat**.
- +
-Așadar ​vom declara o metodă virtual pură în interfața ​**ProdusElectronic** și vom anunța compilatorul că vom supraîncărca și operatorul de afișare.+
  
 <code cpp> <code cpp>
-class ProdusElectronic+class FiguraGeomertica // interfata
 { {
-protected: 
- 
- virtual void afisare(std::​ostream&​ out) const = 0; // va fi folosita pentru operatorul << 
- 
 public: public:
  
- virtual ~ProdusElectronic() = 0; + virtual float getArie() const = 0; 
- + virtual ​float getPerimetru() const = 0;
- virtual float getPret() const = 0; +
- virtual ​char* getProducator() const = 0+
- +
- friend std::​ostream&​ operator<<​(std::​ostream&​ out, const ProdusElectronic* const& produsElectronic);+
 }; };
 </​code>​ </​code>​
  
-Iar implementarea operatorului de afișare este evidentă acum.+Iar în continuare vom prezenta clasele **Cerc** ​și **Patrat**.
  
 <code cpp> <code cpp>
-std::​ostream&​ operator<<​(std::​ostream&​ out, const ProdusElectronic* const& produsElectronic)+class Cerc public FiguraGeomertica
 { {
- produsElectronic->​afisare(out);​ // ne folosim de late binding + int raza;
- return out; +
-+
-</​code>​ +
- +
-Tot ce ne mai rămâne acum de făcut este să implementăm metoda virtual pură în clasele derivate și de restul se va ocupa **late binding-ul**. +
- +
-<code cpp> +
-class Laptop : public ProdusElectronic +
-+
- float pret; +
- char* producator;​ +
- +
-protected:​ +
- +
- void afisare(std::​ostream&​ out) const override;+
  
 public: public:
  
- Laptop(); + Cerc(const ​intraza = 0);
- Laptop(const ​floatpret, const char* producator);​ +
- ~Laptop();+
  
- float ​getPret() const override; + float ​getArie() const override; 
- char* getProducator() const override;+ float getPerimetru() const override;
 }; };
 </​code>​ </​code>​
 +
 +Iar mai jos sunt prezentate implementările metodelor aferente clasei **Cerc**.
  
 <code cpp> <code cpp>
-class SmartPhone ​public ProdusElectronic+Cerc::Cerc(const int& raza)
 { {
- float pret+ this->​raza = raza
- char* producator;+}
  
-protected:+float Cerc::getArie() const 
 +
 + return 3.14f * raza * raza; 
 +}
  
- void afisare(std::ostream&​ out) const override;+float Cerc::getPerimetru() const 
 +
 + return 2 * 3.14f * raza; 
 +
 +</​code>​ 
 + 
 +Declarația clasei **Patrat** se poate observa mai jos. 
 + 
 +<code cpp> 
 +class Patrat : public FiguraGeomertica 
 +
 + int latura;
  
 public: public:
  
- SmartPhone();​ + Patrat(const ​intlatura = 0);
- SmartPhone(const ​floatpret, const char* producator);​ +
- ~SmartPhone();+
  
- float ​getPret() const override; + float ​getArie() const override; 
- char* getProducator() const override;+ float getPerimetru() const override;
 }; };
 </​code>​ </​code>​
  
-Iar implementările ​celor 2 metode se pot observa în blocurile de cod de mai jos.+Iar implementările ​metodelor sunt disponibile ​mai jos.
  
 <code cpp> <code cpp>
-void Laptop::afisare(std::​ostreamoutconst+Patrat::Patrat(const intlatura)
 { {
- out << "​Pretul laptopului este: " << pret << '​\n';​ + this->​latura ​latura;
- out << "​Numele producatorului de laptopuri este: "; +
- +
- if (producator !nullptr) +
-+
- out << producator << '​\n';​ +
-+
- else +
-+
- out << "​N/​A\n"​; +
- }+
 } }
-</​code>​ 
  
-<code cpp> +float Patrat::getArie() const
-void SmartPhone::afisare(std::​ostream&​ out) const+
 { {
- out << "​Pretul smartphone-ului este: " << pret << '​\n'​+ return(float) latura * latura
- out << "​Numele producatorului de smartphone-uri este: ";+}
  
- if (producator != nullptr+float Patrat::​getPerimetru() const 
-+
- out << producator << '​\n'​; + return(float) 4 * latura;
-+
- else +
-+
- out << "​N/​A\n";​ +
- }+
 } }
 </​code>​ </​code>​
  
-Iar în funcția ​**main** vom afișa detaliile din vector folosind **operatorul %%<<​%%**.+Iar ca și exemplu de testare a funcționalităților ​în funcția main avem:
  
 <code cpp> <code cpp>
-#include "​Laptop.h"​ 
-#include "​SmartPhone.h"​ 
- 
 int main() int main()
 { {
- int nrProduse = 5; + FiguraGeomerticapfg1 = new Cerc(4);
- ProdusElectronic** produse ​= new ProdusElectronic * [nrProduse];+
  
- produse[0] = new Laptop(5499.99f, ​"HP"); + std::cout << ​"Aria cercului este: " ​<< pfg1->​getArie() << '​\n'​
- produse[1] = new SmartPhone(2499.99f, "​Motorola"​); + std::cout << ​"Perimetrul cercului este: " ​<< pfg1->​getPerimetru() << '​\n'​;
- produse[2] = new Laptop(3000.0f, ​"Asus"); +
- produse[3] = new Laptop(7999.99f, "​Lenovo"​);​ +
- produse[4] = new SmartPhone(3999.99f,​ "​Apple"​);+
  
- for (int i = 0; i < nrProduse; i++) + FiguraGeomertica* pfg2 = new Patrat(5);
-+
- std::cout << produse[i] << '​\n'​// se foloseste operatorul << definit in interfata ProdusElectronic +
- }+
  
- for (int i = 0; i nrProdusei++) + std::cout << "​\nAria patratului este: " << pfg2->​getArie(<< '​\n'​
- + std::cout << "​Perimetrul patratului este: " << pfg2->​getPerimetru() << '​\n'​;
- delete produse[i]; +
- }+
  
- delete[] produse;+ delete ​pfg1; 
 + delete pfg2;
  
  return 0;  return 0;
Line 394: Line 430:
 </​code>​ </​code>​
  
-<​note ​tip>​În ​mod similar, procedăm și pentru ceilalți operatori, indiferent dacă sunt supraîncărcați ca **funcții membre** sau ca **funcții friend**. Acești operatori vor fi supraîncărcați **exclusiv** în **clasa de bază**, iar comportamentul lor specific poate fi ulterior personalizat în **clasele derivate** prin intermediul ​**suprascrierii**. +<​note ​important>​În ​cazul clasei ​**FiguraGeometrica** nu este necesară implementarea unui destructor virtual pur deoarece în clasele derivate ​**nu** există membri de tip pointer **alocați dinamic**. Prin urmare ordinea de apel a destructorilor este cea **corectă**, adică sunt apelați mai întâi destructorii claselor derivate (**Cerc** și **Patrat**) și la final va fi apelat destructorul superclasei.</​note>​
  
-Acest lucru se realizează prin definirea unor **funcții virtual pure** în clasa de bază, care stabilesc comportamentul operatorului respectiv în **subclase**. Clasele derivate vor implementa aceste **funcții virtuale**, asigurând astfel logica specifică operatorului în funcție de cerințele fiecărei clase. Această abordare permite un design flexibil și robust, bazat pe mecanismul de **run time polymorphism**.</​note>​ 
 ==== Concluzii ==== ==== Concluzii ====
  
-În acest laborator ​am aprofundat ​utilizarea claselor abstracte și a interfețelor, aplicând aceste concepte ​pentru ​înțelege și a lucra cu **vectori de obiecte neomogene**. Acest tip de colecție permite stocarea de obiecte de tipuri diferite, având la bază relația de moștenire ​și utilizând polimorfismul pentru manipularea lor unitară.+Acest laborator ​a abordat concepte avansate legate de **POO** în limbajul C++, punând accent pe mecanismele de **virtualizare**,​ diferențele dintre **overloading** și **overriding**,​ și utilizarea ​**claselor abstracte** și a **interfețelor**. 
 + 
 +== Virtualizarea în C++ == 
 + 
 +  * **Metodele virtuale** oferă flexibilitatea necesară ​pentru ​implementarea **polimorfismului dinamic**, unde comportamentul funcțiilor este stabilit ​în timpul execuției (**late binding**). 
 + 
 +  * **Metodele virtual pure** obligă clasele derivate să implementeze funcționalitățile esențiale, asigurând astfel că fiecare clasă derivată își definește comportamentul specific. 
 + 
 +== Overloading vs Overriding ==
  
-== Vectori ​de obiecte neomogene ==+  * **Overriding** este asociat cu polimorfismul dinamic (**run-time polymorphism**) și presupune redefinirea comportamentului unei metode virtuale din clasa de bază în clasele derivate.
  
-Am înțeles cum să declarăm și să populăm un vector de obiecte neomogene, folosind pointeri către clase abstracte. Acest lucru ne-a permis să lucrăm eficient cu obiecte care împărtășesc o interfață comună, dar pot avea implementări distincte. Vectorii de acest tip reprezintă o continuare firească a conceptelor de **clase abstracte** și **interfețe**, aprofundate ​în laboratorul anterior.+  ​* **Overloading** este asociat cu polimorfismul timpuriu (**compile-time polymorphism**) și permite mai multe metode cu același nume, dar semnături diferite, în cadrul aceleiași clase.
  
-== Destructor virtual pur ==+  * Este important să folosim cuvântul cheie **override** pentru a face codul mai sigur și mai lizibil, prevenind erorile legate de semnături greșite sau lipsa suprascrierii.
  
-Am discutat ​și implementat un **destructor virtual pur**, subliniind importanța sa în ierarhiile de clase. Un destructor virtual permite apelarea corectă a destructorilor din clasele derivate în momentul eliberării memoriei pentru obiectele stocate sub forma pointerilor la clasa de bază abstractă. Prin aceasta, am evitat scurgerile de memorie și alte comportamente neașteptate.+== Clase Abstracte ​și Metode Virtual Pure ==
  
-== Supraîncărcarea operatorului ​de afișare ==+  * **Clasele abstracte** oferă un punct de plecare pentru proiectarea ierarhiilor complexe, combinând funcționalitățile comune ​și abstractizarea.
  
-Am analizat și implementat ​**supraîncărcarea operatorului %%<<​%%** pentru a simplifica afișarea elementelor din vectorul de obiecte. Acest proces a evidențiat diferența fundamentală dintre ​**supraîncărcare (overloading)** și **suprascriere (overriding)**. Deoarece operatorii nu pot fi suprascrișici doar supraîncărcați, am adaptat codul pentru a permite funcționarea acestuia cu obiecte ​de tipuri diferite.+  ​**metodă virtual pură** este o metodă **fără** implementare în clasa de bazăiar clasa devine abstractă dacă conține cel puțin o astfel ​de metodă.
  
-== Polimorfism aplicat ==+  * Destructorii virtuali sunt obligatorii în clasele abstracte pentru a asigura o eliberare corectă a resurselor în ierarhiile de moștenire.
  
-Conceptul de **late binding**, explorat anterior, a fost aplicat pentru a apela metodele obiectelor din vectorul de obiecte neomogene. Acest lucru ne-a permis să tratăm obiecte de tipuri diferite într-o manieră uniformă, demonstrând puterea polimorfismului în proiectarea și implementarea aplicațiilor scalabile.+== Interfețele în C++ ==
  
-== Continuitatea dintre laboratoare ==+  * **Interfețele** sunt un caz particular de clase abstracte care conțin **doar** metode virtual pure.
  
-Activitatea din acest laborator a consolidat noțiunile teoretice ​și practice legate de clase abstracte, interfețe ​și polimorfismoferindu-ne o bază solidă pentru a lucra cu colecții de obiecte complexe șeterogene.+  * Ele definesc **contracte stricte** între clase și sunt utilizate pentru a implementa moștenirea multiplă într-un mod clar și organizatfără ambiguități.
  
 ==== ==== ==== ====
  
-Acest laborator ​ne-demonstrat cum putem combina conceptele avansate ​de **POO** pentru a rezolva probleme realeasigurând un design robust, flexibil ​și extensibil ​al codului.+Acest laborator a evidențiat rolul important al mecanismului ​de **virtualizare**, al **claselor abstracte** ​și al **interfețelor** în proiectarea sistemelor software flexibile și extensibile. Am înțeles diferențele esențiale dintre **overloading** și **overriding** și importanța implementării corecte a **metodelor virtuale**, pentru a asigura funcționarea corectă și eficientă a aplicațiilor **orientate obiect**.
poo-is-ab/laboratoare/09.1733304487.txt.gz · Last modified: 2024/12/04 11:28 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