Document, comentariu, eseu, bacalaureat, liceu si facultate
Top documenteAdmitereTesteUtileContact
      
    


 


Ultimele referate adaugate

Adauga referat - poti sa ne ajuti cu un referat?

Politica de confidentialitate



Ultimele referate descarcare de pe site
  CREDITUL IPOTECAR PENTRU INVESTITII IMOBILIARE (economie)
  Comertul cu amanuntul (economie)
  IDENTIFICAREA CRIMINALISTICA (drept)
  Mecanismul motor, Biela, organe mobile proiect (diverse)
  O scrisoare pierduta (romana)
  O scrisoare pierduta (romana)
  Ion DRUTA (romana)
  COMPORTAMENT PROSOCIAL-COMPORTAMENT ANTISOCIAL (psihologie)
  COMPORTAMENT PROSOCIAL-COMPORTAMENT ANTISOCIAL (psihologie)
  Starea civila (geografie)
 

Ultimele referate cautate in site
   domnisoara hus
   legume
    istoria unui galban
   metanol
   recapitulare
   profitul
   caract
   comentariu liric
   radiolocatia
   praslea cel voinic si merele da aur
 
despre:
 
Supraincarcarea operatorilor in C++
Colt dreapta
Vizite: ? Nota: ? Ce reprezinta? Intrebari si raspunsuri
 

Supraincarcarea functiilor si a operatorilor (overloading) sunt mecanisme importante in C++ care ofera flexibilitate si extensibilitate limbajului. Pentru tipurile fundamentale ale limbajului sunt definite seturi de operatori care permit operatii de baza executate intr-un mod convenabil. Dar, dupa cum este cunoscut, in limbaj sunt definite prea putine tipuri de date ca date fundamentale, iar pentru reprezentarea altor tipuri care sunt necesare in diferite domenii (cum ar fi aritmetica numerelor complexe, algebra matricilor, etc.), se definesc clase care contin functii ce pot opera asupra acestor tipuri. Definirea operatorilor care sa opereze asupra obiectelor unei clase permite un mod mult mai convenabil de a manipula obiectele decat prin folosirea unor functii ale clasei. z6v11vb
O functie care defineste pentru o clasa o operatie echivalenta operatiei efectuate de un operator asupra unui tip predefinit este numita functie operator. Majoritatea operatorilor limbajului C++ pot fi supraincarcati, si anume:

new delete () ai
+ - * / % ^ & | I
! = < > += -= *= /= %=
^= &= |= << >> >>= <<= == !=
<= >= && || ++ -- , ->* ->

Operatorul () este apelul unei functii, iar operatorul ai este operatorul de indexare. Urmatorii operatori nu se pot supraincarca:
. .* :: ?: sizeof
Exista cateva reguli care trebuie sa fie respectate la supraincarcarea operatorilor:

• Functiile operator=(), operator()() si operatorai() trebuie sa fie membri nestatici ai clasei.
• Cu exceptia functiei operator=(), toate celelate functii operator pot fi mostenite.
• Nu pot fi supraincarcati operatorii pentru tipurile predefinite ale limbajului.
• Functiile operator nu pot avea argumente implicite.




Functiile operator pentru o anumita clasa pot sa fie sau nu functii membre ale clasei. Daca nu sunt functii membre ele sunt, totusi, functii friend ale clasei si trebuie sa aiba ca argument cel putin un obiect din clasa respectiva sau o referinta la aceasta. Exceptie fac operatorii =, (), ai, ->, care nu pot fi supraincarcati folosind functii friend ale clasei. De asemenea, functiile operator new() si operator delete()au implementari mai deosebite care vor fi detaliate mai jos.

4.1 Functii operator membre ale claselor

Forma generala pentru functiile operator membre ale clasei este urmatoarea:

tip_returnat operator#(lista_argumente)A
// operatii
S

In aceasta forma generala semnul # reprezinta oricare dintre operanzii care pot fi supraincarcati.

? Exemplul 4.1

Fie o clasa Point care descrie un vector intr-un plan bidimensional prin doua numere de tip double, x si y. Valorile x si y reprezinta coordonatele punctului de extremitate al vectorului. Pentru aceasta clasa se pot defini mai multe operatii cu vectori, ca de exemplu:

• Suma a doi vectori
• Diferenta a doi vectori
• Produsul scalar a doi vectori
• Multiplicarea unui vector cu o constanta (scalare)
• Incrementarea/decrementarea componentelor vectorului
• Oglindire (negarea fiecarei componente).

Aceste operatii se pot implementa prin supraincarcarea corespunzatoare a operatorilor. Pentru inceput se vor defini functiile operator+() si operator-;() pentru calculul sumei, respectiv a diferentei a doi vectori.

class PointA double x; double y; public:
Point()A x = 0; y = 0;
S
Point(double a, double b)A x = a; y = b;
S void Display() A cout << x << " " << y << endl;
S
Point operator+(Point op2); // suma a doi vectori
Point operator-(Point op2); // diferenta a doi vect double operator*(Point op2);// produs scalar
Point& operator*(double v); // multipl. cu o const.

