4.12. Pointeurs et références de fonctions
4.12.1. Pointeurs de fonctions
Il est possible de faire des pointeurs de fonctions. Un pointeur de fonction contient l'adresse du début du code binaire constituant la fonction. Il est possible d'appeler une fonction dont l'adresse est contenue dans un pointeur de fonction avec l'opérateur d'indirection *.
Pour déclarer un pointeur de fonction, il suffit de considérer les fonctions comme des variables. Leur déclaration est identique à celle des tableaux, en remplaçant les crochets par des parenthèses :
type (*identificateur)(paramètres);
où type est le type de la valeur renvoyée par la fonction, identificateur est le nom du pointeur de la fonction et paramètres est la liste des types des variables que la fonction attend comme paramètres, séparés par des virgules.
Exemple 4-14. Déclaration de pointeur de fonction
int (*pf)(int, int); /* Déclare un pointeur de fonction. */
pf est un pointeur de fonction attendant comme paramètres deux entiers et renvoyant un entier.
Il est possible d'utiliser typedef pour créer un alias du type pointeur de fonction :
typedef int (*PtrFonct)(int, int);
PtrFonct pf;
PtrFonct est le type des pointeurs de fonctions.
Si f est une fonction répondant à ces critères, on peut alors initialiser pf avec l'adresse de f. De même, on peut appeler la fonction pointée par pf avec l'opérateur d'indirection.
Exemple 4-15. Déréférencement de pointeur de fonction
#include <stdio.h> /* Autorise l'emploi de scanf et de printf. */
int f(int i, int j) /* Définit une fonction. */
{
return i+j;
}
int (*pf)(int, int); /* Déclare un pointeur de fonction. */
int main(void)
{
int l, m; /* Déclare deux entiers. */
pf = &f; /* Initialise pf avec l'adresse de la fonction f. */
printf("Entrez le premier entier : ");
scanf("%u",&l); /* Initialise les deux entiers. */
printf("\nEntrez le deuxième entier : ");
scanf("%u",&m);
/* Utilise le pointeur pf pour appeler la fonction f
et affiche le résultat : */
printf("\nLeur somme est de : %u\n", (*pf)(l,m));
return 0;
}
L'intérêt des pointeurs de fonction est de permettre l'appel d'une fonction parmi un éventail de fonctions au choix.
Par exemple, il est possible de faire un tableau de pointeurs de fonctions et d'appeler la fonction dont on connaît l'indice de son pointeur dans le tableau.
Exemple 4-16. Application des pointeurs de fonctions
#include <stdio.h> /* Autorise l'emploi de scanf et de printf. */
/* Définit plusieurs fonctions travaillant sur des entiers : */
int somme(int i, int j)
{
return i+j;
}
int multiplication(int i, int j)
{
return i*j;
}
int quotient(int i, int j)
{
return i/j;
}
int modulo(int i, int j)
{
return i%j;
}
typedef int (*fptr)(int, int);
fptr ftab[4];
int main(void)
{
int i,j,n;
ftab[0]=&somme; /* Initialise le tableau de pointeur */
ftab[1]=&multiplication; /* de fonctions. */
ftab[2]="ient;
ftab[3]=&modulo;
printf("Entrez le premier entier : ");
scanf("%u",&i); /* Demande les deux entiers i et j. */
printf("\nEntrez le deuxième entier : ");
scanf("%u",&j);
printf("\nEntrez la fonction : ");
scanf("%u",&n); /* Demande la fonction à appeler. */
if (n < 4)
printf("\nRésultat : %u.\n", (*(ftab[n]))(i,j) );
else
printf("\nMauvais numéro de fonction.\n");
return 0;
}
4.12.2. Références de fonctions
Les références de fonctions sont acceptées en C++. Cependant, leur usage est assez limité. Elles permettent parfois de simplifier les écritures dans les manipulations de pointeurs de fonctions. Mais comme il n'est pas possible de définir des tableaux de références, le programme d'exemple donné ci-dessus ne peut pas être récrit avec des références.
Les références de fonctions peuvent malgré tout être utilisées à profit dans le passage des fonctions en paramètre dans une autre fonction. Par exemple :
#include <stdio.h> // Autorise l'emploi de scanf et de printf.
// Fonction de comparaison de deux entiers :
int compare(int i, int j)
{
if (i<j) return -1;
else if (i>j) return 1;
else return 0;
}
// Fonction utilisant une fonction en tant que paramètre :
void trie(int tableau[], int taille, int (&fcomp)(int, int))
{
// Effectue le tri de tableau avec la fonction fcomp.
// Cette fonction peut être appelée comme toute les autres
// fonctions :
printf("%d", fcomp(2,3));
⋮
return ;
}
int main(void)
{
int t[3]={1,5,2};
trie(t, 3, compare); // Passage de compare() en paramètre.
return 0;
}
4.13. Paramètres de la fonction main - ligne de commande
L'appel d'un programme se fait normalement avec la syntaxe suivante :
nom param1 param2 [...]
où nom est le nom du programme à appeler et param1, etc. sont les paramètres de la ligne de commande. De plus, le programme appelé peut renvoyer un code d'erreur au programme appelant (soit le système d'exploitation, soit un autre programme). Ce code d'erreur est en général 0 quand le programme s'est déroulé correctement. Toute autre valeur indique qu'une erreur s'est produite en cours d'exécution.
La valeur du code d'erreur est renvoyée par la fonction main. Le code d'erreur doit toujours être un entier. La fonction main peut donc (et même normalement doit) être de type entier :
int main(void) ...
Les paramètres de la ligne de commandes peuvent être récupérés par la fonction main. Si vous désirez les récupérer, la fonction main doit attendre deux paramètres :
• le premier est un entier, qui représente le nombre de paramètres ;
• le deuxième est un tableau de chaînes de caractères (donc en fait un tableau de pointeurs, ou encore un pointeur de pointeurs de caractères).
Les paramètres se récupèrent avec ce tableau. Le premier élément pointe toujours sur la chaîne donnant le nom du programme. Les autres éléments pointent sur les paramètres de la ligne de commande.
Exemple 4-17. Récupération de la ligne de commande
#include <stdio.h> /* Autorise l'utilisation des fonctions */
/* printf et scanf. */
int main(int n, char *params[]) /* Fonction principale. */
{
int i;
/* Affiche le nom du programme : */
printf("Nom du programme : %s.\n",params[0]);
/* Affiche la ligne de commande : */
for (i=1; i<n; ++i)
printf("Argument %d : %s.\n",i, params[i]);
return 0; /* Tout s'est bien passé : on renvoie 0 ! */
}
4.14. DANGER
Les pointeurs sont, comme on l'a vu, très utilisés en C/C++. Il faut donc bien savoir les manipuler.
Mais ils sont très dangereux, car ils permettent d'accéder à n'importe quelle zone mémoire, s'ils ne sont pas correctement initialisés. Dans ce cas, ils pointent n'importe où. Accéder à la mémoire avec un pointeur non initialisé peut altérer soit les données du programme, soit le code du programme lui-même, soit le code d'un autre programme ou celui du système d'exploitation. Cela conduit dans la majorité des cas au plantage du programme, et parfois au plantage de l'ordinateur si le système ne dispose pas de mécanismes de protection efficaces.
VEILLEZ ہ TOUJOURS INITIALISER LES POINTEURS QUE VOUS
UTILISEZ.
Pour initialiser un pointeur qui ne pointe sur rien (c'est le cas lorsque la variable pointée n'est pas encore créée ou lorsqu'elle est inconnue lors de la déclaration du pointeur), on utilisera le pointeur prédéfini NULL.
VةRIFIEZ QUE TOUTE DEMANDE D'ALLOCATION MةMOIRE A ةTة
SATISFAITE.
La fonction malloc renvoie le pointeur NULL lorsqu'il n'y a plus ou pas assez de mémoire. Le comportement des opérateurs new et new[] est différent. Théoriquement, ils doivent lancer une exception si la demande d'allocation mémoire n'a pas pu être satisfaite. Cependant, certains compilateurs font en sorte qu'ils renvoient le pointeur nul du type de l'objet à créer.
S'ils renvoient une exception, le programme sera arrêté si aucun traitement particulier n'est fait. Bien entendu, le programme peut traiter cette exception s'il le désire, mais en général, il n'y a pas grand chose à faire en cas de manque de mémoire. Vous pouvez consulter le chapitre traitant des exceptions pour plus de détails à ce sujet.
Dans tous les cas,
LORSQU'ON UTILISE UN POINTEUR, IL FAUT VةRIFIER S'IL EST VALIDE
(par un test avec NULL ou le pointeur nul, ou en analysant l'algorithme). Cette vérification inclut le test de débordement lors des accès aux chaînes de caractères et aux tableaux. Cela est extrêmement important lorsque l'on manipule des données provenant de l'extérieur du programme, car on ne peut dans ce cas pas supposer que ces données sont valides.