Differences

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

Link to this comparison view

sd-ca:laboratoare:laborator-02 [2015/03/10 10:27]
silviu_emil.popescu [Exerciții]
sd-ca:laboratoare:laborator-02 [2016/02/21 19:51] (current)
radu.stochitoiu
Line 1: Line 1:
-====== ​Laborator ​02 - Noțiuni de C++ ======+====== ​Articol ​02 - Noțiuni de C++ ======
  
-Responsabili  +===== Obiective =====
-  * [[mailto:​andrei.vasiliu2211@cti.pub.ro | Andrei Vasiliu]] +
-  * [[mailto:​daniel.ciocirlan1607@cti.pub.ro | Daniel Ciocîrlan]]+
  
 +În urma parcurgerii acestui articol studentul va: 
  
-===== Obiective =====+  * învăța ce înseamnă o clasă 
 +  * învăța ce înseamnă constructor / destructor 
 +  * 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
  
-În urma parcurgerii acestui laborator studentul va:  
  
-  * înțelege conceptul de template 
-  * înțelege conceptul de referințe din C++ 
-  * înțelege conceptul de read-only introdus prin identificatorul const 
  
-===== Templates =====+==== Clase ====
  
-Motivul principal ​pentru ​care folosim ​C++ în cadrul SD este datorită funcționalității oferite de template-uri.+Formal am făcut deja primii pași mai sus pentru ​a implementa o clasă în C++, utilizând keyword-ul //struct//.
  
-Acestea permit generalizarea tipurilor ​de date folosite în interiorul funcțiilor și claselor.+Totuși, ce înseamnă o clasă? Nu trebuie decât să ne gândim la ce am făcut mai sus: 
 +  *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)
  
-Sintaxa pentru acestea este: +Cu această adăugare menționată, putem să ne referim la ceea ce înseamnă o **clasă**, respectiv ​un **obiect**.
-<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.+
  
-==== Function Template ==== +Ne referim la o **clasă** ca fiind o amprentă (blueprint) sau descriere generală. 
-În primul rând template-urile pot fi aplicate funcțiilor.+Un **obiect** sau o **instanță a clasei** este o variabilă concretă ce se conformează descrierii clasei.
  
-Un exemplu comun și simplu este următorul:​ +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.
-<code c++> +
-template<​typename T> +
-T getMax(T a, T b) { +
-    return ​a > b ? a : b; +
-} +
-</​code>​+
  
-Funcția poate fi apelată astfel: +Când discutăm despre tipul de date //complex// ne referim la clasă. 
-<code c++> +Când discutăm despre variabila ​//number// ne referim la un obiect, o instanță a clasei.
-getMax<​int>​(2,​ 3); +
-getMax<​double>​(3.2, 4.6); +
-</code>+
  
-==== Class Template ​==== +==== Keyword-ul "​class"​ vs. "​struct" ​====
-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.+Ș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.
  
-Iată cum putem face acest lucru: +<columns 100% 100% -> 
-<code c++ KeyStorage.h> +<code c++ complex.h> 
-template<​typename T> +class Complex ​{ 
-class KeyStorage ​{ +    ​double re; 
-public: +    ​double im; 
-    ​int key; +    ​ 
-    ​T member;+    Complex conjugate(); 
 }; };
 </​code>​ </​code>​
  