S;
Point Point::operator+(Point op2)A point temp; temp.x = x + op2.x; temp.y = y + op2.y; return temp;
S
Point Point::operator-(Point op2)A point temp; temp.x = x + op2.x; temp.y = y + op2.y; return temp;
S double Point::operator*(Point op2)A return x*op2.y + y*op2.x;
S
Point& Point::operator*(double v)A x *=v; y *=v; return *this;
S void f1()A
Punct pct1(10,20);
Punct pct2(30,40);
Punct pct3; pct1.Display(); // afiseaza 10 20 pct2.Display(); // afiseaza 30 40 pct3 = pct1 + pct2; pct3.Display(); // afiseaza 40 60 pct3 = pct2 -; pct1; pct3.Display(); // afiseaza 20 20
S

Functia operator+() are un singur argument, chiar daca ea supraincarca un operator binar (operatorul +), care necesita doi operanzi. Argumentul transmis functiei este operandul din dreapta operatiei, iar operandul din stanga este chiar obiectul pentru care se apeleaza functia operator. Expresia: pct3 = pct1 + pct2; semnifica, de fapt: pct3 = pct1.operator+(pct2); si chiar poate fi apelata astfel. Acest lucru inseamna ca obiectul din stanga operatorului este cel pentru care se apeleaza functia operator, care are acces la acesta prin pointerul this pasat implicit functiei operator membra a clasei. Pentru functia operator+() ordinea operanzilor nu are importanta, dar aceasta conventie de apel este importanta pentru alte functii, ca de exemplu functia operator-().
Pentru acelasi operator se pot defini mai multe functii supraincarcate, cu conditia ca selectia uneia dintre ele in functie de numarul si tipul argumentelor sa nu fie ambigua. In clasa Point s-a supraincarcat operatorul * cu doua functii: prima pentru calculul produsului scalar a doi vectori, cealalta pentru multiplicarea vectorului cu o constanta. ?

In implementarea prezentata, functia operator+() creaza un obiect temporar, care este distrus dupa returnare. In acest fel, ea nu modifica nici unul dintre operanzi, asa cum nici operatorul + pentru tipurile predefinite nu modifica operanzii.
Intr-o functie operator se pot efectua orice fel de operatii, dar, in mod obisnuit, se pastreaza semnificatia actiunii operatorului respectiv. Cu exceptia functiilor operator new (), operator delete() si operator->(), valoarea returnata de o functie operator poate fi de orice tip, dar, tot pentru pastrarea contextului utilizarii normale, de obicei se returneaza un obiect din aceeasi clasa, sau o referinta la aceasta. Acest lucru este important pentru utilizarea operatorului in expresii, asa cum este cea scrisa mai sus.
In expresia scrisa mai sus, pct3 = pct1 + pct2, se executa, pe langa operatia de adunare, si o operatie de asignare pentru o variabila de tip Point. Pentru clasa Point, aceasta operatie se executa corect, chiar daca nu a fost supraincarcata functia operator=(), deoarece asignarea implicita se executa printr-o copiere membru cu membru si in acest caz nu produce erori. Conditiile in care este absolut necesara supraincarcarea functiei operator=() vor fi discutate in subsectiunea urmatoare.
In general, un operator binar poate fi supraincarcat fie printr-o functie membra nestatica cu un argument, fie printr-o functie nemembra cu doua argumente.
Un operator unar poate fi supraincarcat fie printr-o functie membra nestatica fara nici un argument, fie printr-o functie nemembra cu un argument. La supraincarcarea operatorilor de incrementare sau decrementare (++, --) se poate diferentia un operator prefix de un operator postfix folosind doua versiuni ale functiei operator. In continuare sunt prezentate cateva functii operator ale clasei Point pentru operatori unari. class PointA
//…………… public:
Point operator!();
Point operator++();
Point operator—();
Point operator++(int x);
Point operator—(int x);
S;
Point operator!()A x = -x; y = -y; return *this;
S
Point Point::operator++()A x++; y++; return *this;
S
Point Point::operator--()A x--; y--; return *this;
S
Point Point::operator ++(int x)A
++x;
++y; return *this;
S
Point Point::operator --(int x)A
--x;
--y; return *this;
S

Daca ++ precede operandul, este apelata functia operator++(); daca ++ urmeaza operandului, atunci este apelata functia operator++(int x), iar x are valoarea 0.

4.2 Functii operator friend

La supraincarcarea unui operator folosind o functie care nu este membra a clasei este necesar sa fie transmisi toti operanzii necesari, deoarece nu mai exista un obiect al carui pointer (this) sa fie transferat implicit functiei. Din aceasta cauza, functiile operator binar necesita doua argumente de tip clasa sau referinta la clasa, iar functiile operator unar necesita un argument de tip clasa sau referinta la clasa. In cazul operatorilor binari, primul argument transmis este operandul stanga, iar al doilea argument este operandul dreapta.
In exemplul urmator se reiau unele functiile operator ale clasei Point, implementate ca functii friend ale clasei.
? Exemplul 4.3

