Elementi di programmazione ad oggetti a. a. 2009/2010 Corso di Laurea Magistrale in Ingegneria Elettronica Docente: Mauro Mazzieri, Dipartimento di Ingegneria Informatica, Gestionale e dell’Automazione Lezione 5 Sovraccarico degli operatori Sovraccarico degli operatori Il meccanismo del sovraccarico delle funzioni vale anche per gli operatori predefiniti Gli operatori non sono altro che funzioni in forma infissa a *= 5 equivale a a.operator*=(5) Il sovraccarico consente di ridefinire il comportamento degli operatori predefiniti Non è possibile creare nuovi operatori Operatori ridefinibili È possibile ridefinire gli operatori + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- ->* , -> [] () new new[] delete delete[] Non è possibile ridefinire gli operatori :: . .* ?: sizeof typeid Definire un operatore Affinché un operatore possa operare sui membri di una classe deve essere ridefinito Fanno eccezione Operatore di indirizzo & Operatore di assegnamento = Restituisce l’indirizzo dell’oggetto Esegue una copia membro a membro Entrambi possono comunque essere ridefiniti Non è possibile ridefinire operatori che operano sui tipi predefiniti Non è possibile cambiare l’arità o l’associatività di un operatore Associatività degli operatori Per tutti gli operatori l’associatività è da sinistra a destra Tranne = per cui l’associatività è da destra a sinistra z+y+z viene valutata come (x+y)+z x=y=z viene valutata come x=(y=z) Assegnamento membro a membro L’assegnamento di un oggetto ad un altro oggetto viene gestito di default come una copia membro a membro unOggetto = unAltroOggetto; Non viene utilizzato il costruttore per copia ma un operatore implicito di assegnamento Ad ogni membro non statico di unOggetto viene assegnato il corrispondente valore del membro di unAltroOggetto Dopo la copia, i due oggetti hanno gli stessi dati, ma rimangono due oggetti distinti Definizione di un operatore Il sovraccarico di un operatore si esegue definendo una funzione il cui nome è operator<operatore> class Complex { double re, im; public: Complex& operator+(Complex c) { re += c.re; im += c.im; return *this; } }; Sovraccarico di un operatore unario Un operatore unario può essere definito come funzione membro (senza argomenti) o funzione non membro (con un argomento) L’argomento è un oggetto della classe o un suo riferimento È necessario che sia un riferimento quando si deve modificare l’oggetto Complex& Complex::operator-() { re = -re; im = -im; return *this; } Sovraccarico di un operatore binario Un operatore binario può essere definito come funzione membro (con un argomento) o funzione non membro (con due argomenti) Gli argomenti sono oggetti della classe o loro riferimenti È necessario che sia un riferimento quando si deve modificare l’oggetto Quando l’operatore viene definito tramite una funzione membro, l’oggetto alla sinistra dell’operatore invoca l’operatore e quello sulla destra viene passato come argomento Complex& Complex::operator-(Complex& c) { re -= c.re; im -= c.im; return *this; } Valore di ritorno di un operatore Un operatore può restituire una copia dell’oggetto risultato o un riferimento Complex& Complex::operator-(Complex& c) { re -= c.re; im -= c.im; return *this; } Complex Complex::operator-(Complex& c) { re -= c.re; im -= c.im; return *this; } Operatori friend Un operatore che ha bisogno di accedere ai dati privati di un oggetto può essere definito come friend invece che come membro Le funzioni friend non hanno un puntatore this, per cui bisogna passare esplicitamente entrambi i parametri Gli operatori friend sono utili in quei casi in cui l’operazione riguarda oggetti di tipi diversi Es. * vettore-matrice, - magazzino-ordine Sovraccarico di << e >> Gli operatori << e >> (inserimento ed estrazione da uno stream) vanno ridefiniti tramite operatori friend per poterli usare su oggetti definiti dall’utente class Complex { // … friend std::ostream& operator<<(std::ostream&, const Complex); friend std::istream& operator>>(std::istream&, Complex&); }; std::ostream& operator<<(std::ostream& o, const Complex c) { return o << "(" << c.re << ", " << c.im << ")"; } std::istream& operator>>(std::istream& i, Complex& c) { i >> c.re >> c.im; return i; } Operatori new e delete new e delete sono degli operatori, ed in quanto tali possono essere ridefiniti void* operator new(size_t size) { // Esegue l'allocazione. // Restituisce un puntatore alla memoria allocata, oppure 0 } void operator delete(void* p) { // rilascia la memoria } Quando viene chiamato new, viene automaticamente chiamato il costruttore appropriato Quando viene chiamato delete, viene automaticamente chiamato il distruttore new e delete su array void* operator new[](size_t size) { // Esegue l'allocazione. // Restituisce un puntatore alla memoria allocata, oppure 0 } void operator delete[](void* p) { // Rilascia la memoria. // Il distruttore di ogni elemento viene chiamato automaticamente } Operatore [] Esistono due versioni dell’operatore [] const e non const Passare ad una funzione un array const e poi utilizzare l’operatore per accedere al contenuto modificandolo sarebbe un problema Il compilatore non consente l’uso di funzioni membro non const con oggetti const