-În funcția main, să presupunem ​că vrem să folosim clasa cu membrul de tip long. +<​newcolumn>​ 
-<code c++ main.cpp> + 
-#include "KeyStorage.h"+<​code ​c++ complex.cc> 
 +#include "​complex.h"​ 
 +Complex Complex::​conjugate() { 
 +    Complex conjugate;​ 
 +    conjugate.re = this->​re;​ 
 +    conjugate.im = -(this->​im);​ 
 +     
 +    return conjugate;​ 
 +} 
 +</​code>​ 
 + 
 +<​newcolumn>​ 
 + 
 +<code c++ main.cc> 
 +#include <stdio.h> 
 +#include "complex.h"
  
 int main() { int main() {
-    ​KeyStorage<​long>​ keyElement;+    ​Complex number; 
 +    number.re = 2; 
 +    number.im = 4; 
 +     
 +    printf("​%.2lf %.2lf\n",​ number.re, number.im);​ 
 +    ​
     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. 
  
-==== Where'​s the magic happening? ==== 
-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. 
  
-Template-urile sunt de fapt indicii pentru compilator pentru a genera cod la rândul lui! 
-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. 
  
-Ce trebuie să rețineți din asta? Totul se întâmplă la **compile time**, nu la run time.+==== Specificatori de acces ==== 
 +Am observat mesajul de eroare în urma compilării fișierelor de mai sus.
  
-Compilatorul practic analizează modul în care voi folosiți clasa respectivă și generează pentru fiecare mod în care o folosiți șablonul corespunzător. +Astfel, **singura diferență** folosirea celor două keyword-uri este nivelul implicit de vizibilitate a metodelor ​și atributelor. 
-Folosirea KeyStorage<​int>​ și KeyStorage<​float>​ determină compilatorul să genereze cod pentru ​ambele clase (înlocuind o dată T cu int și altă cu float). +  ​***private** - pentru ​clasele declarate ​cu **class** 
-  +  ​***public**  ​- pentru clasele declarate cu **struct**
-==== 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.+
  
-Asta înseamnă că: +Membri precedați de label-ul ​**private** pot fi folosiți numai în interiorul clasei, în cadrul metodelor acesteia. 
-  *Trebuie să scrieți întreaga implementare în header! //sau// +Ei nu pot fi citiți sau modificați din afara clasei.
-  ​*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).+Iată cum puteam remedia soluția: 
 +<code c++ complex.h> 
 +class Complex { 
 +public: 
 +    double re; 
 +    double im;
  
-==== Clasa KeyStorage ​==== +    Complex conjugate();​ 
-Iată mai jos o structură mai dezvoltată pentru clasa KeyStorage, în care cheia este setată în constructor. +}; 
-. +</​code>​ 
-<code c++ KeyStorage.h> + 
-template<​typename T> +==== Constructori și destructori ​==== 
-class KeyStorage ​{+ 
 +Studiați codul de mai jos. 
 + 
 +<columns 100% 100% -> 
 +<code c++ complex.h> 
 +class Complex ​{
 public: public:
-    ​KeyStorage(int k); +    ​// Constructor 
-    ​~KeyStorage();+    ​Complex(double re, double im);
     ​     ​
-    ​T getMember();​ +    ​// Destructor 
-    ​T setMember(T element);+    ​~Complex();
     ​     ​
 +    double getRe();
 +    double getIm();
 +    ​
 +    Complex conjugate(); ​
 +
 private: private:
-    ​T member; +    ​double re; 
-    ​int key;+    ​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>​ </​code>​
  
-Implementarea completa a ei poate fi realizată:​ +</columns>
-  *î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> +<code c++ main.cc> 
-#include "KeyStorage.h"+#include <stdio.h> 
 +#include "complex.h"
  
-template<​typename T> +int main() { 
-KeyStorage<​T>::​KeyStorage(int k) { +    ​Complex number(2, 3); 
-//TODO+    printf("​%lf %lf\n",​ number.getRe(),​ number.getIm());​ 
 +     
 +    return 0;
 } }
 +</​code>​
 +</​columns>​
  
-template<typename T> +==== Constructor ==== 
-KeyStorage<T>::~KeyStorage() {+ 
 +Observăm două bucăți din cod în mod special: 
 +<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. 
 + 
 +Ce operații sunt uzuale în constructor?​ 
 +  *inițializarea membrilor clasei cu valori predefinite sau date ca parametru 
 +  *alocarea memoriei pentru anumiți membri 
 + 
 +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). 
 + 
 + 
 +Î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);
  
-//TODO: restul metodelor.+=== Când se apelează? ===
  
-// La sfarsit, cu tipurile de date pe care le veti folosi. +1) Apel explicit
-template class KeyStorage<​int>;​ +
-template class KeyStorage<​long>;​+
  
 +<code c++ explicit_copy_constructor_call.cpp>​
 +MyClass m;
 +MyClass x = MyClass(m); /* apel explicit al copy-constructor-ului */
 </​code>​ </​code>​
  
-===== Referințe =====+2) Transfer prin valoare ca argument într-o funcție
  
-In C++ există două modalități de a lucra cu adrese de memorie: ​ +<code c++ call_by_value.cpp>​ 
-  * pointeri ​(la fel ca cei din C)  +void f(MyClass obj); 
-  * referințe.+... 
 +MyClass o; 
 +f(o); /* se apelează copy-constructor */ 
 +</​code>​
  
-Referinţa poate fi privită ca un pointer constant inteligent, a cărui iniţializare este forţată de către compilator (la definire) şi care este dereferenţiat automat.+3) Transfer prin valoare ca return al unei funcții
  
-Semantic, referințele reprezintă aliasuri ale unor variabile existente. La crearea unei referinţe, aceasta trebuie iniţializată cu adresa unui obiect ​(nu cu o valoare constantă).+<code c++ return_by_value.cpp> 
 +MyClass f() 
 +{ 
 +    MyClass a; 
 +    return a; /* se apelează copy-constructor */ 
 +} 
 +</​code>​
  