class Point
A int x; int y; public:
//………………………. friend Point operator+(Point op1, Point op2); friend Point operator-(Point op1, Point op2); friend Point operator++(Point &p); friend Point operator++(Point &p, int x); friend Point operator--(Point &p); friend Point operator--(Point &p, int x);
S;
Point operator+(Point op1, Point op2)A
Point temp; temp.x = op1.x + op2.x; temp.y = op1.y + op2.y; return temp;
S
Point operator-(Point op1, Point op2)A
Point temp; temp.x = op1.x - op2.x; temp.y = op1.y - op2.y; return temp;
S
Point operator++(Point &p)
A p.x++; p.y++; return p;
S
Point operator ++(Point &p, int x)
A
++p.x;
++p.y; return p;
S
Point operator--(Point &p)
A p.x--; p.y--; return p;
S
Point operator --(Point &p, int x)
A
--p.x;
--p.y; return p;
S

void f2()A
Punct pct1(10,20);
Punct pct2(30,40);
Punct pct3; pct1.Display(); // afiseaza 10 20 pct2.Display(); // afiseaza 30 40 pct3 = pct1 + pct2; pct3.Display(); // afiseaza 40 60 pct3 = pct2 -; pct1; pct3.Display(); // afiseaza 20 20 pct1++; pct1.Display(); // afiseaza 11 21
S
?

Referitor la supraincarcarea operatorilor folosind functii nemembre ale clasei se mai pot face cateva observatii.
Daca functia operator nu ar fi declarata functie friend a clasei, ea nu ar avea acces la variabilele protejate ale clasei. Problema s-ar putea rezolva prin adaugarea unor functii publice de citire si scriere ale datelor membre ale clasei respective, care sa fie apelate in functia operator. Dar o astfel de solutie este incomoda, ineficienta si nu aduce nici un avantaj. Se poate evidentia acest aspect daca se incearca o modificare a functiei operator+() a clasei Point astfel:

class Point
A public:
//………………………. double getx() Areturn x;S double gety() Areturn y;S void setx(double a) Ax = a;S void sety(double b) Ay = b;S
S;
Point operator+(Point op1, Point op2)A // nu este friend
Point temp; temp.setx(op1.getx()+op2.getx()); temp.sety(op1.gety()+op2.gety()); return temp;
S

Se poate observa ca, daca se folosesc functii nemembre ale clasei pentru supraincarcarea operatorilor, atunci este mult mai eficient si mai comod ca acestea sa fie declarate functii friend ale clasei respective.

O alta observatie referitoare la supraincarcarea operatorilor folosind functii friend este aceea ca pentru operatorii care trebuie sa modifice operandul (cum sunt operatorii de incrementare, decrementare, complement, etc) este necesar transmiterea operandului ca parametru prin referinta, ceea ce permite modificarea lui in functia operator. Acest mod de apel se observa la definirea functiilor operator++() si operator--() din Exemplul 4.2.

4.3 Supraincarcarea operatorului de asignare

Exista mai multi operatori de asignare: =, *=, /=, %=, +=, -=, >>=, <<=, &=, ^=, |=, dintre care primul este operatorul simplu de asignare, ceilalti fiind combinatie cu alti operatori.
In asignarea simpla (=), valoarea expresiei care reprezinta operandul dreapta inlocuieste valoarea operandului stanga. Daca ambii operanzi sunt de tip aritmetic, operandul dreapta este convertit la tipul operandului stanga, dupa care are loc atribuirea valorii. Nu exista o conversie implicita la tipul enumerare, astfel incat, daca operandul stanga este o enumerare, cel din dreapta trebuie sa fie de acelasi tip.
Pentru tipurile definite de utilizator, functia operator de asignare (operator=()) trebuie sa fie o functie membra nestatica a clasei si nu poate fi mostenita. In multe privinte, functia operator=() seamana mai mult cu constructorii decat cu ceilalti operatori, dar este, totusi diferita de operatia de initializare pe care o efectueaza constructorii. In lipsa unei functii operator=() definita de utilizator pentru o clasa X, este utilizata definitia implicita de asignare prin copierea membru cu membru astfel:

class X A
//…….
S;
X& X::operator=(const X& op2)A
// copiere membru cu membru
S

void f()A
X a,b; a = b; // se foloseste asignarea implicita
S

Ceilalti operatori de asignare (+=, -=,*=, etc) nu au o semnificatie predefinita pentru clase, deci, pentru a putea fi folositi, trebuie sa fie definiti de utilizator.
In cazul claselor de obiecte care nu contin date alocate dinamic la initializare sau prin intermediul altor functii membre, asignarea prin copiere membru cu membru functioneaza corect si, in general, nu mai este necesar sa fie supraincarcat operatorul de asignare. Acest lucru s-a remarcat si in executiile din Exemplele 4.1 sau 4.2.
Nu acelasi lucru este valabil pentru clasele care contin date alocate dinamic. Situatia este foarte asemanatoare celei prezentate in cazul constructorilor de copiere: daca o clasa contine date alocate dinamic, copierea membru cu membru care se executa implicit la asignare sau la constructia prin copiere, are ca efect copierea pointerilor la datele alocate dinamic, deci doi pointeri din doua obiecte vor indica catre aceeasi zona din memoria heap. Acesta situatie conduce la numeroase si subtile erori care se vor analiza mai sugestiv pe un exemplu de clasa care modeleaza un sir de caractere, clasa String.

