Differences

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

Link to this comparison view

poo-is-ab:laboratoare:07 [2025/01/19 22:29]
razvan.cristea0106
poo-is-ab:laboratoare:07 [2025/11/08 17:18] (current)
razvan.cristea0106 [Moștenirea multiplă]
Line 1: Line 1:
-===== Laborator ​08 Funcții ​și clase template ​=====+===== Laborator ​07 Moștenire multiplă ​și agregare ​=====
  
 **Autor: Răzvan Cristea** **Autor: Răzvan Cristea**
Line 7: Line 7:
 Studentul va fi capabil la finalul acestui laborator să: Studentul va fi capabil la finalul acestui laborator să:
  
-  * recunoască și să definească funcții template +  * definească și să utilizeze namespace-uri 
-  * recunoască și să definească clase template +  * recunoască și să înțeleagă ​conceptul ​de moștenire multiplă și agregare între mai multe clase 
-  * înțeleagă ​importanța conceptului ​de programare generică +  * construiască ierarhii ​și legături între clase folosind relațiile de tip "​is-a" ​și "​has-a"​ 
-  * organizeze în fișiere header ​și .cpp codul pentru ​funcțiile și clasele template+  * 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
  
 ==== Introducere ==== ==== Introducere ====
  
-Pe parcursul primului an de studiu, în cadrul ​disciplinei ​**Proiectarea Algoritmilor**, ați avut ocazia să explorați o gamă variată de **structuri ​de date** și **algoritmi**. Majoritatea implementărilor de **algoritmi** și **structuri ​de date** studiate s-au bazat pe un **tip de date specific** – de exemplustructuri de date care funcționau doar cu valori de tip întreg sau doar cu șiruri de caractere. ​**Programarea generică** își propune ​să depășească aceste limitări și să ofere soluții care pot fi **adaptate ​pentru ​orice tip de date**, fără ​fi nevoie ​să rescriem codul pentru fiecare tip nou.+În cadrul ​acestui laborator vom aprofunda conceptele legate de **moștenire**, extinzând noțiunile discutate în laboratorul anterior ș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 C++, **programarea generică** este realizată prin intermediul **template-urilor**. Un template este un **model reutilizabil (șablon)** care poate fi definit **o singură dată** și utilizat pentru o **gamă variată de tipuri de date**. **Template-urile** permit astfel crearea de **funcții** și **clase** care pot funcționa generic, pentru orice tip. De exemplu, o funcție de sortare implementată cu template-uri poate fi aplicată atât pe liste de întregi, cât și pe liste de numere în virgulă mobilă sau pe liste de obiecte de orice tip care suportă **operatorul de comparație**.+==== Moștenirea multiplă ====
  
-Principalele avantaje ​și caracteristici ale **programării generice** în C++ sunt:+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ăsă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.
  
-  * **Reutilizarea codului** ​<=Cu template-uriputem crea funcții și clase care nu depind de un anumit tip de date. Codul devine mai flexibil ​și reutilizabilreducând efortul de scriere și întreținere. +<note important>În generalgăsirea unei combinații potrivite de clase care să respecte **moștenirea multiplă** poate fi o provocaredeoarece 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>​
-  +
-  ​* **Flexibilitate ​și extensibilitate** <​=> ​**Funcțiile** ș**clasele** template permit adaptarea ușoară la tipuri noi de date și comportamente diferite, fără modificări majore în structura codului. +
-  +
-  ​* **Tipizarea la timp de compilare** <=> C++ folosește template-urile într-un mod specificdenumit ​**generare la timp de compilare**. Asta înseamnă că tipul de date efectiv este determinat ​atunci când se realizează apelul ​**funcției** sau **metodei**, optimizând performanța și ajutând la identificarea erorilor de tip încă din faza de compilare.+
  
-  ​* **Specializare ​de template-uri** <=> În anumite situații, se poate utiliza specializarea ​**template-urilor**, o tehnică prin care se definește un comportament specific al funcției sau clasei pentru ​un anumit tip de dateAceasta permite adăugarea de optimizări sau modificări atunci când un tip special necesită un tratament diferit.+**Moștenirea** poate fi vizualizată ca o structură arborescentă,​ ceea ce explică ​de ce organizarea claselor într-un astfel de sistem este denumită ​**ierarhie de clase**. Într-o **ierarhie**, fiecare **clasă derivată** își are rădăcinile în **clasele părinte**, iar această organizare permite construirea unei structuri **logice** și **ordonate** a relațiilor dintre clase. De exemplu, în C++, putem observa în imaginea de mai jos un **arbore ​de fluxuri (ierarhia claselor de fluxuri)** în care clasele specifice de intrare și ieșire (precum **''​ifstream''​**,​ **''​ofstream''​**,​ **''​stringstream''​**) sunt derivate din clase generale precum **''​istream''​**,​ **''​ostream''​** sau **''​iostream''​**Această structură arborescentă oferă atât claritate conceptuală,​ cât și flexibilitate în reutilizarea și extinderea funcționalităților claselor.
  
