This shows you the differences between two versions of the page.
|
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 exemplu, structuri 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ă a 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 și 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-uri, putem crea funcții și clase care nu depind de un anumit tip de date. Codul devine mai flexibil și reutilizabil, reducând efortul de scriere și întreținere. | + | <note important>În general, găsirea unei combinații potrivite de clase care să respecte **moștenirea multiplă** poate fi o provocare, deoarece 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 a **evita** situațiile de ambiguitate și a păstra codul ușor de întreținut și de extins.</note> |
| - | + | ||
| - | * **Flexibilitate și extensibilitate** <=> **Funcțiile** și **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 specific, denumit **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 date. Aceasta 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 char* brand); |
| + | 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 și 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 T1, typename T2, typename T3> | + | class CameraWeb : public ProdusComercial, public 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& x, T& y) | + | |
| { | { | ||
| - | T aux = x; | + | rezolutie = 0; |
| - | x = 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(T* x, T* y) | + | |
| - | { | + | |
| - | 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(a, b); | + | if (culoare != nullptr) |
| + | { | ||
| + | this->culoare = new char[strlen(culoare) + 1]; | ||
| + | strcpy(this->culoare, culoare); | ||
| + | } | ||
| + | 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 CameraWeb& cameraWeb) : 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 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. | + | <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 mecanism, putem 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ărinte, acest 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ș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**. | + | |
| - | + | ||
| - | Î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 principal, motiv 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::ostream& out, const CameraWeb& cameraWeb) |
| - | + | ||
| - | template<typename T> | + | |
| - | T adunare(const T& x, const T& y) | + | |
| { | { | ||
| - | 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 Student& student); | + | Laptop(const double& pret, const CameraWeb& cameraWeb); |
| - | Student& operator=(const Student& student); | + | |
| - | ~Student(); | + | |
| - | + | ||
| - | char* getNume() const; | + | |
| - | T getMedieAnuala() const; | + | |
| - | + | ||
| - | void setNume(const char* nume); | + | |
| - | void setMedieAnuala(const T& medieAnuala); | + | |
| - | template <typename T> | + | friend std::ostream& operator<<(std::ostream& out, const Laptop& laptop); |
| - | 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 double& pret, 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 Laptop& laptop) |
| - | + | ||
| - | <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 a + 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 int& a, const int& b) |
| - | void Student<T>::setMedieAnuala(const T& medieAnuala) | + | |
| { | { | ||
| - | if (medieAnuala <= 0) | + | return a - b; |
| - | { | + | |
| - | return; | + | |
| - | } | + | |
| - | + | ||
| - | this->medieAnuala = medieAnuala; | + | |
| } | } | ||
| - | </code> | ||
| - | === Implementarea operatorului de afișare === | + | int MathHelper::inmultire(const int& a, const int& b) |
| - | + | ||
| - | <code cpp> | + | |
| - | template <typename T> | + | |
| - | std::ostream& operator<<(std::ostream& out, 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 (b == 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 y = 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 codului, mai 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-uri, putem construi un cod care este adaptabil pentru diverse tipuri de aplicații, de 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 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. |