? Exemplul 4.3

Fie clasa String care implementeaza un sir de caractere, folosind constante de tip sir:

#include <string.h> class StringA char *str; int size; public:
String();
String(const char *p);
String(const String& r);
IString(); friend ostream& operator <<(ostream &stream, const String &r); friend istream& operator >>(istream &stream,
String &r);
String& operator=(const String &op2);
String& perator=(const char* p);
S;

Clasa String contine un pointer la un sir de caractere, str, si o variabila de tip intreg size care memoreaza dimensiunea vectorului de caractere corespunzator, deci inclusiv spatiul necesar pentru caracterul 0 de la sfarsitul sirului. Constructorii si destructorul sunt simplu de implementat. Constructorul implicit creaza un obiect String cu un sir de caractere de lungime zero. Constructorul de initializare construiestc un obiect String avand ca argument un sir de caractere terminat cu nul. Dimensiunea tabloului de caractere pe care il aloca in memoria heap este strlen(p)+1, pentru a se insera caracterul 0 de la sfarsit. Constructorul de copiere este absolut necesar, pentru a aloca un sir nou in memoria heap si a evita astfel erorile care ar putea apare prin copierea pointerului, care ar indica catre acelasi sir in memoria heap. Se asemenea este absolut necesar sa fie definit destructorul, pentru eliminarea sirului de caractere alocat dinamic in heap.

String::String()A cout << "Constructor implicit\n"; str = 0; size = 0;
S
String::String(const char *p)A cout << "Constructor init\n"; size = strlen(p) + 1; str = new charasizei; strcpy(str, p);
S
String::String(const String& r)A cout << "Constructor copiere\n"; size = r.size; str = new charasizei; strcpy(str, r.str);
S
String::IString()A cout << "Destructor\n"; if (str)A delete aistr; str = 0;
S
S
Functiile operator <<() si operator >>() care definesc operatiile de inserare si de extragere a unui obiect String dintr-un stream definite in acest punct vor fi prezentate detaliat in sectiunea 6.

ostream& operator <<(ostream &stream, const String &r)A stream << r.str; return stream;
S istream& operator >>(istream &stream, String &r)A char bufa256i; cin.get(buf,256); r = buf; return stream;
S

Operatorul de asignare este asemanator constructorului de copiere: nu efectueaza copierea membru cu membru a datelor ci aloca un spatiu nou in memoria heap pentru sirul de caractere str si efectueaza copierea continutului acestuia. In plus, la asignare, mai este necesara stergerea sirului pe care obiectul String pentru care se executa operatia de asignare ar fi putut sa-l aiba alocat in memoria heap. S-au definit doua functii operator=(), cu argumente de tip diferit (o referinta la clasa String si un pointer si un sir de caractere), selectia intre aceste functii efectuandu-se pe baza tipului argumentului de apel.

String& String::operator=(const String &op2)A cout << "Operator=(String&)\n"; if (str) delete aistr; size = op2.size; str = new charasizei; strcpy(str, op2.str); return *this;
S

String& String::operator=(const char* p)A cout << "Operator = (char*) \n"; if (str) delete aistr; size = strlen(p) + 1; str = new charasizei; strcpy(str, p); return *this;
S

Se pot studia situatiile in care se folosesc diferiti constructori sau operatori de asignare pentru obiecte din clasa String. Fie functia f3():

void f3 ()A
String sir1("123456"); // constr. initializ cout << sir1 <<endl;
String sir2 = "abcd"; // constr. initializ cout << sir2 << endl;
String sir3 = sir1; // constr. copiere cout << sir3 << endl; sir3 = sir2; // operator=(String&) cout << sir3 << endl; sir3 = "mnp"; // operator=(char*) cout << sir3 << endl;
S

La executia acestei functii, la consola se afiseaza mesajele:

Constructor init
123456
Constructor init abcd
Constructor copiere
123456
Operator=(String&) abcd
Operator=(char*) mnp
Destructor
Destructor
Destructor

