Curs 8 - 9

Descărcați ca pdf sau txt
Descărcați ca pdf sau txt
Sunteți pe pagina 1din 58

CAPITOLUL 6

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:

Expresia C=A+B este interpretată ca


C = A.operator + (B)

Expresia C=A+B+C poate fi interpretată, în funcţie


de compilator, astfel:

1. Unele compilatoare crează un obiect temporar T:

T = A.operator + (B)
C = T.operator + (C)

2. Alte compilatoare interpretează expresia ca:


C=(A.operator + (B)).operator + (C).
B. SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR PRIN
FUNCŢII
FUNC II PRIETENE
• Reamintind faptul că funcţiile prietene au acces la
membrii privaţi ai unei clase, însă nu primesc ca
argument implicit pointerul către obiectul curent
(this)
• Dacă supraîncărcăm acelaşi operator printr-o
metodă şi printr-o funcţie prietenă, funcţia prietenă
va avea, întotdeauna, un parametru în plus faţă de
metodă (deoarece funcţiei prietene nu i se
transmite ca parametru implicit pointerul this).
• Vom supraîncărca operatorul + printr-o funcţie
prietenă a clasei punct (exemplu):
Exemplul 3:
class punct {
double x, y;
public:
// . . . . . .
friend punct operator + (punct, punct);
};
//Metodele clasei punct
punct operator + (punct a, punct b)
{ punct p;
p.x=a.x + b.x;
p.y=a.y + b.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:
1. Expresia C=A+B este interpretată de compilator ca
C=operator + (A, B);
2. Expresia C=A+B+C este evaluată ţiinându-se cont de regulile de
prioritate şi de asociativitate a operatorului: (A+B)+C, ceea ce
conduce la un apel de forma:
operator + (operator + (A, B), C);
3. În exemplul anterior, transmiterea parametrilor către funcţia
prietenă de supraîncărcare a operatorului + se realizează prin
valoare.
4. Parametrii pot fi transmişi şi prin referinţă, pentru a evita
crearea (în momentul apelului funcţiei) unor copii locale ale
parametrilor efectivi în cei formali. La transmiterea parametrilor
prin referinţă, funcţia operator + are prototipul:
punct operator + (punct &, punct &);
5. Pentru a proteja argumentele transmise prin referinţă la
eventualele modificări, se poate folosi modificatorul de acces
const:
punct operator + (const punct &, const punct &);
SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR PRIN FUNCŢII
FUNC II
MEMBRE vs.
vs. SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR
PRIN FUNCŢII
FUNC II PRIETENE

Funcţiile operator pot fi:


– Funcţii membru
• Folosesc cuvântul cheie this pentru a obţine implicit
argumentul
• Obţin operandul stâng pentru operatorii binari (ex. +)
• Obiectele din partea stângă trebuie să fie din aceeaşi
clasă ca şi operatorul
– Funcţii non membru (prietene)
• Necesită parametri de tip obiect pentru ambii operanzi
• Pot avea obiecte din clase diferite apoi operatori
• Trebuie să fie funcţii prieten pentru a accesa date
private sau protejate
Concluzii:
Concluzii
1. Funcţiile obţinute prin supraîncărcarea
operatorilor au aceeaşi prioritate ca şi
operatorii respectivi
2. Funcţiile obţinute prin supraîncărcarea
operatorilor au aceeaşi asociativitate ca
operatorii respectivi
3. Funcţiile obţinute prin supraîncărcarea
operatorilor au aceeaşi n-aritate ca şi
operatorii respectivi:
– Dacă operatorul este unar şi funcţia este unară:
– Dacă operatorul este binar şi funcţia este binară
Concluzii:
Concluzii
4. Se poate atribui unui operator orice semnificaţie cât mai
apropiată de semnificaţia naturală.
5. În cazul supradefinirii operatorilor, nu se poate conta pe
comutativitatea acestora.
De exemplu, dacă se supraîncarcă operatorul + pentru clasa
complex printr-o funcţie prietenă a clasei complex:
complex operator + (complex, double)
corect: a+7.8 (a este obiect al clasei complex),
incorect: 7.8 + a
6. Dacă un operator trebuie să primească ca prim parametru un
tip predefinit, acesta nu poate fi supradefinit printr-o funcţie
membră.
7. Diferenţa între forma prefixată şi postfixată, la
supraîncărcarea operatorilor predefiniţi ++ şi --, se poate
face doar de anumite compilatoare
OPERATORI CE POT FI SUPRAÎNCĂRCA
SUPRAÎNC RCAŢI
RCA I

Operatori ce pot fi supraîncărcaţi

+ - * / % ^ & |
~ ! = < > += -= *=
/= %= ^= &= |= << >> >>=
<<= == != <= >= && || ++
-- ->* , -> [] () 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 --

• Operatorii unari de incrementare ++ şi de


decrementare -- pot fi folosiţi atât prefixaţi
cât şi postfixaţi.
• Supraîncărcarea acestor operatori poate fi
făcută şi ea atât prefixată cât şi postfixată,
pentru compilatoarele care permit acest
lucru, funcţiile de supraîncărcare având
prototipuri diferite astfel încât compilatorul
să le deosebească.
SUPRAÎNC
SUPRAÎNCĂRCAREA
ÎNC RCAREA PREFIXATĂ
PREFIXAT A
OPERATORULUI DE INCREMENTARE ++
• Presupunem că vrem să incrementăm ziua dintr-un obiect d1
al clasei Data. Când compilatorul întâlneşte expresia de
preincrementare ++d1, generează apelul funcţiei membru
d1.operator++()
• Prototipul acestei funcţii operator va fi:
Data &operator++();
• Dacă operatorul de incrementare prefixat este implementat ca
o funcţie globală, la întâlnirea expresiei ++d1, compilatorul
generează apelul funcţiei
operator++( d1 )
• Prototipul acestei funcţii operator va fi declarat în clasa Data
astfel:
Data &operator++( Data & );
SUPRAÎNC
SUPRAÎNCĂRCAREA
ÎNC RCAREA POSTFIXATĂ
POSTFIXAT A
OPERATORULUI DE INCREMENTARE ++
• Supraîncărcarea postfixată a operatorului de incrementare trebuie să
poată fi distinsă de compilator diferit faţă de cea prefixată
• Convenţia adoptată în limbajul C++, când compilatorul întâlneşte
expresia de postincrementare d1++, este să genereze apelul funcţiei
membru:
d1.operator++(0)
• Prototipul acestei funcţii operator va fi:
Data &operator++(int);
Argumentul 0 are rolul strict de a permite compilatorului să deosebească
funcţiile operator de incrementare prefixată de cea postfixată.
• Dacă operatorul de incrementare prefixat este implementat ca o
funcţie globală, la întâlnirea expresiei d1++, compilatorul generează
apelul funcţiei
operator++( d1, 0 )
• Prototipul acestei funcţii operator va fi declarat în clasa Data astfel:
Data &operator++( Data &, int );
Exemplul 4:
class punct {
int x, y;
public:
// . . .
punct & operator ++ (int ); //forma postfixată
punct & punct::operator++(); //forma prefixată
};

punct punct::operator ++ (int)


{ punct p=*this; x++; y++; return p; }

punct & punct::operator++()


{ x++; y++; return *this; }
void main()
{ punct A(11, 10); punct C=A++; A.afişare( ); C.afişare( );
punct C=++A; A.afişare( ); C.afişare( );
}
SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR
BINARI
• Funcţiile obţinute prin supraîncărcarea
operatorilor binari:
– Realizate cu funcţie membru au un parametru
Ex. class String {
public:
const String &operator+=( const String & );
...
};
y += z este echivalent cu y.operator+=( z )
– Realizate cu funcţie friend au doi parametri (unul
trebuie să fie obiect al clasei sau referinţă la un
obiect al clasei)
Ex. class String {
friend const String &operator+=(
String &, const String & );
...
};
y += z este echivalent cu operator+=( y, z )
MEMBRII CONSTANŢI
CONSTAN I AI UNEI CLASE
• O clasă poate avea membrii statici:
– date membru statice - figurează într-un singur
exemplar pentru toate instanţele clasei
– metode statice - nu li se transmite pointerul this şi pot
modifica doar date membru statice
• O clasă poate avea metode constante - o
metodă este declarată constantă prin utilizarea
modificatorului const în antetul său, după lista
parametrilor formali. Metodele constante nu
modifică obiectul pentru care sunt apelate.
• Ca oricăror variabile de tip predefinit, şi obiectelor
de tip definit de utilizator li se poate aplica
modificatorul const.
• Pentru un obiect constant este permis doar
apelul metodelor constante, a constructorilor şi a
destructorilor.
SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR
INSERTOR << ŞII EXTRACTOR >>
• Operatorul << se numeşte operator insertor, deoarece inserează
date în stream-ul (fluxul) de ieşire.
• Operatorul >> se numeşte operator extractor, deoarece extrage
date din stream-ul (fluxul) de intrare.

Exemplu: aceşti operatori sunt supraîncărcaţi pentru clasa complex,


astfel încât să poată fi folosiţi ca pentru obiectele de tip predefinit.
complex z1, z2;
cin>>z1>>z2; //extrage valorile lui z1 şi z2
cout<<"z1="<<z1<<'\n'; //inserează şir constant, apoi valoarea lui z1
cout<<"z2="<<z2<<'\n'; //inserează şir constant, apoi valoarea lui z2

Obs. Deoarece întotdeauna operandul stâng este de tip istream (cin


este obiect predefinit, de tip istream) sau ostream (cout este
obiect predefinit, de tip ostream), şi nu de tipul introdus prin clasă,
operatorii << şi >> pot fi supraîncărcaţi numai prin funcţii
prietene.
SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORILOR
INSERTOR << ŞII EXTRACTOR >>
Prototipurile operatorilor sunt:
friend ostream &operator << (ostream &,const complex&);
//operator afişare complex
friend istream & operator >> (istream &,complex&);
//operator citire complex

Definiţiile funcţiilor operator:


ostream &operator << (ostream &ecran, const complex &z)
{ ecran<<"("<<z.re;
if (z.im>=0) ecran<<'+';ecran<<z.im<<"*i)"; return ecran; }
istream &operator >> (istream &tastatura, complex &z)
{ tastatura >>z.re>>z.im; return tastatura; }

Prototipurile funcţiilor operator << şi >> pentru un tip abstract tip,


sunt:
friend ostream &operator << (ostream &, const tip&);
friend istream &operator >> (istream &, tip&);
SUPRAÎNCĂRCAREA
SUPRAÎNC RCAREA OPERATORULUI
DE ATRIBUIRE =
• În cazul în care operatorul de atribuire = nu este supraîncărcat
explicit, compilatorul generează implicit unul, care va fi binar,
cu ambii operanzi de acelaşi tip şi va copia valorile datelor
membre ale operandului drept în datele membre ale
operandului stâng.
• Operatorul = nu este niciodată moştenit de clasele derivate
Exemplu:
punct a (8,9), b;
b=a; /* operator atribuire implicit: zona de memorie
ocupat de a se copiază, bit cu bit, în zona de
memorie ocupată de b: b.x=a.x si b.y=a.y */
• Operatorul de atribuire implicit este nesatisfăcător în situaţiile în
care obiectele clasei au ca date membre pointeri, sau în
situaţiile în care memoria este alocată în mod dinamic.
• O supraîncărcare explicită a operatorului pentru clasa complex
(ambii operanţi de tip complex) poate fi făcută fie prin metodă
(funcţie membru), fie prin funcţie prietenă
Exemplul 5: prin funcţie
func ie membru
class complex
temp.re temp.im
{ double re, im; temp
public:
complex operator = (complex ); a.re a.im
}; a
complex complex::operator = (complex z)
{ re=z.re; im=z.im; return *this; b.re b.im
/*this este pointer către obiectul curent, a b
în main*/
}
void main() z.re z.im
z
{ complex a, b;
a = b; //a.operator=(b); (Obiectul z este local funcţiei operator=)
} Supraîncărcarea operatorului = prin

Deoarece funcţia operator= returnează metodă a clasei complex


valoare de tip complex, se
construieşte un obiect temporar temp,
a cărui valoare se atribuie lui a.
Exemplul 6: prin funcţie
func ie membru
• O modalitate, mai eficientă, de a supraîncărca operatorul de
atribuire prin metodă a clasei complex, este aceea prin care
funcţia primeşte ca parametru, referinţa către operandul drept (se
lucrează, astfel, chiar cu obiectul b, deoarece z şi b sunt variabile
referinţă;
• în plus, modificatorul const interzice modificarea operandului
transmis ca parametru referinţă; în plus, nu se mai crează
obiectul local z, se ia ca referinţă obiectul existent) şi returnează
o referinţă (adresa obiectului a)

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]

