Corso di Programmazione ad Oggetti
Funzioni e classi “friend”. Overloading di operatori
a.a. 2008/2009
Claudio De Stefano
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
1
Le funzioni friend
■
Nella definzione di una classe è possibile elencare quali funzioni, esterne alla
classe, possono accedere ai memebri privati della classe. Queste funzioni sono
dette friend (amiche).
■
Per dichiarare una funzione friend è necessario includere il prototipo nella classe,
facendola precedere dalla parola chiave friend:
class Myclass {
.
.
.
friend Tipo funz(...);
};
■
Le funzioni friend sono particolarmente utili quando due o più classi contengono
membri correlati con altre parti del programma.
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
2
Le funzioni friend: Esempio
class Myclass {
int a,b;
public:
void set_ab(int i, int j)
friend int sum(Myclass x);
};
main()
{
Myclass n;
n.set_ab(2, 4);
cout<<sum(n);
}
Myclass::set_ab(int i, int j)
{
a = i;
b = j;
}
// sum non è membro della classe
int sum(Myclass x)
{
return x.a + x.b;
}
La funzione Sum non è
membro della classe, ma
essendo
friend
può
accedere ai suoi membri
privati
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
3
Le classi friend
■
In C++ è anche possibile rendere un'intera classe friend di un'altra classe.
■
In tal caso tutte le funzioni della classe dichiarata friend avranno accesso ai
membri privati della classe.
■
La dichiarazione di classe friend è del tipo:
class C1 {
.
.
.
friend class C2;
};
■
Osserviamo che le funzioni membro di C2 possono accedere ai membri di C1 e
non viceversa.
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
4
Overloading degli operatori
■
Il meccanismo di overloading degli operatori consente di attribuire ulteriori
significati agli operatori del linguaggio.
■
In C++ è possibile eseguire l'overloading della maggior parte degli operatori, per
consentire loro di svolgere operazioni specifiche rispetto a determinate classi.
■
l'overlading di un operatore, estende l'insieme dei tipi al quale esso può essere
applicato, lasciando invariato il suo uso originale.
■
L'overloading degli operatori consente di integrare meglio le nuove classi create
dall'utente nell'ambiente di programmazione.
■
L'overloading degli operatori è alla base delle operazioni di I/O del C++.
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
5
Overloading degli operatori
■
L'overlaoding degli operatori viene realizzato per mezzo delle funzioni operator.
■
Un funzione operator definisce le specifiche operazioni che dovranno essere
svolte dall'operatore sovraccaricato (overloaded) rispetto alla classe specificata.
■
Ci sono due modi per sovraccaricare un operatore.
– Tramite funzioni membro della classe;
– Tramite funzioni esterne che però devono esseredefinite friend per la classe.
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
6
Funzioni operator membro
■
Le funzioni operator membro di una classe hanno la seguente froma generale:
Tipo nome-classe::operator#(Tipo1 arg1, Tipo2 arg2, ...)
{
// istruzioni
.
.
.
}
dove # è il simbolo dell'operatore da sovraccaricare
■
Nella maggior parte dei casi le funzioni operator restituiscono un oggetto della
classe su cui operano, ma in generale possono restituire qualsiasi tipo valido.
■
Quando si esegue l'overloading di un operatore binario, la funzione operator ha
un solo argomento, mentre se l'operatore è unario la funzione non ha argomenti.
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
7
Overloading degli operatori: le regole da seguire
■
È possibile modificare il significato di un operatore esistente;
■
Non è possibile creare nuovi operatori e non e’ opportuno ridefinire la semantica
di un operatore applicato a tipi predefiniti;
■
Non è possibile cambiare precedenza, associatività e “arity” (numero di operandi);
■
non è possibile usare argomenti di default.
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
8
Il comportamento del compilatore
■
L'overloading degli operatori viene realizzato dal compilatore.
■
Il compilatore, quando incontra un’espressione della forma
x op y
verifica nell’ordine:
– se nella classe X dell’oggetto x vi è una funzione membro della forma
operator op(Y), dove Y indica il tipo dell’oggetto y;
– se vi è una funzione non membro della forma “operator op(X, Y)”
se una delle due condizioni è verificata provvede ad invocare la funzione
individuata, altrimenti segnala un errore.
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
9
Funzioni operator membro: esempio
class Complex {
float re;
float im;
public:
Complex(float r=0.0, float i=0.0) {re=r; im=i;};
float getRe() const {return re; };
float getIm() const {return im; };
void setRe(float r) {re=r; };
void setIm(float i) {im=i; };
void show();
Complex operator+(Complex op2);
};
void Complex::show()
{
cout<<endl<<”re: ”<<re<<” im: ”<<im;
}
Complex Complex::operator+(Complex op2)
{
Complex tmp;
tmp.re = re + op2.re;
tmp.im = im + op2.im;
return tmp;
}
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
10
Funzioni operator membro: esempio
main()
{
Complex c1, c2(1, 1), c3(4,5);
c1.show();
c2.show();
c3.show();
output
>>
>>
>>
>>
c1 = c2 + c3;
c1.show();
}
re:
re:
re:
re:
0
1
4
5
im:
im:
im:
im:
0
1
5
6
NOTA
c1 = c2 + c3;
diventa
c1 = c2.operator+(c3)
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
11
Funzioni operator: alcune osservazioni
■
quando si esegue l'oveloading di un operatore binario è l'oggetto di sinistra a
generare la chiamata alla funzione operator.
■
l'operatore di assegnamento può essere usato solo perchè operator+ restituisce
un oggetto della classe Complex.
■
La funzione operator+ NON modifica gli operandi. In generale, è opportuno
definire sempre delle funzioni operator che non modificano gli operandi, in
analogia con gli operatori standard.
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
12
Altri operatori
Complex Complex::operator-(Complex op2)
{
Complex tmp;
tmp.re = re - op2.re;
tmp.im = im - op2.im;
return tmp;
}
Complex Complex::operator++()
{
re++;
im++;
return *this
}
poiché è l'oggetto di sinistra a
generare la chiamata a
operator- i dati di op2 devono
essere sottratti a quelli dell'oggetto
chiamante, al fine di
conservare la semantica della
sottrazione
In questo modo si definise
l'operatore prefisso di incremento
l'operatore non ha parametri
perchè è un operatore unario.
In questo caso viene modificato
l'operando.
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
13
Altri operatori: esempio d'uso
main()
{
Complex c1(1,2), c2(3,5), c3(9,9);
output
c1.show();
c2.show();
++c1;
c1.show();
c2 = ++c1;
c1.show();
c2.show();
c1 = c2 – c3;
c1.show();
>> re: 1 im: 2
>> re: 3 im: 5
>> re: 2 im: 3
>> re: 3 im: 4
>> re: 3 im: 4
>> re: -6 im: -5
}
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
14
L'operatore di assegnamento
■
L'operatore di assegnamento viene usato in tutte le espressioni in cui è presente il
segno ‘=‘.
■
L'assegnamento di default effettua un copia bit a bit. Pertanto l'overloading è
necessario per le classi che hanno un'estensione dinamica.
■
Deve essere una funzione membro. Ha la forma:
const C& operator=(const C&)
■
L’operatore di assegnamento deve consentire assegnazioni multiple a=b=c….
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
15
L'operatore di assegnamento: esempio
Complex Complex::operator=(Complex op2)
{
re = op2.re;
im = op2.im;
return *this;
}
NOTA
l'operatore restituisce *this ovvero l'oggetto che ha generato la chiamata. Questo
accorgimento rende possibile assegnamenti multipli del tipo:
c1 = c2 = c3;
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
16
Operatori di incremento postfissi
■
Per definire operatori di incremento(decremento) postfissi bisogna usare la forma:
nome_classe nome_classe::operator++(int x)
{
// istruzioni
}
■
Nell'esempio precedente avremo:
Complex Complex::operator++(int x)
{
re++;
im++;
return *this;
}
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
17
Overloading delle forme abbreviate
■
È possibile effettuare anche l'overloading delle forme abbreviate degli operatori,
tipo: +=, *=, -= ecc.
Esempio
Complex Complex::operator+=(Complex op2)
{
main
{
c1, c2(1,1);
im += op2.im;
re += op2.re;
.
.
.
c1 +=c2;
return *this;
}
}
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
18
Overloading tramite funzioni friend
■
Come detto in precedenza l'ooveloading può essere eseguito anche per mezzo di
funzioni non membro, ma definite friend per la classe in esame.
■
Nel caso delle funzioni friend il numero di argomenti coincide con il numero di
operandi.
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
19
Overloading tramite funzioni friend: esempio
class Complex {
.
.
.
friend Complex operator+(Complex op1, Complex op2);
friend Complex operator++(Complex op);
};
Complex operator+(Complex op1, Complex op2)
{
Complex tmp;
tmp.re = op1.re + op2.re;
tmp.im = op1.im + op2.im;
return tmp;
}
Complex operator++(Complex &op)
{
++op.re;
++op.im;
return *this;
}
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
20
Confronto tra operatori membro e funzioni friend
■
Le funzioni friend offrono in generale maggiore flessibilità. Consideriamo il caso
in cui alla classe Complex sia stata definita un'ulteriore funzione membro
operator+ che prende come operando un intero:
class Complex {
.
.
.
Complex operator +(float val);
};
Complex operator+(float val)
{
Complex tmp;
tmp.re = re +val;
tmp.im = im +val;
return tmp;
}
Main()
{
Complex c1, c2;
c2 = c1 + 100; // OK
c1 = 100 + c1; // ERRORE!
}
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
21
Confronto tra operatori membro e funzioni friend
■
È possibile rimediare al problema visto in precedenza in questo modo:
class Complex {
.
.
.
friend Complex operator +(Complex op, float val);
friend Complex operator +(float val, Complex op);
};
Complex operator+(Complex op, float val)
{
Complex tmp;
tmp.re = op.re +val;
tmp.im = op.im +val;
Main()
{
Complex c1, c2;
return tmp;
}
Complex operator+(float val, Complex op)
{
Complex tmp;
c2 = c1 + 100; // OK
c1 = 100 + c1; // OK
}
tmp.re = op.re +val;
tmp.im = op.im +val;
return tmp;
}
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
22
Overloading degli operatori di stream
■
se si vuole permettere il flusso su stream della propria classe , e` necessario
ricorrere ad una funzione ordinaria, perche` il primo argomento degli operatori di
shift non e` una istanza della classe
class Complex {
public:
.
.
.
private:
float Re, Im;
friend ostream& operator<<(ostream& os,Complex C);
friend istream& operator>>(istream& in,Complex& C);
};
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
23
Overloading degli operatori di stream
ostream& operator<<(ostream& os, Complex op)
{
os << op.re;
if (op.im > 0)
os << ” + ”;
else if (op.im < 0)
o s<< ” - ”;
os << op.im;
return os;
}
istream& operator>>(istream& in, Complex &op)
{
Complex tmp;
in >> temp.re;
in >> temp.im;
op = temp;
return in;
}
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
24
Overloading degli operatori di stream
void main()
{
Complex c1, c2, c3;
cout << "\n inserisci il primo operando: ";
cin >> c1;
cout << "\n inserisci il secondo operando: ";
cin >> c2;
c3 = c1 + c2;
cout << c3;
cout << "\n";
}
cout << c3;
Equivale
cin >> c1;
Equivale
operator<<(cout,c3);
operator>>(cin,c3);
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
25
Un esempio oveloading di operatori: la classe string
■
La libreria standard del C++ mette a disposizione la classe standard string per la
gestione delle stringhe.
■
Proprio grazie all'oveloading degli operatori, questa classe rende molto semplice
l'uso delle stringhe.
■
Un altro aspetto importante di questa classe è che la lunghezza della stringa non
deve essere dichiarata a priori.
■
Ecco l'elenco degli operatori sovraccaricati delle corrispondenti funzioni C della
libreria string.h
=
==, <, >, <=, >=, !=
<<, >>
+, +=
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
26
La classe string: esempi d'uso
#include <iostream>
#include <string>
using namespace std;
main()
{
string s1(”alfa”), s2(“beta”), s3(“omega”), s4, s5;
char *C_str = “ciao”;
s4 = s1;
// assegnamento tra stringhe
s4 = s1 + s2
s4+= s3;
// questa è una stringa stile C
// assegna a s4 il concatenamento di s1 e s2
// concatena s4 a s3
s4 = s1 + “-” + s2; //
}
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
27
operatori di concatenamento e assegnazione
■
Usando gli operatori sovraccariricati =, + e += le operazioni di assegnamento e
concatenazione diventano immediate:
s4 = s1;
// assegnamento tra stringhe
s4 = s1 + s2
s4+= s3;
// assegna a s4 il concatenamento di s1 e s2
// concatena s4 a s3
s4 = s1 + “-” + s2; //
s1 = “questa è una stringa chiusa dal carattere nullo”;
s3 = s1 + “pippo”;
s3 = “pippo” + s1; // ERRORE!
s4 = C_str;
// si possono usare stringhe classiche.
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
28
Operatori di confronto
■
Anche le operazioni di confronto sono immediate:
if (s1 > s2)
cout<<endl<<”s2 precede s1”;
if (s1 == “s2”)
cout<<endl<<”le stringhe s1 e s2 sono uguali”;
■
Così come le opreazioni di I/O.
cout<<endl<<”introdurre una stringa: ”;
cin>> s4;
cout<<endl<<s5;
}
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
29
Alcune osservazioni
■
Le dimensioni delle stringhe non sono specificate.
■
Gli oggetti di tipo string vengono dimensionati automaticamente per contenere la
stringa fornita.
■
Pertanto quando si assegnano o concatenano stringhe le dimensioni della stringa
destinazione cresceranno automaticamente per contenere la nuova stringa.
■
Questa gestione dinamica evita tutti gli errori delle gestione delle stringhe del C
dovuti al superamento degli array.
Claudio De Stefano – Programmazione ad Oggetti - a.a. 2008/2009
30