Differences

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

Link to this comparison view

poo-is-ab:laboratoare:07 [2024/11/03 19:55]
razvan.cristea0106
poo-is-ab:laboratoare:07 [2025/11/08 17:18] (current)
razvan.cristea0106 [Moștenirea multiplă]
Line 1: Line 1:
-===== Laboratorul ​07 - 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 fișiere .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> <code cpp>
-template ​<typename T+#pragma once 
-T adunare(const T& a, const T& b)+#​include ​<cstring
 +#include <​iostream>​ 
 + 
 +class PiesaElectronica
 { {
- return a + b+    char* brand; 
-}+ 
 +public: 
 + 
 +    PiesaElectronica();​ 
 +    PiesaElectronica(const char* brand); 
 +    PiesaElectronica(const PiesaElectronica&​ piesaElectronica);​ 
 +    PiesaElectronica&​ operator=(const PiesaElectronica&​ piesaElectronica);​ 
 +    ~PiesaElectronica();​ 
 + 
 +    friend std::​ostream&​ operator<<​(std::​ostream&​ out, const PiesaElectronica&​ piesaElectronica)
 +};
 </​code>​ </​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. Astfelfuncția poate fi folosită cu diferite tipuri de date **fără** a necesita rescrierea pentru fiecare tip în parteasigurând ​**flexibilitate** și reducând ​**duplicarea codului**.</​note>​+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**.
  
-Apelarea funcției se poate face în maniera următoare.+Prin urmare declarația clasei **CameraWeb** ​poate fi observată în codul de mai jos.
  
 <code cpp> <code cpp>
-#​include ​<​iostream>​+#pragma once 
 +#​include ​"​ProdusComercial.h"​ 
 +#include "​PiesaElectronica.h"​
  
-template <​typename T> +class CameraWeb : public ProdusComercialpublic PiesaElectronica // CameraWeb mosteneste atat clasa ProdusComercial cat si clasa PiesaElectronica
-T adunare(const T& aconst T& b)+
 { {
- return a + b+    int rezolutie
-}+    char* culoare;
  
-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*/ +    CameraWeb(); 
- float ​sumaFloaturi ​adunare<​float>​(3.5f, 8.25f); // valid+    ​CameraWeb(const ​float& pret, const char* brand, const int& rezolutie, const char* culoare); 
 +    CameraWeb(const CameraWeb&​ cameraWeb);​ 
 +    CameraWeb&​ operator=(const CameraWeb&​ cameraWeb);​ 
 +    ~CameraWeb();
  
- std::cout << ​"Suma numerelor intregi este: " << sumaIntregi << '​\n';​ +    friend ​std::ostream&​ operator<<(std::ostream&​ out, const CameraWeb&​ cameraWeb)
- std::cout << "Suma numerelor reale este: " << sumaFloaturi << '​\n'​+};
- +
- return 0; +
-}+
 </​code>​ </​code>​
  
-Dacă am vrea să adunăm două tipuri diferite de date și să returnăm suma lor într-un alt tip de date am putea folosi următoarea formă de funcție template.+=== Implementarea constructorilor în clasa derivată === 
 + 
 +Se procedează în manieră similară moștenirii simple cu adăugările de rigoare după cum urmează în exemplele ​de mai jos. 
 + 
 +== Implementarea constructorului fără parametri ==
  
 <code cpp> <code cpp>
-template <​typename T1, typename T2, typename T3> +CameraWeb::​CameraWeb() : ProdusComercial()PiesaElectronica()
-T1 adunare(const T2& aconst T3& b)+
 { {
- return (T1)a + b;+    rezolutie = 0; 
 +    culoare = nullptr;
 } }
 </​code>​ </​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.+<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>​
  
-<code cpp> +== Implementarea constructorului cu parametri ==
-#include <​iostream>​+
  
