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