This shows you the differences between two versions of the page.
sd-ca:laboratoare:laborator-03 [2015/03/11 20:49] andrei.vasiliu2211 |
sd-ca:laboratoare:laborator-03 [2016/02/21 19:51] (current) darius.neatu |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Laborator 03 - Notiuni avansate de C++ ====== | + | ====== Articol 03 - Notiuni avansate de C++ ====== |
- | Responsabili | ||
- | * [[andrei.vasiliu2211@cti.pub.ro| Andrei Vasiliu]] | ||
- | * [[daniel.ciocirlan1607@cti.pub.ro| Daniel Ciocîrlan]] | ||
===== Obiective ===== | ===== Obiective ===== | ||
- | În urma parcurgerii acestui laborator studentul va: | + | În urma parcurgerii acestui articol studentul va: |
+ | * înțelege conceptul de template | ||
- | * afla funcționalitățile claselor/funcțiilor prietene | + | ===== Templates ===== |
- | * realiza supraîncărcarea operatorilor din C++ | + | |
- | * înțelege conceptul de copy constructor | + | |
- | * înțelege conceptul de rule of three | + | |
- | ===== Clase/metode prietene ===== | + | Motivul principal pentru care folosim C++ în cadrul SD este datorită funcționalității oferite de template-uri. |
- | Așa cum am văzut în primul laborator, fiecare membru al clasei poate avea 3 specificatori de acces: | + | Acestea permit generalizarea tipurilor de date folosite în interiorul funcțiilor și claselor. |
- | * public | + | |
- | * private | + | |
- | * protected | + | |
- | Alegerea specificatorilor se face în special în funcție de ce funcționalitate vrem să exportăm din clasa respectivă. | + | Sintaxa pentru acestea este: |
+ | <code c++> | ||
+ | template <class identifier> declaratie; | ||
+ | template <typename identifier> declaratie; | ||
+ | </code> | ||
+ | //declaratie// poate fi fie o funcție, fie o clasă. | ||
+ | Nu există nicio diferență între keyword-ul //class// și //typename// - important este că ceea ce urmează după ele este un placeholder pentru un tip de date. | ||
- | Dacă vrem să accesăm datele private/protejate din afara clasei, avem următoarele opțiuni: | + | ==== Function Template ==== |
- | * Funcții care ne întorc/setează valorile membre | + | În primul rând template-urile pot fi aplicate funcțiilor. |
- | * Funcții/Clase prietene (friend) cu clasa curentă. | + | |
- | O funcție prieten are următoarele proprietăți: | + | Un exemplu comun și simplu este următorul: |
- | * O funcţie este considerată prietenă al unei clase, dacă în declararea clasei, este declarată funcţia respectivă precedată de specificatorul **friend** | + | <code c++> |
- | * Declararea unei funcţii prieten poate fi făcută în orice parte a clasei(publică, privată sau protejată). | + | template<typename T> |
- | * Definiţia funcţiei prieten se face global, în afara clasei. | + | T getMax(T a, T b) { |
- | * Funcția declarată ca **friend** are acces liber la orice membru din interiorul clasei. | + | return a > b ? a : b; |
- | + | ||
- | + | ||
- | O clasă prieten are următoarele proprietăți: | + | |
- | * O clasă B este considerată prieten al unei clase A, dacă în declararea clasei A s-a întâlnit expresia: ''friend class B'' | + | |
- | * Clasa B poate accesa orice membru din clasa A, fără nici o restricție. | + | |
- | + | ||
- | De asemenea, dacă clasa A este considerată prieten cu clasa B, nu înseamnă că si clasa B este considerată prieten cu clasa A. Nici tranzitivitatea nu este valabilă în relaţia de prietenie dintre clase. | + | |
- | + | ||
- | Exemplu: | + | |
- | + | ||
- | <code cpp> | + | |
- | class Complex{ | + | |
- | + | ||
- | private: | + | |
- | int re; | + | |
- | int im; | + | |
- | public: | + | |
- | int GetRe(); | + | |
- | int GetIm(); | + | |
- | friend double ComplexModul(Complex c); //am declarat fct ComplexModul ca prieten | + | |
- | friend class Polinom; //Acum clasa Polinom care acces deplin la membrii **re** și **im** | + | |
- | }; | + | |
- | + | ||
- | double ComplexModul(Complex c) | + | |
- | { | + | |
- | return sqrt(c.re*c.re+c.im*c.im); //are voie, intrucat e prietena | + | |
} | } | ||
- | |||
</code> | </code> | ||
- | ===== Supraîncarcarea operatorilor ===== | + | Funcția poate fi apelată astfel: |
- | + | <code c++> | |
- | Un mecanism specific C++ este supraîncarcarea operatorilor, prin care programatorul poate asocia noi semnificaţii operatorilor deja existenţi. De exemplu, dacă dorim ca două numere complexe să fie adunate, în C trebuie să scriem funcții specifice, nenaturale. În C++ putem scrie foarte ușor: | + | getMax<int>(2, 3); |
- | + | getMax<double>(3.2, 4.6); | |
- | <code c> | + | |
- | Complex a(2,3); | + | |
- | Complex b(4,5); | + | |
- | Complex c=a+b; //operatorul + a fost supraîncarcat pentru a aduna două numere complexe | + | |
</code> | </code> | ||
- | Acest lucru este posibil, întrucât un operator este văzut ca o funcție, cu declarația: | + | ==== Class Template ==== |
+ | Concret, să presupunem că avem o clasă numită KeyStorage care are: | ||
+ | *o cheie (de tip int) | ||
+ | *un membru de date generic (al cărui tip de date nu îl știm la momentul scrierii clasei). | ||
- | tip_rezultat operator#(listă_argumente); | + | Vrem să putem folosi codul clasei indiferent de tipul de date al membrului. |
- | Așadar pentru a supraîncărca un operator pentru o anumită clasă, este necesar să declarăm funcția următoare în corpul acesteia: | + | Iată cum putem face acest lucru: |
- | tip_rezultat operator#(listă_argumente); | + | <code c++ KeyStorage.h> |
- | + | template<typename T> | |
- | + | class KeyStorage { | |
- | Există câteva restricții cu privire la supraîncarcare: | + | |
- | + | ||
- | * Nu pot fi supraîncărcaţi operatorii: ::, ., .*, ?:, sizeof. | + | |
- | * Setul de operatori ai limbajul C++ nu poate fi extins prin asocierea de semnificaţii noi unor caractere, care nu sunt operatori, de exemplu nu putem defini operatorul === . | + | |
- | * Prin supraîncărcarea unui operator nu i se poate modifica aritatea (astfel operatorul ! este unar şi poate fi redefinit numai ca operator unar). | + | |
- | * Asociativitatea şi precedenţa operatorului se menţin. | + | |
- | * La supraîncărcarea unui operator nu se pot specifica argumente cu valori implicite. | + | |
- | + | ||
- | + | ||
- | + | ||
- | ==== Operatori supraîncărcaţi ca funcţii prieten ==== | + | |
- | + | ||
- | + | ||
- | + | ||
- | Un operator binar va fi reprezentat printr-o funcţie nemembră cu două argumente, iar un operator unar, printr-o funcţie nemembră cu un singur argument. | + | |
- | + | ||
- | Utilizarea unui operator binar sub forma **a#b** este interpretată ca **operator#(a,b)**. | + | |
- | + | ||
- | Argumentele sunt clase sau referinţe constante la clase. | + | |
- | + | ||
- | + | ||
- | ==== Supraîncărcarea operatorilor << şi >> ==== | + | |
- | + | ||
- | În C++, orice dispozitiv de I/O este văzut drept un stream, așadar operațiile de I/O sunt operații cu stream-uri, care se definesc în felul următor: | + | |
- | * **Citire**: se execută cu operatorul de extracție >>, membru al clasei istream | + | |
- | * **Scriere**: se execută cu operatorul de inserție <<, membru al clasei ostream | + | |
- | + | ||
- | Acești operatori pot fi supraîncărcați pentru o clasă pentru a defini operații de I/O direct pe obiectele clasei. | + | |
- | + | ||
- | Supraîncărcarea se poate efectua folosind funcții friend utilizând următoarea sintaxă: | + | |
- | + | ||
- | <code c> | + | |
- | istream& operator>> (istream& f, clasa & ob); //Acum pot scrie in >> ob | + | |
- | ostream& operator<< (ostream& f, const clasa & ob); //Acum pot scrie out << ob | + | |
- | </code> | + | |
- | + | ||
- | <note important>Operatorii >> și << întorc fluxul original, pentru a scrie înlănțuiri de tipul ''f>>ob1>>ob2''. </note> | + | |
- | + | ||
- | Funcţiile operator pentru supraîncărcarea operatorilor de I/O le vom declara ca funcţii prieten al clasei care interacţionează cu fluxul. | + | |
- | + | ||
- | <code c++ Complex.h> | + | |
- | #include <iostream> | + | |
- | + | ||
- | class Complex | + | |
- | { | + | |
public: | public: | ||
- | double re; | + | int key; |
- | double im; | + | T member; |
- | + | ||
- | Complex(double real=0, double imag=0): re(real), im(imag) {}; | + | |
- | + | ||
- | //supraîncărcarea operatorilor +, - ca functii de tip "friend" | + | |
- | friend Complex operator+(const Complex& s, const Complex& d); | + | |
- | friend Complex operator-(const Complex& s, const Complex& d); | + | |
- | + | ||
- | //funcţii operator pentru supraîncărcarea operatorilor de intrare/ieşire | + | |
- | //declarate ca funcţii de tip "friend" | + | |
- | friend std::ostream& operator<< (std::ostream& out, const Complex& z); | + | |
- | friend std::istream& operator>> (std::istream& is, Complex& z); | + | |
}; | }; | ||
</code> | </code> | ||
- | <code c++ Complex.cpp> | + | În funcția main, să presupunem că vrem să folosim clasa cu membrul de tip long. |
- | #include "complex.h" | + | <code c++ main.cpp> |
+ | #include "KeyStorage.h" | ||
- | Complex operator+(const Complex& s, const Complex& d){ | + | int main() { |
- | return Complex(s.re+d.re,s.im+d.im); | + | KeyStorage<long> keyElement; |
- | } | + | return 0; |
- | + | ||
- | Complex operator-(const Complex& s, const Complex& d){ | + | |
- | return Complex(s.re+d.re,s.im+d.im); | + | |
- | } | + | |
- | + | ||
- | std::ostream& operator<<(std::ostream& out, const Complex& z){ | + | |
- | out << "(" << z.re << "," << z.im << ")"<< std::endl; | + | |
- | return out; | + | |
- | } | + | |
- | + | ||
- | std::istream& operator>>(std::istream& is, Complex& z){ | + | |
- | is >> z.re >> z.im; | + | |
- | return is; | + | |
} | } | ||
</code> | </code> | ||
+ | Practic, oriunde folosim tipul de date T în clasă, este înlocuit cu tipul pe care îl specificăm. | ||
- | <code c++ main.cpp> | + | ==== Where's the magic happening? ==== |
- | #include "complex.h" | + | Sunt destul de multe lucruri de spus despre template-uri, dar ne vom concentra pe lucrurile care schimbă modul în care ați implementat până acum. |
- | int main() { | + | Template-urile sunt de fapt indicii pentru compilator pentru a genera cod la rândul lui! |
- | Complex a(1,1), b(-1,2); | + | Practic, voi îi spuneți compilatorului un șablon generic pe care ați vrea să-l folosiți și el trebuie să fie pregătit să îl pună la dispoziția voastră când aveți nevoie. |
- | std::cout << "A: " << a << "B: " << b; | + | |
- | std::cout << "A+B: " << (a+b); | + | |
- | std::cin >> b; | + | |
- | std::cout << "B: " << b; | + | |
- | a=b; | + | |
- | std::cout << "A: " << a << "B: " << b; | + | |
- | }</code> | + | |
- | ==== Operatori supraîncărcaţi ca funcţii membre ==== | + | Ce trebuie să rețineți din asta? Totul se întâmplă la **compile time**, nu la run time. |
- | Funcţiilor membru li se transmite un argument implicit **this** (adresa obiectului curent), motiv pentru care un operator binar poate fi implementat printr-o funcţie membru nestatică cu un singur argument. | + | Compilatorul practic analizează modul în care voi folosiți clasa respectivă și generează pentru fiecare mod în care o folosiți șablonul corespunzător. |
+ | Folosirea KeyStorage<int> și KeyStorage<float> determină compilatorul să genereze cod pentru ambele clase (înlocuind o dată T cu int și altă cu float). | ||
+ | |||
+ | ==== Guideline-uri implementare ==== | ||
+ | Pentru că totul se întâmplă la compile time, înseamnă că în momentul în care compilatorul întâlnește secvența de cod ce folosește template-uri trebuie să știe //toate// modurile în care aceasta este folosita. | ||
- | Operatorii sunt interpretați în modul următor: | + | Asta înseamnă că: |
- | * Operatorul binar **a#b** este interpretat ca **a.operator#(b)** | + | *Trebuie să scrieți întreaga implementare în header! //sau// |
- | * Operatorul unar prefixat **#a** este interpretat ca **a.operator#()** | + | *Scrieți descrierea clasei generice în header, în fișierul de implementare fiecare metodă declarată este de fapt o funcție cu template și la sfârșitul implementării adăugat //template class numeclasa<numetip>//; |
- | * Operatorul unar postfixat **a#** este interpretat ca **a.operator#(int)** | + | |
- | <code c++ Complex.h> | + | Ultimul rând de fapt forțează folosirea template-ului cu un anumit tip de date și deci compilatorul generează cod corespunzător (trebuie să scrieți asta pentru toate tipurile). |
- | #include <iostream> | + | |
- | class Complex | + | ==== Clasa KeyStorage ==== |
- | { | + | Iată mai jos o structură mai dezvoltată pentru clasa KeyStorage, în care cheia este setată în constructor. |
+ | . | ||
+ | <code c++ KeyStorage.h> | ||
+ | template<typename T> | ||
+ | class KeyStorage { | ||
public: | public: | ||
- | double re; | + | KeyStorage(int k); |
- | double im; | + | ~KeyStorage(); |
- | + | ||
- | Complex(double real, double imag): re(real), im(imag) {}; | + | |
- | + | ||
- | //operatori supraîncărcaţi ca funcţii membre | + | |
- | Complex operator+(const Complex& d); | + | |
- | Complex operator-(const Complex& d); | + | |
- | Complex& operator+=(const Complex& d); | + | |
| | ||
- | friend std::ostream& operator<< (std::ostream& out, const Complex& z); | + | T getMember(); |
- | friend std::istream& operator>> (std::istream& is, Complex& z); | + | T setMember(T element); |
+ | |||
+ | private: | ||
+ | T member; | ||
+ | int key; | ||
}; | }; | ||
- | </code> | ||
- | <code c++ Complex.cpp> | ||
- | #include "complex.h" | ||
- | |||
- | Complex Complex::operator+(const Complex& d){ | ||
- | return Complex(re+d.re, im+d.im); | ||
- | } | ||
- | |||
- | Complex Complex::operator-(const Complex& d){ | ||
- | return Complex(re-d.re, im-d.im); | ||
- | } | ||
- | |||
- | Complex& Complex::operator+=(const Complex& d){ | ||
- | re+=d.re; | ||
- | im+=d.im; | ||
- | return *this; | ||
- | } | ||
- | |||
- | std::ostream& operator<<(std::ostream& out, const Complex& z){ | ||
- | out << "(" << z.re << "," << z.im << ")"<< std::endl; | ||
- | return out; | ||
- | } | ||
- | |||
- | std::istream& operator>>(std::istream& is, Complex& z){ | ||
- | is >> z.re >> z.im; | ||
- | return is; | ||
- | } | ||
</code> | </code> | ||
- | ==== Supraîncărcarea operatorului de atribuire ==== | + | Implementarea completa a ei poate fi realizată: |
- | + | *în header (în cazul template-urilor, acest mod este cel mai indicat). | |
- | Așa cum am amintit mai sus, majoritatea operatorilor pot fi supraîncărcați. O atenție importantă trebuie acordată operatorului de atribuire, dacă nu este supraîncărcat, realizează o copiere membru cu membru. | + | *în fișierul de implementare .cc / .cpp (al cărui schelet parțial îl găsiți mai jos). |
- | + | ||
- | Pentru obiectele care nu conţin date alocate dinamic la iniţializare, atribuirea prin copiere membru cu membru funcţionează corect, motiv pentru care nu se supraîncarcă operatorul de atribuire. | + | |
- | + | ||
- | <note important>Pentru clasele ce conţin date alocate dinamic, copierea membru cu membru, executată în mod implicit la atribuire conduce la copierea pointerilor la datele alocate dinamic, în loc de a copia datele.</note> | + | |
- | + | ||
- | Operatorul de atribuire poate fi redefinit numai ca funcţie membră, el fiind legat de obiectul din stânga operatorului =, motiv pentru care va întoarce o referinţă la obiect. | + | |
- | + | ||
- | <code c++ String.h> | + | |
- | class String{ | + | |
- | char* s; | + | |
- | int n; // lungimea sirului | + | |
- | + | ||
- | public: | + | |
- | String(); | + | |
- | String(const char* p); | + | |
- | String(const String& r); | + | |
- | ~String(); | + | |
- | String& operator=(const String& d); | + | |
- | String& operator=(const char* p); | + | |
- | }; | + | |
- | + | ||
- | + | ||
- | </code> | + | |
- | <code c++ String.cpp> | + | <code c++ KeyStorage.cpp> |
- | #include "String.h" | + | #include "KeyStorage.h" |
- | #include <string.h> | + | |
- | String& String::operator=(const String& d){ | + | template<typename T> |
- | if(this != &d){ //evitare autoatribuire | + | KeyStorage<T>::KeyStorage(int k) { |
- | if(s) //curatire | + | //TODO |
- | delete [] s; | + | |
- | n=d.n; //copiere | + | |
- | s=new char[n+1]; | + | |
- | strcpy(s, d.s); | + | |
- | } | + | |
- | return *this; //intoarce referinta la obiectul modificat | + | |
} | } | ||
- | String& String::operator=(const char* p){ | + | template<typename T> |
- | if(s) | + | KeyStorage<T>::~KeyStorage() { |
- | delete [] s; | + | |
- | n=strlen(p); | + | |
- | s=new char[n+1]; | + | |
- | strcpy(s, p); | + | |
- | return *this; | + | |
} | } | ||
- | </code> | + | //TODO: restul metodelor. |
- | ==== Copy-constructor ==== | + | // La sfarsit, cu tipurile de date pe care le veti folosi. |
+ | template class KeyStorage<int>; | ||
+ | template class KeyStorage<long>; | ||
- | Reprezintă un tip de constructor special care se folosește când se dorește/este necesară o copie a unui obiect existent. Dacă nu este declarat, se va genera unul default de către compilator. | ||
- | |||
- | Poate avea unul din următoarele prototipuri | ||
- | |||
- | * MyClass(const MyClass& obj); | ||
- | * MyClass(MyClass& obj); | ||
- | |||
- | === Când se apelează? === | ||
- | |||
- | 1) Apel explicit | ||
- | |||
- | <code c++ explicit_copy_constructor_call.cpp> | ||
- | MyClass m; | ||
- | MyClass x = MyClass(m); /* apel explicit al copy-constructor-ului */ | ||
</code> | </code> | ||
+ | <hidden> | ||
- | 2) Transfer prin valoare ca argument într-o funcție | + | -[**5p**] Implementati clasa **Fractie**, cu următoarele particularități: |
- | + | - [**2p**] doi constructori: | |
- | <code c++ call_by_value.cpp> | + | - primul vid |
- | void f(MyClass obj); | + | - al doilea va primi ca argumente numitorul și numărătorul |
- | ... | + | - Se vor implementa funcţii membre pentru: |
- | MyClass o; | + | - [**0.5p**] determinarea numitorului și numărătorului |
- | f(o); /* se apelează copy-constructor */ | + | - [**1p**] supraîncărcarea operatoriilor de comparație <, >, == (aveți grijă la egalitatea a două fracții) |
- | </code> | + | - [**0.5p**] supraîncărcarea operatorilor +, - |
- | + | - [**bonus 1p**] Supraîncărcați operatorii ++ și -- unari astfel încât numărul rațional reprezentat să crească/scadă cu o unitate. Căutați să vedeți cum se face diferența între ++ prefixat și postfixat. | |
- | 3) Transfer prin valoare ca return al unei funcții | + | - [**5p**] Implementaţi clasa template **Vector** care să permită lucrul cu vectori de obiecte, cu următoarele particularități: |
- | + | - Vor exista doi constructori: | |
- | <code c++ return_by_value.cpp> | + | - primul, vid, va inițializa numărul de elemente la 0 și pointerul de elemente la NULL |
- | MyClass f() | + | - al doilea va primi ca argument numărul de elemente și va aloca memorie pentru pointer |
- | { | + | - [**0.5p**] Se va defini și un destructor, care va dezaloca memoria alocată dinamic |
- | MyClass a; | + | - Se vor implementa funcţii friend (nemembre) pentru: |
- | return a; /* se apelează copy-constructor */ | + | - [**1p**] testul de egalitate a doi vector ( supraîncărcarea operatorului == ) |
- | } | + | - [**0.5p**] supraîncărcarea operatorului << (pentru scriere) |
- | </code> | + | - [**0.5p**] supraîncărcarea operatorului >> (pentru citire) |
- | + | - Se vor implementa funcţii membre pentru: | |
- | 4) La inițializarea unei variabile declarate pe aceeași linie | + | - [**1p**] supraîncărcarea operatorului de atribuire între două obiecte de tip vector |
- | + | - [**0.5p**] supraîncărcarea operatorului de indexare [] ce va permite accesul la elementele individuale prin indexare **Operatorul de indexare** este un operator binar, având ca prim termen obiectul care se indexează, iar ca al doilea termen indicele. (//''obiect[indice]'' este interpretat ca ''obiect.operator[](indice)''//. | |
- | <code c++ init.cpp> | + | |
- | MyClass m; | + | |
- | MyClass x = m; /* se apelează copy-constructor */ | + | |
- | </code> | + | |
- | + | ||
- | ==== Rule of Three ==== | + | |
- | + | ||
- | Reprezintă un concept de ** must do** pentru C++. Astfel: | + | |
- | + | ||
- | <note important>Dacă programatorul și-a declarat/definit unul dintre ** constructor default**, ** operator de assignment** sau ** copy-constructor**, trebuie să îi declare/definească și pe ceilalți 2</note> | + | |
- | + | ||
- | Explicație: dacă funcționalitatea vreunuia dintre cei 3 se vrea mai specială decât cea oferită default, atunci mai mult ca sigur se dorește schimbarea funcționalității default și pentru ceilalți 2 rămași. | + | |
- | + | ||
- | <code c++ rule_of_3.cpp> | + | |
- | class Complex | + | |
- | { | + | |
- | private: | + | |
- | int re; | + | |
- | int im; | + | |
- | public: | + | |
- | Complex() | + | |
- | { | + | |
- | re = 0; | + | |
- | im = 0; | + | |
- | printf("constructor default\n"); | + | |
- | } | + | |
- | + | ||
- | Complex(const Complex& c) | + | |
- | { | + | |
- | re = c.re; | + | |
- | im = c.im; | + | |
- | printf("copy contructor\n"); | + | |
- | } | + | |
- | + | ||
- | void operator=(const Complex& c) | + | |
- | { | + | |
- | re = c.re; | + | |
- | im = c.im; | + | |
- | printf("assignment operator\n"); | + | |
- | } | + | |
- | }; | + | |
- | </code> | + | |
<note warning>Tipul parametrului pentru copy-constructor trebuie să fie identic cu cel al parametrului pentru operatorul de assignment</note> | <note warning>Tipul parametrului pentru copy-constructor trebuie să fie identic cu cel al parametrului pentru operatorul de assignment</note> | ||
- | <hidden> | ||
===== Exerciții ===== | ===== Exerciții ===== | ||
- [**3p**] Implementați clasa **Complex**, cu următoarele particularități: | - [**3p**] Implementați clasa **Complex**, cu următoarele particularități: | ||
Line 387: | Line 169: | ||
- [**3p**] Implementați clasa **Vector3D**, cu următoarele particularități: | - [**3p**] Implementați clasa **Vector3D**, cu următoarele particularități: | ||
- [**0.5p**] un constructor fără parametri care inițializează membrii la 0 | - [**0.5p**] un constructor fără parametri care inițializează membrii la 0 | ||
- | - [**0.5p**] un constructor cu partea reală și imaginară | + | - [**0.5p**] un constructor cu 3 parametrii care inițializează cele 3 componente scalare ale obiectului de tip Vecto3D |
- [**0.5p**] un copy-constructor | - [**0.5p**] un copy-constructor | ||
- [**0.5p**] supraîncărcarea operatorilor = și == | - [**0.5p**] supraîncărcarea operatorilor = și == | ||
Line 414: | Line 196: | ||
- [**1p**] supraîncărcarea operatorului de atribuire între două obiecte de tip vector | - [**1p**] supraîncărcarea operatorului de atribuire între două obiecte de tip vector | ||
- [**0.5p**] supraîncărcarea operatorului de indexare [] ce va permite accesul la elementele individuale prin indexare **Operatorul de indexare** este un operator binar, având ca prim termen obiectul care se indexează, iar ca al doilea termen indicele. (//''obiect[indice]'' este interpretat ca ''obiect.operator[](indice)''//. | - [**0.5p**] supraîncărcarea operatorului de indexare [] ce va permite accesul la elementele individuale prin indexare **Operatorul de indexare** este un operator binar, având ca prim termen obiectul care se indexează, iar ca al doilea termen indicele. (//''obiect[indice]'' este interpretat ca ''obiect.operator[](indice)''//. | ||
+ | |||
- [**4p**] Implementaţi clasa template **Set** care să permită lucrul cu mulțimi de obiecte, cu următoarele particularități: | - [**4p**] Implementaţi clasa template **Set** care să permită lucrul cu mulțimi de obiecte, cu următoarele particularități: | ||
- Constructorul va primi dimensiunea maximă de elemente care pot fi ținute în mulțime și va aloca spațiul necesar. | - Constructorul va primi dimensiunea maximă de elemente care pot fi ținute în mulțime și va aloca spațiul necesar. |