This shows you the differences between two versions of the page.
sd-ca:laboratoare:laborator-02 [2016/02/21 19:33] darius.neatu |
sd-ca:laboratoare:laborator-02 [2016/02/21 19:51] (current) radu.stochitoiu |
||
---|---|---|---|
Line 3: | Line 3: | ||
===== Obiective ===== | ===== Obiective ===== | ||
- | În urma parcurgerii acestui laborator studentul va: | + | În urma parcurgerii acestui articol studentul va: |
- | * înțelege conceptul de template | + | * învăța ce înseamnă o clasă |
- | * înțelege conceptul de referințe din C++ | + | * învăța ce înseamnă constructor / destructor |
- | * înțelege conceptul de read-only introdus prin identificatorul const | + | * afla funcționalitățile claselor / funcțiilor prietene |
+ | * realiza supraîncărcarea operatorilor din C++ | ||
+ | * înțelege conceptul de copy constructor | ||
+ | * înțelege conceptul de rule of three | ||
- | ===== Templates ===== | ||
- | Motivul principal pentru care folosim C++ în cadrul SD este datorită funcționalității oferite de template-uri. | ||
- | Acestea permit generalizarea tipurilor de date folosite în interiorul funcțiilor și claselor. | + | ==== Clase ==== |
- | Sintaxa pentru acestea este: | + | Formal am făcut deja primii pași mai sus pentru a implementa o clasă în C++, utilizând keyword-ul //struct//. |
- | <code c++> | + | |
- | template <class identifier> declaratie; | + | Totuși, ce înseamnă o clasă? Nu trebuie decât să ne gândim la ce am făcut mai sus: |
- | template <typename identifier> declaratie; | + | *am definit un tip de date |
+ | *i-am adăugat atribute (am definit ce proprietăți îl caracterizează: partea reală și partea imaginară) | ||
+ | *i-am adăugat metode (am definit cum se comportă: inițializarea și conjugarea) | ||
+ | |||
+ | Cu această adăugare menționată, putem să ne referim la ceea ce înseamnă o **clasă**, respectiv un **obiect**. | ||
+ | |||
+ | Ne referim la o **clasă** ca fiind o amprentă (blueprint) sau descriere generală. | ||
+ | Un **obiect** sau o **instanță a clasei** este o variabilă concretă ce se conformează descrierii clasei. | ||
+ | |||
+ | Vom numi **clasă** tipul de date definit de //struct complex// sau //class complex// și **obiect** o instanțiere (o alocare dinamică sau locală) a tipului de date. | ||
+ | |||
+ | Când discutăm despre tipul de date //complex// ne referim la clasă. | ||
+ | Când discutăm despre variabila //number// ne referim la un obiect, o instanță a clasei. | ||
+ | |||
+ | ==== Keyword-ul "class" vs. "struct" ==== | ||
+ | |||
+ | Și totuși, C++ adăugă keyword-ul //class//. Care este diferența între //class// și //struct//? | ||
+ | Iată cum definim complet clasa de mai sus, separând antetul de implementare și de programul principal. | ||
+ | |||
+ | <columns 100% 100% -> | ||
+ | <code c++ complex.h> | ||
+ | class Complex { | ||
+ | double re; | ||
+ | double im; | ||
+ | |||
+ | Complex conjugate(); | ||
+ | }; | ||
</code> | </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. | ||
- | ==== Function Template ==== | + | <newcolumn> |
- | În primul rând template-urile pot fi aplicate funcțiilor. | + | |
- | Un exemplu comun și simplu este următorul: | + | <code c++ complex.cc> |
- | <code c++> | + | #include "complex.h" |
- | template<typename T> | + | Complex Complex::conjugate() { |
- | T getMax(T a, T b) { | + | Complex conjugate; |
- | return a > b ? a : b; | + | conjugate.re = this->re; |
+ | conjugate.im = -(this->im); | ||
+ | |||
+ | return conjugate; | ||
} | } | ||
</code> | </code> | ||
- | Funcția poate fi apelată astfel: | + | <newcolumn> |
- | <code c++> | + | |
- | getMax<int>(2, 3); | + | <code c++ main.cc> |
- | getMax<double>(3.2, 4.6); | + | #include <stdio.h> |
+ | #include "complex.h" | ||
+ | |||
+ | int main() { | ||
+ | Complex number; | ||
+ | number.re = 2; | ||
+ | number.im = 4; | ||
+ | |||
+ | printf("%.2lf %.2lf\n", number.re, number.im); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
</code> | </code> | ||
+ | </columns> | ||
- | ==== 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). | ||
- | Vrem să putem folosi codul clasei indiferent de tipul de date al membrului. | ||
- | Iată cum putem face acest lucru: | + | |
- | <code c++ KeyStorage.h> | + | ==== Specificatori de acces ==== |
- | template<typename T> | + | Am observat mesajul de eroare în urma compilării fișierelor de mai sus. |
- | class KeyStorage { | + | |
+ | Astfel, **singura diferență** folosirea celor două keyword-uri este nivelul implicit de vizibilitate a metodelor și atributelor. | ||
+ | ***private** - pentru clasele declarate cu **class** | ||
+ | ***public** - pentru clasele declarate cu **struct** | ||
+ | |||
+ | Membri precedați de label-ul **private** pot fi folosiți numai în interiorul clasei, în cadrul metodelor acesteia. | ||
+ | Ei nu pot fi citiți sau modificați din afara clasei. | ||
+ | |||
+ | Iată cum puteam remedia soluția: | ||
+ | <code c++ complex.h> | ||
+ | class Complex { | ||
public: | public: | ||
- | int key; | + | double re; |
- | T member; | + | double im; |
+ | |||
+ | Complex conjugate(); | ||
}; | }; | ||
</code> | </code> | ||
- | În funcția main, să presupunem că vrem să folosim clasa cu membrul de tip long. | + | ==== Constructori și destructori ==== |
- | <code c++ main.cpp> | + | |
- | #include "KeyStorage.h" | + | Studiați codul de mai jos. |
+ | |||
+ | <columns 100% 100% -> | ||
+ | <code c++ complex.h> | ||
+ | class Complex { | ||
+ | public: | ||
+ | // Constructor | ||
+ | Complex(double re, double im); | ||
+ | |||
+ | // Destructor | ||
+ | ~Complex(); | ||
+ | |||
+ | double getRe(); | ||
+ | double getIm(); | ||
+ | |||
+ | Complex conjugate(); | ||
+ | |||
+ | private: | ||
+ | double re; | ||
+ | double im; | ||
+ | }; | ||
+ | </code> | ||
+ | |||
+ | <newcolumn> | ||
+ | |||
+ | <code c++ complex.cc> | ||
+ | #include "complex.h" | ||
+ | Complex::Complex(double re, double im) { | ||
+ | this->re = re; | ||
+ | this->im = im; | ||
+ | } | ||
+ | |||
+ | Complex::~Complex() { | ||
+ | } | ||
+ | |||
+ | Complex Complex::conjugate() { | ||
+ | Complex conjugat(re, -im); | ||
+ | return conjugat; | ||
+ | } | ||
+ | |||
+ | double Complex::getRe() { | ||
+ | return re; | ||
+ | } | ||
+ | |||
+ | double Complex::getIm() { | ||
+ | return im; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | </columns> | ||
+ | |||
+ | <code c++ main.cc> | ||
+ | #include <stdio.h> | ||
+ | #include "complex.h" | ||
int main() { | int main() { | ||
- | KeyStorage<long> keyElement; | + | Complex number(2, 3); |
+ | printf("%lf %lf\n", number.getRe(), number.getIm()); | ||
+ | | ||
return 0; | return 0; | ||
} | } | ||
</code> | </code> | ||
+ | </columns> | ||
- | Practic, oriunde folosim tipul de date T în clasă, este înlocuit cu tipul pe care îl specificăm. | + | ==== Constructor ==== |
- | ==== Where's the magic happening? ==== | + | Observăm două bucăți din cod în mod special: |
- | 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. | + | <code c++> |
+ | Complex::Complex(double re, double im); | ||
+ | </code> | ||
+ | Linia de mai sus **nu are tip** returnat, spre deosebire de celelalte linii. | ||
+ | Acesta este **constructorul** clasei, care este apelat în momentul alocării unui obiect. | ||
- | Template-urile sunt de fapt indicii pentru compilator pentru a genera cod la rândul lui! | + | Ce operații sunt uzuale în constructor? |
- | 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. | + | *inițializarea membrilor clasei cu valori predefinite sau date ca parametru |
+ | *alocarea memoriei pentru anumiți membri | ||
- | Ce trebuie să rețineți din asta? Totul se întâmplă la **compile time**, nu la run time. | + | A doua bucată observată este: |
+ | <code c++> | ||
+ | Complex numar(2, 3); | ||
+ | </code> | ||
+ | Până acum nu ați mai alocat astfel structurile. Ce se întâmplă în spate este exact ceea ce intuiți: este apelat constructorul obiectului și se execută instrucțiunile acestuia pentru variabila numar (reprezentată ca pointer prin this, direct în interiorul constructorului). | ||
- | 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). | + | În constructorul definit mai sus, tot ceea ce se întâmplă este să se inițializeze membri. Pentru asta, C++ vă pune la dispoziție o sintaxă simplă: |
+ | <code c++> | ||
+ | Complex::Complex(double real, double imaginar) : | ||
+ | re(real), | ||
+ | im(imaginar) { | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Cei doi constructori sunt identici ca funcționalitate. | ||
+ | |||
+ | ==== Copy-constructor ==== | ||
+ | |||
+ | 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> | ||
+ | |||
+ | 2) Transfer prin valoare ca argument într-o funcție | ||
+ | |||
+ | <code c++ call_by_value.cpp> | ||
+ | void f(MyClass obj); | ||
+ | ... | ||
+ | MyClass o; | ||
+ | f(o); /* se apelează copy-constructor */ | ||
+ | </code> | ||
+ | |||
+ | 3) Transfer prin valoare ca return al unei funcții | ||
+ | |||
+ | <code c++ return_by_value.cpp> | ||
+ | MyClass f() | ||
+ | { | ||
+ | MyClass a; | ||
+ | return a; /* se apelează copy-constructor */ | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | 4) La inițializarea unei variabile declarate pe aceeași linie | ||
+ | |||
+ | <code c++ init.cpp> | ||
+ | MyClass m; | ||
+ | MyClass x = m; /* se apelează copy-constructor */ | ||
+ | </code> | ||
+ | |||
+ | |||
+ | ==== Destructor ==== | ||
+ | Așa cum probabil ați observat, **constructorul** este apelat în mod **explicit** de către voi. **Destructorul** însă, în cazul de mai sus, este apelat **implicit** la terminarea blocului care realizează dealocărea automată a obiectului. | ||
+ | |||
+ | Un destructor nu are parametri și se declară în interiorul clasei astfel: | ||
+ | <code c++> | ||
+ | ~Complex(); | ||
+ | </code> | ||
+ | |||
+ | Dacă în constructor sau în interiorul clasei ați fi alocat memorie, cel mai probabil în destructor ați fi făcut curat și ați fi apelat free pe membrul respectiv. | ||
+ | |||
+ | ==== 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> | ||
+ | |||
+ | ===== Clase/metode prietene ===== | ||
+ | |||
+ | Așa cum am văzut în primul articol, fiecare membru al clasei poate avea 3 specificatori de acces: | ||
+ | * public | ||
+ | * private | ||
+ | * protected | ||
+ | |||
+ | Alegerea specificatorilor se face în special în funcție de ce funcționalitate vrem să exportăm din clasa respectivă. | ||
+ | |||
+ | Dacă vrem să accesăm datele private/protejate din afara clasei, avem următoarele opțiuni: | ||
+ | * Funcții care ne întorc/setează valorile membre | ||
+ | * Funcții/Clase prietene (friend) cu clasa curentă. | ||
+ | |||
+ | O funcție prieten are următoarele proprietăți: | ||
+ | * O funcţie este considerată prietenă al unei clase, dacă în declararea clasei, este declarată funcţia respectivă precedată de specificatorul **friend** | ||
+ | * Declararea unei funcţii prieten poate fi făcută în orice parte a clasei(publică, privată sau protejată). | ||
+ | * Definiţia funcţiei prieten se face global, în afara clasei. | ||
+ | * Funcția declarată ca **friend** are acces liber la orice membru din interiorul clasei. | ||
+ | |||
+ | |||
+ | 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> | ||
+ | |||
+ | ===== Supraîncarcarea operatorilor ===== | ||
+ | |||
+ | 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: | ||
+ | |||
+ | <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> | ||
+ | |||
+ | Acest lucru este posibil, întrucât un operator este văzut ca o funcție, cu declarația: | ||
+ | |||
+ | tip_rezultat operator#(listă_argumente); | ||
+ | |||
+ | 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: | ||
+ | tip_rezultat operator#(listă_argumente); | ||
+ | |||
+ | |||
+ | 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> | ||
- | ==== Guideline-uri implementare ==== | + | <note important>Operatorii >> și << întorc fluxul original, pentru a scrie înlănțuiri de tipul ''f>>ob1>>ob2''. </note> |
- | 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. | + | |
- | Asta înseamnă că: | + | 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. |
- | *Trebuie să scrieți întreaga implementare în header! //sau// | + | |
- | *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>//; | + | |
- | 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). | + | <code c++ Complex.h> |
+ | #include <iostream> | ||
- | ==== Clasa KeyStorage ==== | + | class Complex |
- | 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: | ||
- | KeyStorage(int k); | + | double re; |
- | ~KeyStorage(); | + | double im; |
+ | |||
+ | 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); | ||
| | ||
- | T getMember(); | + | //funcţii operator pentru supraîncărcarea operatorilor de intrare/ieşire |
- | T setMember(T element); | + | //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 c++ Complex.cpp> | ||
+ | #include "complex.h" | ||
+ | |||
+ | Complex operator+(const Complex& s, const Complex& d){ | ||
+ | return Complex(s.re+d.re,s.im+d.im); | ||
+ | } | ||
+ | |||
+ | 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 c++ main.cpp> | ||
+ | #include "complex.h" | ||
+ | |||
+ | int main() { | ||
+ | Complex a(1,1), b(-1,2); | ||
+ | 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 ==== | ||
+ | |||
+ | 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. | ||
+ | |||
+ | Operatorii sunt interpretați în modul următor: | ||
+ | * Operatorul binar **a#b** este interpretat ca **a.operator#(b)** | ||
+ | * Operatorul unar prefixat **#a** este interpretat ca **a.operator#()** | ||
+ | * Operatorul unar postfixat **a#** este interpretat ca **a.operator#(int)** | ||
+ | |||
+ | <code c++ Complex.h> | ||
+ | #include <iostream> | ||
+ | |||
+ | class Complex | ||
+ | { | ||
+ | public: | ||
+ | double re; | ||
+ | double im; | ||
+ | |||
+ | 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); | ||
| | ||
- | private: | + | friend std::ostream& operator<< (std::ostream& out, const Complex& z); |
- | T member; | + | friend std::istream& operator>> (std::istream& is, Complex& z); |
- | 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> | ||
- | Implementarea completa a ei poate fi realizată: | + | ==== Supraîncărcarea operatorului de atribuire ==== |
- | *în header (în cazul template-urilor, acest mod este cel mai indicat). | + | |
- | *în fișierul de implementare .cc / .cpp (al cărui schelet parțial îl găsiți mai jos). | + | |
- | <code c++ KeyStorage.cpp> | + | 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. |
- | #include "KeyStorage.h" | + | |
- | template<typename T> | + | 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. |
- | KeyStorage<T>::KeyStorage(int k) { | + | |
- | //TODO | + | <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> | ||
+ | #include "String.h" | ||
+ | #include <string.h> | ||
+ | |||
+ | String& String::operator=(const String& d){ | ||
+ | if(this != &d){ //evitare autoatribuire | ||
+ | if(s) //curatire | ||
+ | delete [] s; | ||
+ | n=d.n; //copiere | ||
+ | s=new char[n+1]; | ||
+ | strcpy(s, d.s); | ||
+ | } | ||
+ | return *this; //intoarce referinta la obiectul modificat | ||
} | } | ||
- | template<typename T> | + | String& String::operator=(const char* p){ |
- | KeyStorage<T>::~KeyStorage() { | + | if(s) |
+ | delete [] s; | ||
+ | n=strlen(p); | ||
+ | s=new char[n+1]; | ||
+ | strcpy(s, p); | ||
+ | return *this; | ||
} | } | ||
- | //TODO: restul metodelor. | + | </code> |
- | // La sfarsit, cu tipurile de date pe care le veti folosi. | ||
- | template class KeyStorage<int>; | ||
- | template class KeyStorage<long>; | ||
- | </code> | ||
<hidden> | <hidden> | ||
Line 138: | Line 574: | ||
*[**2p**] Setati tipul de return a metodelor de tip getter astfel incat sa puteti modifica valorea intoarsa. | *[**2p**] Setati tipul de return a metodelor de tip getter astfel incat sa puteti modifica valorea intoarsa. | ||
*[**1p**] Arătați funcționalitatea prin adăugarea de cod în fișierul main.cpp. Rezolvați, dacă e cazul, toate erorile/leak-urile depistate de Valgrind. | *[**1p**] Arătați funcționalitatea prin adăugarea de cod în fișierul main.cpp. Rezolvați, dacă e cazul, toate erorile/leak-urile depistate de Valgrind. | ||
- | </hidden> | ||
*[**5p**] Clasa Complex - clasă ce implementează conceptul de număr complex | *[**5p**] Clasa Complex - clasă ce implementează conceptul de număr complex | ||
Line 155: | Line 590: | ||
*[**2p**] Alocați o instanță de tip MappingEntry local și dinamic (utilizând new / delete). | *[**2p**] Alocați o instanță de tip MappingEntry local și dinamic (utilizând new / delete). | ||
*[**1p**] Arătați funcționalitatea prin adăugarea de cod în fișierul main.cpp. Rezolvați, dacă e cazul, toate erorile/leak-urile depistate de Valgrind. | *[**1p**] Arătați funcționalitatea prin adăugarea de cod în fișierul main.cpp. Rezolvați, dacă e cazul, toate erorile/leak-urile depistate de Valgrind. | ||
- | <hidden> | + | |
*[**5p**] Clasa Punct2D - clasă ce implementează conceptul de punct în plan | *[**5p**] Clasa Punct2D - clasă ce implementează conceptul de punct în plan | ||
*[**2p**] Implementați și folosiți utilizând template-uri clasa Punct2D, adăugând constructor și destructor. | *[**2p**] Implementați și folosiți utilizând template-uri clasa Punct2D, adăugând constructor și destructor. | ||
Line 166: | Line 601: | ||
- | </hidden> | + | |
*[**5p**] Clasa Punct2D - clasă ce implementează conceptul de punct în plan | *[**5p**] Clasa Punct2D - clasă ce implementează conceptul de punct în plan | ||
Line 183: | Line 618: | ||
*[**1p**] Arătați funcționalitatea prin adăugarea de cod în fișierul main.cpp. Rezolvați, dacă e cazul, toate erorile/leak-urile depistate de Valgrind. | *[**1p**] Arătați funcționalitatea prin adăugarea de cod în fișierul main.cpp. Rezolvați, dacă e cazul, toate erorile/leak-urile depistate de Valgrind. | ||
- | <hidden> | + | |
===== Interviu ===== | ===== Interviu ===== | ||
- | Această secțiune nu este punctată și încearcă să vă facă o oarecare idee a tipurilor de întrebări pe care le puteți întâlni la un job interview (internship, part-time, full-time, etc.) din materia prezentată în cadrul laboratorului. | + | Această secțiune nu este punctată și încearcă să vă facă o oarecare idee a tipurilor de întrebări pe care le puteți întâlni la un job interview (internship, part-time, full-time, etc.) din materia prezentată în cadrul articolului. |
* Care este diferența între struct și class în C++? | * Care este diferența între struct și class în C++? |