Ex. Să considerăm clasa vector, definită astfel:

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 :

tip_element & operator [ ] (const int);

(const protejează argumentul la modificările


accidentale)

• În cazul în care operatorul se supraîncarcă printr-o


funcţie prietenă, prototipul funcţiei este:

tip_elem & operator (tip, int);


SUPRAÎNC
SUPRAÎNCĂRCAREA
ÎNC RCAREA OPERATORULUI ( )
• Supraîncărcarea operatorului "apel de funcţie" ()
permite crearea unui operator binar, nestatic, de
forma:
expresie (lista_param_efectivi);

Avantajele unui astfel de operator sunt:


– Evaluarea şi verificarea listei de argumente în mod similar
unei funcţii obişnuite;
– Modul de funcţionare a mecanismului de apel. Deşi
operatorul este binar, cel de-al doilea operand fiind o listă
de argumente (inclusiv listă vidă), funcţia operator poate
avea oricâţi parametri.
• În cazul în care numele funcţiei este un pointer
către o anumită funcţie (vezi pointeri către funcţii),
apelul funcţiei se realizează prin:
(*point_f) (lista_param_efectivi);
SUPRAÎNC
SUPRAÎNCĂRCAREA
ÎNC RCAREA OPERATORULUI ( )
• Funcţia care supraîncarcă operatorul ( ) trebuie să fie metodă
nestatică.
• Supraîncărcarea operatorului ( ) se utilizează în mod frecvent la
definirea aşa-numitului iterator.
• Iteratorii se utilizează în legătură cu tipuri abstracte de date,
care conţin colecţii de elermente (liste, arbori, tabele de
dispersie, etc.).
• Pe lângă protecţia datelor, iteratorii oferă un mijloc simplu de
acces la elementele unei colecţii, fără a intra în detaliile legate
de implementarea colecţiei.
• În principiu, un iterator se implementează printr-o clasă ataşată
unui tip abstract care conţine o colecţie de elemente.
De exemplu, clasa container care este o colecţie de obiecte de
tip oarecare, care poate fi implementată ca un tablou de
obiecte, ca o listă de noduri, ca un arbore binar, etc.
• În funcţie de această organizare, clasa container conţine o dată
membră de tip obiect sau un pointer către obiect şi este
capabil să livreze, pe rând, obiectele elemente ale colecţiei.
SUPRAÎNC
SUPRAÎNCĂRCAREA
ÎNC RCAREA OPERATORULUI ( )
Avantajele utilizării funcţiei operator ()
– Evaluarea şi verificarea listei de argumente se face
întocmai ca la o funcţie obişnuită
– Modul de funcţionare al mecanismului de apel. Deşi
operatorul este binar, funcţia operator poate avea
oricâţi parametri – cel de-al doilea operand find o listă
de argumente, ea poate fi şi vidă, deci poate lipsi cu
totul.
• Operatorul () este supraîncărcat pentru a
permite accesul simplificat la datele unui obiect
Exemplul 10:
10:
Supraîncărcarea operatorului () pentru a permite accesul la
elementele unei matrice:
#include <stdio.h>
#include<iostream.h>
class matrice
{
int v[10][5];
public: int& operator() (int, int);
};
Int& vector::operator() (int, i int j)
{ return v[i][j]; }
void main()
{
matrice a;
a(4, 3)=15;
cout<<a(4,3);
}
SUPRAÎNC
SUPRAÎNCĂRCAREA
ÎNC RCAREA OPERATORULUI ->
• Supraîncărcarea operatorului unar -> se
realizează printr-o metodă nestatică.
Expresia
obiect -> expresie
va fi interpretată ca
(obiect.operator->())->expresie
• De aceea, funcţia-operator trebuie să
returneze:
– fie un pointer la un obiect al clasei
– fie un obiect de un tip pentru care este
supradefinit operatorul ->.
Exemplul 11:
11:
#include <iostream.h>
typedef struct ex { int membru; };
class ex1 {
ex *pointer;
public:
void set(ex &p) { pointer=&p; }
ex *operator -> (void) { return pointer; }
};
class ex2 {
ex1 *pointer;
public:
void set(ex1 &p) { pointer=&p; }
ex1 * operator -> (void) { return pointer; }
};
void main()
{ ex A; ex1 B; ex2 C; B.set(A);
B->membru=10; //apel al funcţiei ex1::operator->()
cout<<B->membru<<'\n';
}
CONVERSII
• Există unele situaţii în care supraîncărcarea
operatorilor nu poate rezolva toate
problemele
• Neajunsurile se pot rezolva prin conversii