-template ​<typename T1, typename T2, typename T3+Î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. 
-T1 adunare(const ​T2a, const T3b)+ 
 +<code cpp
 +CameraWeb::​CameraWeb(const ​floatpret, const char* brand, const intrezolutie, const char* culoare) : ProdusComercial(pret),​ PiesaElectronica(brand)
 { {
- return ​(T1)b;+    if (rezolutie <= 0) 
 +    { 
 +        this->​rezolutie = 0; 
 +    } 
 +    else 
 +    { 
 +        this->​rezolutie = rezolutie;​ 
 +    } 
 + 
 +    if (culoare != nullptr) 
 +    { 
 +        this->​culoare = new char[strlen(culoare) + 1]; 
 +        strcpy(this->​culoare,​ culoare); 
 +    } 
 +    else 
 +    { 
 +        this->​culoare = nullptr; 
 +    }
 } }
 +</​code>​
 +
 +== Implementarea constructorului de copiere ==
  
-int main()+<code cpp> 
 +CameraWeb::​CameraWeb(const CameraWeb&​ cameraWeb) : ProdusComercial(cameraWeb),​ PiesaElectronica(cameraWeb)
 { {
- double suma = adunare<​double>​(2, 7.5f); +    if (cameraWeb.rezolutie <= 0
- /*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*/+        rezolutie ​0
 +    } 
 +    else 
 +    { 
 +        rezolutie ​cameraWeb.rezolutie; 
 +    }
  
- std::cout << "Suma este: " << suma << '​\n'​+    if (cameraWeb.culoare != nullptr) 
- +    { 
- return 0;+        culoare = new char[strlen(cameraWeb.culoare) + 1]
 +        ​strcpy(culoare,​ cameraWeb.culoare);​ 
 +    } 
 +    else 
 +    { 
 +        culoare = nullptr; 
 +    }
 } }
 </​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.+<​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>​
  
-<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>​+=== Implementarea operatorului ​de asignare ​în clasa derivată ===
  
-=== Supraîncărcarea unei funcții template ===+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.
  
-**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.+<code cpp> 
 +CameraWeb&​ CameraWeb::​operator=(const CameraWeb&​ cameraWeb) 
 +
 +    if (this == &​cameraWeb) 
 +    { 
 +        return ​*this; 
 +    }
  
-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**.+    ProdusComercial::​operator=(cameraWeb);​ 
 +    PiesaElectronica::​operator=(cameraWeb);​
  
-<code cpp> +    if (culoare != nullptr) 
-#include <​iostream>​+    { 
 +        delete[] culoare; 
 +    }
  
-template <​typename T> +    if (cameraWeb.rezolutie <= 0
-void interschimbare(T& x, T& y+    
-+        ​rezolutie ​0
- T aux x+    } 
- x = y; +    else 
- aux+    { 
-}+        ​rezolutie ​cameraWeb.rezolutie
 +    }
  
-template <​typename T> +    if (cameraWeb.culoare != nullptr
-void interschimbare(T* x, T* y+    
-+        ​culoare ​new char[strlen(cameraWeb.culoare+ 1]; 
- if (x == nullptr || y == nullptr+        ​strcpy(culoare,​ cameraWeb.culoare);​ 
-+    } 
- return+    else 
- }+    ​
 +        ​culoare = nullptr
 +    }
  
- T aux = *x; +    return ​*this;
- *x = *y; +
- *y = aux;+
 } }
 +</​code>​
  
-int main()+=== Implementarea destructorului în clasa derivată === 
 + 
 +<code cpp> 
 +CameraWeb::​~CameraWeb()
 { {
- int a 22; +    if (culoare !nullptr) 
- int b = 6;+    { 
 +        delete[] culoare; 
 +    } 
 +
 +</​code>​
  
- interschimbare(ab);+<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>​
  
- std::​cout ​<< ​"​a ​" ​<< ​<< ​'​\n';​ +=== Implementarea operatorului ​<< ​în clasa derivată ​=== 
- std::cout << ​"b = " ​<< ​<< ​'​\n'​;+ 
 +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> 
 +std::ostream&​ operator<<(std::​ostream&​ out, const CameraWeb&​ cameraWeb) 
 +
 +    operator<<(out, (ProdusComercial&​)cameraWeb);​ 
 +    operator<<(out, (PiesaElectronica&​)cameraWeb);
  
- interschimbare(&​a,​ &b);+    out << "​Rezolutia camerei web este: " << cameraWeb.rezolutie << " pixeli\n";​ 
 +    out << "​Culoarea camerei web este: ";
  
- std::cout << "​\na ​" ​<< ​<< '​\n';​ +    if (cameraWeb.culoare !nullptr) 
- std::​cout ​<< "b = " << b << '\n';+    { 
 +        out << ​cameraWeb.culoare ​<< '​\n';​ 
 +    } 
 +    else 
 +    { 
 +        out << "N/A\n"; 
 +    }
  
- return ​0;+    ​return ​out;
 } }
 </​code>​ </​code>​
  
