Curs 8 - 9
Curs 8 - 9
Curs 8 - 9
SUPRAÎNCĂRCAREA
OPERATORILOR
NOŢIUNI
NO IUNI INTRODUCTIVE
• Supraîncărcarea operatorilor (supradefinirea
- overloading) permite atribuirea de noi
semnificaţii operatorilor uzuali (operatorilor
intâlniţi pentru tipurile de date predefinite).
• Programatorul poate să redefinească operatorii
C++ fără ca vechile sensuri să se piardă
• Astfel, operatorul + se foloseşte la adunarea a
două date de tip int, float sau double, însă
aceluiaşi operator i se poate atribui, prin
supraîncărcare, semnificaţia de "alipire" a două
obiecte de tipul şir, sau de adunare a două
obiecte de tipul complex, vector sau matrice.
SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR
• Aşa cum am subliniat în numeroase rânduri,
clasa reprezintă un tip de date (o mulţime de
valori pentru care s-a adoptat un anumit
mod de reprezentare şi o mulţime de
operaţii care pot fi aplicate acestora).
• Tipurile de date
– Predefinite (int, char) sau definite de utilizator
– Pot folosi operatorii existenţi cu tipurile definite
de utilizator
• Nu pot crea (defini) noi operatori
SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR
• Pentru a putea folosi operatorii asupra
obiectelor unei clase
– Operatorul trebuie să fie supraîncărcat
pentru acea clasă
– Excepţii:
• Operatorul de asignare (atribuire) =
• Operatorul adresă &
• Ambii pot fi supraîncărcaţi
MODURI DE SUPRAÎNCĂRCARE
SUPRAÎNC RCARE A
OPERATORILOR
• Prin supraîncărcarea operatorilor, operaţiile
care pot fi executate asupra instanţelor
(obiectelor) unei clase pot fi folosite ca şi în
cazul tipurilor de date predefinite.
• Din punct de vedere al limbajului, operatorii
supraîncărcaţi (redefiniţi) sunt nişte metode
oarecare (funcţii non-statice sau globale)
purtând nume de operatori
Supradefinirea operatorilor
• Pentru a nu provoca erori de sintaxă, numele
metodelor respective vor fi prefixate cu
cuvântul cheie “operator” urmat de simbolul
operatorului supraîncărcat (ex. operator+)
• Deci, limbajul C++ permite supradefinirea
operatorului op prin definirea unei funcţii
numite
operator op
• Funcţia trebuie să poată accesa datele
membre private ale clasei, deci supradefinirea
operatorilor se poate realiza în două moduri:
– printr-o funcţie membră a clasei;
– printr-o funcţie prietenă a clasei.
Exemplul 1:
class matrice
{
//…
public:
matrice AdunMatrice(matrice&);
matrice operator + (matrice&);
//…
};
Diferenţa dintre cele două metode este comoditatea apelului în cazul celei
de a doua:
matrice a, b, c;
//...
c=a.AdunMatrice(b); //corect, dar incomod
c=a.operator+(b); //corect, dar incomod
c=a+b; //prin supraîncărcare, mult mai comod şi mai natural
A. SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA
OPERATORILOR PRIN FUNCŢII
FUNC II MEMBRE
• În cazul în care operatorii sunt supraîncărcaţi prin
funcţii membre, acestea trebuie să fie funcţii non-
statice, deoarece ele trebuie să poată fi apelate de
un un obiect al clasei şi operareze cu acest obiect
• În situaţia în care supraîncărcarea operatorului + se
realizează printr-o funcţie membră, aceasta primeşte
ca parametru implicit adresa obiectului curent (pentru
care este apelată). Deci primul operand al
operatorului este transmis implicit.
• Supraîncărcarea operatorilor este potrivită mai ales
pentru clasele matematice care necesită adesea un
set substanţial de operatori supraîncărcaţi pentru a
asigura consistenţa modului în care aceste clase
sunt prelucrate în lumea reală.
Exemplul 2:
class punct
{
double x, y;
public:
punct operator + (punct);
};
//Metodele clasei punct
...
punct punct::operator + (punct a)
{ punct p;
p.x=x + a.x; //echivalent cu p.x=this->x+a.x;
p.y=y + a.y; //echivalent cu p.y=this->y+a.y;
return p;
}
void main()
{ punct A(1.1, 2.2); A.afişare();
punct B(-5.5, -6.6); B.afişare();
punct C;
C=A+B; C.afişare();
C=A+B+C; C.afişare();
}
Observaţii:
T = A.operator + (B)
C = T.operator + (C)
+ - * / % ^ & |
~ ! = < > += -= *=
/= %= ^= &= |= << >> >>=
<<= == != <= >= && || ++
-- ->* , -> [] () new delete
new[] delete[]
RESTRICŢII
RESTRIC II LA SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA
OPERATORILOR
1. Se pot supraîncărca doar operatorii existenţi; nu se
pot crea noi operatori.
2. Nu se poate modifica n-aritatea (numărul de
operanzi) operatorilor limbajului (operatorii unari nu
pot fi supraincărcaţi ca operatori binari, şi invers).
3. Nu se poate modifica precedenţa şi asociativitatea
operatorilor.
4. Deşi operatorii supraîncărcaţi păstrează n-aritatea
şi precedenţa operatorilor predefiniţi, ei nu
moştenesc şi comutativitatea acestora.
5. Directivele preprocesor # şi ## nu pot fi redefinite
deoarece ele nu sunt operatori
6. Nu pot fi supraîncărcaţi operatorii:
. .* :: ?: şi :
SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR
UNARI
• Operatorii unari pot fi supradefiniţi printr-o funcţie
membră nestatică (fără parametri expliciţi) sau
printr-o funcţie prietenă cu un parametru explicit de
tipul clasă.
• Funcţiile obţinute prin supraîncărcarea operatorilor
unari:
• Realizate cu funcţie membru nu au parametri
• Realizate cu funcţie friend au un singur parametru
(care trebuie să fie obiect al clasei sau referinţă la
un obiect al clasei)
Ca exemplu, să supraîncarcăm operatorul unar ++
pentru clasa punct, pentru a putea fi folosit atât în
formă prefixată, cât şi postfixată. Vom folosi clasa
punct implementată anterior, cu modificarea că
datele membre sunt de tipul int.
SUPRAÎNC
SUPRAÎNCĂRCAREA
ÎNC RCAREA OPERATORILOR
OPERATORILOR DE
INCREMENTARE ++ ŞII DECREMENTARE --
complex &complex::operator =
a.re a.im
(const complex &z) a
{ re=z.re; im=z.im; return *this; }
void main() b,z
b.re b.im
{ complex a, b;
a = b; Supraîncărcarea operatorului = prin metodă
a clasei complex
//a.operator=(b);
}
Exemplul 6: prin funcţie
func ie friend
• Operatorul binar de atribuire = poate fi supraîncărcat prin funcţie
prietenă (în acest caz, nu primeşte parametrul implicit this, deci are doi
operanzi).
• Paramertrii z1, z2 sunt transmişi prin referinţă, deci se lucrează chiar cu
obiectele a, b.
• Funcţia returnează adresa obiectului a. Modificatorul const interzice
modificarea operandului drept.
class complex
{ double re,im; a.re a.im
a,z1
public:
friend complex&operator=
(complex&,const complex&); b.re b.im
//funcţie prietenă constantă b,z2
}; Supraîncărcarea operatorului = prin
complex & operator = (complex &z1, complex
&z2) funcţie prietenă a clasei complex
{ z1.re=z2.re; z1.im=z2.im; return z1;}
void main()
{ complex a, b;
a = b; //a.operator=(b);
}
SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR
NEW ŞII DELETE
• Avantajul alocării dinamice a memoriei şi a eliberării
acesteia cu ajutorul operatorilor new şi delete, faţă de
utilizarea funcţiilor malloc, calloc sau free, constă în faptul
că operatorii alocă (eliberează) memorie pentru obiecte,
date de tip abstract. Acest lucru este posibil deoarece
aceşti operatori au o supraîncărcare globală, standard.
1. Pentru operatorul new, funcţia care supraîncarcă
operatorul new are prototipul:
void * nume_clasa::operator new (size_t lungime);
• Modul de utilizare pentru operatorul new:
nume_clasa *p = new nume_clasa;
sau: nume_clasa *p = new nume_clasa(p1, p2, p3);
Obs. - Aplicarea operatorului new supradefinit de utilizator
determină, automat, apelul constructorului
corespunzător clasei, sau al constructorului implicit.
- În a doua formă, la alocarea dinamică a memoriei
apar şi parametrii constructorului (p1, p2, p3).
Exemplul 7:
class c1
{
double n1, n2;
public:
c1()
{ n1=0;
n2=0; }
};
//. . . .
void main( )
{ c1 *pc1=new c1; /*se alocă memorie pentru păstrarea unui
obiect de tip c1*/
c1 *pc2=new c1(7, 5); /*odată cu alocarea dinamică, se
realizează şi iniţializarea*/
}
SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR
NEW ŞII DELETE
2. Operatorul delete se supradefineşte printr-o
funcţie cu prototipul:
void nume_clasa::operator delete (void *);
• La aplicarea operatorului delete se apelează,
automat, destructorul clasei.
Operatorii new şi delete permit alocarea dinamică şi
pentru tablouri.
• În aceste situaţii, se utilizează întotdeauna
operatorii supraîncărcaţi global predefiniţi şi se
apelează constructorul fără parametri (dacă
acesta există).
Exemplul 8:
class c1
{
double n1, n2;
public:
c1()
{ n1=0;
n2=0; }
c1(double x, double y)
{ n1=x;
n2=y; }
};
c1 *pct1;
pct1=new c1[100]; /*Se rezervă memorie ptr.
100 obiecte de tip c1. Se
apelează constructorul implicit
de 100 de ori */
}
SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR
NEW ŞII DELETE
• Operatori standard new şi delete pot fi apelaţi
atât în domeniul de vizibilitate al clasei ce-i
redefineşte, cât şi în cel al altor clase derivate din
aceasta, în două moduri:
– Implicit – crează/distrug obiecte de alt tip decât
cel în cauză
– Explicit – prin formule de genul “::operator new”
sau “::operator delete”
• În corpul constructorului se alocă memorie
dinamic (cu operatorul new).
• Destructorul eliberează memoria alocată dinamic
(cu operatorul delete).
SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR
NEW ŞII DELETE
Restricţii:
• new trebuie să returneze “void *” şi să aibă ca
prim parametru un “size_t” (tip de dată definit
în “stdlib.h” şi utilizat pentru manipularea
lungimii zonelor de memorie destinate păstrării
obiectelor)
• delete trebuie să returneze “void” şi să aibă un
prim parametru “void *” şi eventual un al doilea
de tipul “size_t”
• Ambii operatori vor fi implicit şi obligatoriu
metode statice
• Ambii operatori nu pot fi virtuali
Exemplul 9:
#include <iostream.h>
class exemplu
{
int a, b, c;
static short lungime;
/*declarat static pentru a putea fi accesat de
metodele statice new şi delete*/
public:
exemplu(void)
{ cout<<“Apel constructor Exemplu\n”;
a=b=c=0;
}
~exemplu(void)
{ cout<<“Apel destructor Exemplu\n”;
}
Exemplul 9 cont:
cont:
void * operator new(size_t lng)
/*parametrul lng permite lungimi diferite pentru
eventualele clase derivate */
{ cout<<“operatorul \”new\” redefinit \n”;
lungime=lng;
return (void *) new char(lng);
/*tipul argumentului şir de caractere e suficient pentru a
specifica că se apelează operatorul standard new*/
}
void operator delete (void *p)
{ cout<< exemplu::lungime<<“\n Operatorul \”delete\” \n”;
/*se tipăreşte lungimea obiectului în octeţi*/
delete[lungime] (char *) p;
/*tipul argumentului pointer la char e suficient pentru a
specifica că se apelează operatorul standard delete*/
}
};
Exemplul 9 cont:
cont:
class derivat: public exemplu Observaţii:
{ double dbl; • Setarea valorii
public: parametrului “lng” ne se
derivat (void) face de programator, ea va
{ cout<<“Apel constructor Derivat \n”;
fi dedusă de compilator în
dbl=12.4;
} momentul apelului “new”.
~derivat (void) • Valoarae sa va fi egală cu
{ cout<< “Apel destructor derivat \n”; numărul de octeţi necesar
} pentru memorare unui
}; obiect de tipul dat.
void main() • Prezenţa lui este absolut
{
necesară deoarece
exemplu *ptr;
derivat *ptd; metoda “new” va fi
ptr=new exemplu; moştenită şi de clasa
delete ptr; “derivat” ale cărei
} instanţieri au o altă
lungime.
SUPRAÎNC
SUPRAÎNCĂRCAREA
PRAÎNC RCAREA OPERATORULUI
DE INDEXARE [ ]
• Operatorul de indexare [ ] este un operator binar şi
are forma generală:
nume[expresie]
class vector {
private:
int nrcomp; //nr. componente
double *tabcomp; //tabloul componentelor; adresa de început
public:
double &operator[](int);
};
SUPRAÎNC
SUPRAÎNCĂRCAREA
PRAÎNC RCAREA OPERATORULUI
DE INDEXARE [ ]
• Pentru tipul abstract vector, operatorul de indexare [ ] poate fi
supradefinit, astfel încât să permită accesarea elementului de
indice n.
• Operatorul de indexare [ ] se poate supraîncărca printr-o funcţie
membră a clasei (deoarece operandul stâng este de tipul clasei),
şi poate fi folosit sub forma:
v[n]
unde: v - obiect al clasei vector;
n- expresie întreagă
• Expresia v[n] este echivalentă cu v.operator[ ](n) (apelul explicit
al funcţiei operator [ ] ).
• Transferul parametrului către funcţia care supraîncarcă
operatorul se poate face prin valoare sau prin referinţă.
• În mod obligatoriu, funcţia trebuie să returneze referinţa către
elementul aflat pe poziţia n (pentru a permite eventualele
modificări ale elementului, deoarece vector[n] este lvalue).
SUPRAÎNC
SUPRAÎNCĂRCAREA
PRAÎNC RCAREA OPERATORULUI
DE INDEXARE [ ]
• Pentru un tip abstract, prototipul funcţiei membru
care supradefineşte operatorul de indexare este :
class complex{
double re, im;
public:
complex (double r=0, double i=0) { re=r; im=i; }
complex (fracţie &f) { re=(double)f.nrt/f.nmt; im=0; }
// conversie fracţie->complex
operator double() const { return re; } //conversie complex->double
friend ostream &operator<<(ostream &, const complex &);
};
ostream &operator<<(ostream &ies, const complex &z)
{ ies<<'('<<z.re<<','<<z.im<<")=\n"; return ies; }
Exemplul 12 cont.
void main()
{ complex a(6.98, 3.2), b(9), c, d, e, q, s; int i=12, j=5, k;
double x=1234.999, y=74.9897, u, z; c=i; fractie r(7, 3), r1(9), t;
d=x; /*conversie double->complex (constructor complex cu arg.
double)*/
e=complex(y,j); /*apel explicit al constr. complex; întâi conversie j de la int
la double*/
k=a; //conversie complex->double->int
u=a; //conversie complex->double
z=(double)a/3; //conversie complex->double
cout<<"r="<<r<<" q="<<q<<'\n';
cout<<"int(r)="<<int(r)<<" (int)r="<<(int)r<<'\n‘; //conversie fractie->int
cout<<"r="<<r<<'\n'; cout<<r.intreg()<<'\n';
s=r; // conversie fracţie->complex
cout<<"(complex)r="<<(complex)r<<" complex (r)="<<complex (r)<<'\n';
// conversie fracţie->complex
}