• Există următoarele tipuri de conversii:


– Conversii implicite;
– Conversii explicite.
TIPURI DE CONVERSII
1. Conversiile implicite au loc în următoarele situaţii:
• În cazul aplicării operatorului de atribuire: operandul drept este
convertit la tipul operandului stâng.
• La apelul unei funcţii: Dacă tipul parametrilor efectivi (de apel)
diferă de tipul parametrilor formali, se încearcă conversia tipului
parametrilor efectivi la tipul parametrilor formali.
• La revenirea dintr-o funcţie: Dacă funcţia returnează o valoare în
programul apelant, la întâlnirea instrucţiunii return expresie; se
încearcă conversia tipului expresiei la tipul specificat în antetul
funcţiei.
2. Conversiile explicite pot fi :
a. tip_predefinit_1 -> tip_predefinit_2
b. tip_predefinit -> tip_definit_de_utilizator (clasă)
c. clasă -> tip_predefinit
d. clasă_1 -> clasă_2
CONVERSII DIN TIP PREDEFINIT1 ÎN TIP
PREDEFINIT2
• Pentru realizarea unor astfel de conversii, se foloseşte
operatorul unar de conversie explicită (cast), de forma:
(tip) operand
Exemplu:
int k; double x;
x = (double) k / (k+1);
/* În situaţia în care se doreşte obţinerea rezultatului real al
împărţirii întregului k la k+1, trebuie realizată o conversie
explicită*/

