Lezione5 - Dipartimento di Ingegneria dell`Informazione
Transcript
Lezione5 - Dipartimento di Ingegneria dell`Informazione
Lezione 5 Overload di operatore Overloading degli operatori • In C++ si creano e manipolano dei tipi di dato creati dall utente (il programmatore) • abbiamo già visto come il C++ mette a disposizione degli strumenti per creare e distruggere gli oggetti creati con tipi definiti dall’utente • sarebbe utile poter estendere questo parallelismo e poter operare su i tipi definiti dall’utente così come si opera su i tipi primitivi (int, char, etc) • Questo permetterebbe la manipolazione degli oggetti con un formalismo intuitivo e conciso (si pensi alla somma di due vettori) 1 Overloading di operatore • Il C++ ha il meccanismo di overload (sovraccaricamento) per le funzioni • gli operatori non sono altro che un modo conciso per scrivere delle funzioni • Es. a+b è in realtà funzione_somma(a,b) • In C++ gli operatori si possono indicare esplicitamente e sovraccaricarli, rendendoli così specifici per i tipi utente • Nota: non è possibile creare operatori nuovi, cioè inventarsi un operatore come $ o £ o ¬ Overloading degli operatori • L’operazione di definire un comportamento per un operatore va sotto il nome di overloading dell’operatore (sovraccarico dell’operatore) • Per effettuare l’overloading di operatore si procede come nel caso di funzioni (membro o non) • Il nome della funzione è composto dalla parola chiave operator seguita dal simbolo dell’operatore – Es. per sovraccaricare il + si ha operator+ MyClass MyClass::operator+(const MyClass & obj){ //definizione operazione } 2 Operatori in C++ • Operatori per i quali è ammesso l’overload: + - * / % ^ & | ~ ! = < > , [ ] ( ) new delete += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ --> ->* • Operatori per i quali è ammesso l’overload: . :: .* ?: sizeof Overloading di Operatori • Perché un operatore possa operare su gli oggetti di una classe deve necessariamente essere ridefinito • fanno eccezione: – operatore di assegnamento = il suo comportamento di default è di eseguire una copia di membro a membro dei dati di una classe – operatore di indirizzo & il suo comportamento di default è di restituire l’indirizzo di memoria dell’oggetto • tuttavia anche = e & possono essere ridefiniti se serve 3 Overloading di Operatore • gli operatori devono essere esplicitamente overloaded non ci si deve aspettare che se si è implementato l’overload per + automaticamente sia disponibile += • per assicurare consistenza si usi operatori simili per ridefinire altri operatori – usare + per definire +=, usare == per definire != Overloading di operatore • Il senso di una ridefinizione di un operatore dovrebbe essere quanto più vicino al senso che l’operatore ha per i tipi predefiniti • anche se è possibile definire in modo arbitrario ciò che fa un operatore si crea solo confusione se un operatore ha una semantica inaspettata – ad es si può sovraccaricare l’operatore + per eseguire una sottrazione ma il codice diventa fuorviante e di difficile interpretazione 4 Overloading di operatore • non si può ridefinire un operatore che opera su i tipi predefiniti (int, char, float, etc) • non si può cambiare l’arità di un operatore né la sua associatività • l’arità è il numero di parametri che l’operatore prende – a+b ha arità 2 infatti è operator+(int a, int b) – a++ ha arità 1 infatti è operator++(int &a) Overloading di operatore unario • Un operatore unario può essere ridefinito come funzione membro senza argomenti • oppure come funzione non membro con un argomento • l’argomento deve essere un oggetto o un riferimento ad un oggetto di una classe • il passaggio di un riferimento è necessario nel caso di funzioni non membro quando l’operatore deve modificare l’oggetto che invoca l’operatore stesso • Es. ridefinendo l’operatore ++ si ha che x++ viene trattato come x.operator++( ) 5 Overloading di operatore unario class MyInt{ friend MyInt& operator--(MyInt &); public: MyInt(int usr_dat=0){dat=usr_dat;} MyInt& operator++(){ dat++; return *this; } bool operator!()const{ if(dat==0) return true; else return false; } private: int dat; }; MyInt& operator--(MyInt & obj){return (obj.dat--);} Overloading di operatore binario • Un operatore binario può essere ridefinito come funzione membro con un argomento • oppure come funzione non membro con due argomenti • l’argomento deve essere un oggetto o un riferimento ad un oggetto di una classe • Es. ridefinendo l’operatore += si ha che x+=y viene trattato come x.operator+=(y) 6 Overloading di operatore binario • class MyInt{ friend MyInt operator-=(MyInt &, const MyInt &); public: MyInt(int usr_dat=0){dat=usr_dat;} MyInt operator+(const MyInt &obj){ MyInt temp; temp.dat=(*this).dat+obj.dat; return temp; } MyInt operator+=(const MyInt &obj){ (*this).dat += obj.dat; //oppure dat += obj.dat; return *this; } int dat; }; MyInt operator-=(MyInt & a, const MyInt & b){ a.dat-=b.dat; return a; } Cosa restituiscono gli operatori • In linea di principio si può restituire: – copia dell’oggetto risultato – riferimento all’oggetto risultato • Es: MyInt operator+(const MyInt &obj){ (*this).dat=(*this).dat+obj.dat; return *this; } MyInt& operator++(){ (*this).dat++; return *this; } • Dipende dalla semantica dell’operatore 7 Nota • Attenzione! MAI restituire un alias ad un oggetto temporaneo! MyInt & operator-=(MyInt & a, const MyInt & b){ MyInt temp; temp.a=a.dat-b.dat; return temp; } • E’ un errore! Una espressione come c=d+(a-b) potrebbe avere un risultato imprevedibile • Nessuno garantisce infatti che la cella di memoria dove risiede l’oggetto temp non sia sovrascritta prima di eseguire la somma! Operatori come funzioni friend • Può capitare che sia utile che un operatore sia una funzione non-membro di una classe • Es. se si è definito l’operatore + per una classe ObjClass allora: Obj+100; //OK • tuttavia: 100+Obj; //ERROR • infatti nel secondo caso stiamo utilizzando un operatore del tipo predefinito int • cioè è l’oggetto 100 a chiamare l’operatore + e non Obj 8 Operatori come funzioni friend class MyInt{ friend MyInt operator+(const MyInt &obj1, int); friend MyInt operator+(int, const MyInt &obj1); public: MyInt(int usr_dat=0){dat=usr_dat;} private: int dat; }; MyInt operator+(const MyInt &obj1, int x){ MyInt temp; temp.dat = obj1.dat + x; return temp; } MyInt operator+(int x, const MyInt &obj1){ MyInt temp; temp.dat = x + obj1.dat; return temp; } Overloading di operatore • Nota: non stiamo ridefinendo l’operatore + per gli interi con la seguente espressione: friend MyInt operator+(int, const MyInt &obj1); • infatti la ridefinizione non permessa è: int operator+(int, int); 9 Conversione tra tipi diversi • In generale le operazioni si svolgono fra dati con lo stesso tipo (es. somme fra interi che restituiscono interi) • può però sorgere la necessità di convertire dati da un tipo all’altro Conversione tra tipi diversi • un caso tipico è quello appena presentato in cui sarebbe necessario ridefinire esplicitamente ogni operatore per le varie combinazioni di tipi in ingresso: • operator+(int, MyInt obj1); • operator+(MyInt obj1, int); • operator+(MyInt obj1, MyInt obj2); • una alternativa è quella di utilizzare delle conversioni • tramite la conversione si avrebbe: • 3+o; //operator+(MyInt(int), MyInt obj1); • o+3; //operator+(MyInt obj1, MyInt(int)); • o1+o2; //operator+(MyInt obj1, MyInt obj2); 10 Costruttore di conversione • Per implementare la conversione si utilizza semplicemente un costruttore che prende in ingresso il tipo desiderato • tale costruttore viene indicato con il nome di costruttore di conversione • la sintassi è quella ordinaria di un costruttore: NomeClasse(tipo var){//definizione} Esempio #ifndef HUGEINT1_H #define HUGEINT1_H #include < iostream> using std::ostream; class HugeInt { friend ostream &operator <<( ostream &, const HugeInt & ); public: HugeInt ( long = 0 ); HugeInt ( const char * ); // conversion/default constructor // conversion constructor HugeInt operator+( const HugeInt & ); // add another HugeInt HugeInt operator+( int ); // add an int HugeInt operator+( const char * ); // add an int in a char * private: short integer[ 30 ]; }; #endif 11 Esempio #include <cstring> #include "hugeint1.h" // Conversion constructor HugeInt::HugeInt( long val ) { int i; for ( i = 0; i <= 29; i++ ) integer[ i ] = 0; // initialize array to zero for ( i = 29; val != 0 && i >= 0; i-- ) { integer[ i ] = val % 10; val /= 10; } } Esempio HugeInt::HugeInt( const char *string ) { int i, j; for ( i = 0; i <= 29; i++ ) integer[ i ] = 0; for ( i = 30 - strlen( string ), j = 0; i <= 29; i++, j++ ) if ( isdigit( string[ j ] ) ) integer[ i ] = string[ j ] - '0'; } 12 Esempio // Addition HugeInt HugeInt ::operator+( const HugeInt &op2 ) { HugeInt temp ; int carry = 0; for ( int i = 29; i >= 0; i-- ) { temp.integer[ i ] = integer[ i ] + op2.integer[ i ] + carry; if ( temp .integer[ i ] > 9 ) { temp.integer[ i ] %= 10; carry = 1; } else carry = 0; } return temp; } Esempio // Addition HugeInt HugeInt ::operator+( int op2 ) { return *this + HugeInt ( op2 ); } // Addition HugeInt HugeInt ::operator+( const char *op2 ) { return *this + HugeInt ( op2 ); } ostream& operator <<( ostream &output, const HugeInt &num ) { int i; for ( i = 0; ( num.integer [ i ] == 0 ) && ( i <= 29 ); i++ ) ; // skip leading zeros if ( i == 30 ) output << 0; else for ( ; i <= 29; i++ ) output << num .integer[ i ]; return output; } 13 Esempio // Test driver for HugeInt class #include < iostream> #include "hugeint1.h" int main () { HugeInt n1( 7654321 ), n2( 7891234 ), n3( "99999999999999999999999999999" ), n4( "1" ), n5; cout << "n1 is " << n1 << "\nn2 is " << n2 << "\nn3 is " << n3 << "\nn4 is " << n4 << "\nn5 is " << n5 << "\n\n"; n5 = n1 + n2; cout << n1 << " + " << n2 << " = " << n5 << "\n\n"; cout << n3 << " + " << n4 << "\n= " << ( n3 + n4 ) << "\n\n"; n5 = n1 + 9; cout << n1 << " + " << 9 << " = " << n5 << "\n\n"; n5 = n2 + "10000"; cout << n2 << " + " << "10000" << " = " << n5 << endl ; return 0; } Esercizio • Scrivere la definizione degli operatori di confronto per la classe HugeInt 14 Operatore di conversione • Un costruttore di conversione non può però – specificare una conversione da un tipo utente ad un tipo base (i tipi base non sono oggetti e non se ne possono quindi sovraccaricare i metodi) – specificare una conversione da un tipo utente A ad un altro tipo utente B senza modificare B (potremmo non avere accesso alla classe B) • per questi casi si utilizza un operatore di conversione • Sintassi: Classe::operator tipo() const{ //definizione } • Nota: non si indica il tipo di ritorno perché è ovvio dalla conversione • Esempio: HugeInt::operator int() const{ //definizione } Note su l’operatore di conversione • attenzione quando il passaggio da un tipo ad un altro implica perdita di informazione – Es da HugeInt a int • attenzione alle ambiguità quando si usano operatori predefiniti – Es: HugeInt a; int b; a+b – quale tra?: • operator+(a,HugeInt(b)) • int(a)+b • è bene avere uno solo dei due meccanismi ma non entrambi 15 Overloading degli operatori >> e << • Operatori utili sono quelli di inserzione ed estrazione dallo stream • vanno ridefiniti come funzioni friend per poterli usare come cin>>obj e cout<<obj • l’istruzione cin>>obj (cout<<obj) invoca la chiamata di funzione operator>>(cin, obj) (operator<<(cout,obj)) Esempio #include <iostream> #include <iomanip> class PhoneNumber { friend ostream &operator<<( ostream&, const PhoneNumber & ); friend istream &operator>>( istream&, PhoneNumber & ); private: char areaCode[ 4 ]; char exchange[ 4 ]; char line[ 5 ]; }; // 3-digit area code and null // 3-digit exchange and null // 4-digit line and null // Overloaded stream-insertion operator (cannot be // a member function if we would like to invoke it with // cout << somePhoneNumber;). ostream &operator<<( ostream &output, const PhoneNumber &num ) { output << "(" << num.areaCode << ") " << num.exchange << "-" << num.line; return output; // enables cout << a << b << c; } 16 Esempio istream &operator>>( istream &input, PhoneNumber &num ) { input.ignore(); // skip ( input >> setw( 4 ) >> num.areaCode; // input area code input.ignore( 2 ); // skip ) and space input >> setw( 4 ) >> num.exchange; // input exchange input.ignore(); // skip dash (-) input >> setw( 5 ) >> num.line; // input line return input; // enables cin >> a >> b >> c; } int main() { PhoneNumber phone; // create object phone cout << "Enter phone number in the form (123) 456-7890:\n"; // cin >> phone invokes operator>> function by // issuing the call operator>>( cin, phone ). cin >> phone; // cout << phone invokes operator<< function by // issuing the call operator<<( cout, phone ). cout << "The phone number entered was: " << phone << endl; return 0; } Esempio: classe array • Realizziamo una classe per vettori di interi • le specifiche includono: – – – – controllo sugli indici assegnazione unitaria operatori di egualianza, diversità unitari stampa e input unitari tramite << e >> 17 // Simple class Array (for integers ) #ifndef ARRAY1_H #define ARRAY1_H #include <iostream> class Array { friend ostream &operator<<( ostream &, const Array & ); friend istream &operator>>( istream &, Array & ); public: Array( int = 10 ); // default constructor Array( const Array & ); // copy constructor ~Array(); // destructor int getSize() const; // return size const Array &operator=( const Array & ); // assign arrays bool operator==( const Array & ) const ; // compare equal // Determine if two arrays are not equal and // return true , otherwise return false (uses operator==). bool operator!=( const Array &right ) const { return ! ( *this == right ); } int &operator[]( int ); const int &operator[]( int ) const; static int getArrayCount(); // // // // subscript operator subscript operator Return count of arrays instantiated. private: int size; // size of the array int *ptr; // pointer to first element of array static int arrayCount; // # of Arrays instantiated }; #endif //definitions #include <iostream> using std::cout; using std::cin; using std::endl; #include <iomanip > using std::setw; #include <cstdlib > #include <cassert > #include "array1.h" // Initialize static data member at file scope int Array::arrayCount = 0; // no objects yet // Default constructor for class Array (default size 10) Array ::Array( int arraySize ) { size = ( arraySize > 0 ? arraySize : 10 ); ptr = new int[ size ]; // create space for array assert( ptr != 0 ); // terminate if memory not allocated ++arrayCount; // count one more object for ( int i = 0; i < size; i++ ) ptr[ i ] = 0; // initialize array } 18 // Copy constructor for class Array // must receive a reference to prevent infinite recursion Array ::Array( const Array &init ) : size( init.size ) { ptr = new int[ size ]; // create space for array assert( ptr != 0 ); // terminate if memory not allocated ++arrayCount; // count one more object for ( int i = 0; i < size; i++ ) ptr[ i ] = init.ptr[ i ]; // copy init into object } // Destructor for class Array Array ::~Array() { delete [] ptr; // reclaim space for array --arrayCount; // one fewer objects } // Get the size of the array int Array::getSize() const { return size; } // Overloaded assignment operator // const return avoids: ( a1 = a2 ) = a3 const Array &Array::operator=( const Array &right ) { if ( &right != this ) { // check for self-assignment // for arrays of different sizes, deallocate original // left side array, then allocate new left side array . if ( size != right.size ) { delete [] ptr; // reclaim space size = right.size; // resize this object ptr = new int[ size ]; // create space for array copy assert( ptr != 0 ); // terminate if not allocated } for ( int i = 0; i < size; i++ ) ptr[ i ] = right.ptr[ i ]; // copy array into object } return *this; // enables x = y = z; } 19 // Determine if two arrays are equal and // return true, otherwise return false. bool Array::operator==( const Array &right ) const { if ( size != right.size ) return false; // arrays of different sizes for ( int i = 0; i < size; i++ ) if ( ptr[ i ] != right. ptr[ i ] ) return false; // arrays are not equal return true; // arrays are equal } // Overloaded subscript operator for non-const Arrays // reference return creates an lvalue int &Array::operator[]( int subscript ) { // check for subscript out of range error assert( 0 <= subscript && subscript < size ); return ptr[ subscript ]; // reference return } // Overloaded subscript operator for const Arrays // const reference return creates an rvalue const int &Array::operator[]( int subscript ) const { // check for subscript out of range error assert( 0 <= subscript && subscript < size ); return ptr[ subscript ]; // const reference return } // Return the number of Array objects instantiated // static functions cannot be const int Array::getArrayCount() { return arrayCount; } 20 // Overloaded input operator for class Array; // inputs values for entire array. istream &operator >>( istream &input, Array &a ) { for ( int i = 0; i < a.size; i++ ) input >> a. ptr[ i ]; return input; // enables cin >> x >> y; } // Overloaded output operator for class Array ostream &operator <<( ostream &output, const Array &a ) { int i; for ( i = 0; i < a.size; i++ ) { output << setw( 12 ) << a.ptr[ i ]; if ( ( i + 1 ) % 4 == 0 ) // 4 numbers per row of output output << endl; } if ( i % 4 != 0 ) output << endl; return output; // enables cout << x << y; } // Clientfor simple class Array #include <iostream> #include "array1.h" int main() { // no objects yet cout << "# of arrays instantiated = " << Array::getArrayCount() << '\n';//out:# of arrays instantiated = 0 // create two arrays and print Array count Array integers1( 7 ), integers2; cout << "# of arrays instantiated = " << Array::getArrayCount() << "\n\n"; //out:# of arrays instantiated = 2 // print integers1 size and contents cout << "Size of array integers1 is " << integers1.getSize() << "\nArray after initialization:\n" << integers1 << '\n'; /* Size of array integers1 is 7 Array after initialization: 0 0 0 0 0 0 0 */ 21 // print integers2 size and contents cout << "Size of array integers2 is " << integers2.getSize() << "\nArray after initialization:\n" << integers2 << '\n'; // input and print integers1 and integers2 cout << "Input 17 integers :\n"; cin >> integers1 >> integers2; cout << "After input, the arrays contain:\n" << "integers1:\n" << integers1 << "integers2:\n" << integers2 << '\n'; // use overloaded inequality (!=) operator cout << "Evaluating: integers1 != integers2\n"; if ( integers1 != integers2 ) cout << "They are not equal\n"; // create array integers3 using integers1 as an // initializer ; print size and contents Array integers3( integers1 ); cout << << << << "\nSize of array integers3 is " integers3.getSize() "\nArray after initialization:\n" integers3 << '\n'; // use overloaded assignment (=) operator cout << "Assigning integers2 to integers1:\n"; integers1 = integers2; cout << "integers1:\n" << integers1 << "integers2:\n" << integers2 << '\n'; // use overloaded equality (==) operator cout << "Evaluating: integers1 == integers2\n"; if ( integers1 == integers2 ) cout << "They are equal \n\n"; // use overloaded subscript operator to create rvalue cout << "integers1[5] is " << integers1[ 5 ] << '\ n'; // use overloaded subscript operator to create lvalue cout << "Assigning 1000 to integers1[5]\n"; integers1[ 5 ] = 1000; cout << "integers1:\n" << integers1 << '\n'; // attempt to use out of range subscript cout << "Attempt to assign 1000 to integers1[15]" << endl; integers1[ 15 ] = 1000; // ERROR: out of range return 0; } 22 Nota • Si ricorda che: – mentre per tutti gli operatori l’associatività è da sinistra a destra – l’associatività dell’operatore = è da destra a sinistra • pertanto – x+y+z viene valutata come (x+y)+z • mentre – a=b=c viene valutata come a=(b=c) Nota • Esistono due versioni dell’operatore [ ], una const e una non const • quella const è utilizzata quando stiamo operando su un array dichiarato const • passare ad una funzione un array const e poi essere in grado di modificare il contenuto dell’array accedendo tramite l’operatore [ ] ai valori del vettore sarebbe un errore logico • il compilatore pertanto non permette l’uso di funzioni membro non const con oggetti const 23 Template Introduzione ai Template • E’ possibile definire delle funzioni o classi dette generiche • ovvero che abbiano come parametro il tipo di dato • una funzione (classe) generica definisce una serie di operazioni applicabili ad un qualsiasi tipo di dato • ovvero l’algoritmo implementato si applica a qualsiasi tipo • una funzione (classe) generica si chiama funzione (classe) template 24 Introduzione ai Template • Sintassi: template<class T> retType NomeFunzione(argType (T)); • Esempio: template<class T> void swap(T& a, T& b){ T temp; temp=a; a=b; b=temp; } • Uso: int a=1;int b=2; swap(a,b); char c_a=‘x’;char c_b=‘y’; swap(c_a,c_b); Introduzione ai Template • Il compilatore genera tutte le istanze possibili della funzione template • è equivalente ad un overloading automatico • vengono generate tutte le istanze che servono, che vengono poi effettivamente utilizzate • le funzioni template sono limitate rispetto al caso generale di overloading perché non si possono specificare comportamenti diversi della funzione al variare del tipo 25 Esempio #include <iostream> void f(int i){cout<<“il valore è: ”<<i;} void f(char i){cout<<“il carattere è: ”<<i;} template<class T> void g(T i){cout<<“val: ”<<i;} int main(){ int a=56; char b=‘x’; f(a);//Stampa: il valore è: 56 f(b);//Stampa: il carattere è: x g(a);//Stampa: val: 56 g(b);//Stampa: val: x return 0;} Funzioni con più di un tipo generico • Nel caso in cui si debbano specificare più di due tipi so si fa con la seguente sintassi: • Sintassi template<class T1, class T2> retType F(argType); • Es: template<class T1, class T2> void func(T1 x, T2 y){ cout<<“prima:”<<x<<“ poi:”<<y; } 26 Overloading esplicito • Se si esegue un overloading esplicito di una funzione template questa maschera quella generata implicitamente template<class T> void g(T i){cout<<“val: ”<<i;} void g(char i){cout<<“il carattere è: ”<<i;} int main(){ int a=56; char b=‘x’; g(a);//Stampa: il valore è: 56 g(b);//Stampa: il carattere è: x return 0;} Uso delle funzioni generiche template<class T> T max(T *v, int size){ T max=v[0]; for(int i=1;i<size;i++) if(max<v[j]) max=v[j]; return max; } main(){ int arrayI[7]={4,7,2,4,9,3,2}; double arrayD[6]={7.1,9.4,2.6,5.7,4.8,6.9} int resI; double resD; resI=max(arrayI,7); resD=max(arrayD,6); } 27 Classi Template • Una classe generica o template può definire i propri membri in modo generico • sintassi in dichiarazione: template<class T> class NomeClasse{}; • sintassi in definizione: template<class T> retType NomeClasse<T>::funcName(argType parameter){} • sintassi in uso: main(){ NomeClasse<type> obj(init); } • Nota: per le classi si deve sempre esplicitare il tipo nella dichiarazione Esempio Vettore generico template<class T> class Vector{ public: Vector(int usr_size=10){size=usr_size; v=new T[size];} ~Vector(){delete [] v;} T& operator[](int); private: int size; T* v; }; template<class T> T& Vector<T>::operator[](int i) {return v[i];} 28 Esempio void main{ Vector<int> vI(100); Vector<char> vC(5); int i; for(i=0;i<100;i++) vI[i]=i*i; for(i=0;i<5;i++) vC[i]=‘e’; for(i=0;i<100;i++) cout<<vI[i]<<“ ”; cout<<endl; } Oggetti funzione • Si può simulare il comportamento di una funzione con un oggetto • Per fare quest si sovraccarica l’operatore parentesi di un oggetto • Il risultato è chiamato un oggetto funzione • Si usano oggetti funzione per scrivere delle funzioni che accettino altre funzioni come parametri 29 Oggetti funzione • Si dichiara in genere una classe con il metodo ( ) sovraccaricato • se si vuole usare la funzione per modificare dei dati non si restituisce niente, ma piuttosto si modifica il parametro passato per riferimento class FuncObj{ public: void operator()(MyClass & obj){ //modifica di obj } } Oggetti funzione • Oppure si usa l’oggetto funzione per verificare una condizione complessa • in questo caso si restituisce un bool class FuncObj{ public: bool operator()(MyClass & obj){ if//verifica di proprietà di obj return true; else return false; } } 30 Oggetti funzione • Questa tecnica diviene molto utile quando usata insieme ai template • si consideri la seguente funzione template template<class Ob, class F> void for_each(Ob* my_obj, int num, F func){ for(int i=0;i<num;i++) func(*(my_obj++)); } • La funzione for_each applica la funzione func a tutti gli oggetti in un vettore di cui è noto il puntatore al primo elemento e il numero di elementi class Complex{ public: Complex(double r=0, double i=0):re(r),im(i){} void operator+=(const Complex z){re+=z.re; im+=z.im;} private: double re,im; }; void add23(Complex &c){ c+=Complex(2,3); }; int main(){ const int dim=100; Complex *cV=new Complex[dim]; for_each(cV,dim,add23); return 0; } 31 Oggetti funzione • Come si può rendere l’esempio più generico? • Come fare per passare alla funzione una parte reale e immaginaria arbitraria da aggiungere? • Si potrebbe definire un oggetto funzione e immagazzinare il valore in un dato membro class Add{ public: Add(Complex c):val(c){} void operator()(Complex & c)const{c+=val;} private: Complex val; }; int main(){ const int dim=100; Complex *cV=new Complex[dim]; Complex z(2,3); for_each(cV,dim,Add(z)); return 0; } 32 Oggetti funzione • Nel caso di valutazione di un predicato si consideri invece una funzione che stampa un valore solo se un criterio è soddisfatto, come: template<class Ob,class P> void print_if(Ob & my_obj, P pred){ if(pred(my_obj)) cout<<my_obj; } class Odd{ public: bool operator()(Complex & c)const{return (c.re%2);} }; int main(){ const int dim=100; Complex *cV=new Complex[dim]; for(int i=0;i<dim;i++) print_if(*(cV++),Odd()); return 0; } 33 Nota • Si noti che nel template è specificato che il secondo parametro è un oggetto e non un tipo • pertanto non è corretto scrivere print_if(*cV,Odd); • poiché Even è un tipo definito dall’utente e non un oggetto • tuttavia Even() è un oggetto, anche se un oggetto senza nome • questo oggetto viene copiato nel secondo argomento di print_if dove acquista il nome di pred • il metodo operator() di pred viene poi utilizzato per valutare l’espressione Nota • L’oggetto funzione Odd ha il metodo operator() sovraccaricato che restituisce un valore booleano • se la parte reale del numero passato come argomento è divisibile per due, ovvero il resto (modulo) della divisione per due è 0 allora il risultato è 0=false, altrimenti è vero • in altri termini se è divisibile per due è falso che sia dispari (infatti è pari) • si sarebe potuto scrivere una funzione Even come return !((c.re%2)==0); 34