Sunteți pe pagina 1din 10

Programare orientată pe obiecte Curs 3

PROGRAMARE ORIENTATĂ PE OBIECTE

Supraîncărcarea operatorilor (continuare)


Supraîncărcarea operatorilor de conversie
La evaluarea unei expresii se efectuează conversii implicite pentru tipurile predefinite. Deoarece
nu există conversii implicite de la/spre o clasă la/de la un tip predefinit, programatorul poate defini
conversii explicite prin supraîncărcarea unui operator de conversie al clasei (operatorul “cast”) sau
prin intermediul constructorilor.
Conversia unui obiect din clasa idClasa la un tip primitiv sau la un tip definit se realizează
definind în clasa idClasa o funcţie membru nestatică de conversie.
Sintaxă pentru a realiza conversia de la tipul idClasa la tipul TipData.

class idClasa{
...
operator TipData ();//TipData este un nume de tip
...
};
Important: Nu se specifică nici un tip de rezultat întors, deoarece se returnează implicit valoarea
obiectului convertită la tipul pentru care este definită conversia şi nu are parametri.
Apelul explicit al funcţiei de conversie de la un obiect din clasa idClasa la tipul TipData se
specifică prin TipData(obiect) sau (TipData) obiect.
Conversia unui tip TipData la clasa idClasa se face prin intermediul constructorilor cu un singur
argument. Pentru a evita eventuale efecte nedorite, constructorul se declară precedat de cuvântul
cheie explicit. Constructorii declarați explicit pentru conversie trebuie să aibă un singur argument,
de tipul dorit pentru conversie.

Exemplu

class NumarRational{
int x, y;
public:
NumarRational(int x = 0, int y=1){
this->x = x;
this->y = y;
}
explicit NumarRational(float f){
x=f;
y=1;
}

1
Programare orientată pe obiecte Curs 3

friend ostream& operator<<(ostream& out, const NumarRational& nr){


out << nr.x << "/"<< nr.y;
return out;
}
operator float()const{ //conversie NumarRational->float
return x/(float)y;
}
};

//functii de test
void functie1(float r){
cout << "Apel functie1( "<< r << " )\n";
}
void functie2(NumarRational nr){
cout << "Apel functie2 ( "<<nr << " )\n";
}

void main()
{
NumarRational r1(1,2), r2(3,4);
float f1, f2;
f1 = (float) r1;//conversie explicita
cout << "f1="<< f1 << endl;
f2 = r2;//conversie implicita la atribuire
cout << "f2="<< f2 << endl;
functie1(f1);//apel fara conversie
functie1(r1);//conversie la transferul parametrilor
f1 = f1 + r1;//conversie implicita r1->float
cout << "f1="<< f1 << endl;
f2 = r1 + r2;//conversie implicita r1,r2->float
cout << "f2="<< f2 << endl;
f1 = r2 + 3.21;//conversie implicita r2 -> float -> double
cout << "f1="<< f1 << endl;
r1 = (NumarRational)f1;//conversie explicita
cout << "r1="<<r1<< endl;
r2 = r1;//conversie implicita la atribuire
cout << "r2="<<r2<< endl;
functie2(f2);//conversie implicita la transfer de parametri
}

Supraîncărcarea operatorilor new şi delete.

Operatorii unari new și delete pot fi supraîncărcați pentru a realiza operații specializate de
alocare/eliberare dinamica a memoriei.
Funcția operator new trebuie să primească un argument de tipul size_t care să precizeze
dimensiunea în octeți a obiectului alocat și să returneze un pointer de tip void conținând adresa
zonei alocate (sau sa returneze NULL daca eșuează).
Sintaxă:
void *operator new(size_t);

Important: Chiar dacă parametrul de tip size_t este obligatoriu, calculul dimensiunii obiectului în
cauză și generarea sa se face de către compilator. Tipul size_t este definit in stdlib.h.

2
Programare orientată pe obiecte Curs 3

După alocarea dinamică este apelat constructorul clasei pe care o realizează. Pointerul universal
(void*) returnat are ca valoare adresa de început a zonei de memorie alocată.

Funcția operator delete trebuie să primească ca prim parametru un pointer de tipul clasei în cauză
sau void, continând adresa obiectului de distrus, și un al doilea parametru, optional, de tip size_t.
Functia nu intoarce nici un rezultat.
Sintaxă:
void operator delete(void *, size_t);

Operatorul redefinit delete apelează întotdeauna destructorul clasei înainte de a elibera memoria
alocată dinamic.

