Dans certains cas il est commode de pouvoir passer une fonction comme paramètre
à une autre fonction.
Quelques exemples:
-
Si nous écrivons une fonction qui calcule numériquement une intégral
il est commode de pouvoir passer la fonction f à intégrer par paramètre.
Certainement nous ne voulons pas écrire une fonction d'intégration séparée pour
f(x) = sin(x) une autre pour g(x) = tan(x) et encore une autre
pour h(x)=x/(1+x²) etc., nous voulons plutôt une fonction qui nous permet d'intégrer
n'importe quelle fonction f.
-
Dans un module de traitement de listes chaînées
il peut être commode d'avoir une fonction générale de suppression
liste supprimer(l, cond)
qui supprime de la liste l
tous les éléments qui satisfont
une condition cond
. Dans ce cas le paramètre cond
est une fonction
qui prendra comme paramètre un élément de la liste est retourne 1
si cet élément est à supprimer et
0
sinon.
Encore une fois nous ne voulons pas écrire des fonctions de suppressions séparément pour chaque condition possible.
-
Pour écrire une fonction de tri d'un tableau il suffit juste d'être capable de comparer les éléments de tableau
deux à deux pour savoir lequel est plus petit. Si on fixe l'algorithme de tri, par exemple tri rapide, alors
trier les nombres ou les chaînes de caractères ou encore quelque chose d'autre ne change rien dans l'algorithme,
chaque fois c'est juste la relation d'ordre qui est différente.
Parfois il aussi aussi commode de pouvoir utiliser de tableaux de fonctions.
Par exemple si on affiche un menu où l'utilisateur choisit entre plusieurs actions et
chaque action est
implémentée par une fonction et si nous avons un tableau de fonctions fon
nous pouvons exécuter l'action i
par fon[i](param)
.
Déclaration de variables de type pointeur de fonction
Une variable pf
de type pointeur de fonction se déclare par
type_de_retour (*pf)(liste_de_paramètres);
Par exemple
double (*pf)(double, char *);
déclare une variable pf
: «pointeur de fonction à
deux paramètres double
et char *
et qui retourne une valeur double
».
Notez où se trouve le nom de la variable pf
dans cette déclaration et les parenthèses autour de
*pf
. Ces parenthèses sont nécessaires, si on les supprime on obtient:
double *pf(double, char *);
c'est-à-dire un prototype de fonction où pf
n'est
plus une variable mais le nom d'une fonction définie quelque part ailleurs dans le programme.
Un autre exemple
char *(*pd)(char *, double, int);
déclare une variable pd
: pointeur de fonction retournant char *
(retournant un pointeur de char
) avec trois paramètres: char *
, double
et
int
.
La variable pd
ci-dessus n'est pas initialisée.
Une fois déclarée, nous pouvons lui affecter NULL
:
pd = NULL;
Comme pour d'autres variables nous pouvons effectuer une initialisation
au moment de la déclaration:
char *(*pd)(char *, double, int) = NULL;
déclare un pointeur de fonction et initialise pd
avec la valeur NULL
.
Si dans notre programme nous avons des (définitions de) fonctions f
et g
:
char * f(char *s, double, int i){
/* definition de f */
}
char * g(char *t, double, int j){
/* definition de g */
}
les deux avec la même signature (paramètres
et la valeur de retour) que dans la déclaration de pd
alors nous pouvons écrire par exemple
int i;
char *s;
char *(*pd)(char *, double, int);
if( i == 0 )
pd = f;
else
pd = g;
s = pd("toto", 12.4, 5);
Les instructions
pd = f;
et
pd = g;
affectent à la variable pd
soit un pointeur vers f
soit un pointeur vers g
.
L'instruction
s = pd("toto", 12.4, 5);
donnera lieu à un appel soit à la fonction f
soit à la fonction g
,
en fonction de la valeur de la variable pd
.
Notez que dans
l'instruction
pd = f;
pd
est une variable tandis que f
joue rôle d'une constante
qui sera affectée à pd
.
Définition de type «pointeur de fonction»
Un code qui utilise beaucoup de pointeurs de fonctions devient vite illisible. Dans
#include <math.h>
double (*tab[])(double) = {sin, cos, sinh, sqrt, tan};
pouvez vous déchiffrer ce que c'est tab
?
En fait, c'est une déclaration d'un tableau de pointeurs de fonctions, chacune de ces fonction prend un double
en paramètre et retourne aussi un double.
Le tableau tab
est initialisé avec 5 valeurs: sin, cos, sinh, sqrt, tan
,
ce sont des fonctions mathématiques de la bibliothèque de C.
Et maintenant
double d = tab[3](5.3);
fait appel à la fonction tab[3]
, c'est-à-dire sqrt
, avec l'argument 5.3
.
Mais le tableau tab
ci-dessus est plus facile à déclarer si on définit auparavant le type
des éléments de tab
:
#include <math.h>
typedef double (*p_fonct)(double);
p_fonct tab[] = {sin, cos, sinh, sqrt, tan};
Ici on commence par déclarer le type p_fonct
pointeur de fonction et
ensuite un tableau tab
des éléments du type p_fonct
initialisé comme précédemment
avec 5 fonctions mathématiques.
Notez la place du nom de type p_fonct
dans typedef
:
à la même position que le nom de variable dans une déclaration d'une variable «pointeur de fonctions».
La fonction qsort
La fonction qsort
de C standard déclarée dans stdlib.h
implémente le tri rapide (quick sort).
void qsort(void *, /* tableau */
size_t, /* nombre d'element du tableau */
size_t, /* taille d'un element en octets */
int (*)(const void *, const void *));
Le premier paramètre est utilisé pour passer l'adresse du premier élément d'un tableau à trier.
Le deuxième paramètre de qsort
sert à passer le nombre d'éléments
de ce tableau et le troisième c'est la taille d'un élément.
Le dernier paramètre est un pointeur de fonction. Cette fonction permet de comparer les éléments du tableau.
Elle prend comme paramètre les pointeurs vers deux éléments à comparer, a
et b
, et retourne
un entier inférieur à 0 si a<b
, 0
si a = b
et un entier supérieur à 0
si a>b
.
Exemple
Si
int tab[100];
est un tableau d'entiers alors pour le trier avec la fonction qsort
il nous faut écrire une fonction de comparaison
int cmp_int(const void *x, const void *y){
int a= *(int *)x;
int b= *(int *)y;
return a - b;
}
Notez que x
et y
sont des pointeurs vers les données à comparer mais dans notre cas
il s'agit d'un tableau d'entiers alors x
et y
sont en fait de pointeurs vers int
.
Alors avec le cast (int *)x
nous obtenons les pointeurs
vers int
et en appliquant *
nous
récupérons les valeurs int
à comparer.
Et maintenaant nous pouvons trier tab
qsort( tab, /* tableau */
sizeof(tab)/sizeof(tab[0]), /* le nombre d'elemnts */
sizeof(tab[0]), /* la taille d'un element */
cmp_int); /* la fonction qui compare les elements */
Exemple
Si nous voulons trier un tableau de chaînes de caractères en utilisant
qsort
il nous faut une fonction de comparaison:
int cmp_strings(const void *x, const void * y){
return strcmp(* ((char **)x), *((char **)y));
}
Les données à trier étant de type char *
(chaînes de caractères en C) et les paramètres
x
et y
étant des pointeurs vers les données,
(c'est-à-dire char **
) nous commençons par un cast ((char **)x)
et ((char **)y)
et nous appliquant l'opérateur *
pour retrouver deux chaînes de caractères qui sont comparées avec
la fonction de C standard strcmp
.
Maintenant nous pouvons trier les paramètres de main
int main(int argc, char *argv[]){
qsort( &argv[1], /* l'adresse du premier element */
argc - 1, /* nombre d'elements */
sizeof(char *), /* taille d'un element */
cmp_strings); /* la fonction utilisee pour comparer les elements */
Attention
il est impossible de faire passer strcmp
à la place de notre fonction
cmp_strings
dans qsort
et cela pour deux raisons.
-
Les deux paramètres de
strcmp
sont de type char *
et qsort
demande une fonction avec les paramètres void *
.
-
Mais surtout
strcmp
compare ces deux arguments tandis que cmp_strings
prend en paramètre les pointeurs vers les données à comparer,
dans notre cas les pointeurs vers les chaînes de caractères.
Utilisation des paramètres «pointeur de fonction» dans une fonction
La fonction
int
cmp_fon( double (*f)(double),
double (*g)(double),
double x){
return ( f(x) + g(x) )/ 2;
}
prend comme paramètres deux pointeurs de fonctions f
et g
et un nombre double. Nous pouvons voir qu'à l'intérieur
de cmp_fon
f
et g
sont utilisés comme des fonctions.