Puntatori a funzioni
Ver. 2.4
© 2010 - Claudio Fornaro - Corso di programmazione in C
Indirizzo di una funzione
Si può chiamare una funzione utilizzando l’indirizzo di memoria dal quale inizia il codice eseguibile della funzione stessa
L’indirizzo di memoria di una funzione può essere assegnato ad una variabile puntatore, memorizzato in un vettore, passato a una funzione, restituito da una funzione
3
Definizione di variabili
tipo (*nome)(parametri );
definisce la variabile nome come puntatore a una funzione che restituisce un valore del tipo indicato e richiede i parametri indicati
Le parentesi intorno al nome sono necessarie altrimenti si ha:
tipo *nome(parametri );
e questa costituisce il prototipo di una
funzione che restituisce un puntatore del tipo indicato e richiede i parametri indicati
4
Definizione di variabili
double (*fp)();
definisce fp come variabile puntatore a una funzione che restituisce un double, nulla è indicato per i suoi parametri, quindi non vengono controllati (un compilatore C++
invece considera l’argomento come void)
double (*fp)(double, double);
definisce fp come variabile puntatore a una
funzione che ha come parametri due double
e restituisce un double
Definizione di variabili
int (*fpv[3])(long);
definisce fpv come vettore di 3 elementi, ciascuno è un puntatore a una funzione che ha come parametri un long e restituisce un int
Le definizioni esterne inizializzano a NULL le variabili puntatori a funzioni
Inizializzazione
Il nome di una funzione equivale al suo indirizzo di memoria (come per i vettori)
Con le seguenti funzioni (prototipi):
double pow(double,double);
double atan2(double,double);
int f1(long), f2(long);
si possono avere le seguenti inizializzazioni:
double (*fp)(double,double) = NULL;
double (*fp)(double,double) = pow;
int (*fpv[3])(long)={f1,f2};
/*+1NULL*/int (*fpv[3])(long)={NULL};
/*3 NULL*/7
Utilizzo con typedef
typedef può definire un tipo puntatore a funzione con determinati parametri e valore restituito
typedef double (*fpType)( double , double );
dichiara fpType come tipo puntatore a funzione con due argomenti double e che restituisce un double
fpType fp = pow;
fpType fpv[5] = {pow,NULL,atan2};
Nota: fpv[3] e fpv[4] sono inizializzati a NULL perché l’inizializzazione dei primi elementi impone 0 (NULL) per i rimanenti
8
Assegnazione
Il nome di una funzione equivale al suo indirizzo di memoria (come per i vettori)
L’operatore & è ininfluente se applicato al nome di una funzione
Esempi (l’operatore & può essere omesso):
fp = &atan2;
fp = atan2;
fpv[2] = &f1;
fpv[2] = f1;
Confronto
È un normale confronto tra puntatori, permette ad esempio di determinare se il puntatore si riferisce ad una certa funzione o no oppure se è NULL
if (fp == pow)
printf("fp punta a pow\n");
Chiamata della funzione
La funzione il cui puntatore è stato
memorizzato nella variabile fp può essere richiamata in due modi equivalenti:
(*fp)(argomenti)
fp(argomenti)
Il primo metodo rende più evidente che si tratta di un puntatore a funzione e non del nome di una funzione, il secondo è più leggibile e comodo
x = (*fp)(2.23, 4.76);
x = fp(2.23, 4.76);
i = (*fpv[1])(22);
i = fpv[1](22);
11
Passaggio a funzione
Un puntatore a funzione può essere passato ad una funzione come argomento
Questo permette di passare ad una funzione [puntatori a] funzioni diverse per ottenere dalla stessa funzione comportamenti diversi
Il parametro attuale deve essere il nome di una funzione
Il parametro formale deve essere il prototipo delle funzioni chiamabili (è lì che viene
definito il nome del puntatore visto dall’interno della funzione stessa)
12
Passaggio a funzione
Esempio
Si supponga di avere le due funzioni seguenti:
int piu(int a,int b) fa la somma di 2 numeri interi
int meno(int a,int b) fa la differenza di 2 numeri interi
m = calc(12, 23, piu);
calcola somma dei due valori n = calc(12, 23, meno);
calcola la differenza dei due valori
Passaggio a funzione
int piu(int a,int b) {return a+b;}
int meno(int a,int b) {return a-b;}
int calc( int x, int y, int (*funz)(int,int) ) {
return (*funz)(x,y);
}
main() {
int m, n;
m = calc(12, 23, piu);
n = calc(12, 23, meno);
...
il nome funz viene dichiarato qui…
…e usato qui
Valore restituito da funzione
Una funzione può restituire un puntatore a funzione (2 metodi)
Primo metodo
Si definisce con l’istruzione typedef il tipo del puntatore-a-funzione restituito dalla funzione chiamata:
typedef int (*TipoFunz)(int,int);
TipoFunz è un nuovo tipo: puntatore-a- funzione-che-ha-2-argomenti- int -e- restituisce-un- int
E lo si usa nel solito modo:
TipoFunz operaz(int x);
15
Valore restituito da funzione
typedef int (*TipoFunz)(int,int);
int piu(int a,int b) {return a+b;}
int meno(int a,int b) {return a-b;}
TipoFunz operaz(int f) {
static TipoFunz fpv[2]={piu,meno};
return fpv[f];
}
main() {
int y;
TipoFunz op; /* var. puntatore */
op = operaz(1); /* scelta op. */
y = (*op)(12, 23);
}
static per poter usare il puntatore dopo che la
funzione è terminata
16
Valore restituito da funzione
Secondo metodo (meno chiaro) Si scrive ogni cast esplicitamente
int (* operaz(int f))(int,int) {
static int (* fpv[2])(int,int) = {piu,meno};
return fpv[f];
}
La funzione definita è operaz e f ne è l’argomento
la parte non sottolineata è il tipo della funzione restituita da operaz (dove (int,int) sono i parametri della funzione restituita)
static è riferito a fpv
Valore restituito da funzione
Secondo metodo (Continuazione) main()
{
int y;
int (*op)(int,int);/*puntatore */
op = operaz(1); /* scelta op. */
y = (*op)(12, 23);
}
Cast
Un cast di tipo puntatore-a-funzione
“fa credere” al compilatore che la funzione a cui viene applicato abbia il tipo di quella del puntatore
Si ricorda che il tipo di una funzione è definito dal tipo e numero dei parametri e dal tipo del valore restituito
Il cast può essere premesso al nome di una funzione o a un puntatore a funzione
Attenzione che il cast di tipo puntatore-a- funzione non applica anche il cast agli argomenti e al valore restituito
19
Cast
Il cast di puntatore-a-funzione può essere utilizzato per rendere una funzione
compatibile con quella richiesta come argomento da un’altra funzione
E’ indispensabile che gli argomenti siano comunque compatibili (in generale sono puntatori)
Si veda l’esempio del qsort seguente
Passaggio a funzione con cast
20qsort
La funzione qsort in <stdlib.h> ordina un vettore, deve essere chiamata indicando il nome della funzione da usare per il confronto degli elementi, il suo prototipo è:
void qsort(
void *base, puntatore al vett da riordinare
size_t n, sua dim (numero di elementi)
size_t size, dim di ogni elemento (in byte)
int (*cmp)(const void*,const void*) )
cmp è il puntatore alla funzione da utilizzare
per confrontare due elementi di quel vettore
Passaggio a funzione con cast
qsort
La funzione passata per cmp deve:
avere come argomenti due puntatori agli elementi del vettore da confrontare (qsort passerà a cmp gli indirizzi dei due elementi mediante &base[i])
restituire un valore int:
<0 se il 1
oelemento è minore del 2
o
0 se sono uguali
>0 se il 1
oè maggiore del 2
o
La clausola const indica che gli oggetti da confrontare non saranno modificati
Il tipo void* permette di passare puntatori di qualsiasi tipo: sarà la funzione passata ad utilizzarli ritrasformandoli al tipo corretto
Passaggio a funzione con cast
qsort su vettore di interi
#include <stdlib.h>
#include <stdio.h>
int cfr(const int *a, const int *b) {
if (*a < *b) return -1;
else if (*a > *b) return +1;
else return 0;
}
main() {
int vett[10] = {3,2,5,4,7,9,0,1,6,8};
qsort(vett, 10, sizeof(int),
(int (*)(const void*,const void*))cfr );
}
Passaggio a funzione con cast
23qsort su vettore di interi
La funzione passata cfr ha prototipo non identico a quello richiesto, ma compatibile (i puntatori sono sempre assegnabili gli uni agli altri anche se di tipo diverso); quindi viene fatto un cast della funzione passata:
( int (*)(const void*,const void*) ) cfr
I valori che la funzione qsort passa
automaticamente a cfr per essere confrontati sono i vari &vett[i] che vengono usati
come puntatori a int (quali sono)
Passaggio a funzione con cast
24qsort su vettore di stringhe
Per ordinare con qsort un vettore di stringhe char *vs[10] non si può utilizzare
direttamente strcmp
Gli elementi che qsort() passerebbe alla funzione di confronto sarebbero &vs[i], cioè i puntatori (indirizzi) ai puntatori agli
elementi, mentre strcmp() richiede
puntatori a char
Passaggio a funzione con cast
qsort su vettore di stringhe
Bisogna definire una funzione ausiliaria:
int cp( const void *p1,const void *p2 ) {
return strcmp(* (char * const *) p1,
* (char * const *) p2);
}
Gli argomenti di cp sono generici puntatori costanti a void (const void * ), il cast (char * const *) li riconverte nel loro vero tipo (puntatore a puntatore costante a char) e la deferenziazione produce un puntatore
costante a char come la strcmp richiede
Passaggio a funzione con cast
qsort su vettore di strutture
Per ordinare un vettore di struct bisogna definire una funzione di confronto ausiliaria:
int structcmp(const void *p1, const void *p2) {
const struct nome *sp1;
const struct nome *sp2;
sp1=(const struct nome *)p1;
sp2=(const struct nome *)p2;
if (sp1->membro > sp2->membro) return +1;
else if (...
27
Esercizi
1.
Scrivere un programma che riordini con qsort un vettore di stringhe
2.
Scrivere un programma che
1.
definisca un vettore di 10 struct contenenti due valori interi x e y
2.
inizializzi il vettore
3.
ordini il vettore con qsort in base alla somma dei valori (se due somme sono uguali, il confronto è tra i primi membri, es. {4,6}>{3,7})
4.