Operatorii new și delete pot fi supraîncărcați global, astfel încat orice utilizare a lor să folosească
versiunea supraîncărcată, sau pot fi supraîncărcăți pentru o anumită clasă, și în această situație
funcțiile operator trebuie să fie membre statice ale clasei. Funcțiile membre ce supradefinesc acești
operatori, fiind de interes general pentru clasa respectivă, sunt automat statice, fără să fie nevoie
de utilizarea specificatorului static. Oricum operatorul new nu ar putea fi supraîncărcat prin funcție
nestatică deoarece el nu ar putea aparține unui obiect pe care tot el să-l și aloce. La întâlnirea unuia
dintre operatorii new sau delete pentru un anumit tip, compilatorul verifică mai întâi dacă acesta a
fost supraîncărcat pentru clasa respectivă. Dacă a fost supraîncărcat, se folosește versiunea
supraîncărcată a clasei; dacă nu, este apelat operatorul new sau delete global.

Exemplu
//Supraincarcarea operatorului new astfel incat sa initializeze memoria alocata la 0
void* Exemplu::operator new(size_t dim){
//Parametrul dim va contine numarul de octeti necesar pentru a memora
// obiectul pentru care se face alocarea
void *p = new char[dim];
return memset(p, 0, sizeof(Exemplu));
}

// Supraincarcarea operatorului new astfel incat sa initializeze memoria alocata la o


//valoare data ca parametru
void* Exemplu::operator new[](size_t dim, unsigned v){
int n = dim / sizeof(Exemplu);
Exemplu *p = ::new Exemplu [n];
for(int i=0; i<n; i++){
...
}
return p;
}

Important: Când operatorii new și delete sunt funcții membre ale unei clase, operatorii astfel
supraîncărcați vor fi folosiți numai pentru obictele clasei , iar când se utilizează operatorii new și
delete pentru alte tipuri de date se va apela new și delete impliciți din C++.

3
Programare orientată pe obiecte Curs 3

Exemplu
class Localitate{
int longitudine,latitudine;
public:
Localitate(int lg,int lt){longitudine=lg; latitudine=lt;}
void *operator new (size_t dim){
return malloc(dim);
}
void operator delete(void *p){
free(p);
}

};

Localitate *p1,*p2;
p1=new Localitate(20,99); //apel new supraincarcat
if(!p1){
cout<<"EROARE DE ALOCARE "<<endl;
}
delete p1; //apel delete supraincarcat
int *f=new int[10]; //apel new implicit din C++ nu cel supraincarcat
delete f; //apel delete implicit din C++ nu cel supraincarcat

Important: Când sunt supraîncărcați prin funcție independentă declarată global, definiția inițială a
operatorului new și delete nu mai este recunoscută pentru nici un tip de bază sau clasă , alocarea și
gestionarea memoriei revenindu-i în exclusivitate programatorului, prin urmare sunt ignorați new
și delete impliciti din C++.

Exemplu

class Localitate{
int longitudine,latitudine;
public:
Localitate(int lg,int lt){longitudine=lg; latitudine=lt;}
};

//new global (independent)


