http://alpha-thema-online.uv.ro
Fa-ti si tu propriul forum, pe www.myforum.ro!
Pagina de start a forumului http://alpha-thema-online.uv.ro Autentificare Inregistrare FAQ Membri Cautare

Pagina de start a forumului http://alpha-thema-online.uv.ro » C / C++ / C# » [C++]Manual C++ part. 8
Creaza un subiect nou  Raspunde la subiect Subiectul anterior :: Subiectul urmator 
[C++]Manual C++ part. 8
MesajTrimis: 24/08/2007 13:54 Raspunde cu citat (quote)
Ad_Infinitum
Administrator
Administrator
Ad_Infinitum
Data inscrierii: 23/Iun/2007
Mesaje: 212




Supraîncãrcarea operatorilor





Supraîncãrcarea funcþiilor ºi a operatorilor (overloading) sunt mecanisme importante în C++ care oferã flexibilitate ºi extensibilitate limbajului. Pentru tipurile fundamentale ale limbajului sunt definite seturi de operatori care permit operaþii de bazã executate într-un mod convenabil. Dar, dupã cum este cunoscut, în limbaj sunt definite prea puþine soiuri de date ca date fundamentale, iar pentru reprezentarea altor tipuri care sunt necesare în diferite domenii (cum ar fi aritmetica numerelor complexe, algebra matricilor, etc.), se definesc clase care conþin funcþii ce pot opera asupra acestor tipuri. Definirea operatorilor care sã opereze asupra obiectelor unei clase permite un mod mult mai convenabil de a manipula obiectele decât prin folosirea unor funcþii ale clasei.
O funcþie care defineºte pentru o clasã o operaþie echivalentã operaþiei efectuate de un operator asupra unui tip predefinit este numitã funcþie operator. Majoritatea operatorilor limbajului C++ pot fi supraîncãrcaþi, ºi anume:

new delete () []
+ - * / % ^ & | ~
! = < > += -= *= /= %=
^= &= |= << >> >>= <<= == !=
<= >= && || ++ -- , ->* ->

Operatorul () este apelul unei funcþii, iar operatorul [] este operatorul de indexare. Urmãtorii operatori nu se pot supraîncãrca:
. .* :: ?: sizeof
Existã câteva reguli care trebuie sã fie respectate la supraîncãrcarea operatorilor:

Funcþiile operator=(), operator()() ºi operator[]() trebuie sã fie membri nestatici ai clasei.
Cu excepþia funcþiei operator=(), toate celelate funcþii operator pot fi moºtenite.
Nu pot fi supraîncãrcaþi operatorii pentru tipurile predefinite ale limbajului.
Funcþiile operator nu pot avea argumente implicite.

Funcþiile operator pentru o anumitã clasã pot sã fie sau nu funcþii membre ale clasei. Dacã nu sunt funcþii membre ele sunt, totuºi, funcþii friend ale clasei ºi trebuie sã aibã ca argument cel puþin un obiect din clasa respectivã sau o referinþã la aceasta. Excepþie fac operatorii =, (), [], ->, care nu pot fi supraîncãrcaþi utilizând funcþii friend ale clasei. De asemenea, funcþiile operator new() ºi operator delete()au implementãri mai deosebite care vor fi detaliate mai jos.


Funcþii operator membre ale claselor

Forma generalã pentru funcþiile operator membre ale clasei este urmãtoarea:

tip_returnat operator#(lista_argumente){
// operaþii
}

În aceastã formã generalã semnul # reprezintã oricare dintre operanzii care pot fi supraîncãrcaþi.

Exemplul 4.1

Fie o clasã Point care descrie un vector într-un plan bidimensional prin douã numere de tip double, x ºi y. Valorile x ºi y reprezintã coordonatele punctului de extremitate al vectorului. Pentru aceastã clasã se pot defini mai multe operaþii cu vectori, ca de exemplu:

Suma a doi vectori
Diferenþa a doi vectori
Produsul scalar a doi vectori
Multiplicarea unui vector cu o constantã (scalare)
Incrementarea/decrementarea componentelor vectorului
Oglindire (negarea fiecãrei componente).

