Responsabili
În urma parcurgerii acestui laborator studentul va:
Așa cum am văzut în primul laborator, fiecare membru al clasei poate avea 3 specificatori de acces:
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:
O funcție prieten are următoarele proprietăți:
O clasă prieten are următoarele proprietăți:
friend class B
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:
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 }
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:
Complex a(2,3); Complex b(4,5); Complex c=a+b; //operatorul + a fost supraîncarcat pentru a aduna două numere complexe
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:
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.
Î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:
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ă:
istream& operator>> (istream& f, clasa & ob); //Acum pot scrie in >> ob ostream& operator<< (ostream& f, const clasa & ob); //Acum pot scrie out << ob
f»ob1»ob2
.
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.
#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); };
#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; }
#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; }
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:
#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); };
#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; }
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.
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.
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); };
#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; }
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
1) Apel explicit
MyClass m; MyClass x = MyClass(m); /* apel explicit al copy-constructor-ului */
2) Transfer prin valoare ca argument într-o funcție
void f(MyClass obj); ... MyClass o; f(o); /* se apelează copy-constructor */
3) Transfer prin valoare ca return al unei funcții
MyClass f() { MyClass a; return a; /* se apelează copy-constructor */ }
4) La inițializarea unei variabile declarate pe aceeași linie
MyClass m; MyClass x = m; /* se apelează copy-constructor */
Reprezintă un concept de must do pentru C++. Astfel:
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.
class Complex { private: int re; int im; public: 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"); } ~Complex() { printf("destructor\n"); } };
obiect[indice]
este interpretat ca obiect.operator[](indice)
.