care indica modul in care sunt apelati constructorii, destructorul si functiile operator de asignare. S-au creat trei obiecte din clasa String, obiectele sir1 si sir2 prin constructori de initializare, iar obiectul sir3 prin constructorul de copiere. Asignarile catre obiectul sir3 utilizeaza acea functie operator=() care se potriveste tipului argumentului. La iesirea din blocul funtiei, cele trei obiecte sunt distruse si se apeleaza de fiecare data destructorul, care eliminina din memoria heap sirul de caractere str corespunzator fiecarui obiect.
Aceasta este executia corecta a programului, asigurata de definirea corecta a constructorilor, destructorului si a functiilor de copiere. Lipsa unora dintre aceste functii poate avea urmari dintre cele mai grave.
De exemplu, lipsa functiei operator:
String& operator=(const String &op2); are ca efect folosirea operatorului implicit de asignare, care copiaza in variabila str a obiectului sir3 valoarea pointerului str din obiectul sir2, deci ambii pointeri indica acelasi sir de caractere “abcd”: sir2.str = sir3.str. Eroarea se evidentiaza la iesirea din functia f1(): eliminarea obiectului sir3 produce stergerea sirului de caractere “abcd”, acelasi pe care incearca sa-l steearga apoi si destructorul apelat pentru obiectul sir2. Aceasta operatie de stergere a unor date care au fost deja sterse din memorie provoaca executia anormala a programului si la consola va apare un mesaj de eroare. Mai sunt posibile si alte erori de executie provenite din aceasta asignare eronata, care pot fi studiate in exercitiile propuse.
Tot ca exercitii se vor studia si alte situatii care evidentiaza comportamentul functiilor operator de asignare.

4.4 Supraincarcarea operatorului de indexare

O functie operatorai() poate fi folosita pentru a defini o operatie de indexare pentru obiecte de tipuri definite de utilizator (clase). Ca si functia operator de asignare, functia operator de indexare nu poate fi decat functie membra nestatica a clasei respective. Argumentul functiei reprezinta al doilea operand al operatiei de indexare si este un indice. Acesta argument poate fi orice tip de date, spre deosebire de indicii in tablouri care nu pot avea decat valori intregi. Primul argument al functiei este obiectul de tip String pentru care se executa operatia de indexare si pointerul la acesta (pointerul this) este transmis implicit functiei operator de asignare care este membra a clasei.

In clasa String se poate adauga functia operator ai(int i), care returneaza referinta la caracterul din pozitia i a sirului de caractere str continut de un obiect String:

char& String::operatorai(int i)A return straii;
S

Se poate remarca faptul ca aceasta implementare este cea mai simpla posibila, dar pot apare erori de executie atunci cand se executa indexarea pentru valori ale argumentului care depasesc dimensiunea sirului de caractere. Modul cum se trateaza astfel de erori in C++ este prezentat in sectiunea 8.

? Exemplul 4.4

Fie functia f4():

void f4()A char vai = "123456789";
String sir(v); int s = strlen(v); for(int i=0;i<s;i++) cout << siraii; cout << endl; for(i=0;i<s;i++) siraii = 65 + i; cout << sir << endl;
S

La executia acesteia, la consola se afiseaza urmatoarele rezultate:

Constructor init
123456789
ABCDEFGH
Destructor

Datorita faptului ca functia operator de indexare returneaza o referinta la un element al sirului, a fost posibila folosirea indexarii atat pentru un membru dreapta cat si pentru un membru stanga al unei expresii.

4.5 Supraincarcarea operatorilor new si delete

Operatorii new si delete sunt utilizati pentru alocarea dinamica a datelor in memoria heap. Pentru supraincarcarea acestor operatori se pot folosi numai functii membre statice ale clasei. Prototipul functiilor de supraincarcare a operatorilor new si delete sunt:

void* operator new(size_t lungime); void operator delete(void* p);

In ambele situatii, functiile sunt implicit statice, fara sa fie nevoie de utilizarea specificatorului static.
Tipul size_t este un tip definit in fisierul de biblioteca stdlib.h (intreg fara semn), iar lungime este dimensiunea in numar de octeti a zonei de memorie care trebuie sa fie alocata. Aceasta valoare (lungime) nu trebuie sa fie specificata la apelul unei functii operator new, deoarece compilatorul calculeaza in mod automat dimensiunea obiectului pentru care se aloca zona de memorie. Pointerul universal (void*) returnat are ca valoare adresa de inceput a zonei de memorie alocata.
Functia operator delete() primeste un pointer catre regiunea de memorie pe care trebuie sa o elibereze.
Operatorii new si delete pot fi supraincarcati global, astfel incat orice utilizare a lor sa foloseasca versiunea supraincarcata, sau pot fi supraincarcati pentru o anumita clasa, si in aceasta situatie functiile operator trebuie sa fie membre statice ale clasei.
La intalnirea unuia dintre operatorii new sau delete pentru un anumit tip, compilatorul verifica mai intai daca acesta a fost supraincarcat pentru clasa respectiva. Daca a fost supraincarcat, se foloseste versiunea supraincarcata a clasei; daca nu, este apelat operatorul new sau delete global. Daca acestea au fost supraincarcate, se utilizeaza aceste versiuni.

? Exemplul 4.5

Se considera supraincarcarea operatorilor new si delete pentru clasa Point. In acest exemplu simplu operatorul supraincarcat nu modifica modul de alocare sau de dezalocare a memoriei, ci doar adauga un mesaj la consola.

class PointA
//……………. public:
//………………. void* operator new(size_t dim); void operator delete(void *p);
S;

