Informatica
Supraincarcarea operatorilor - supraincarcarea operatorului de atribuire1. Supraincarcare. 2. Functii prieten. 3. Modalitati de supraincarcare a operatorilor. 4. Operatori redefiniti ca functii prieten. 5. Operatori redefiniti ca functii membri. 6. Supraincarcarea operatorului de atribuire. 7. Supraincarcarea operatorului de indexare. 8. Supraincarcarea operatorilor new si delete. 9. Supraincarcarea operatorului apel de functie. 10. Supraincarcarea operatorilor de conversie. 11. Supraincarcarea operatorilor << si >>. 12. Probleme. Operatorii sunt notatii concise, infixate, pentru operatii matematice uzuale. Limbajul C++, ca orice limbaj de programare asigura un set de operatori pentru tipurile primitive. In plus, fata de limbajul C, C++ ofera posibilitatea asocierii operatorilor existenti cu tipurile definite de utilizator . Astfel, prezinta interes extinderea operatorilor in aritmetica complexa, algebra matriciala, in lucrul cu siruri de caractere, etc. Un operator poate fi privit ca o functie, in care termenii sunt argumentele functiei (in lipsa operatorului , expresia a+b s-ar calcula apeland functia aduna(a,b)). Limbajul C++ introduce urmatorii operatori noi: new si delete- pentru gestiunea memoriei dinamice, operatorul de rezolutie (::) si operatorii de acces la membri: . si ->* 1. Supraincarcare. Prin supraincarcarea unui operator, acesta este utilizat in contexte diferite, avind aceeasi semnificatie sau o semnificatie diferita. Astfel operatorul + este supraincarcat pentru adunarea intregilor si a realilor; operatorul << este supraincarcat cu semnificatia de deplasare la stinga, daca termenii sunt intregi sau inserare in flux, daca un termen este un flux de date, iar celalalt un obiect. Programatorul poate asocia noi semnificatii operatorilor existenti prin supraincarcarea operatorilor. Vom evita folosirea termenului redefinire, caruia ii vom da o semnificatie diferita, in contextul mostenirii. Anumiti operatori pot fi utilizati cu orice tip de termeni (sunt deja supraincarcati global de catre compilator). Acestia sunt: new, delete, sizeof, ::, =, &, ->*, .*, ., ->. Pot fi supraincarcati urmatorii operatori: Nu pot fi supraincarcati operatorii: ::, ., .*, ?:, sizeof. Setul de operatori ai limbajul C++ nu poate fi extins prin asocierea de semnificatii noi unor caractere, care nu sunt operatori de exemplu nu putem defini operatorul **) Prin supraincarcarea unui operator nu i se poate modifica aritatea (astfel operatorul este unar si poate fi redefinit numai ca operator unar. De asemeni asociativitatea si precedenta operatorului se mentin. La supraincarcarea unui operator nu se pot specifica argumente cu valori implicite. Operatorii supraincarcati intr-o clasa sunt mosteniti in clasele derivate (exceptie face operatorul de atribuire Semnatura unei functii in care se supraincarca un operator este: tip_rezultat operator#(lista_argumente); 2. Functii prieten. In afara functiilor membre, datele private si cele protejate mai pot fi accesate de functiile prieten (friend) ale clasei. O functie este considerata prietena al unei clase, daca declararea functiei precedate de specificatorul friend, apare in declararea clasei,. Definitia functiei prieten se face global, in afara clasei. Daca o functie are ca parametri obiecte apartinand la doua clase diferite, atunci ea poate fi declarata ca functie membru a unei clase si prieten al celeilalte clase, sau ca functie prietena ambelor clase. De exemplu fie o functie de inmultire a unei matrice cu un vector. Parametrii functiei vor fi doua obiecte - o matrice si un vector. Clasele respective Mat si Vec pot declara fiecare, functia MatVec() ca prietena: class Mat; class Vec; De asemenea functiile prieten se utilizeaza in contextual supraincarcarii operatorilor. Declararea unei functii prieten poate fi facuta in orice parte a clasei (publica, privata sau protejata). O functie prietena a unei clase poate accesa toti membrii clasei. Daca o clasa este prietena cu alta clasa, membrii ei pot accesa membrii celeilalte clase. 3. Modalitati de supraincarcare a operatorilor. O functie operator avand ca prim argument un tip primitiv nu poate fi functie membru. 4. Operatori supraincarcati ca functii prieten. Operatorii folositi in mod uzual pot fi unari sau binari. Utilizarea unui operator binar sub forma a#b este interpretata ca operator#(a,b) Asadar, un operator binar va fi reprezentat printr-o functie nemembra cu doua argumente, iar un operator unar, printr-o functie nemembra cu un singur argument. Argumentele se iau clase sau referinte constante la clase (pentru o preluare economica, asigurand protectia datelor) Vom exemplifica pentru clasa Cplx: class Cplx; //definitii operatori in afara domeniului clasei Cplx operator+(const Cplx& s, const Cplx& d); Cplx operator-(const Cplx& s, const Cplx& d); Cplx operator*(const Cplx& s, const Cplx& d); Cplx operator/(const Cplx& s, const Cplx& d);
int operator==(const Cplx& s, const Cplx& d); int operator!=(const Cplx& s, const Cplx& d); Cplx operator-(const Cplx& z); Cplx operator!(const Cplx& z); Cplx& operator++(Cplx& z); Cplx operator++(Cplx& z,int); Pentru a face deosebirea intre semnaturile functiilor operatori de incrementare prefix si postfix s-a introdus un argument fictiv pentru cel din urma. Incrementarea prefix intoarce valoarea incrementata, deci trebuie sa fie o L-valoare (rezultatul intors va fi o referinta), in timp ce postincrementarea intoarce valoarea dinaintea incrementarii. 5. Operatori supraincarcati ca functii membri. Functiilor membru li se transmite un argument implicit (ascuns) this (adresa obiectului, care poate reprezenta primul termen), motiv pentru care un operator binar poate fi implementat printr-o functie membru nestatica cu un singur argument (termenul din dreapta operatorului). a#b este interpretat ca a.operator#(b) O functie membru operator unar ca o functie membru nestatica fara argumente ( #a se interpreteaza ca a.operator#(); pentru operatorul postfixat a conventia este a.operator#(int) In acest context, operatorii clasei Cplx se scriu: int Cplx::operator==(const Cplx& d); int Cplx::operator!=(const Cplx& d); Cplx& Cplx::operator-(); Cplx& Cplx::operator!(); Cplx& Cplx::operator++(); Cplx Cplx::operator++(int); 6. Supraincarcarea operatorului de atribuire. Operatia de atribuire simpla, daca nu este supraincarcata, realizeaza o copiere membru cu membru. Pentru obiectele care nu contin date alocate dinamic la initializare, atribuirea prin copiere membru cu membru functioneaza corect, motiv pentru care nu se supraincarca operatorul de atribuire. Pentru clasele ce contin date alocate dinamic, copierea membru cu membru, executata in mod implicit la atribuire conduce la copierea pointerilor la datele alocate dinamic, in loc de a copia datele. Operatorul de atribuire poate fi redefinit numai ca functie membra, el fiind legat de obiectul din stanga operatorului =, o L-valoare, motiv pentru care va intoarce o referinta la obiect. O prima cerinta la scrierea functiei operator= este evitarea autoatribuirii a=a. In acest scop se efectueaza testul this != &d (unde d este parametrul functiei obiectul din dreapta operatorului = Urmeaza apoi curatirea" membrului stang (eliberarea memoriei alocate dinamic). Copierea propriu zisa este facuta de obicei folosind constructorul de copiere. Functia operator returneaza o referinta la obiectul modificat (return *this Exemplificam pentru clasa String, definita astfel: class String //ctor de copiere String::String& operator=(const String& d); return *this; //intoarce obiectul modificat //ctor de initializare (cu un sir tip C) String::String& operator=(const char* p); 7. Supraincarcarea operatorului de indexare. Operatorul de indexare este un operator binar, avand ca prim termen obiectul care se indexeaza, iar ca al doilea termen indicele: obiect [ indice ] si este interpretat ca: obiect.operator[ ](indice) Functia operator de indexare trebuie sa fie functie membra nestatica. Primul termen, transmis implicit prin this este obiectul asupra caruia se executa indexarea. Argumentul functiei reprezinta indicele, care poate fi de orice tip, spre deosebire de indicii predefiniti si este al doilea termen din operator. Valoarea intoarsa de functia operator este o referinta la elementul indexat din obiect. Exemplificam cu operatorul de indexare pentru clasa String: class String; 8. Supraincarcarea operatorilor new si delete. Operatorii new si delete pot fi supraincarcati ca functii operatori membre statice. Functia operator new are semnatura: void* operator new(size_t); Operatorul redefinit apeleaza constructorul clasei dupa alocarea dinamica pe care o realizeaza. Functia operator delete are semnatura: void operator delete(void*); unde parametrul este un pointer la obiectul eliminat. Operatorul redefinit delete apeleaza intotdeauna destructorul clasei inainte de a elibera memoria alocata dinamic. Daca in clasa C se supraincarca operatorul new, atunci versiunea furnizata de sistem se obtine prin C *p = ::new C(); Exemplu1:Supraincarcati operatorul new, astfel incat sa initializeze memoria alocata la 0. void* C::operator new(size_t dim) Exemplu1:Supraincarcati operatorul new[], astfel incat sa initializeze memoria alocata la o valoare data ca parametru. void* C::operator new (size_t dim, unsigned v) 9. Supraincarcarea operatorului apel de functie. Apelul unei functii este o constructie de forma desemnand operatorul binar() aplicat celor doi termeni nume_functie si lista_argumente Numele functiei poate fi inlocuit printr-un pointer la functie: Functia operator redefinit apel de functie va avea asadar 2 parametri: un obiect si o lista de argumente. Implementata ca functie membra nestatica, primul parametru este transmis implicit, astfel ca un apel de forma: obiect(lista_argumente); este interpretat ca: Supraincarcarea operatorului () se utilizeaza la definirea functorilor (obiecte functii). Un functor este un obiect pentru care se supraincarca operatorul apel de functie (operator()()) si care se comporta ca o functie. In mod uzual obiectele functii se folosesc in locul pointerilor la functii. Functiile inline nu pot folosi ca parametri pointeri la functii (care sunt apelati indirect), ci obiecte functii. Operatorul apel de functie asigura o sintaxa uniforma pentru obiectele care se comporta ca si functiile. Algoritmii generici din STL au printre argumente si o biecte functii. Acestea pot fi: - generatori = functori fara argumente - functii unare = functori cu un argument - functii binare = functori cu doua argumente. O functie unara booleana este un predicat, in timp ce o functie binara booleana este un predicat binar. De exemplu, pentru o dreapta, definita prin taietura in origine si panta putem defini clasa: class Dreapta //ctor double operator()(double x) //functor Clasa va fi folosita intr-o functie main() astfel: Dreapta d1; Dreapta d2(2., 3.); double y1=d1(5.5); //y1=5.5 double y2=d2(1.5); //y2=2*1.5+3=6 Definiti un obiect functie (functor) care genereaza termenul de rang n din sirul lui Fibonacci. class Fibo; //constructor long operator()(int n); //supraincarcare operator functie private: long x, y; long Fibo::operator ()(int n); return x; 10. Supraincarcarea operatorilor de conversie. In C++ la evaluarea unei expresii se efectueaza conversii implicite pentru tipurile predefinite. Deoarece nu exista conversii implicite de la o clasa la un tip predefinit, programatorul isi poate defini conversii explicite prin supraincarcarea unui operator de conversie al clasei. Astfel pentru clasa C, functia membru nestatica de conversie C::operator T() unde T este un nume de tip primitiv realizeaza conversia de la tipul C la tipul T. Aceasta nu specifica nici un tip de rezultat intors, deoarece se returneaza implicit valoarea obiectului convertita la tipul pentru care este definita conversia si nu are parametri. Functia operator primeste ca prim parametru implicit adresa obiectului si intoarce valoarea convertita de tipul numelui functiei operator de conversie. Apelul explicit al functiei de conversie de la un obiect din clasa C la tipul primitiv T se specifica prin T(obiect) sau (T) obiect. Pentru conversia unui obiect intre doua clase C1 si C2,C1->C2 se defineste functia membru operator de conversie C1::operator C2(); Pentru ca aceasta functie sa aiba acces la membrii clasei C2, se declara functie prieten clasei C2. Sunt premise astfel conversii intre o clasa si un tip predefinit sau intre doua clase, dar nu de la un tip predefinit la o clasa. Constructorii cu un singur argument pot fi folositi ca functii de conversie de la tipul argumentului la clasa constructorului. Acest efect nu este intotdeauna dorit, si pentru a-l evita, constructorul se declara precedat de cuvantul cheie explicit. Daca o metoda a unei clase foloseste parametri care sufera conversii implicite, metoda se va defini ca functie prietena, nu ca functie membra. Definim ca exemplu clasa Inch: class Inch //ctor void Inch_cm(); operator int() const ; //fctie conversie 11. Supraincarcarea operatorilor << si >>. Operatorii de intrare / iesire pot fi supraincarcati. Operatorul de extractie >>, membru al clasei istream serveste pentru extragerea datelor de tipuri predefinite din fluxul de intrare cin. Prin supraincarcarea operatorului putem extrage din fluxul de intrare f, obiecte ob, scriind f >> ob; Functia operator corespunzatoare are semnatura:
|