Aceste operaþii se pot implementa prin supraîncãrcarea corespunzãtoare a operatorilor. Pentru început se vor defini funcþiile operator+() ºi operator–() pentru calculul sumei, respectiv a diferenþei a doi vectori.

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

};
Point Point::operator+(Point op2){
point temp;
temp.x = x + op2.x;
temp.y = y + op2.y;
return temp;
}
Point Point::operator-(Point op2){
point temp;
temp.x = x + op2.x;
temp.y = y + op2.y;
return temp;
}
double Point::operator*(Point op2){
return x*op2.y + y*op2.x;
}
Point& Point::operator*(double v){
x *=v;
y *=v;
return *this;
}
void f1(){
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
}

Funcþia operator+() are un singur argument, chiar dacã ea supraîncarcã un operator binar (operatorul +), care necesitã doi operanzi. Argumentul transmis funcþiei este operandul din dreapta operaþiei, iar operandul din stânga este chiar obiectul pentru care se apeleazã funcþia operator. Expresia:
pct3 = pct1 + pct2;
semnificã, de fapt:
pct3 = pct1.operator+(pct2);
ºi chiar poate fi apelatã astfel. Acest lucru înseamnã cã obiectul din stânga operatorului este cel pentru care se apeleazã funcþia operator, care are acces la acesta prin pointerul this pasat implicit funcþiei operator membrã a clasei. Pentru funcþia operator+() ordinea operanzilor nu are importanþã, dar aceastã convenþie de apel este importantã pentru alte funcþii, ca de exemplu funcþia operator-().
Pentru acelaºi operator se pot defini mai multe funcþii supraîncãrcate, cu condiþia ca selecþia uneia dintre ele în funcþie de numãrul ºi tipul argumentelor sã nu fie ambiguã. În clasa Point s-a supraîncãrcat operatorul * cu douã funcþii: prima pentru calculul produsului scalar a doi vectori, cealaltã pentru multiplicarea vectorului cu o constantã. 

În implementarea prezentatã, funcþia operator+() creazã un obiect temporar, care este distrus dupã returnare. În acest fel, ea nu modificã nici unul dintre operanzi, aºa cum nici operatorul + pentru tipurile predefinite nu modificã operanzii.
Într-o funcþie operator se pot efectua orice fel de operaþii, dar, în mod obiºnuit, se pãstreazã semnificaþia acþiunii operatorului respectiv. Cu excepþia funcþiilor operator new (), operator delete() ºi operator->(), valoarea returnatã de o funcþie operator poate fi de orice tip, dar, tot pentru pãstrarea contextului utilizãrii normale, de obicei se returneazã un obiect din aceeaºi clasã, sau o referinþã la aceastã. Acest lucru este important pentru utilizarea operatorului în expresii, aºa cum este cea scrisã mai sus.
În expresia scrisã mai sus, pct3 = pct1 + pct2, se executã, pe lângã operaþia de adunare, ºi o operaþie de asignare pentru o variabilã de tip Point. Pentru clasa Point, aceastã operaþie se executã corect, chiar dacã nu a fost supraîncãrcatã funcþia operator=(), pentru că asignarea implicitã se executã printr-o copiere penis cu membru ºi în acest caz nu realizează erori. Condiþiile în care este perfect necesarã supraîncãrcarea funcþiei operator=() vor fi discutate în subsecþiunea urmãtoare.
În general, un operator binar poate fi supraîncãrcat fie printr-o funcþie membrã nestaticã cu un argument, fie printr-o funcþie nemembrã cu douã argumente.
Un operator unar poate fi supraîncãrcat fie printr-o funcþie membrã nestaticã fãrã nici un argument, fie printr-o funcþie nemembrã cu un argument. La supraîncãrcarea operatorilor de incrementare sau decrementare (++, --) se poate diferenþia un operator prefix de un operator postfix întrebuințând douã versiuni ale funcþiei operator. În continuare sunt prezentate câteva funcþii operator ale clasei Point pentru operatori unari.