void* Point::operator new(size_t dim)A cout << "Creare punct nou\n"; return ::new Point;
S void Point::operator delete(void* p)A cout << "Distrugere punct\n";
::delete p;
S void f5()A
Point *p1 = new Point; p1->Display(); delete p1; p1 = new Point(7); p1->Display(); delete p1; p1=new Point(5,8); delete p1;
Point *p2 = new Pointa7i; delete aip2;
S

Expresiile de forma ::new sau ::delete se refera la versiunea predefinita (globala) a operatorilor de alocare dinamica, folosind operatorul de rezolutie ::. Daca si aceste versiuni au fost supraincarcate, atunci este apelata functia supraincarcata, dar aceasta este o practica mai putin frecventa. La executia functiei f5(), la consola sunt afisate urmatoarele mesaje:

Creare punct nou
0 0
Distrugere punct
Creare punct nou
7 7
Distrugere punct
Creare punct nou
5 8
Distrugere punct

Operatorul new supraincarcat se apeleaza la fel ca operatorul new predefinit:
X* pX = new X(arg1, arg2, …);
Daca in clasa X operatorul new a fost supraincarcat, atunci este apelata versiunea supraincarcata a acestuia, iar argumerntele arg1, arg2,… sunt folosite pentru selectia constructorului clasei X, apelat implicit de functia operator new().
In functia f5() s-au apelat trei constructori diferiti (constructorul implicit, cu un argument si cu doua argumente) pentru primele trei alocari a cate unui obiect din clasa Point si, de fiecare data, la distrugere a fost apelata functia operator delete supraincarcata a clasei Point. Mesajele afisate evidentiaza acest luccru.
?

Din exemplul prezentat mai sus se poate observa ca la constructia unui tablou de obiecte de tip Point, (new Pointa7i) este folosit operatorul new predefinit (global) si nu functia operator new a clasei Point. La fel, la stergerea tabloului se foloseste operatorul global delete ai.
Aceasta situatie apare datorita faptului ca, pentru alocarea dinamica a tablourilor de obiecte folosind operatori supraincarcati, trebuie sa fie supraincarcati operatorii new si delete in versiunea pentru tablouri de obiecte care arata astfel:

void* operator newai(size_t lungime); void operator deleteai(void* p);

4.6 Supraincarcarea operatorului de apel functie

Operatorul de apel al unei functii poate fi considerat o expresie binara de forma: nume_functie(lista_argumente) unde lista_argumente introduce argumentele efective de apel ale functiei cu numele nume_functie. In aceasta expresie binara operandul stanga este numele functiei, iar operandul dreapta este lista de argumente de apel. Operatorul () poate fi supraincarcat pentru o clasa data folosind o functie membra nestatica a clasei printr-o constructie de forma: tip_returnat operator() (lista_argumente);
Lista de argumente este evaluata si utilizata dupa regulile obisnuite de transfer ale argumentelor. Functia operator de apel poate fi supraincarcata pentru orice clasa, dar ea este utila in special pentru acele clase care au fie o singura functie, fie una dintre functiile membre este predominanta ca utilizare.
De exemplu, supraincarcarea functiei operator de apel pentru clasa Point, poate arata astfel:

class PointA
//………….. public:
//………….
Point operator()(double a, double b)A cout << "Operator () " << endl; x = a; y = b; return *this;
S
S;

Pentru functia operator()() s-a ales o operatie de atribuire a unor valori datelor membre ale clasei si utilizarea ei intr-o functie oarecare f6() poate arata astfel:

void f6()A
Point p3(11,22); p3.Display(); // afiseaza 11 22 p3(4,5); // apel functie afiseaza Operator () p3.Display(); // afiseaza 4 5
S

Supraincarcarea operatorului de apel este frecvent utilizata in definirea iteratorilor, care permit parcurgerea in ordinea dorita a elementelor unei colectii, fara ca aceasta ordine sa depinda de modul de ordonare interna a elementelor colectiei. Un astfel de exemplu este dat in sectiunea 8. De asemenea, functia operator () () mai este utilizata in operatii cu subsiruri si ca operator de indexare in tablourile multidimensionale.

4.7 Supraincarcarea operatorului pointer

Operatorul pointer -> poate fi considerat un operator unar postfix, care se aplica operandului care il precede. Supraincarcarea acestui operator se poate face printr-o functie nestatica membra a clasei, cu acceasi sintaxa ca cea prezentata la inceputul acestei sectiuni. Fie o operatie de selectie membru cu forma generala: obiect->expresie unde obiect este o instanta a unei clase oarecare X pentru care s-a supraincarcat operatorul pointer. Evaluarea pentru expresie se executa in functie de tipul de data returnat de functia supraincarcata a clasei X operator ->()astfel:

a) Daca functia operator->() a unei clase X returneaza un obiect de tipul X, atunci se acceseaza elementul corespunzator (obtinut prin evaluarea expresie) a clasei X.

b) Daca functia operator->() a unei clase Y returneaza un pointer la o data de un tip oarecare, atunci se aplica operatorul -> predefinit, adica se selecteaza o componenta a obiectului catre care indica pointerul returnat.

