Differences

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

Link to this comparison view

sd-ca:laboratoare:laborator-02 [2016/02/21 19:35]
radu.stochitoiu
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ț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
  
-  ​*Învățăce înseamnă ​constructor ​destructor+ 
 + 
 +==== Clase ==== 
 + 
 +Formal am făcut deja primii pași mai sus pentru a implementa o clasă în C++, utilizând keyword-ul //​struct//​. 
 + 
 +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) 
 + 
 +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>​ 
 + 
 +<​newcolumn>​ 
 + 
 +<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() { 
 +    Complex number; 
 +    number.re = 2; 
 +    number.im = 4; 
 +     
 +    printf("​%.2lf %.2lf\n",​ number.re, number.im);​ 
 +     
 +    return 0; 
 +
 +</​code>​ 
 +</​columns>​ 
 + 
 + 
 + 
 + 
 +==== Specificatori de acces ==== 
 +Am observat mesajul de eroare în urma compilării fișierelor de mai sus. 
 + 
 +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: 
 +    double re; 
 +    double im; 
 + 
 +    Complex conjugate();​ 
 +}; 
 +</code>
  
 ==== Constructori și destructori ==== ==== Constructori și destructori ====
Line 106: Line 198:
  
 Cei doi constructori sunt identici ca funcționalitate. 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 ==== ==== Destructor ====
Line 116: Line 253:
  
 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. 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>​
 + 
 +<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>​
 +
  
  
Line 123: 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 140: 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 151: 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 168: 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++?
sd-ca/laboratoare/laborator-02.1456076143.txt.gz · Last modified: 2016/02/21 19:35 by radu.stochitoiu
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