class Point{
//……………
public:
Point operator!();
Point operator++();
Point operator—();
Point operator++(int x);
Point operator—(int x);
};
Point operator!(){
x = -x;
y = -y;
return *this;
}
Point Point::operator++(){
x++;
y++;
return *this;
}
Point Point::operator--(){
x--;
y--;
return *this;
}
Point Point::operator ++(int x){
++x;
++y;
return *this;
}
Point Point::operator --(int x){
--x;
--y;
return *this;
}

Dacã ++ precede operandul, este apelatã funcþia operator++(); dacã ++ urmeazã operandului, atunci este apelatã funcþia operator++(int x), iar x are valoarea 0.


Funcþii operator friend

La supraîncãrcarea unui operator folosind o funcþie care nu este membrã a clasei este necesar sã fie transmiºi toþi operanzii necesari, deoarece nu mai existã un obiect al cãrui pointer (this) sã fie transferat implicit funcþiei. Din aceastã cauzã, funcþiile operator binar necesitã douã argumente de tip clasã sau referinþã la clasã, iar funcþiile operator unar necesitã un argument de tip clasã sau referinþã la clasã. În cazul operatorilor binari, inițialul argument transmis este operandul stânga, iar al doilea argument este operandul dreapta.
În exemplul urmãtor se reiau unele funcþiile operator ale clasei Point, implementate ca funcþii friend ale clasei.
Exemplul 4.3

class Point
{
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);
};
Point operator+(Point op1, Point op2){
Point temp;
temp.x = op1.x + op2.x;
temp.y = op1.y + op2.y;
return temp;
}
Point operator-(Point op1, Point op2){
Point temp;
temp.x = op1.x - op2.x;
temp.y = op1.y - op2.y;
return temp;
}
Point operator++(Point &p)
{
p.x++;
p.y++;
return p;
}
Point operator ++(Point &p, int x)
{
++p.x;
++p.y;
return p;
}
Point operator--(Point &p)
{
p.x--;
p.y--;
return p;
}
Point operator --(Point &p, int x)
{
--p.x;
--p.y;
return p;
}

void f2(){
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
}


Referitor la supraîncãrcarea operatorilor folosind funcþii nemembre ale clasei se mai pot face câteva observaþii.
Dacã funcþia operator nu ar fi declaratã funcþie friend a clasei, ea nu ar avea acces la variabilele protejate ale clasei. Problema s-ar putea rezolva prin adãugarea unor funcþii publice de citire ºi scriere ale datelor membre ale clasei respective, care sã fie apelate în funcþia operator. Dar o astfel de soluþie este incomodã, ineficientã ºi nu asigură nici un avantaj. Se poate evidenþia acest formă dacã se încearcã o modificare a funcþiei operator+() a clasei Point astfel:

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

Se poate observa cã, dacã se folosesc funcþii nemembre ale clasei pentru supraîncãrcarea operatorilor, atunci este mult mai eficient ºi mai confortabil ca acestea sã fie declarate funcþii friend ale clasei respective.
O altã observaþie referitoare la supraîncãrcarea operatorilor folosind funcþii friend este aceea cã pentru operatorii care trebuie sã modifice operandul (cum sunt operatorii de incrementare, decrementare, complement, etc) este necesar transmiterea operandului ca parametru prin referinþã, ceea ce îngăduie modificarea lui în funcþia operator. Acest mod de apel se observã la definirea funcþiilor operator++() ºi operator--() din Exemplul 4.2.


Supraîncãrcarea operatorului de asignare