-Prin acest mecanism de **supraîncărcare a 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.+==== Agregare ====
  
-=== Separarea declarației de implementărea unei funcții 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.
  
-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. ​+<​note>​Agregarea ​este de două tipuri după cum urmează:
  
-Î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 template-urilor la momentul compilăriiimplementarile 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ă.+  * **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 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.+  ​* **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>​
  
-== Declararea funcției == +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.
- +
-Pentru început ​vom muta antetul funcției într-un fișier header după cum urmează.+
  
 <code cpp> <code cpp>
 #pragma once #pragma once
-#​include ​<​iostream>​+#​include ​"​CameraWeb.h"​
  
-template <​typename T> +class Laptop 
-T adunare(const T& x, const T& y)+
-</code>+    double pret
 +    ​CameraWeb cameraWeb; /relatia de has-a
  
-== Implementarea funcției ==+public:
  
-Fiind o **funcție template** va trebui să înștiințăm compilatorul acest lucru după cum urmează.+    Laptop(); 
 +    Laptop(const double& pret, const CameraWeb&​ cameraWeb);
  
-<code cpp> +    friend std::​ostream&​ operator<<(std::​ostreamout, const Laptoplaptop); 
-#include "​Template.h"​ +};
- +
-template<typename T> +
-T adunare(const Tx, const Ty) +
-+
- return x + y+
-}+
 </​code>​ </​code>​
  
-== Apelarea funcției în alt fișier == +Iar implementările metodelor ​și a operatorului de afișare pentru clasa **Laptop** le putem observa ​în blocul de cod de mai jos.
- +
-Apelarea funcției se face în maniera următoare.+
  
 <code cpp> <code cpp>
-#include "​Template.h"+Laptop::​Laptop() 
 +
 +    pret = 0.0; 
 +    cameraWeb = CameraWeb();​ // se foloseste operatorul egal din clasa CameraWeb 
 +}
  
-int main()+Laptop::​Laptop(const double& pret, const CameraWeb&​ cameraWeb)
 { {
- int a 22+    if (pret <0.0) 
- int b 6;+    { 
 +        this->​pret = 0.0
 +    } 
 +    else 
 +    { 
 +        this->​pret ​pret; 
 +    }
  
- std::cout << ​adunare(ab) << ​'\n';+    this->​cameraWeb = cameraWeb; // se foloseste operatorul egal din clasa CameraWeb 
 +
 + 
 +std::ostream&​ operator<<(std::​ostream&​ outconst Laptop& laptop) 
 +
 +    out << ​"​Pretul laptopului este: " << laptop.pret << " ron\n"; 
 +    out << "​\nDetalii despre camera web a laptopului\n\n";​ 
 +    out << laptop.cameraWeb;
  
- return ​0;+    ​return ​out;
 } }
 </​code>​ </​code>​
  
-=== ===+Codul cu implementările complete ale tuturor claselor prezentate în acest laborator poate fi descărcat de {{:​poo-is-ab:​laboratoare:​mostenire_multipla_agregare.zip|aici}}.
  
-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**.+==== Namespace-uri definite ​de către programator ====
  
-<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șadar, compilatorul ​**trebuie să aibă acces** la implementarea completă a **funcției template** de fiecare dată când o utilizează cu **un nou tip de date**.+**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 ș**facilitează organizarea codului în module logice**.
  
-Î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ă ​**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>​+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 denumire acesta ​se numește **namespace anonim** și poate fi utilizat doar în fișierul ​în care este declarat.
  
-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. Aceasta 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.+<​code ​cpp
 +#pragma once
  
-<​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 **forț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>​+namespace MathHelper 
 +
 +    int rest(const int& aconst int& b); 
 +    int adunare(const int& ​a, const int& b); 
 +    int diferenta(const int& ​a, const int& b); 
 +    int inmultire(const int& a, const int& b); 
 +    int impartire(const int& a, const int& b); 
 +
 +</​code>​ 
 + 
 +Iar implementările funcțiilor din cadrul namespace-ului se pot observa mai jos.
  
 <code cpp> <code cpp>
