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 13:37]
emil.racec [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 ​> 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 ș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țieste 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.+3Transfer prin valoare ca return al unei funcții
  
-Semantic, referințele reprezintă aliasuri ale unor variabile existenteLa 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 memoriecel mai probabil în destructor ați fi făcut curat șați fi apelat free pe membrul respectiv.
-  * referinţele sunt iniţializate la creare (pointerii se pot iniţializa oricând) +
-  * referinţeste legată de un singur obiect şaceastă legătură nu poate fi modificată pentru un alt obiect +
-  * referințele nu au operații specialetoți operatorii aplicaț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țiedacă funcționalitatea vreunuia dintre cei 3 se vrea mai specială decât cea oferită defaultatunci 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 obiectobiectul 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 constanteO 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; +===== Supraîncarcarea operatorilor ===== 
-c1.GetRe(); ​  //​corect + 
-c1.SetRe(5); ​ //corect +Un mecanism specific C++ este supraîncarcarea operatorilor,​ prin care programatorul poate asocia noi semnificaţii operatorilor deja existenţiDe 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:
-c2.GetRe(); ​  //​corect +
-c2.SetRe(5); ​ //incorect+
  
 +<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 ​referinţă la obiectadică: +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-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 Complexd); 
 +    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 248: 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 
 +    *[**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.
  
   *[**3p**] Simple use of const   *[**3p**] Simple use of const
Line 255: Line 585:
     *[**0.5p**] Creati un pointer constant la o variabila de tip intreg constanta.     *[**0.5p**] Creati un pointer constant la o variabila de tip intreg constanta.
     *[**1.5p**] Initializati pointerul si variabila referita pentru fiecare caz. Explicati si rezolvati erorile de compilare.     *[**1.5p**] Initializati pointerul si variabila referita pentru fiecare caz. Explicati si rezolvati erorile de compilare.
-<​hidden>​ 
  
   *[**5p**] Clasa MappingEntry - conține 2 membri de tipuri potențial diferite și realizează,​ din punct de vedere conceptual, asocierea între două valori (una se numește //cheie//, iar cealaltă //​valoare//​).   *[**5p**] Clasa MappingEntry - conține 2 membri de tipuri potențial diferite și realizează,​ din punct de vedere conceptual, asocierea între două valori (una se numește //cheie//, iar cealaltă //​valoare//​).
Line 271: 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 291: 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 304: 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.1425987444.txt.gz · Last modified: 2015/03/10 13:37 by emil.racec
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