Existã mai mulþi operatori de asignare: =, *=, /=, %=, +=, -=, >>=, <<=, &=, ^=, |=, dintre care primul este operatorul simplu de asignare, ceilalþi fiind combinaþie cu alþi operatori.
În asignarea simplã (=), valoarea expresiei care reprezintã operandul dreapta înlocuieºte valoarea operandului stânga. Dacã ambii operanzi sunt de tip aritmetic, operandul dreapta este convertit la tipul operandului stânga, dupã care are loc atribuirea valorii. Nu existã o conversie implicitã la tipul enumerare, astfel încât, dacã operandul stânga este o enumerare, cel din dreapta trebuie sã fie de acelaºi tip.
Pentru tipurile definite de utilizator, funcþia operator de asignare (operator=()) trebuie sã fie o funcþie membrã nestaticã a clasei ºi nu poate fi moºtenitã. În multe privinþe, funcþia operator=() seamãnã mai mult cu constructorii decât cu ceilalþi operatori, dar este, totuºi diferitã de operaþia de iniþializare pe care o efectueazã constructorii. În lipsa unei funcþii operator=() definitã de utilizator pentru o clasã X, este utilizatã definiþia implicitã de asignare prin copierea membru cu membru astfel:

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

void f(){
X a,b;
a = b; // se foloseºte asignarea implicitã
}

Ceilalþi operatori de asignare (+=, -=,*=, etc) nu au o semnificaþie predefinitã pentru clase, deci, pentru a putea fi folosiþi, trebuie sã fie definiþi de utilizator.
În cazul claselor de obiecte care nu conþin date alocate dinamic la iniþializare sau prin intermediul altor funcþii membre, asignarea prin copiere membru cu membru funcþioneazã corect ºi, în general, nu mai este necesar sã fie supraîncãrcat operatorul de asignare. Acest lucru s-a remarcat ºi în execuþiile din Exemplele 4.1 sau 4.2.
Nu acelaºi lucru este valabil pentru clasele care conþin date alocate dinamic. Situaþia este foarte asemãnãtoare celei prezentate în cazul constructorilor de copiere: dacã o clasã conþine date alocate dinamic, copierea membru cu membru care se executã implicit la asignare sau la construcþia prin copiere, are ca efect copierea pointerilor la datele alocate dinamic, deci doi pointeri din douã obiecte vor indica cãtre aceeaºi zonã din memoria heap. Acestã situaþie conduce la multiple ºi subtile erori care se vor analiza mai sugestiv pe un exemplu de clasã care modeleazã un ºir de caractere, clasa String.

Exemplul 4.3

Fie clasa String care implementeazã un ºir de caractere, folosind constante de tip ºir:

#include <string.h>
class String{
char *str;
int size;
public:
String();
String(const char *p);
String(const String& r);
~String();
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);
};

Clasa String conþine un pointer la un ºir de caractere, str, ºi o variabilã de tip întreg size care memoreazã mărimea vectorului de caractere corespunzãtor, deci inclusiv spaþiul necesar pentru caracterul 0 de la sfârºitul ºirului. Constructorii ºi destructorul sunt necomplicat de implementat. Constructorul implicit creazã un obiect String cu un ºir de caractere de lungime zero. Constructorul de iniþializare construieºtc un obiect String având ca argument un ºir de caractere terminat cu nul. Măsură tabloului de caractere pe care îl alocã în memoria heap este strlen(p)+1, pentru a se insera caracterul 0 de la sfârºit. Constructorul de copiere este absolut necesar, pentru a aloca un ºir nou în memoria heap ºi a evita astfel erorile care ar putea apare prin copierea pointerului, care ar indica cãtre acelaºi ºir în memoria heap. Se întocmai este absolut necesar sã fie definit destructorul, pentru eliminarea ºirului de caractere alocat dinamic în heap.

String::String(){
cout << "Constructor implicit\n";
str = 0;
size = 0;
}
String::String(const char *p){
cout << "Constructor init\n";
size = strlen(p) + 1;
str = new char[size];
strcpy(str, p);
}
String::String(const String& r){
cout << "Constructor copiere\n";
size = r.size;
str = new char[size];
strcpy(str, r.str);
}
String::~String(){
cout << "Destructor\n";
if (str){
delete []str;
str = 0;
}
}
Funcþiile operator <<() ºi operator >>() care definesc operaþiile de inserare ºi de extragere a unui obiect String dintr-un stream definite în acest punct vor fi prezentate detaliat în secþiunea 6.

ostream& operator <<(ostream &stream, const String &r){
stream << r.str;
return stream;
}
istream& operator >>(istream &stream, String &r){
char buf[256];
cin.get(buf,256);
r = buf;
return stream;
}

