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