? Exemplul 4.6

Fie doua clase X si Y in care se supraincarca in mod deosebit operatorul de pointer: in clasa X functia operator->() returneaza un pointer la tipul X, iar in clasa Y functia operator->()returneaza un pointer la tipul X (deci diferit de tipul Y al clasei respective).

class XA public: int x;
X* operator->() Areturn this;S void Display()A cout << x << endl;

S friend ifstream& operator>>(ifstream& stream,
X& obX);
S; ifstream& operator>>(ifstream& stream, X& obX)A stream >> obX.x; return stream;
S
X* read_from_disk(const char *p)A ifstream file(p);
X* pX = new X; file >> (*pX); return pX;
S class YA
X* pX; const char* name; public:
Y(const char *p):name(p) ApX=0;S
X* operator->() A if (pX==0) pX = read_from_disk(name); return pX;
S
S; void f7()A
X obX; obX->x = 10; cout << obX->x <<" " << obX.x << endl; //10 10

Y obY("test.txt"); obY->Display(); //afiseaza valoarea citita
S

In acest exemplu implementarea functiei read_from_disk() folosind operatii cu streamuri este mai putin interesanta (ea poate fi inteleasa mai bine dupa prcurgerea operatiilor cu streamuri), ccea ce intereseaza este faptul ca returneaza un pointer la un obiect de tipul X, obiect creat in memoria heap si initializat prin citirea unor date dintr-un fisier de pe disc.
Din acest exemplu se pot observa cele doua modalitati de utilizare a functiei operator->() supraincarcate in cele doua clase X si Y.
Functia operator->() din clasa X returneaza un pointer la X (chiar pointerul this), de aceea poate fi utilizata pentru a accesa o data membra a clasei X. De aceea, operatiile obX->x si obX.x sunt echivalente si mesajele afisate la consola sunt identice, indicand valoarea datei membre x a obiectului obX de tip X.
Functia operator->() din clasa Y returneaza un pointer la X, de aceea poate fi utilizata pentru accesul la o data membra a clasei X, si anume functia Display(). La constructia obiectului obY, sunt initializate datele membre name = “test.txt” si pX=0. Apelul functiei operator de dereferentiere pentru obiectul obY din clasa Y apeleaza functia read_from_disk() care creaza un obiect de clasa X in memoria heap, il initializeaza cu date citite din fisierul cu numele “test.txt”, returneaza pointerul la acest obiect creat. Acest pointer este folosit pentru selectarea functiei Display() a obiectului. La consola se afiseaza valoarea numarului intreg citit din fisierul al carui nume este transferat ca argument constructorului obiectului obY (“test.txt”)
?

Supraincarcarea operatorului pointer este o trasatura puternica a limbajului C++, care permite construirea de programe interesante si flexibile.

4.8 Supraincarcarea operatorilor de conversie

Utilizarea unui constructor pentru a specifica o conversie de tip este posibila pentru un numar limitat de situatii deoarece:

a) Nu exista o conversie implicita de la un tip definit de utilizator (clasa) la un tip predefinit. b) Nu se poate defini o conversie de la un tip nou de date la un tip definit mai inainte, fara modificarea tipului vechi.

Aceste probleme se pot rezolva prin definirea unui operator de conversie al clasei. Intr-o clasa X, o functie membra X::operator T(), unde T este un nume de tip (predefinit sau definit de utilizator) realizeaza conversia de la tipul X la tipul T, in modul descris de functia operator. Se pot diferentia doua categorii de operatori de conversie: conversie dintr-un tip definit de utilizator intr-un tip predefinit si conversie dintr-un tip definit de utilizator in alt tip definit de utilizator.

4.8.1 Conversia dintr-un tip definit de utilizator intr-un tip predefinit

Una din utilizarile frecvente ale operatorilor de conversie este aceea de a converti un tip definit de utilizator (clasa) intr-un tip predefinit printr-o functie membra nestatica a clasei respective. O astfel de functie nu are nici un argument, dat fiind ca operatorul de conversie este un operator unar (foloseste numai obiectul pentru care a fost apelat, al carui pointer this il primeste implicit).
Pentru o clasa data se pot supraincarca mai multi operatori de conversie de la clasa respectiva la unul din tipurile predefinite. Conversia definita prin supraincarcarea operatorului de conversie poate fi apelata explicit sau implicit. Apelul explicit al operatorului de conversie pentru un obiect din clasa X catre tipul predefinit T poate avea doua forme:
T(obiect) sau T(obiect)
Conversia implicita (deci apelul implicit al functiei operator de conversie) are loc la utilizarea unui obiect dintr-o clasa in care s-a definit o astfel de functie intr-o expresie aritmetica sau conditionala. Daca sunt definite mai multe functii operatori de conversie pot sa apara ambiguitati in selectarea uneia dintre acestea in cazul conversiei implicite. Cateva situatii de conversie sunt prezentate in exemplul urmator.

? Exemplul 4.7

Se completeaza clasa Point cu definirea catorva operatori de conversie, care apoi sunt apelati in diferite modalitati.