Operatorul de asignare este asemãnãtor constructorului de copiere: nu efectueazã copierea membru cu membru a datelor ci alocã un spaþiu nou în memoria heap pentru ºirul de caractere str ºi efectueazã copierea conþinutului acestuia. În plus, la asignare, mai este necesarã ºtergerea ºirului pe care obiectul String pentru care se executã operaþia de asignare ar fi putut sã-l aibã alocat în memoria heap. S-au definit douã funcþii operator=(), cu argumente de tip diferit (o referinþã la clasa String ºi un pointer ºi un ºir de caractere), selecþia între aceste funcþii efectuându-se pe baza tipului argumentului de apel.

String& String::operator=(const String &op2){
cout << "Operator=(String&)\n";
if (str)
delete []str;
size = op2.size;
str = new char[size];
strcpy(str, op2.str);
return *this;
}

String& String::operator=(const char* p){
cout << "Operator = (char*) \n";
if (str)
delete []str;
size = strlen(p) + 1;
str = new char[size];
strcpy(str, p);
return *this;
}

Se pot studia situaþiile în care se folosesc diferiþi constructori sau operatori de asignare pentru obiecte din clasa String. Fie funcþia f3():

void f3 (){
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;
}

La execuþia acestei funcþii, la consolã se afiºeazã mesajele:

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

care indicã modul în care sunt apelaþi constructorii, destructorul ºi funcþiile operator de asignare. S-au creat trei obiecte din clasa String, obiectele sir1 ºi sir2 prin constructori de iniþializare, iar obiectul sir3 prin constructorul de copiere. Asignãrile cãtre obiectul sir3 utilizeazã acea funcþie operator=() care se potriveºte tipului argumentului. La ieºirea din blocul funþiei, cele trei obiecte sunt distruse ºi se apeleazã de fiecare datã destructorul, care elimininã din memoria heap ºirul de caractere str corespunzãtor fiecãrui obiect.
Aceasta este execuþia corectã a programului, asiguratã de definirea corectã a constructorilor, destructorului ºi a funcþiilor de copiere. Lipsa unora dintre aceste funcþii poate avea urmãri dintre cele mai grave.
De exemplu, lipsa funcþiei operator:
String& operator=(const String &op2);
are ca consecință folosirea operatorului implicit de asignare, care copiazã în variabila str a obiectului sir3 valoarea pointerului str din obiectul sir2, deci ambii pointeri indicã acelaºi ºir de caractere “abcd”: sir2.str = sir3.str. Eroarea se evidenþiazã la ieºirea din funcþia f1(): eliminarea obiectului sir3 cauzează ºtergerea ºirului de caractere “abcd”, acelaºi pe care încearcã sã-l ºteeargã apoi ºi destructorul apelat pentru obiectul sir2. Aceastã operaþie de ºtergere a unor date care au fost deja ºterse din memorie provoacã execuþia anormalã a programului ºi la consolã va apare un mesaj de eroare. Mai sunt posibile ºi alte erori de execuþie provenite din aceastã asignare eronatã, care pot fi studiate în exerciþiile propuse.
Tot ca exerciþii se vor studia ºi alte situaþii care evidenþiazã comportamentul funcþiilor operator de asignare.
Vezi profilul utilizatorului Trimite mesaj privat

[C++]Manual C++ part. 8
 Pagina de start a forumului http://alpha-thema-online.uv.ro » C / C++ / C#
Nu puteti crea un subiect nou in acest forum
Nu puteti raspunde in subiectele acestui forum
Nu puteti modifica mesajele proprii din acest forum
Nu puteti sterge mesajele proprii din acest forum
Nu puteti vota in chestionarele din acest forum
Ora este GMT + 3 ore  
Pagina 1 din 1  

  
  
 Creaza un subiect nou  Raspunde la subiect  


Fa-ti si tu propriul forum GRATUIT, pe www.myforum.ro
    Powered by phpBB © 2001-2004 phpBB Group. phpBB Skin by Vjacheslav Trushkin.