• În limbajul C++ acelaşi efect se poate obţine şi astfel:


x= double (k) / (k+1);
deoarece se apelează explicit constructorul tipului double.
CONVERSII DIN TIP PREDEFINIT ÎN CLASĂ
CLAS
• Astfel de conversii se pot realiza atât implicit, cât şi explicit, în cazul în care
pentru clasa respectivă există un constructor cu parametri impliciţi, de tipul
predefinit.
Exemplu: Pentru clasa fracţie definită în cursurile anterioare:
class fracţie{
int nrt, nmt;
public:
fracţie( int nrt = 0, int nmt = 1);
// . . .
};
fracţie f;
f = 20;
/* Conversie IMPLICITĂ: înaintea atribuirii se converteşte operandul drept (de tip
int) la tipul operandului stâng (tip fracţie). */
f = fractie(20);
/*Conversie EXPLICITĂ: se converteşte întregul 20 într-un obiect al clasei fracţie
(nrt=20 şi nmt=1). */
}
CONVERSII DIN CLASĂ
CLAS ÎN TIP PREDEFINIT
• Acest tip de conversie se realizează printr-un operator special (cast) care
converteşte obiectul din clasă la tipul predefinit.
• Operatorul de conversie explicită se supraîncarcă printr-o funcţie membră
nestatică.
nume_clasa:: operator nume_tip_predefinit( );
• La aplicarea operatorului se foloseşte una din construcţiile:
(nume_tip_predefinit)obiect;
nume_tip_predefinit (obiect);
Exemplu: Pentru clasa fracţie, să se supraîncarce operatorul de conversie
explicită, care să realizeze conversii fracţie -> int.
#include <iostream.h>
class fracţie{
long nrt, nmt;
public:
fracţie(int n=0, int m=1) { nrt=n; nmt=m; }
friend ostream &operator<<(ostream &, const fracţie &);
operator int( ) { return nrt/nmt; } //conversie fracţie -> int
};
CONVERSII DIN CLASĂ
CLAS ÎN TIP PREDEFINIT
cont.
ostream &operator<<(ostream &ies, const fracţie &f)
{ ies<<'('<<f.nrt<<'/'<<f.nmt<<")\n";
return ies;
}
void main()
{ fracţie a(5,4), b(3), c; int i=7, j=14; c=a; c=7;
cout<<(fracţie)243<<'\n'; cout<<"(int)a="<<(int)a<<'\n';
//conversie explicită
cout<<"int(a)="<<int(a)<<'\n'; //conversie explicită
int x=a; //conversia se face implicit, înainte de atribuire
}
CONVERSII DIN CLASĂ1
CLAS 1 ÎN CLASĂ2
CLAS 2
• Conversia din tip_abstract_1 în tip_abstract_2 (din clasă1 în clasă2),
se realizează cu ajutorul unui constructor al clasei2, care primeşte ca
parametri obiecte din clasa1 (fracţie -> complex)
Exemplul 12:
#include <iostream.h>
class fracţie {
int nrt, nmt;
public:
fracţie(int nr=0, int nm=1) { nrt=nr; nmt=nm; }
operator int() const { return nrt/nmt; } //conversie fracţie -> int
friend ostream &operator<<(ostream&, const fracţie&);
friend class complex;
int întreg(){return nrt/nmt; }
};
ostream &operator <<(ostream &ostr, const fracţie &f)
{ ostr<<'('<<f.nrt<<'/'<<f.nmt<<")\n"; return ostr; }
Exemplul 12 cont.
/*cls. complex este clasa prietena ptr. clasa fracţie, fiecare funcţie din
complex este prietena pentru fracţie*/

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
}

S-ar putea să vă placă și