-**Programarea generică** în C++ îmbină aceste concepte pentru a realiza soluții mai **scalabile**,​ **eficiente** și **organizat structurate**. În cadrul acestui laborator, veți învăța cum să creați funcții și clase template, cum să organizați și să utilizați template-uri specializate și cum să structurați proiectele pentru a încorpora aceste practici de programare generică.+{{ :poo-is-ab:​laboratoare:​arbore_fluxuri.jpg?400 |}}
  
-==== Funcții template ====+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 și o **piesă electronică**.
  
-**Funcțiile template** sunt similare cu funcțiile obișnuite, însă oferă un avantaj important: permit crearea de funcții generice, care pot lucra cu diferite tipuri de date. În loc să definim funcții separate pentru fiecare tip de date (de exemplu, ​**''​int''​**,​ **''​float''​**,​ **''​double''​**),​ o funcție template ne permite să scriem o singură funcție care să funcționeze pentru toate aceste tipuri.  +Declarația clasei ​**ProdusComercial** se poate observa în blocul ​de cod mai jos.
- +
-Să luăm spre exemplu o funcție care face suma a două numere ​de același tip primite ca parametru și întoarce un rezultat de același tip. Vom implementa două funcții de adunare pentru numere întregi și pentru numere de tip **''​float''​** după cum urmează ​mai jos.+
  
 <code cpp> <code cpp>
 +#pragma once
 #include <​iostream>​ #include <​iostream>​
  
-int adunare(const int& a, const int& b)+class ProdusComercial
 { {
- return a + b; +    float pret;
-}+
  
-float adunare(const float& a, const float& b) +public:
-+
- return a + b; +
-}+
  
-int main(+    ProdusComercial(const float& pret 0.0f); 
-+    ​ProdusComercial&​ operator=(const ProdusComercial&​ produsComercial);
- int sumaIntregi ​adunare(2, 3); +
- float sumaFloaturi ​adunare(3.5f, 8.25f);+
  
- std::cout << ​"Suma numerelor intregi este: " << sumaIntregi << '​\n';​ +    friend ​std::ostream&​ operator<<(std::ostream&​ out, const ProdusComercial&​ produsComercial)
- std::cout << "Suma numerelor reale este: " << sumaFloaturi << '​\n'​+};
- +
- return 0; +
-}+
 </​code>​ </​code>​
  
-Se poate observa că singurele diferențe între cele 2 funcții sunt **tipul de return** și **tipul parametrilor** acestora. Prin urmare putem spune că avem un cod duplicat care ne crește numărul de linii. Soluția mai elegantă și mai corectă este să implementăm o **funcție template** care va respecta structura celor 2 funcții de mai sus cu avantajul că va fi scrisă o singură dată pentru o gamă mai extinsă de tipuri de date. +Iar declarația clasei ​**PiesaElectronica** se află în codul sursă de mai jos.
- +
-=== Declararea și implementarea funcțiilor template === +
- +
-În C++ pentru a declara o **funcție generică** se folosesc cuvintele cheie **template** și respectiv **typename** deasupra antetului funcției. Să reimplementăm acum funcția ​de **adunare** a două numere de același tip dar de acestă dată ca **funcție template**. +
- +
-<code cpp> +
-template <​typename T> +
-T adunare(const T& a, const T& b) +
-+
- return a + b; +
-+
-</​code>​ +
- +
-<note important>​**T-ul** reprezintă tipul de date folosit de **funcția template**, atât ca **tip de returnare**,​ cât și ca **tip pentru parametrii funcției**. Observăm că am definit **o singură** funcție generică, iar la **momentul compilării**,​ aceasta își va adapta **automat** tipul de date pe baza tipurilor parametrilor primiți. Astfel, funcția poate fi folosită cu diferite tipuri de date **fără** a necesita rescrierea pentru fiecare tip în parte, asigurând **flexibilitate** și reducând **duplicarea codului**.</​note>​ +
- +
-Apelarea funcției se poate face în maniera următoare.+
  
 <code cpp> <code cpp>
 +#pragma once
 +#include <​cstring>​
 #include <​iostream>​ #include <​iostream>​
  
