Puntatori e Funzioni

 


 

Funzioni che restituiscono puntatori

 

Il valore di ritorno restituito da una funzione può essere di qualsiasi tipo, compreso il tipo puntatore.
Es.:     int* funz();     dichiara una funzione funz che restituisce un valore puntatore a int

Come in generale, il valore di ritorno, anche se é un puntatore, viene trasmesso by value e quindi ne viene creata una copia nel programma chiamante; ciò garantisce che il puntatore sopravviva alla funzione anche se è stato creato all'interno del suo ambito.

Tuttavia la variabile puntata potrebbe non sopravvivere alla funzione (se é stata creata nel suo ambito e non dichiarata static). Ciò porterebbe a un errore di dangling references. Notare l'analogia con il tipo di errore generato quando un valore di ritorno, trasmesso by reference, corrisponde a una variabile che cessa di esistere: in quel caso tuttavia, il compilatore ha il controllo della situazione e quindi può segnalare l'errore (o almeno un warning); nel caso di un puntatore, invece, il suo contenuto (cioè l'indirizzo della variabile puntata) é determinato in esecuzione e quindi l'errore non può essere segnalato dal compilatore. Spetta al programmatore fare la massima attenzione a che ciò non si verifichi.

Il più frequente uso di funzioni che restituiscono puntatori si ha nel caso di puntatori a char, cioè di stringhe. Nella stessa libreria Run-time ci sono molte funzioni che restituiscono stringhe.
Esempio di funzione di libreria:

                            char*
strcat(char* str1, char* str2);  

concatena
la stringa str2 alla stringa str1 e restituisce il risultato sia nella stessa str1 che come valore di ritorno. Notare che in questo caso non c'è pericolo di errore, purchè lo spazio di memoria per str1 sia stato adeguatamente allocato nel programma chiamante.

[p33]

 


 

Puntatori a Funzione

 

In C++ (come in C) esistono anche i puntatori a funzione! Questi servono quando il programma deve scegliere quale funzione chiamare fra diverse possibili, e la scelta non é definita a priori ma dipende dai dati del programma stesso. Questo processo si chiama late binding ("aggancio ritardato"): gli indirizzi delle funzioni da chiamare non vengono risolti al momento della compilazione, come avviene normalmente (early binding) ma al momento dell'esecuzione.

I puntatori a funzione non devono essere definiti, ma solo dichiarati, come nel seguente esempio:
                     int* (*pfunz)(double , char* );
dichiara un puntatore a funzione pfunz che restituisce un puntatore a int e ha due argomenti: il primo é di tipo double, il secondo é un puntatore a char. Notare le parentesi intorno al nome della funzione, in assenza delle quali la dichiarazione sarebbe interpretata in modo diverso (una normale funzione pfunz che restituisce un puntatore a puntatore a int).

Nel corso del programma il puntatore a funzione deve essere assegnato (o inizializzato) con il nome di una funzione "vera", che deve essere precedentemente dichiarata con lo stesso tipo del valore di ritorno e gli stessi argomenti del puntatore. Continuando l'esempio precedente:

             int* funz1(double , char* );
             int* funz2(double , char* );
             if ( ......... ) pfunz = funz1 ;
             else
pfunz = funz2;

notare che i nomi delle funzioni e del puntatore vanno indicati da soli, senza i loro argomenti (e senza le parentesi).

In una chiamata della funzione, tutti i testi di C dicono che il puntatore va dereferenziato (in realtà non é necessario):
          (*pfunz)(12.3,"Ciao");        ... ma va bene anche:         pfunz(12.3,"Ciao");

[p34]

 


 

Array di puntatori a funzione

 

In C++ (come in C) è consentito dichiarare array di puntatori a funzione, nella forma specificata dal seguente esempio:
                   double (*apfunz[5])(int);
dichiara l'array apfunz di 5 puntatori a funzione, tutti con valore di ritorno di tipo double e con un argomento di tipo int.

L'array può essere inizializzato con un elenco di nomi di funzioni, già dichiarate e condividenti tutte le stesso tipo di valore di ritorno e gli stessi argomenti:

double (*apfunz[5])(int) = {f1, f2, f3, f4, f5} ;

dove f1 ecc... sono tutte funzioni dichiarate: double f1(int); ecc...

I singoli elementi dell'array possono anche essere assegnati tramite l'operatore [ ], che funziona come l-value nel modo consueto:
                  apfunz[3]= fn;
dove fn é una funzione dichiarata: double fn(int);

Nelle chiamate, si usa ancora l'operatore [ ] per selezionare l'elemento desiderato:
                  apfunz[i](n);         (non é necessario dereferenziare il puntatore)
dove l'indice i permette di accedere alla funzione precedentemente assegnata all'i-esimo elemento dell'array.

Gli array di puntatori a funzione possono essere utili, per esempio, quando la funzione da eseguire é selezionata da un menù: in questo caso l'indice i , corrispondente a una voce di menù, indirizza direttamente la funzione prescelta, senza bisogno di istruzioni di controllo, come if o switch, per determinarla.

 


 

Funzioni con argomenti puntatori a funzione

 

E' noto che, quando nella chiamata di una funzione compare come argomento un'altra funzione, questa viene eseguita per prima e il suo valore di ritorno é utilizzato come argomento dalla prima funzione. Quindi il vero argomento della prima funzione non é la seconda funzione, ma un normale valore, che può avere qualsiasi origine (variabile, espressione ecc...), e in particolare in questo caso è il risultato dell'esecuzione di un'altra funzione (il cui tipo di valore di ritorno deve coincidere con il tipo dichiarato dell'argomento).

Quando invece una funzione dichiara fra i suoi argomenti un puntatore a funzione, allora sono parametrizzate proprio le funzioni e non i loro valori di ritorno. Nelle chiamate é necessario specificare come argomento il nome di una funzione "vera", precedentemente dichiarata, che viene sostituito a quello del puntatore.
Es.:     dichiarazioni: void fsel(int (*)(float));
int funz1(float);

int funz2(float);
chiamate: fsel(funz1);
fsel(funz2);
definizione fsel:     void fsel(int (*pfunz)(float))
{ .... n = pfunz(r); .....}
(dove n é di tipo int e r é di tipo float)

l'istruzione n=pfunz(r) viene sostituita la prima volta con n=funz1(r) e la seconda volta con n=funz2(r) . Notare che, nelle chiamate, l'argomento-funzione deve essere a sua volta specificato senza argomenti e senza le parentesi tonde.

Nell'esempio abbiamo supposto che la variabile r, argomento della pfunz, sia creata all'interno della fsel; anche se r fosse passato dal programma chiamante, la forma: fsel(funz1(r)) sarebbe comunque errata: l'unico modo per passare r potrebbe essere quello di dichiararlo come ulteriore argomento della fsel, cioè:
        void
fsel(float, int (*pfunz)(float));                 e nelle chiamate specificare:
        fsel(r, funz1);        ...oppure...       fsel(r, funz2);

[p35]

 


 

Torna all'Indice