-Sintaxa pentru declararea unei referințe este:+4) La inițializarea unei variabile declarate pe aceeași linie
  
-    tip& referinta ​= valoare;+<code c++ init.cpp>​ 
 +MyClass m; 
 +MyClass x = m; /* se apelează copy-constructor */ 
 +</​code>​
  
-Exemplu: + 
-<code > +==== Destructor ==== 
-    int x=1, y=2; +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. 
-    int& rx = x; //​referinta + 
-    rx = 4; //​modificarea variabilei prin referinta +Un destructor nu are parametri și se declară în interiorul clasei astfel: 
-    rx = 15; //​modificarea variabilei prin referinta +<code c++> 
-    rx =y; //​atribuirea ​are ca efect copierea continutului ​ +~Complex();
-           //din y in x si nu modificarea adresei referintei+
 </​code>​ </​code>​
  
-Spre deosebire de pointeri: +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.
-  * referinţele sunt iniţializate la creare (pointerii se pot iniţializa oricând) +
-  * referinţa este legată de un singur obiect şi această legătură nu poate fi modificată pentru un alt obiect +
-  * referințele nu au operații speciale, toți operatorii aplicați asupra referințelor sunt de fapt aplicați asupra variabilei referite(de exemplu extragerea adresei unei referințe va returna adresa variabilei referite) +
-  * __nu există referinţe nule__ – ele sunt întotdeauna legate de locaţii de memorie+
  
-Referinţele se folosesc: +==== Rule of Three ====
-  * în listele de parametri ale funcţiilor +
-  * ca valori de întoarcere ale funcţiilor +
-__Motivul__ pentru aceste tipuri de utilizări este unul destul de simplu: când se transmit parametrii funcțiilor,​ se copiază conținutul variabilelor transmise pe stivă, lucru destul de costisitor. Prin transmiterea de referințe, nu se mai copiază nimic, așadar intrarea sau ieșirea dintr-o funcție sunt mult mai putin costisitoare.+
  
-==== Keyword const====+Reprezintă un concept de ** must do** pentru C++. Astfel:
  
-În C++, există mai multe întrebuințări ale cuvântului cheie **const**: +<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>​
-  ​* specifică un obiect a cărui valoare nu poate fi modificată +
-  ​* specifică metodele unui obiect read-only care pot fi apelate+
  
-Pentru a specifica, un obiect a cărui valoare nu poate fi modificată,​ **const** se poate folosi în următoarele feluri: +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.
-  * ''​const tip variabila'' ​  => specifică o variabilă constantă +
-  * ''​tip const& referinta_ct = variabilă;'' ​  => specifică o referință constantă la un obiect, obiectul neputând fi modificat +
-  * ''​const int *p_int'' ​   => specifică un pointer la int modificabil,​ dar conținutul locației de memorie către care ''​p_int''​ arată __nu__ se poate modifica. +
-  * ''​int * const p_int'' ​   => specifică un pointer la int care nu poate fi modificat (Variabilei ''​p_int''​ nu i se poate asigna nici o valoare, dar conținutul locației de memorie către care ''​p_int''​ arată se poate modifica)+
  
 +<code c++ rule_of_3.cpp>​
 +class Complex
 +{
 +    private:
 +        int re;
 +        int im;
 +    public:
 +        Complex()
 +        {
 +            re = 0;
 +            im = 0;
 +            printf("​constructor default\n"​);​
 +        }
  
-Orice obiect constant poate apela doar funcții declarate constante. O funcție constantă se declară folosind sintaxa: +        Complex(const Complex&​ c) 
-<​code ​c>  +        { 
-     void fct_nu_modifica_obiect() const; //am utilizat cuvântul cheie const +            re = c.re; 
-                       //​dupa declarația funcției fct_nu_modifica_obiect +            im = c.im; 
-</​code> ​    +            ​printf("copy contructor\n"​); 
 +        }
  
-Această declaratie a functiei garantează faptul ​că obiectul pentru care va fi apelată nu se va modifica.+        void operator=(const Complex&​ c) 
 +        { 
 +            re = c.re; 
 +            im = c.im; 
 +            printf("​assignment operator\n"​);​ 
 +        } 
 +}; 
 +</​code>​
  
-Regula de bază a apelării membrilor de tip funcție ai claselor este: +=====  Clase/​metode prietene ​ =====
-  * funcțiile ''​const''​ pot fi apelate pe toate obiectele +
-  * funcțiile non-const pot fi apelate doar pe obiectele non-const.+
  