-template <​typename T> +class PiesaElectronica
-T adunare(const T& a, const T& b)+
 { {
- return a + b; +    char* brand;
-}+
  
-int main() +public:
-+
- int sumaIntregi = adunare(2, 3); +
- /*int sumaIntregi = adunare<​int>​(2,​ 3); // corect si asa deoarece este pus explicit*/+
  
- /*float sumaFloaturi = adunare(3.5f, 8.25f); // valid*/ +    PiesaElectronica(); 
- float sumaFloaturi ​adunare<​float>​(3.5f, 8.25f); // valid+    PiesaElectronica(const charbrand); 
 +    ​PiesaElectronica(const PiesaElectronica&​ piesaElectronica);​ 
 +    PiesaElectronica&​ operator=(const PiesaElectronica&​ piesaElectronica);​ 
 +    ~PiesaElectronica();
  
- std::cout << ​"Suma numerelor intregi este: " << sumaIntregi << '​\n';​ +    friend ​std::ostream&​ operator<<(std::ostream&​ out, const PiesaElectronica&​ piesaElectronica)
- std::cout << "Suma numerelor reale este: " << sumaFloaturi << '​\n'​+};
- +
- return 0; +
-}+
 </​code>​ </​code>​
  
-Dacă am vrea să adunăm două tipuri diferite ​de date șsă returnăm suma lor într-un alt tip de date am putea folosi următoarea formă de funcție template.+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ăm să extindem cele două clase, **ProdusComercial** și respectiv **PiesaElectronica**,​ în clasa derivată **CameraWeb**.
  
-<code cpp> +Prin urmare declarația clasei ​**CameraWeb** poate fi observată în codul de mai jos.
-template <​typename T1, typename T2, typename T3> +
-T1 adunare(const T2& a, const T3& b) +
-+
- return (T1)a + b; +
-+
-</​code>​ +
- +
-**T1** reprezintă tipul de return al funcției iar **T2** și respectiv **T3** reprezintă tipurile de parametri pe care funcția îi poate primi. Funcția realizează suma valorilor celor 2 parametri și o convertește la tipul **T1** înainte ​de a o returna. Ca și exemple de apel putem scrie în felul următor.+
  
 <code cpp> <code cpp>
-#​include ​<​iostream>​+#pragma once 
 +#​include ​"​ProdusComercial.h"​ 
 +#include "​PiesaElectronica.h"​
  
-template <​typename T1typename T2, typename T3> +class CameraWeb : public ProdusComercialpublic PiesaElectronica // CameraWeb mosteneste atat clasa ProdusComercial cat si clasa PiesaElectronica
-T1 adunare(const T2& a, const T3& b)+
 { {
- return (T1)a + b+    int rezolutie
-}+    char* culoare;
  
-int main() +public:
-+
- double suma = adunare<​double>​(2,​ 7.5f); +
- /*double suma = adunare<​double,​ int, float>​(2,​ 7.5f); // corect de asemenea*/​ +
- /*double suma = adunare(2, 7.5f); // incorect deoarece compilatorul nu stie ce tip de return sa utilizeze*/+
  
- std::cout << "Suma este: " << suma << '​\n'​;+    CameraWeb();​ 
 +    CameraWeb(const float& pret, const char* brand, const int& rezolutie, const char* culoare); 
 +    CameraWeb(const CameraWeb&​ cameraWeb);​ 
 +    CameraWeb&​ operator=(const CameraWeb&​ cameraWeb);​ 
 +    ~CameraWeb();
  
- return 0+    friend std::​ostream&​ operator<<​(std::​ostream&​ out, const CameraWeb&​ cameraWeb)
-}+};
 </​code>​ </​code>​
  
-Astfel, putem observa că **funcțiile template** oferă o formă de **polimorfism** cunoscută sub numele de **polimorfism static** sau **polimorfism la compilare** (**compile time polymorphism** sau **early polymorphism**). În loc să definim mai multe **funcții supraîncărcate** pentru fiecare tip de date posibil, folosim **un singur model generic**, iar **compilatorul generează automat** versiunile corespunzătoare pentru fiecare tip de date specific **atunci când funcția este apelată**. Aceasta este o abordare eficientă pentru a obține **flexibilitate** și **reutilizare a codului**, asigurând totodată **performanță optimă**, deoarece tipurile sunt determinate și validate la **compilare**,​ eliminând nevoia de verificări suplimentare la run time.+=== Implementarea constructorilor în clasa derivată ===
  
-<note warning>​În cazul exemplului de mai sus a fost **obligatorie** speficarea **tipului de date returnat** între **parantezele unghiulare**,​ deoarece compilatorul **nu** ar fi știut în ce tip de date să facă **conversia** rezultatului obținut ​în urma operației de adunare. Pentru celelalte două tipuri de date **nu a fost necesară** menționarea lor între **parantezele unghiulare**,​ deoarece compilatorul a știut să pună **automat** tipurile ​de date corecte pe baza valorilor parametrilor funcției **adunare** în momentul în care aceasta a fost apelată în **funcția main**.</​note>​+Se procedează în manieră similară moștenirii simple cu adăugările de rigoare după cum urmează în exemplele de mai jos.
  
