Pointeurs sur des fonctions

Il est parfaitement possible d’utiliser des pointeurs sur des fonctions en C++. Il s’agit de pointeurs particuliers qui désignent le point d’entrée d’une fonction dans le programme. On les déclare ainsi :

int (*pf1)(double);long (*pf2)(void);void (*pf3)(int, float);

Le pointeur pf1 est un pointeur sur une fonction ayant un argument double, et de résultat entier. Le pointeur pf2 désigne une fonction sans argument de résultat entier long. Le pointeur pf3 désigne une fonction ayant deux arguments, le premier entier, le second float, et sans résultat.

On notera que du fait de la précédence de l’opérateur () sur le déréférencement *, il est obligatoire de placer des parenthèses.

Exercice 5.4

Que désignent les écritures suivantes :

int *p1(float, int[]);     void (*p2)(int (*)(float, float));     long (*) (double[], int) (*p3) (long);
Voir solution
Exercice 5.5

Comment déclarer un tableau de pointeurs sur des fonctions renvoyant un int et acceptant un double comme paramètre ?

Voir solution

Pour initialiser ces pointeurs, on indique simplement l’adresse de la fonction sur laquelle ils pointeront, avec l’opérateur d’adressage &, comme si c’était de simples variables :

int unefonction(double d);.....int (*pf1)(double) = &unefonction;

Ensuite, on peut appeler la fonction pointée à l’aide d’un déréférencement (même remarque que précédemment à propos de la précédence de () sur *) :

int i = (*pf1)(10.1);

Noter que pour affecter l’adresse d’une fonction à un pointeur, l’adéquation des arguments et du résultat doit être parfaite, et ce jusqu’aux variantes unsigned ou const. Par exemple, les trois initialisations suivantes seront refusées par le compilateur :

int fonct1(unsigned u);void fonct2(const char *s);float fonct3(void);// ....int (*p1)(int) = &fonct1;        // non, incorrectvoid (*p2)(char *s) = &fonct2;   // idemdouble (*p3)(void) = &fonct3;    // idem

On doit dans ce cas faire un changement de type :

int (*p1)(int) = (int (*)(int)) &fonct1;    // okvoid (*p2)(char *s) = (void (*)(char*)) &fonct2;    // ok

Le changement de type serait aventureux pour p3, car la promotion de float en double ne sera pas automatique, entraînant des erreurs graves.

En réalité, la plupart des compilateurs se montrent très tolérants vis-à-vis des écritures avec des pointeurs de fonctions, qui sont aussi considérés comme des références. On peut alors écrire :

int i = pf1(10.1);

De même, on peut écrire :

int (*pf1)(double) = unefonction;

Cependant, il ne faut pas supprimer l’opérateur * dans cette dernière expression, sinon le compilateur croirait à une définition de fonction.

Les pointeurs de fonctions sont des pointeurs spéciaux, et aucune opération arithmétique n’est permise sur eux. Si par exemple on écrit :

pf1++;

le compilateur répond Error : Size of this expression is unknown or zero, la taille de cette expression est inconnue ou nulle, montrant ainsi qu’il ne peut lui donner un sens, puisqu’une fonction, au contraire de tout autre type, n’a pas de taille fixée.

Les déclarations de pointeurs de fonctions seront grandement facilitées en utilisant typedef. Ainsi, voici quelques déclarations :

typedef int pfonc(double);// type pointeur sur fonction(double), de résultat intpfonc pf1 = unefonction;// pointeur sur une telle fonctionpfonc tpf[10] = { unefonction };// tableau de 10 pointeurs de fonctionvoid (*pff)(int, pfonc);// pointeur de fonction sans résultat ayant un int et// un pointeur de fonction pfonc comme arguments

Les fonctions peuvent tout à fait avoir un pointeur de fonction comme paramètre ou comme résultat, comme on l’a déjà vu. C’est même une utilisation fréquente de ces types. Ainsi, on trouve dans la librairie <stdlib.h> une fonction nommée qsort dont l’en-tête est le suivant :

void qsort(void *base, size_t nelem, size_t  largeur,            int (*fcomp)(const void*, const void*) );

Cette fonction effectue un tri en utilisant le célèbre algorithme « QuickSort ». Le premier argument base est un pointeur sur le début de la table à trier ; le second est le nombre d’éléments à trier ; le troisième est la taille en octets de chaque élément ; le dernier est un pointeur sur une fonction qui compare deux éléments, en renvoyant un nombre strictement négatif si le premier est strictement inférieur au second, zéro s’ils sont égaux, et un nombre strictement positif si le premier est strictement supérieur au second.

Exercice 5.6

Écrire un programme qui trie un tableau de 100 entiers, en utilisant qsort. On initialisera le tableau avec des valeurs aléatoires comprises entre 0 et 999, en appelant random(1000).

Voir solution

La même librairie contient deux fonctions de recherche, l’une bsearch est une recherche dichotomique dans une table triée, l’autre lsearch est une recherche linéaire dans une table non triée. Il s’agit là d’utilisations typiques et très utiles des pointeurs sur des fonctions.

Précédent Précédent Sommaire Sommaire Suivant Suivant