void *operator new (unsigned dim){
return malloc(dim);
}
//delete global(independent)
void operator delete(void *p){
free(p);
}
void main()
{
Localitate *p1,*p2;
p1=new Localitate(20,99);
if(!p1){
cout<<"EROARE DE ALOCARE "<<endl;
}
delete p1;
double *f=new double; //foloseste de asemenea new supraincarcat nu pe cel implicit
if(!f){
cout<<"EROARE DE ALOCARE "<<endl;
}
*f=10.111;
cout<<*f<<endl; //10.111
4
Programare orientată pe obiecte Curs 3

delete f; //foloseste delete supraincarcat nu pe cel implicit

Pentru cazul matricelor trebuie să supraîncărcăm operatorii new[] și delete[]. Pentru alocare este
apelată automat funcția constructor a fiecărui obiect al matricei. Operatorul delete[] apelează
repetat destructorul clasei pentru fiecare membru al matricei.

Sintaxă

void *operator new [ ](unsigned dim){


... //efectueaza alocarea
return pointer_la _memorie;
}
void operator delete [ ] (void *p){
free( p);
}

Exemplu

class Localitate{
int longitudine,latitudine;
public:
Localitate(int lg=0,int lt=0){longitudine=lg; latitudine=lt;}
void scrie(){cout<<longitudine<<" "<<longitudine<<endl;}
void *operator new (size_t dim){
return malloc(dim);
}
void operator delete(void *p){
free(p);
}
void *operator new [ ](size_t dim);
void operator delete [ ] (void *p);

};

void *Localitate::operator new[ ](size_t dim){


return malloc(dim);
}
void Localitate::operator delete[ ](void *p){
free(p);
}

void main(){
Localitate *p1,*p2;
p1=new Localitate(20,99);//aloca memorie pt.un obiect
if(!p1){
cout<<"EROARE DE ALOCARE "<<endl;
}
p2=new Localitate[3]; //aloca memorie pt.o matrice
if(!p2){
cout<<"EROARE DE ALOCARE "<<endl;
}
for(int i=0;i<3;i++) p2[i].scrie(); //0 0 0 0 0 0
delete p1; //elibereaza un obiect
5
Programare orientată pe obiecte Curs 3

delete [ ] p2; //elibereaza o matrice

Important: Nu este permisă mixarea celor două mecanisme de alocare și eliberare de memorie
(cu functii malloc( ) și free( ), respectiv cu operatorii new și delete ) adica alocare cu malloc( ) și
dezalocare cu delete( ) sau alocare cu new și dezalocare cu free( ) .

Redefinirea operatorilor new și delete conduce la o gestionare mult mai bună a alocărilor,
verificărilor și setărilor speciale (ex. întreruperea legăturilor către fișiere deschise în constructor;
alocarea în alte zone de memorie (un fișier de pe disc poate fi folosit ca memorie virtuală în acest
caz) dupa epuizarea memoriei speciale de tip heap).

Supraîncărcarea operatorului de indexare


Operatorul de indexare este un operator binar care are ca prim termen obiectul care se indexează,
iar ca al doilea termen indicele: obiect [ indice ] şi este interpretat ca: obiect.operator[ ](indice) .
Primul termen, transmis implicit prin this este obiectul asupra căruia se execută indexarea iar al
doilea reprezintă indicele, care poate fi de orice tip (spre deosebire de indicii predefiniţi).

Important: Acest operator nu poate fi supraîncărcat folosind funcții friend, funcţia operator de
indexare trebuind să fie funcţie membră nestatică. Valoarea întoarsă de funcţia operator este o
referinţă la elementul indexat din obiect.

Exemplu

class String{
int n;
char* s;
public:
. . .
char& operator[](int);
};
char& String:: operator[](int i){
if(i>=0 && i<n)
return s[i];
else
return 0;
};

Supraîncărcarea operatorului apel de funcţie

Aşa cum se definesc pointerii către obiecte de un anumit tip, se pot defini pointeri şi către funcţii
(conţin adrese ale funcţiilor), numele unei funcţii reprezentând un pointer către acea funcţie.

6
Programare orientată pe obiecte Curs 3

Exemplu

int suma(double x,double y){


return (int)(x+y);
}
int (*p)(double,double);

Prin această declaraţie înţelegem că s-a definit p un pointer către funcţii care au două argumente de tip
double şi returnează un întreg.

Sunt posibile declaraţiile:


p=suma;
p(7,9);
În exemplul următor vom defini o funcţie care primeşte drept argument o altă funcţie (prin intermediul
pointerilor).

Exemplu

int suma(double x,double y){


return (int)(x+y);
}
int produs(double x,double y){
return (int)(x*y);
}
int calcul(int (*p)(double,double),double x,double y){
return p(x,y);
}
void main(){
cout<<calcul(suma,7,9);
cout<<calcul(produs,7,9);
}

Supraîncărcarea operatorului () se utilizează la definirea functorilor (obiecte funcţii). Un functor


este un obiect pentru care se supraîncarcă operatorul apel de funcţie (operator()()) şi care se
comportă ca o funcţie. În mod uzual obiectele funcţii se folosesc în locul pointerilor la funcţii.
Funcţiile inline nu pot folosi ca parametri pointeri la funcţii (care sunt apelaţi indirect), ci obiecte
funcţii. Operatorul apel de funcţie asigură o sintaxă uniformă pentru obiectele care se comportă ca
şi funcţiile.
Având în vedere că apelul unei funcţii este o construcţie de forma:
nume_functie(lista_argumente);
desemnând operatorul binar () aplicat celor doi termeni nume_functie şi lista_argumente, numele
funcţiei poate fi înlocuit printr-un pointer la funcţie:
(*pf)(lista_argumente);
Funcţia operator redefinit apel de funcţie va avea aşadar 2 parametri: un obiect şi o listă de
argumente. Implementată ca funcţie membră nestatică, primul parametru este transmis implicit,
astfel că un apel de forma:
obiect(lista_argumente);
este interpretat ca:
obiect.operator()(lista_argumente);

7
Programare orientată pe obiecte Curs 3

Exemplu

class Dreapta{
double m,y0;
public:
Dreapta(double m=1, double y0=0){//Constructor
this->m=m;
this->y0=y0;
}
double operator()(double x){ return y0+m*x; }//functor
};

void main(){
Dreapta d(2, 3);
double y1=d(1.1);//y1=2*1.1=2.2
double y2=d(1.5);//y2=2*1.5+3=6
}

class Fibo{
long x0, x1;
public:
Fibo():x0(0),x1(1){}; //constructor
long operator()(int n); //supraincarcare operator functie
};
long Fibo::operator ()(int n){
for(int i=2; i<=n; i++){
long z=x0+x1;
x0=x1;
x1=z;
};
return x0;
};

Important: Pentru utilizarea altor operatori (ex. +,-,*,/,++, -- etc. ) se pot urmări exemplele
prezentate în cadrul laboratoarelor.

Exemplu pentru crearea clasei Matrice:

Fișierul Header.h

#pragma once
#include <iostream>
using namespace std;

class Matrice{
int nrL,nrC,**el;
public:
//constructori
Matrice();//implicit
Matrice(int, int, int**);//de initializare
Matrice(int, int, int*);//de initializare pentru cazul int[][]
Matrice(const Matrice&);//de copiere
8
Programare orientată pe obiecte Curs 3

//destructor
~Matrice();
//metode
int daNrL(){ return nrL; }//inline
int daNrC(){ return nrC; }//inline
size_t dimensiune();//size_t poate fi gandit ca unsigned int
//supraincarcare operatori
Matrice& operator=(const Matrice&);
int* operator[](int);
friend ostream& operator<<(ostream&, const Matrice&);
};

Fișierul Source.cpp
#include "Header.h"

Matrice::Matrice(){
nrL=nrC = 0;
el = 0;
cout << "S-a apelat constructorul implicit!" << endl;
}
Matrice::Matrice(int nrL, int nrC, int** el){
this->nrL = nrL;
this->nrC = nrC;
this->el = new int*[nrL];
for (int i = 0; i < nrL; i++){
this->el[i] = new int[nrC];
for (int j = 0; j < nrC; j++)this->el[i][j] = el[i][j];
}
cout << "S-a apelat constructorul de initializare!" << endl;
}
Matrice::Matrice(int nrL, int nrC, int* el){
this->nrL = nrL;
this->nrC = nrC;
this->el = new int*[nrL];
for (int i = 0; i < nrL; i++){
this->el[i] = new int[nrC];
for (int j = 0; j < nrC; j++)this->el[i][j] = el[i*nrC+j];
}
cout << "S-a apelat constructorul de initializare!" << endl;
}
Matrice::Matrice(const Matrice& M){
nrL = M.nrL;
nrC = M.nrC;
el = new int*[M.nrL];
for (int i = 0; i < M.nrL; i++){
el[i] = new int[nrC];
for (int j = 0; j < M.nrC; j++)el[i][j] = M.el[i][j];
}
cout << "S-a apelat constructorul de copiere!" << endl;
}
Matrice::~Matrice(){
if (el){
for (int i = 0; i < nrL; i++){
if (el[i])delete [] el[i];
}
delete[] el;
}
cout << "S-a apelat destructorul!" << endl;
}

9
Programare orientată pe obiecte Curs 3

size_t Matrice::dimensiune(){
return nrL*nrC*sizeof(int);
}
Matrice& Matrice::operator=(const Matrice& M){
if (this != &M){//important
if (el){
for (int i = 0; i < nrL; i++){
if (el[i])delete[] el[i];
}
delete[] el;
}
nrL = M.nrL;
nrC = M.nrC;
el = new int*[M.nrL];
for (int i = 0; i < M.nrL; i++){
el[i] = new int[M.nrC];
for (int j = 0; j < M.nrC; j++)el[i][j] = M.el[i][j];
}
}
cout << "S-a folosit operatorul '=' supraincarcat!" << endl;
return *this;
}
int* Matrice::operator[](int index){
if(index>=0 && index<nrL)return el[index];
return 0;
}
ostream& operator<<(ostream& out, const Matrice& M){
for (int i = 0; i < M.nrL; i++){
for (int j = 0; j < M.nrC; j++)out << M.el[i][j] << (j<(M.nrC-1)?" ":"\r\n");
}
return out;
}
int main(){
Matrice M;
int a[][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
Matrice N(2, 3, *a);
Matrice P = M;
N[0][1] = 10;
P = N;
cout << P <<endl<< " dimensiune (octeti): " << P.dimensiune() << endl;
}

S-a apelat constructorul implicit!


S-a apelat constructorul de initializare!
S-a apelat constructorul de copiere!
S-a folosit operatorul '=' supraincarcat!
1 10 3
456

dimensiune (octeti): 24
S-a apelat destructorul!
S-a apelat destructorul!
S-a apelat destructorul!
Press any key to continue . . .

10

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