-=== Supraîncărcarea unei funcții template === +== Implementarea constructorului fără parametri ​==
- +
-O **funcție generică** poate fi, într-adevăr, **supraîncărcată** folosind același nume, dar să difere prin **numărul sau tipul parametrilor**. Aceasta înseamnă că putem avea **mai multe versiuni** ale unei **funcții generice**, fiecare destinată unui **caz particular**,​ dar accesibile sub **același nume**. Astfel, **compilatorul** va selecta **automat** varianta corespunzătoare în funcție de tipurile de date și de numărul argumentelor transmise. +
- +
-Ca și exemplu propunem două **funcții generice** de **interschimbare** a două valori, una cu parametri ​transmiși prin **referință** și cea de a doua cu parametri transmiși prin **pointer**.+
  
 <code cpp> <code cpp>
-#include <​iostream>​ +CameraWeb::​CameraWeb() : ProdusComercial()PiesaElectronica()
- +
-template <​typename T> +
-void interschimbare(T& xT& y)+
 { {
- T aux x+    rezolutie ​0
- y; +    ​culoare ​nullptr;
- y = aux;+
 } }
 +</​code>​
  
-template ​<typename T> +<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>​
-void interschimbare(TxTy) +
-+
- if (x == nullptr || y == nullptr) +
-+
- return; +
- }+
  
- T aux *x; +== Implementarea constructorului cu parametri ==
- *x *y; +
- *y aux; +
-}+
  
-int main()+Î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. 
 + 
 +<code cpp> 
 +CameraWeb::​CameraWeb(const float& pret, const char* brand, const int& rezolutie, const char* culoare) : ProdusComercial(pret),​ PiesaElectronica(brand)
 { {
- int a 22+    if (rezolutie <0) 
- int b 6;+    { 
 +        this->​rezolutie = 0
 +    } 
 +    else 
 +    { 
 +        this->​rezolutie ​rezolutie; 
 +    }
  
- interschimbare(ab);+    if (culoare != nullptr) 
 +    { 
 +        this->​culoare = new char[strlen(culoare) + 1]; 
 +        strcpy(this->​culoareculoare); 
 +    } 
 +    else 
 +    { 
 +        this->​culoare = nullptr; 
 +    } 
 +
 +</​code>​
  
- std::cout << "​a ​" << a << '​\n';​ +== Implementarea constructorului de copiere ==
- std::cout << "​b ​" << b << '​\n';​+
  
- interschimbare(&a&b); +<code cpp> 
- +CameraWeb::​CameraWeb(const CameraWebcameraWeb) : ProdusComercial(cameraWeb)PiesaElectronica(cameraWeb
- std::​cout ​<< "​\na ​" << a << '​\n'​+{ 
- std::cout << "​b ​" << b << '​\n'​;+    if (cameraWeb.rezolutie ​<= 0) 
 +    { 
 +        rezolutie = 0
 +    } 
 +    else 
 +    { 
 +        rezolutie ​cameraWeb.rezolutie; 
 +    }
  
- return 0;+    if (cameraWeb.culoare != nullptr) 
 +    { 
 +        culoare = new char[strlen(cameraWeb.culoare) + 1]; 
 +        strcpy(culoare,​ cameraWeb.culoare);​ 
 +    } 
 +    else 
 +    { 
 +        culoare = nullptr; 
 +    }
 } }
 </​code>​ </​code>​
  
-Prin acest mecanism ​de **supraîncărcare ​funcțiilor template**, am reușit să extindem funcționalitatea ​**codului generic** pentru a acoperi ​**cazuri specifice**, păstrând totodată **lizibilitatea** și **coerența** codului.+<​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 mecanismputem 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>​
  
-=== Separarea declarației ​de implementărea unei funcții template ​===+=== Implementarea operatorului ​de asignare în clasa derivată ===
  
-Până acum, în exemplele ​de cod cu **funcții template**, am realizat atât declarația,​ cât și implementarea în același fișier. Totuși, pentru a îmbunătăți organizarea codului și a facilita reutilizarea,​ intenționăm să separăm aceste componente. Separarea declarației și implementării funcțiilor template este o practică utilă, mai ales în proiectele ​de mari dimensiuni, deoarece oferă o structură mai clară și face codul mai ușor de întreținut.  +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 C++spre deosebire de funcțiile obișnuite, implementarea ​**funcțiilor template** în fișiere separate reprezintă o provocare datorită mecanismului de instanțiere a template-urilor la momentul compilării,​ implementarile trebuie ​să fie vizibile în orice fișier care le utilizează. De aceea, vom explora modalități pentru a organiza corect template-urile în fișiere separate, păstrându-le accesibile la compilare și în același timp menținând o structură modulară. +
- +
-Ca și exemplu vom scrie funcția ​de **adunare** în fișiere ​**header** și **.cpp** pentru a vedea exact cum trebuie procedat astfel încât să ne putem folosi de ea în orice fișier. +
- +
-== Declararea funcției == +
- +
-Pentru început vom muta antetul funcției într-un fișier header ​după cum urmează.+
  
 <code cpp> <code cpp>
-#pragma once +CameraWeb&​ CameraWeb::​operator=(const CameraWeb&​ cameraWeb) 
-#include <​iostream>​+
 +    if (this == &​cameraWeb) 
 +    { 
 +        return *this; 
 +    }
  
-template <​typename T> +    ProdusComercial::​operator=(cameraWeb); 
-T adunare(const T& x, const T& y); +    ​PiesaElectronica::​operator=(cameraWeb);​
-</​code>​+
  
-== Implementarea funcției ==+    if (culoare !nullptr) 
 +    { 
 +        delete[] culoare; 
 +    }
  
-Fiind o **funcție template** va trebui să înștiințăm compilatorul acest lucru după cum urmează.+    if (cameraWeb.rezolutie <= 0) 
 +    { 
 +        rezolutie = 0; 
 +    } 
 +    else 
 +    { 
 +        rezolutie = cameraWeb.rezolutie;​ 
 +    }
  
-<code cpp> +    if (cameraWeb.culoare != nullptr) 
-#include "​Template.h"+    { 
 +        culoare = new char[strlen(cameraWeb.culoare) + 1]; 
 +        strcpy(culoare,​ cameraWeb.culoare);​ 
 +    } 
 +    else 
 +    { 
 +        culoare = nullptr; 
 +    }
  
-template<​typename T> +    ​return ​*this;
-T adunare(const T& x, const T& y) +
-+
- return ​x + y;+
 } }
 </​code>​ </​code>​
  