-#include "Template.h"+#include "MathUtils.h"
  
-template<​typename T> +int MathHelper::​rest(const ​inta, const intb)
-T adunare(const ​Tx, const Ty)+
 { {
- return ​x + y;+    ​return ​(b == 0) ? -1 : a % b;
 } }
  
-void testare()+int MathHelper::​adunare(const int& a, const int& b)
 { {
- int s1 = adunare(23); +    return a + b; 
- float s2 = adunare(2.3f3.0f); +
- double s3 = adunare(-1.48.24); + 
- unsigned int s4 = adunare(2u, 3u);+int MathHelper::​diferenta(const int& aconst int& b) 
 +
 +    return a - b
 +
 + 
 +int MathHelper::​inmultire(const int& aconst int& b) 
 +
 +    return a * b
 +
 + 
 +int MathHelper::​impartire(const int& aconst int& b
 +
 +    return ​(b == 0? 0 : a / b;
 } }
 </​code>​ </​code>​
  
-Prin urmare putem apela acum funcția ​de adunare pentru patru tipuri de date după cum urmează.+Testarea acestui **namespace** este facută în **funcția ​main** ​după cum urmează ​în codul de mai jos.
  
 <code cpp> <code cpp>
-#include "Template.h"+#include "MathUtils.h"
  
 int main() int main()
 { {
- std::cout << "Suma numerelor este: " << adunare(22, 8) << '​\n'​// valid +    int x = 10
- std::cout << "Suma numerelor este: " << adunare(2.2f,​ 4.5f) << '​\n';​ // valid +    int y = 5;
- 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;+    std::cout << "Suma numerelor este: " << MathHelper::​adunare(x,​ y) << '​\n';​ 
 +    std::cout << "​Diferenta numerelor este: " << MathHelper::​diferenta(x,​ y) << '​\n';​ 
 +    std::cout << "​Produsul numerelor este: " << MathHelper::​inmultire(x,​ y) << '​\n';​ 
 +    std::cout << "​Rezultatul impartirii numerelor este: " << MathHelper::​impartire(x,​ y) << '​\n';​ 
 +    std::cout << "​Restul impartirii numerelor este: " << MathHelper::​rest(x,​ y) << '​\n';​ 
 + 
 +    ​return 0;
 } }
 </​code>​ </​code>​
  
-<note important>​Datorită faptului ​că în funcția de testare ​**nu** am apelat ​instanță a **funcției template** pentru tipul de date **long**, dacă se decomentează linia din codul de mai susva apărea o **eroare ​de linker**. Acest lucru se întâmplă deoarece compilatorul ​**nu** a generat ​implementare ​pentru ​tipul **long**, care nu a fost utilizat ​în funcția de testare.</​note>​+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 ​funcție **specificând și namespace-ul**,​ compilatorul va identifica **automat** funcția corectă, asigurând o structură mai clară și ușor de întreținut,​ mai ales în proiectele mari sau în situațiile în care folosim cu biblioteci externe. 
 + 
 +==== Concluzii ==== 
 + 
 +Închidem prin a menționa faptul că acest laborator ne-a oferit o înțelegere practică a conceptelor de **moștenire multiplă** și de **relație de agregare ("​has-a"​)** între claseevidențiind astfel modurile prin care putem structura relații logice și eficiente între clase.  
 + 
 + 
 +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. 
 + 
 +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. Astfelam putut organiza eficient datele și responsabilitățile între clase, menținând ​separare clară a rolurilor. 
 + 
 +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 soluție eficientă ​pentru ​organizarea codului, mai ales când avem funcții și clase cu nume **identice ca și denumiri** în proiecte mari. 
 + 
 +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**.
  
-==== Clase template ====+Aceste concepte fundamentale de moștenire multiplă, agregare și gestionare a spațiului de nume ne oferă instrumente puternice pentru a structura și organiza codul eficient. Aceste tehnici vor fi extrem de utile în viitoarele proiecte, facilitând modularizarea codului și scalarea aplicațiilor,​ reducând în același timp redundanța.
poo-is-ab/laboratoare/07.1730656508.txt.gz · Last modified: 2024/11/03 19:55 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