This shows you the differences between two versions of the page.
poo-is-ab:laboratoare:07 [2024/11/03 19:32] razvan.cristea0106 [Funcții template] |
poo-is-ab:laboratoare:07 [2025/01/19 22:29] (current) razvan.cristea0106 |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Laboratorul 07 - Funcții și clase template ===== | + | ===== Laborator 08 - Funcții și clase template ===== |
**Autor: Răzvan Cristea** | **Autor: Răzvan Cristea** | ||
Line 10: | Line 10: | ||
* recunoască și să definească clase template | * recunoască și să definească clase template | ||
* înțeleagă importanța conceptului de programare generică | * înțeleagă importanța conceptului de programare generică | ||
- | * organizeze în fișiere header și fișiere .cpp codul pentru funcțiile și clasele template | + | * organizeze în fișiere header și .cpp codul pentru funcțiile și clasele template |
==== Introducere ==== | ==== Introducere ==== | ||
Line 251: | Line 251: | ||
Î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> | Î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. | ||
+ | |||
+ | <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> | ||
+ | |||
+ | <code cpp> | ||
+ | #include "Template.h" | ||
+ | |||
+ | template<typename T> | ||
+ | T adunare(const T& x, const T& y) | ||
+ | { | ||
+ | return x + y; | ||
+ | } | ||
+ | |||
+ | void testare() | ||
+ | { | ||
+ | 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ă. | ||
+ | |||
+ | <code cpp> | ||
+ | #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> | ||
+ | |||
+ | <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> | ||
+ | |||
+ | ==== Clase template ==== | ||
+ | |||
+ | **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**. | ||
+ | |||
+ | 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. | ||
+ | |||
+ | 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ă. | ||
+ | |||
+ | <code cpp> | ||
+ | #pragma once | ||
+ | #include <string> | ||
+ | #include <iostream> | ||
+ | |||
+ | template <typename T> | ||
+ | class Student | ||
+ | { | ||
+ | char* nume; | ||
+ | T medieAnuala; | ||
+ | |||
+ | public: | ||
+ | |||
+ | Student(const char* nume, const T& medieAnuala); | ||
+ | Student(const Student& student); | ||
+ | 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 Student<T>& student); | ||
+ | }; | ||
+ | </code> | ||
+ | |||
+ | În continuare vom prezenta detaliat maniera de implementare a fiecărei metode în parte. | ||
+ | |||
+ | === Implementarea constructorilor === | ||
+ | |||
+ | <code cpp> | ||
+ | template <typename T> | ||
+ | Student<T>::Student(const char* nume, const T& medieAnuala) | ||
+ | { | ||
+ | if (nume != nullptr) | ||
+ | { | ||
+ | this->nume = new char[strlen(nume) + 1]; | ||
+ | strcpy(this->nume, nume); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | this->nume = nullptr; | ||
+ | } | ||
+ | |||
+ | this->medieAnuala = medieAnuala; | ||
+ | } | ||
+ | |||
+ | template<typename T> | ||
+ | Student<T>::Student(const Student<T>& student) | ||
+ | { | ||
+ | if (student.nume != nullptr) | ||
+ | { | ||
+ | nume = new char[strlen(student.nume) + 1]; | ||
+ | strcpy(nume, student.nume); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | nume = nullptr; | ||
+ | } | ||
+ | |||
+ | medieAnuala = student.medieAnuala; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | === Implementarea operatorului de asignare === | ||
+ | |||
+ | <code cpp> | ||
+ | template<typename T> | ||
+ | Student<T>& Student<T>::operator=(const Student<T>& student) | ||
+ | { | ||
+ | if (this == &student) | ||
+ | { | ||
+ | return *this; | ||
+ | } | ||
+ | |||
+ | if (nume != nullptr) | ||
+ | { | ||
+ | delete[] nume; | ||
+ | } | ||
+ | |||
+ | if (student.nume != nullptr) | ||
+ | { | ||
+ | nume = new char[strlen(student.nume) + 1]; | ||
+ | strcpy(nume, student.nume); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | nume = nullptr; | ||
+ | } | ||
+ | |||
+ | medieAnuala = student.medieAnuala; | ||
+ | |||
+ | return *this; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | === Implementarea destructorului === | ||
+ | |||
+ | <code cpp> | ||
+ | template<typename T> | ||
+ | Student<T>::~Student() | ||
+ | { | ||
+ | if (nume != nullptr) | ||
+ | { | ||
+ | delete[] nume; | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | === Implementarea metodelor accesor === | ||
+ | |||
+ | <code cpp> | ||
+ | template<typename T> | ||
+ | char* Student<T>::getNume() const | ||
+ | { | ||
+ | return nume; | ||
+ | } | ||
+ | |||
+ | template<typename T> | ||
+ | T Student<T>::getMedieAnuala() const | ||
+ | { | ||
+ | return medieAnuala; | ||
+ | } | ||
+ | |||
+ | template<typename T> | ||
+ | void Student<T>::setNume(const char* nume) | ||
+ | { | ||
+ | if (nume == nullptr) | ||
+ | { | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | if (this->nume != nullptr) | ||
+ | { | ||
+ | delete[] this->nume; | ||
+ | } | ||
+ | |||
+ | this->nume = new char[strlen(nume) + 1]; | ||
+ | strcpy(this->nume, nume); | ||
+ | } | ||
+ | |||
+ | template<typename T> | ||
+ | void Student<T>::setMedieAnuala(const T& medieAnuala) | ||
+ | { | ||
+ | if (medieAnuala <= 0) | ||
+ | { | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | this->medieAnuala = medieAnuala; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | === Implementarea operatorului de afișare === | ||
+ | |||
+ | <code cpp> | ||
+ | template <typename T> | ||
+ | std::ostream& operator<<(std::ostream& out, const Student<T>& student) | ||
+ | { | ||
+ | out << "Numele studentului este: "; | ||
+ | |||
+ | 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 === | ||
+ | |||
+ | <code cpp> | ||
+ | void testTemplate() | ||
+ | { | ||
+ | Student<int> s1("Ion", 10); | ||
+ | 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> | ||
+ | |||
+ | ==== ==== | ||
+ | |||
+ | Având acum implementarea clasei template **Student** putem să o folosim în codul din programul principal după cum umrează. | ||
+ | |||
+ | <code cpp> | ||
+ | #include "Student.h" | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | Student<int> s1("Ion", 10); | ||
+ | Student<int> s2("George", 9); | ||
+ | Student<int> s3 = s2; | ||
+ | |||
+ | s3 = s1; | ||
+ | |||
+ | s3.setNume("Maria"); | ||
+ | s3.setMedieAnuala(8); | ||
+ | |||
+ | std::cout << s3 << '\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'; | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </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> | ||
+ | |||
+ | ==== 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. | ||
+ | |||
+ | 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. |