-== Apelarea funcției în alt fișier ​=+=== Implementarea destructorului ​în clasa derivată ===
- +
-Apelarea funcției se face în maniera următoare.+
  
 <code cpp> <code cpp>
-#include "​Template.h"​ +CameraWeb::​~CameraWeb()
- +
-int main()+
 { {
- int a 22; +    if (culoare !nullptr) 
- int b = 6; +    { 
- +        ​delete[] culoare
- std::cout << adunare(a, b) << '​\n'​+    }
- +
- return 0;+
 } }
 </​code>​ </​code>​
  
-=== === +<note warning>Reamintim faptul că în destructorul clasei derivate ​**nu vom apela explicit** destructorii claselor părinteacest lucru fiind realizat ​**automat** de către **compilator** în momentul ​în care durata de viață a obiectului ​de tipul **clasei derivate** se încheie.</​note>​
- +
-Dacă vom încerca să rulăm codul exact în maniera în care l-am scris mai sus ne vom confrunta cu o **eroare de linker**. +
- +
-<note warning>**Eroarea de linker** apare în cazul **funcțiilor template** separate în fișiere **header** și **.cpp** din cauza modului în care funcționează **compilarea** template-urilor ​în **C++**. Spre deosebire de funcțiile obișnuite, **funcțiile template** sunt **generate la momentul compilării** pentru **fiecare** tip de date specific utilizat în cod. Așadarcompilatorul ​**trebuie să aibă acces** la implementarea completă a **funcției template** de fiecare dată când o utilizează cu **un nou tip de date**+
- +
-În mod normal, atunci când împărțim o funcție într-un **fișier header** pentru declarare și un **fișier .cpp** pentru implementare,​ **compilatorul** generează **codul obiect** pentru implementare ​în **fișierul .cpp**, iar **linker-ul** leagă acest cod în etapa finală. Însă în cazul **funcțiilor template**, această separare cauzează o **eroare ​de linker** deoarece în momentul compilării fișierului header, compilatorul ​**nu** găsește implementarea completă a **funcției template** în **fișierul .cpp** pentru tipurile de date pe care încă **nu** le-a întâlnit.</​note>​+
  
-Cea mai simplă soluție este să forțăm compilatorul să genereze **funcția template** pentru tipurile de date specifice pe care dorim să le testăm. Acest lucru se poate realiza prin implementarea unei funcții **locale** sau **statice** ​în **fișierul .cpp** care conține implementarea **funcției template**. Funcția respectivă va apela template-ul cu diverse tipuri de date, asigurând astfel compilarea și generarea de cod pentru fiecare tip necesar.+=== Implementarea operatorului << ​în clasa derivată ===
  
-<​note>​Funcția de test **nu trebuie** neapărat apelată ​în codul principalmotiv pentru care este prezentă doar în **fișierul .cpp**. Rolul său este pur și simplu de a **forța compilatorul** să genereze instanțe ale **template-ului** pentru tipurile de date dorite, fără a fi nevoie să fie efectiv utilizată în alte părți ale programului.</​note>​+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ă.
  
 <code cpp> <code cpp>