class PointA
//……….. public: operator double(); operator void*();
//…………
S;
Point::operator double()A return sqrt(x*x + y*y);
S
Point::operator void*()A if (sqrt(x*x + y*y)) return &x; else return 0;
S void f8()A
Point p1(3, 4); double x = p1; cout << x << endl; // afiseaza 5 cout << (double)p1 << endl; // afiseaza 5 cout << double(p1) << endl; // afiseaza 5 void *pp1 = p1; cout << p1 <<" "<< pp1 <<endl;// afiseaza o adresa if (p1) // eroare, conversie ambigua
// intr-o expresie conditionala cout << p1;
S

Se poate evita eroarea de compilare determinata de ambiguitatea la conversia implicita din instructiunea if(p1), fortand in mod explicit unul dintre operatorii de conversie ai clasei, de exemplu astfel: if ((void*)p1)…

4.8.2 Conversia dintr-un tip definit de utilizator intr-un alt tip definit de utilizator

Fie doua clase X si Y. Conversia obiectelor de tip X in obiecte de tip Y se poate realiza fie prin utilizarea constructorilor, fie prin supraincarcarea operatorului de conversie in clasa Y.
Daca in clasa Y se defineste un constructor de tipul Y(X ob), atunci se poate realiza o conversie a unui obiect de tip X intr-un obiect de tip Y. Pentru accesul la datele private sau protected ale clasei X, este necesar declararea friend class Y in clasa X.

? Exemplul 4.8

Se considera clasa Point, definita in aceasta sectiune si clasa Complex, definita in sectiunea 2. Conversia datelor intre cele doua tipuri are si o semnificatie matematica bine precizata, dat fiind ca un numar complex poate fi reprezentat printr-un punct intr-un plan bidimensional (imaginea numarului complex). Modul in care se realizeaza conversia datelor de tip Complex in date de tip Point, deci calculul imaginii unui numar complex, este prezentata in continuare.

class Complex A double re; double im; public:
Complex()Are = 0; im = 0;S
Complex(double r, double i) Are = r; im = i;S friend class Point;
S; class Point A
//………………… public:
Point(Complex c)A x = c.re; y = c.im;
S
S;

void f9()A
Complex c1(2.3, 9.7);
Point p1(c1);
Point p2 = c1; // alternativa de apel cout << p1; // afiseaza 2.3 9.7
S
?

Conversia dintr-un tip definit de utilizator intr-un alt tip definit de utilizator se poate realiza si prin supraincarcarea operatorului de conversie. Pentru conversia obiectelor de tip X in obiecte de tip Y, in clasa X se defineste functia operator de conversie:
X::operator Y();
Pentru ca aceasta functie membra nestatica a clasei X sa aiba acces la date private sau protejate ale clasei Y, ea se declara functie friend in clasa Y.
In exemplul urmator se reia operatia de conversie din clasa Complex in clasa Point prin supraincarcarea operatorului cu conversie in clasa Complex.

? Exemplul 4.9

class Point; class ComplexA double re; double im; public:
Complex()Are = 0; im = 0;S
Complex(double r, double i) Are = r; im = i;S operator Point();

S; class Point A
//………….. public: friend Complex::operator Point();
S;
Complex::operator Point()A
Point tmp; tmp.x = re; tmp.y = im; return tmp;
S void f10()A
Complex c1(4,7);
Point p1; p1 = c1; cout << p1; // afiseaza 4 7
S

Se construieste mai intai obiectul p1 folosind constructorul implicit; instructiunea p1 = c1 apeleaza operatorul de conversie la tipul Point definit in clasa Complex; dupa aceasta conversie mai este apelata functia operator de asignare a clasei Point, care, la returnare, construieste un obiect temporar folosind constructorul de copiere al clasei Point.
?

Este de mentionat faptul ca este admisa definirea unei singure conversii de la un tip de date la altul. Daca in Exemplul 4.9 s-ar pastra si constructorul de conversie definit in Exemplul 4.8, atunci ar apare o eroare de compilare.
Se poate observa ca, in unele cazuri, o valoare de un tip dorit poate fi construita prin utilizarea repetata a constructorilor si operatorilor de conversie. Dintre acestea, o singura conversie implicita definita de utilizator este legala. Situatiile in care un obiect poate fi construit in mai multe feluri sunt ilegale (mesajul de eroare de compilare se refera la o ambiguitate).


Colt dreapta
Creeaza cont
Comentarii:

Nu ai gasit ce cautai? Crezi ca ceva ne lipseste? Lasa-ti comentariul si incercam sa te ajutam.
Esti satisfacut de calitarea acestui document, eseu, cometariu? Apreciem aprecierile voastre.

Nume (obligatoriu):

Email (obligatoriu, nu va fi publicat):

Site URL (optional):


Comentariile tale: (NO HTML)


Noteaza documentul:
In prezent fisierul este notat cu: ? (media unui numar de ? de note primite).

2345678910

 
Copyright© 2005 - 2024 | Trimite document | Harta site | Adauga in favorite
Colt dreapta