Overloading delle funzioni
Il termine overloading (da to overload) significa sovraccaricamento
e nel contesto del C++ overloading delle funzioni indica la possibilita` di
attribuire allo stesso nome di funzione piu` significati. Attribuire piu` significati
vuol dire fare in modo che lo stesso nome di funzione sia in effetti utilizzato
per piu` funzioni contemporaneamente.
Un esempio di overloading ci viene dalla matematica, dove con spesso utilizziamo
lo stesso nome di funzione con significati diversi senza starci a pensare troppo,
ad esempio + e` usato sia per indicare la somma sui naturali che
quella sui reali...
Ritorniamo per un attimo alla nostra funzione Sum ...
Per come e` stata definita, Sum funziona solo sugli interi e non
e` possibile utilizzarla sui float . Quello che vogliamo e` riutilizzare
lo stesso nome, attribuendogli un significato diverso e lasciando al compilatore
il compito di capire quale versione della funzione va utilizzata di volta in
volta. Per fare cio` basta definire piu` volte la stessa funzione:
int Sum(int a, int b); //
per sommare due interi,
float Sum(float a, float b); // per sommare due float,
float Sum(float a, int b); // per la
somma di un
float Sum(int a, float b); // float e un intero.
Nel nostro esempio ci siamo limitati solo a dichiarare piu` volte la funzione
Sum , ogni volta con un significato diverso (uno per ogni possibile
caso di somma in cui possono essere coinvolti, anche contemporaneamente, interi
e reali); e` chiaro che poi da qualche parte deve esserci una definizione per
ciascun prototipo (nel nostro caso tutte le definizioni sono identiche a quella
gia` vista, cambia solo l'intestazione della funzione).
In alcune vecchie versioni del C++ l'intenzione di sovraccaricare una funzione
doveva essere esplicitamente comunicata al compilatore tramite la keyword overload :
overload Sum; // ora si puo`
//
sovraccaricare Sum:
int Sum(int a, int b);
float Sum(float a, float b);
float Sum(float a, int b);
float Sum(int a, float b);
Comunque si tratta di una pratica obsoleta che infatti non e` prevista nello
standard.
Le funzioni sovraccaricate si utilizzano esattamente come le normali funzioni:
#include < iostream >
using namespace std;
/* Dichiarazione ed implementazione delle
varie Sum */
int main(int, char* []) {
int a = 5;
int y = 10;
float f = 9.5;
float r = 0.5;
cout << "Sum(int, int):" << endl;
cout << " " << Sum(a, y) << endl;
cout << "Sum(float, float):" << endl;
cout << " " << Sum(f, r) << endl;
cout << "Sum(int, float):" << endl;
cout << " " << Sum(a, f) << endl;
cout << "Sum(float, int):" << endl;
cout << " " << Sum(r, a) << endl;
return 0;
}
E` il compilatore che decide quale versione di Sum utilizzare,
in base ai parametri forniti; infatti e` possibile eseguire l'overloading di
una funzione solo a condizione che la nuova versione differisca dalle precedenti
almeno nei tipi dei parametri (o che questi siano forniti in un ordine diverso,
come mostrano le ultime due definizioni di Sum viste sopra):
void Foo(int a, float f);
int Foo(int a, float f); // Errore!
int Foo(float f, int a); // Ok!
char Foo(); //
Ok!
char Foo(...); //
OK!
La seconda dichiarazione e` errata perche`, per scegliere tra la prima e la
seconda versione della funzione, il compilatore si basa unicamente sui tipi
dei parametri che nel nostro caso coincidono; la soluzione e` mostrata con la
terza dichiarazione, ora il compilatore e` in grado di distinguere perche` il
primo parametro anzicche` essere un int e` un float .
Infine le ultime due dichiarazioni non sono in conflitto per via delle regole
che il compilatore segue per scegliere quale funzione applicare; in linea di
massima e secondo la loro priorita`:
- Match esatto: se esiste una versione della funzione che richiede
esattamente quel tipo di parametri (i parametri vengono considerati a uno
a uno secondo l'ordine in cui compaiono) o al piu` conversioni banali (tranne
da
T* a const T* o a volatile T* , oppure
da T& a const T& o a volatile T& );
- Mach con promozione: si utilizza (se esiste) una versione della funzione
che richieda al piu` promozioni di tipo (ad esempio da
int a
long int , oppure da float a double );
- Mach con conversioni standard: si utilizza (se esiste) una versione
della funzione che richieda al piu` conversioni di tipo standard (ad esempio
da
int a unsigned int );
- Match con conversioni definite dall'utente: si tenta un matching
con una definizione (se esiste), cercando di utilizzare conversioni di tipo
definite dal programmatore;
- Match con ellissi: si esegue un matching utilizzando (se esiste)
una versione della funzione che accetti un qualsiasi numero e tipo di parametri
(cioe` funzioni nel cui prototipo e` stato utilizzato il simbolo
... );
Se nessuna di queste regole puo` essere applicata, si genera un errore (funzione
non definita!). La piena comprensione di queste regole richiede la conoscenza
del concetto di conversione di tipo per il quale si rimanda all'appendice A;
si accenna inoltre ai tipi puntatore e reference che saranno trattati nel prossimo
capitolo, infine si fa riferimento alla keyword volatile .
Tale keyword serve ad informare il compilatore che una certa variabile cambia
valore in modo aleatorio e che di conseguenza il suo valore va riletto ogni
volta che esso sia richiesto:
volatile int ComPort;
La precedente definizione dice al compilatore che il valore di ComPort
e` fuori dal controllo del programma (ad esempio perche` la variabile e` associata
ad un qualche registro di un dispositivo di I/O).
Il concetto di overloading di funzioni si estende anche agli operatori del linguaggio,
ma questo e` un argomento che riprenderemo piu` avanti.
|