-#include "​Template.h"​ +std::​ostream&​ operator<​<(std::​ostreamout, const CameraWebcameraWeb)
- +
-template<typename T> +
-T adunare(const Tx, const Ty)+
 { {
- return x + y+    operator<<​(out,​ (ProdusComercial&​)cameraWeb)
-}+    ​operator<<​(out,​ (PiesaElectronica&​)cameraWeb);​
  
-void testare() +    out << "​Rezolutia camerei web este: " << cameraWeb.rezolutie << " pixeli\n"​
-+    out << "​Culoarea camerei web este: ";
- int s1 = adunare(2, 3); +
- float s2 = adunare(2.3f, 3.0f)+
- double s3 = adunare(-1.4,​ 8.24); +
- unsigned int s4 = adunare(2u, 3u); +
-+
-</​code>​+
  
-Prin urmare putem apela acum funcția de adunare pentru patru tipuri de date după cum urmează.+    if (cameraWeb.culoare != nullptr) 
 +    { 
 +        out << cameraWeb.culoare << '​\n';​ 
 +    } 
 +    else 
 +    { 
 +        out << "​N/​A\n";​ 
 +    }
  
-<code cpp> +    ​return ​out;
-#include "​Template.h"​ +
- +
-int main() +
-+
- std::cout << "Suma numerelor este: " << adunare(22, 8) << '​\n';​ // valid +
- std::cout << "Suma numerelor este: " << adunare(2.2f,​ 4.5f) << '​\n';​ // valid +
- std::cout << "Suma numerelor este: " << adunare(10.0,​ 7.5) << '​\n';​ // valid +
- std::cout << "Suma numerelor este: " << adunare(4u, 6u) << '​\n';​ // valid +
- /​*std::​cout << "Suma numerelor este: " << adunare(4l, 6l) << '​\n';​ // invalid*/ +
- +
- return ​0;+
 } }
 </​code>​ </​code>​
  
-<note important>​Datorită faptului că în funcția de testare **nu** am apelat o instanță a **funcției template** pentru tipul de date **long**, dacă se decomentează linia din codul de mai sus, va apărea o **eroare de linker**. Acest lucru se întâmplă deoarece compilatorul **nu** a generat o implementare pentru tipul **long**, care nu a fost utilizat în funcția de testare.</​note>​+==== Agregare ====
  
-==== Clase template ====+**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.
  
-**Clasele template**, la fel ca **funcțiile template**, au scopul ​de a susține **programarea generică** și de a elimina duplicarea codului, oferind o soluție flexibilă și reutilizabilă pentru gestionarea mai multor ​tipuri ​de date. Prin **clasele template**, putem crea **structuri de date** și obiecte care să funcționeze **indiferent** de tipul de date cu care lucrează, astfel încât codul să fie mai **ușor de întreținut** și mai **eficient**.+<​note>​Agregarea este de două tipuri ​după cum urmează:
  
-De exemplu, o **clasă template** pentru o structură de date precum un **vector** poate fi scrisă astfel încât să poată stoca orice tip de date, fie că este vorba de **numere întregi**, **șiruri de caractere** sau **obiecte complexe**. Aceasta înseamnă că **nu este nevoie** să redefinim întreaga clasă de fiecare dată când dorim să o utilizăm cu un alt tip de date.+  ​* **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.
  
-Ca și exemplu ​de clasă ​template pentru acest laborator propunem clasa **Student** care are un câmp **medieAnuala** de tip template, deoarece ​media anuală ​poate fi cu sau fără virgulă.+  * **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 tip **"​has-a"​** dintre două clase. Acestă **agregare** este de tip **weak**, deoarece ​un laptop ​poate să nu fie dotat cu o cameră web.
  
 <code cpp> <code cpp>
 #pragma once #pragma once
-#​include ​<​string>​ +#​include ​"​CameraWeb.h"​
-#include <​iostream>​+
  
-template <​typename T> +class Laptop
-class Student+
 { {
- char* nume+    double pret
- T medieAnuala;+    ​CameraWeb cameraWeb// relatia de has-a
  
 public: public:
  
- Student(const char* nume, const T& medieAnuala); +    Laptop(); 
- Student(const ​Studentstudent); +    ​Laptop(const ​doublepret, const CameraWebcameraWeb);
- Student&​ operator=(const Student&​ student); +
- ~Student();​ +
- +
- char* getNume() const; +
- T getMedieAnuala() const; +
- +
- void setNume(const char* nume); +
- void setMedieAnuala(const TmedieAnuala);+
  
- template <​typename T> +    ​friend std::​ostream&​ operator<<​(std::​ostream&​ out, const Laptoplaptop);
- friend std::​ostream&​ operator<<​(std::​ostream&​ out, const Student<​T>​student);+
 }; };
 </​code>​ </​code>​
  
-În continuare vom prezenta detaliat maniera de implementare a fiecărei metode ​în parte. +Iar implementările metodelor și a operatorului de afișare pentru clasa **Laptop** le putem observa ​în blocul de cod de mai jos.
- +
-=== Implementarea constructorilor ===+
  
 <code cpp> <code cpp>
