Comme une fonction n'est pas un objet ou une variable, il n'est pas possible de passer une fonction directement en argument à notre fonction
minimum. Par contre, comme toutes les choses présentes dans un programme C++, les fonctions ont une adresse. Il est donc possible de déclarer un pointeur vers cette adresse et de passer ce pointeur à la fonction
minimum.
Déclarer un pointeur sur fonction
Bon puisque vous êtes encore là, allons-y, déclarons un pointeur sur une fonction. La syntaxe est la suivante:
type_de_retour (*monPointeur) (type_argument_1,type_argument_2,...)
Décortiquons les 3 parties de la déclaration:
La première partie (
en rouge) indique le type de retour de la fonction pointée, cet élément fait donc partie du "type" d'une fonction. Le "type" d'une fonction n'est donc pas uniquement constitué de la signature de la fonction.
La deuxième partie (
en vert) est le nom que vous souhaitez donner à votre pointeur. Les parenthèses sont nécessaires afin que le compilateur sache que l'étoile est liée au nom et pas au type de retour.
La troisième partie (
en bleu) consiste en une liste des types des arguments que la fonction pointée doit recevoir.
Maintenant que nous somme munis de ces quelques notions, déclarons quelques pointeurs:
Code : C++ - Quelques pointeurs sur fonctions 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | int (*pointeur_1)(int);
// Déclaration d'un pointeur nommé "pointeur_1" qui pourra pointer
// sur des fonctions recevant un int et renvoyant un int.
int (*pointeur_2)(int,double);
// Déclaration d'un pointeur nommé "pointeur_2" qui pourra pointer
// sur des fonctions recevant un int et un double et renvoyant un int.
void (*pointeur_3)(double);
// Déclaration d'un pointeur nommé "pointeur_3" qui pourra pointer
// sur des fonctions recevant un double et ne renvoyant rien.
void (*pointeur_4)();
// Déclaration d'un pointeur nommé "pointeur_4" qui pourra pointer
// sur des fonctions ne recevant rien et ne renvoyant rien non plus.
|
int (*ptr)(int,double) n'est pas équivalent à int (*ptr)(double,int), l'ordre des arguments joue un rôle. De même void (*ptr)(double) n'est pas équivalent à void (*ptr)(int), même si les transformations de int en double sont automatiques.
Un pointeur sur fonction est un type comme un autre. Vous pouvez donc tout à fait créer un tableau de pointeurs sur fonctions, un std::vector<> de pointeurs sur fonctions ou même un pointeur sur pointeur de fonction.
Bon c'est bien joli tout ça, mais notre pointeur pour le moment ne pointe sur rien (ou en tout cas pas sur une fonction que vous avez créée), voyons donc comment l'affecter.
Affecter un pointeur sur fonction
Comme on parle de
pointeurs sur fonctions, vous pourriez tout à fait vous dire que c'est très facile de l'affecter, il suffit de récupérer l'adresse mémoire d'une fonction et de la mettre dans le pointeur. Et vous auriez tout à fait raison ! Il est tout à fait possible d'utiliser l'opérateur
& pour récupérer l'adresse d'une fonction. Par exemple :
Code : C++ - Affectation d'un pointeur 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | #include <string>
using namespace std;
int fonction(double a,string phrase) //Une jolie fonction
{
//blablabla
}
int main()
{
int (*monPointeur)(double,string); //On déclare un pointeur sur fonction
monPointeur = &fonction; //Et on le fait pointer sur "fonction"
//...
}
|
Ce code est tout à fait correct, cependant les créateurs du C++ ont voulu simplifier ceci et ont décidé qu'il n'était pas nécessaire d'utiliser l'opérateur
&. Le code suivant revient donc au même:
Code : C++ - Affectation d'un pointeur par la bonne méthode 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | #include <string>
using namespace std;
int fonction(double a,string phrase) //Une jolie fonction
{
//blablabla
}
int main()
{
int (*monPointeur)(double,string); //On déclare un pointeur sur fonction
monPointeur = fonction; //Et on le fait pointer sur "fonction"
// Notez l'absence du '&' !!
//...
}
|
Et c'est cette dernière méthode qui est utilisée par tous les programmeurs. Il n'est pas faux d'utiliser le
&, mais personne ne le fait. Puisque la notation est assez explicite sans, il n'est pas nécessaire pour le compilateur et pour les programmeurs d'ajouter le &.
Il est tout à fait possible d'affecter un pointeur directement à l'initialisation. Le code serait alors:
void (*pointeur)(int) = fonction;
Utiliser la fonction pointée
A nouveau, comme l'on parle de
pointeurs, vous pouvez vous douter que l'on va utiliser l'opérateur
*. Et puisque vous êtes des lecteurs attentifs, vous pourriez penser qu'il existe à nouveau une convention permettant d'omettre le
*. Et je ne pourrais que vous donner raison. Voyons cela sur un exemple:
Code : C++ - Utiliser une fonction pointée 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | int maximum(int a,int b) //Retourne le plus grand de deux entiers
{
return a>b ? a : b;
}
int main()
{
int (*ptr) (int,int); //Un joli pointeur
ptr = maximum; //que l'on affecte à la fonction "maximum"
int resultat = (*ptr)(1,2); //On calcule le maximum de 1 et 2 via la fonction pointée
//Notez l'utilisation obligatoire des ()
int resultat_2 = ptr(3,4); //Et on fait la même chose pour 3 et 4
//Notez l'absence de *
}
|
Les lignes 12 et 15 sont tout à fait équivalentes au niveau du programme généré. Mais comme précédemment, personne n'utilise la version avec
*. On peut donc déduire la règle suivante:
On utilise un pointeur sur fonction de la même manière qu'on utilise la fonction pointée.
Récrire le code de l'exemple initial
Maintenant que nous maitrisons un nouvel outil, nous pouvons récrire le code du premier exemple de la manière suivante:
Code : C++ - La bonne manière de faire 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 | #include <iostream>
#include <cmath>
using namespace std;
//Un petit typedef pour simplifier la notation
typedef double(*Fonction)(double);
//Liste des fonctions "calculables"
double carre(double x) { return x*x;}
double inverse(double x) { return 1/x;}
double racine(double x) { return sqrt(x);}
double exponentielle(double x) { return exp(x);}
double minimum(double a,double b,Fonction f) //On passe le pointeur en argument
{
//Et on reprend le code du tout premier exemple
double min(100000);
for(double x=a; x<b ; x+= 0.01)
min = min< f(x)? min : f(x);
//Mais cette fois c'est la fonction pointée qui est utilisée
return min;
}
int main()
{
cout << "De quelle fonction voulez-vous chercher le minimum ?" << endl;
cout << "1 -- x^2" << endl;
cout << "2 -- 1/x" << endl;
cout << "3 -- racine de x" << endl;
cout << "4 -- exponentielle de x" << endl;
cout << "5 -- sinus de x" << endl;
int reponse;
cin >> reponse;
Fonction monPointeur; //On declare un pointeur sur fonction
switch(reponse){ //Et on déplace le pointeur sur la fonction choisie
case 1: monPointeur = carre; break;
case 2: monPointeur = inverse; break;
case 3: monPointeur = racine; break;
case 4: monPointeur = exponentielle; break;
case 5: monPointeur = sin; break; //On peut même utiliser les fonctions de cmath !
}
//Finalement on affiche le résultat de l'appel de la fonction via le pointeur
cout << "Le minimum de la fonction entre 3 et 4 est: " << minimum(3,4,monPointeur) <<endl;
return 0;
}
|
C'est certainement la meilleure manière de réaliser ce que l'on voulait faire.
La fonction main n'est pas du tout sécurisée au niveau des entrées et du switch, mais ce n'est pas le but ici.