-Exemple:+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> <code cpp>
-//​declarație +class Complex{ 
-class Complex {+
 private: private:
     int re;     int re;
     int im;     int im;
 public: public:
-    ​Complex();​ +    int GetRe(); 
-    ​int GetRe() ​const; +    int GetIm(); 
-    int GetIm() ​const; +    ​friend double ComplexModul(Complex c);   //am declarat fct ComplexModul ca prieten 
-    ​void SetRe(int re); +    ​friend class Polinom; ​  //​Acum clasa Polinom care acces deplin la membrii **re** și **im**
-    ​void SetIm(int ​im);+
 }; };
  
 +double ComplexModul(Complex c)
 +{
 +   ​return sqrt(c.re*c.re+c.im*c.im); ​ //are voie, intrucat e prietena
 +}
  
-//apelare +</code>
-Complex c1; +
-const Complex c2; +
-c1.GetRe(); ​  //​corect +
-c1.SetRe(5); ​ //corect +
-c2.GetRe(); ​  //​corect +
-c2.SetRe(5); ​ //incorect+
  
 +===== 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>​ </​code>​
  
 +Acest lucru este posibil, întrucât un operator este văzut ca o funcție, cu declarația:​
  
-====  Funcții care returnează referințe ​ ====+     ​tip_rezultat operator#​(listă_argumente);​
  
-Pentru clasa Complex, definim funcţiile care asigură accesul la partea reală, respectiv imaginară a unui număr complex: +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: 
-   double getRe(){ return re; } +     tip_rezultat operator#(listă_argumente);
-   ​double getIm(){ return im; }+
  
-Dacă am dori modificarea părţii reale a unui număr complex printr-o atribuire de forma: 
-   ​z.getRe()=2.;​ 
-constatăm că funcţia astfel definită nu poate apărea în partea stângă a unei atribuiri. ​ 
  
-Acest neajuns ​se remediază impunând ​funcţiei să returneze ​o referinţă la obiect, adică: +Există câteva restricții cu privire la supraîncarcare:​ 
-   ​double& ​getRe(){ return re; }+ 
 +  * 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: 
 +    double re; 
 +    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); 
 +     
 +    //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 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); 
 +     
 +    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 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>​ 
 + 
 +==== Supraîncărcarea operatorului de atribuire ==== 
 + 
 +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. 
 + 
 +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>​ 
 +#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 
 +} 
 + 
 +String& String::​operator=(const char* p){ 
 +  if(s) 
 +      delete [] s; 
 +  n=strlen(p);​ 
 +  s=new char[n+1];​ 
 +  strcpy(s, p); 
 +  return *this; 
 +} 
 + 
 +</​code>​
  
-Codul de mai sus returnează o referință către membrul ''​re''​ al obiectului ''​Complex z'',​ așadar orice atribuire efectuată asupra acestui câmp va fi vizibilă și în obiect. 
  
-===== Exerciții ===== 
  
 <​hidden>​ <​hidden>​
Line 247: Line 573:
     *[**2p**] Implementați și folosiți utilizând template-uri clasa MappingEntry de mai sus adăugând constructor,​ destructor si 2 metode de tip getter pentru cele doua valori private.     *[**2p**] Implementați și folosiți utilizând template-uri clasa MappingEntry de mai sus adăugând constructor,​ destructor si 2 metode de tip getter pentru cele doua valori private.
     *[**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.
 +
 +  *[**5p**] Clasa Complex - clasă ce implementează conceptul de număr complex
 +    *[**2p**] Implementați și folosiți utilizând template-uri clasa Complex, adăugând constructor și destructor.
 +    *[**2p**] Adăugați clasei Complex metode pentru adunare, scădere și înmulțire cu un alt număr complex.
     *[**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.
  
Line 269: Line 600:
     *[**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.
  
-  *[**5p**] Clasa Complex - clasă ce implementează conceptul de număr complex + 
-    ​*[**2p**] Implementați și folosiți utilizând template-uri clasa Complex, adăugând constructor și destructor. +
-    *[**2p**] Adăugați clasei Complex metode pentru adunare, scădere și înmulțire cu un alt număr complex. +
-    *[**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
Line 289: Line 617:
     *[**2p**] Alocați o instanță de tip KeyStorage local și dinamic (utilizând new / delete).     *[**2p**] Alocați o instanță de tip KeyStorage 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.
 +
  
 ===== 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++?
Line 302: Line 631:
  
 Și multe altele... Și multe altele...
 +</​hidden>​
 ===== Bibliografie ===== ===== Bibliografie =====
   - [[http://​www.cplusplus.com | C++ Reference]]   - [[http://​www.cplusplus.com | C++ Reference]]
sd-ca/laboratoare/laborator-02.1425976044.txt.gz · Last modified: 2015/03/10 10:27 by silviu_emil.popescu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0