-template <​typename T> +Laptop::Laptop()
-Student<​T>​::Student(const char* nume, const T& medieAnuala)+
 { {
- if (nume !nullptr) +    pret 0.0; 
- +    ​cameraWeb ​CameraWeb(); // se foloseste operatorul egal din clasa CameraWeb
- this->​nume ​new char[strlen(nume+ 1]; +
- strcpy(this->​nume,​ nume); +
-+
- else +
-+
- this->​nume = nullptr; +
-+
-  +
- this->​medieAnuala = medieAnuala;+
 } }
  
-template<​typename T> +Laptop::Laptop(const ​doublepret, const CameraWeb&​ cameraWeb)
-Student<​T>​::Student(const ​Student<​T>​student)+
 { {
- if (student.nume != nullptr+    ​if (pret <= 0.0
-+    
- nume new char[strlen(student.nume) + 1]; +        ​this->​pret ​0.0
- strcpy(nume,​ student.nume)+    
-+    else 
- else +    
-+        ​this->​pret ​pret
- nume nullptr+    }
- }+
  
- medieAnuala ​student.medieAnuala;+    this->​cameraWeb ​cameraWeb// se foloseste operatorul egal din clasa CameraWeb
 } }
-</​code>​ 
  
-=== Implementarea operatorului de asignare === +std::ostream& ​operator<<(std::​ostream&​ out, const Laptoplaptop)
- +
-<code cpp> +
-template<​typename T> +
-Student<​T>&​ Student<​T>​::operator=(const ​Student<​T>​student)+
 { {
- if (this == &​student) +    out << "​Pretul laptopului este: " << laptop.pret << " ron\n";​ 
- +    out << "​\nDetalii despre camera web a laptopului\n\n"​
- return *this+    out << laptop.cameraWeb;​
- }+
  
- if (nume != nullptr) +    return out
-+} 
- delete[] nume+</​code>​
- }+
  
- if (student.nume != nullptr) +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}}.
- { +
- nume = new char[strlen(student.nume) + 1]; +
- strcpy(nume,​ student.nume);​ +
- } +
- else +
-+
- nume = nullptr; +
- }+
  
- medieAnuala ​student.medieAnuala;​+==== Namespace-uri definite de către programator ====
  
- return ​*this; +**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**.
-+
-</​code>​+
  
