Differences

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

Link to this comparison view

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