-=== Implementarea destructorului ===+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> <code cpp>
-template<​typename T> +#pragma once 
-Student<​T>::​~Student()+ 
 +namespace MathHelper
 { {
- if (nume != nullptr+    int rest(const int& a, const int& b); 
- { +    int adunare(const int& a, const int& b); 
- delete[] nume+    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>​ </​code>​
  
-=== Implementarea metodelor accesor ===+Iar implementările funcțiilor din cadrul namespace-ului se pot observa mai jos.
  
 <code cpp> <code cpp>
-template<​typename T> +#include "​MathUtils.h"​
-char* Student<​T>::​getNume() const +
-+
- return nume; +
-}+
  
-template<​typename T> +int MathHelper::rest(const ​int& a, const int& b)
-T Student<​T>​::getMedieAnuala(const+
 { {
- return ​medieAnuala;+    ​return ​(b == 0) ? -1 : a % b;
 } }
  
-template<​typename T> +int MathHelper::adunare(const ​int& a, const int& b)
-void Student<​T>​::setNume(const ​char* nume)+
 { {
- if (nume == nullptr) +    ​return ​b;
-+
- return+
-+
- +
- if (this->​nume != nullptr) +
-+
- delete[] this->​nume;​ +
-+
- +
- this->​nume = new char[strlen(nume) ​1]; +
- strcpy(this->​nume,​ nume);+
 } }
  
-template<​typename T> +int MathHelper::diferenta(const ​inta, const int& b)
-void Student<​T>​::setMedieAnuala(const ​TmedieAnuala)+
 { {
- if (medieAnuala <= 0) +    ​return ​b;
-+
- return+
-+
- +
- this->​medieAnuala = medieAnuala;+
 } }
-</​code>​ 
  
-=== Implementarea operatorului de afișare === +int MathHelper::inmultire(const inta, const intb)
- +
-<code cpp> +
-template <​typename T> +
-std::ostream&​ operator<<​(std::​ostreamout, const Student<​T>​student)+
 { {
- out << "​Numele studentului este: "; +    ​return ​a * b;
- +
- if (student.nume == nullptr) +
-+
- out << "​N/​A\n";​ +
-+
- else +
-+
- out << student.nume << '​\n';​ +
-+
- +
- out << "Media anuala a studentului este: " << student.medieAnuala << '​\n';​ +
- +
- return ​out;+
 } }
-</​code>​ 
  
-=== Crearea funcției de testare === +int MathHelper::​impartire(const int& a, const int& b)
- +
-<code cpp> +
-void testTemplate()+
 { {
- Student<​int>​ s1("​Ion",​ 10); +    return ​(== 0? 0 a / b;
- Student<​int>​ s2("​George",​ 9); +
- Student<​int>​ s3 s2; +
- +
- s3 s1; +
- +
- s3.setNume("​Maria"​)+
- s3.setMedieAnuala(8);​ +
- +
- std::cout << s3 << '​\n';​ +
- std::cout << s3.getNume() << "​\n\n";​ +
- std::cout << s3.getMedieAnuala() << "​\n\n";​ +
- +
- Student<​double>​ s4("​Ion",​ 10); +
- Student<​double>​ s5("​George",​ 9); +
- Student<​double>​ s6 = s5; +
- +
- s5 = s4; +
- +
- s5.setNume("​Maria"​);​ +
- s5.setMedieAnuala(9.9);​ +
- +
- std::cout << s4 << '​\n';​ +
- std::cout << s4.getNume() << "​\n\n";​ +
- std::cout << s4.getMedieAnuala() << "​\n\n"​;+
 } }
 </​code>​ </​code>​
  
-==== ==== +Testarea acestui ​**namespace** este facută în **funcția main** ​după cum urmează în codul de mai jos.
- +
-Având acum implementarea clasei template ​**Student** putem să o folosim ​în codul din programul principal ​după cum umrează.+
  
 <code cpp> <code cpp>
-#include "Student.h"+#include "MathUtils.h"
  
 int main() int main()
 { {
- Student<​int> s1("​Ion", ​10); +    ​int x = 10; 
- Student<​int>​ s2("​George",​ 9)+    int 5;
- Student<int> s3 s2;+
  
- s3 = s1;+    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'​;
  
- s3.setNume("​Maria"​)+    return 0
- s3.setMedieAnuala(8);​+
 +</​code>​
  
- std::cout << s3 << '​\n';​+Unul dintre avantajele majore ale **namespace-urilor** este faptul că permit definirea unor funcții cu **aceeași semnătură**,​ dar cu **implementări diferite**, fără a crea ambiguitate pentru compilator. Astfel, putem evita conflictele de nume, avâ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.
  
- Student<​double>​ s4("​Ion",​ 10); +==== Concluzii ====
- Student<​double>​ s5("​George",​ 9); +
- Student<​double>​ s6 s5;+
  
- s5 = s4;+Î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 clase, evidențiind astfel modurile prin care putem structura relații logice și eficiente între clase. ​
  
- s5.setNume("​Maria"​);​ 
- s5.setMedieAnuala(9.9);​ 
  
- std::cout << s4 << '​\n';​+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 complexe**, dar flexibile. Am discutat despre avantajele și riscurile **moștenirii multiple**, precum posibilele conflicte de nume și gestionarea lor prin tehnici specifice, cum ar fi operatorii din **clasele părinte** și apelul lor **explicit** pentru a evita conflictele.
  
- return 0; +Relația de **"​has-a"​** ne-a permis să definim structuri de tip **container**,​ unde o clasă conține **una sau mai multe** instanțe ale altei clase **fără** a se afla într-o ierarhie de clase. Astfel, am putut organiza eficient datele și responsabilitățile între clase, menținând o separare clară a rolurilor.
-+
-</​code>​+
  
-<note tip>În principiu ​**clasele template** sunt folosite pentru implementarea ​**structurilor ​de date** într-o manieră **generică**, exemplul ​cu clasa **Student** fiind unul pur didactic.</​note>​ +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 context. Namespace-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.
- +
-==== Concluzii ====+
  
-În acest laborator, am explorat conceptul de **programare generică**, care permite scrierea de cod reutilizabil,​ flexibil ​și eficient. Utilizarea template-urilor ne permite să creăm clase și funcții independente de tipul de date specific, fiind astfel mai ușor să dezvoltăm structuri de date și algoritmi care pot fi utilizați pe o varietate de tipuri. Am învățat de asemenea cum să separăm în fișiere header definițiile funcțiilor și claselor template de implementările acestora din fișierele .cpp și ce probleme pot apărea în momentul în care facem acest lucru.+Am observat că funcțiile friend ​**nu** sunt moștenite în mod implicit în **clasele derivate**, fiind 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**.
  
-Template-urile oferă o **bază flexibilă pentru extinderea funcționalităților** fără a modifica ​codul existentÎn mod particular, prin crearea ​de template-uriputem construi un cod care este adaptabil pentru diverse tipuri de aplicațiide la procesarea numerelor până la manipularea textului ​și gestionarea obiectelor complexe.+Aceste concepte fundamentale de moștenire multiplă, agregare și gestionare a spațiului de nume ne oferă instrumente puternice pentru ​structura și organiza ​codul eficientAceste tehnici vor fi extrem ​de utile în viitoarele proiectefacilitând modularizarea codului și scalarea ​aplicațiilorreducând în același timp redundanța.
poo-is-ab/laboratoare/07.1737318574.txt.gz · Last modified: 2025/01/19 22:29 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