Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zér0 > Les tutoriels > Non-Officiels > Programmation > C > Lecture du tutoriel

Les pointeurs de fonctions

Avatar
Auteur : mleg
Créé : le 05/03/2006 14:32:24
Modifié : le 23/01/2008 00:53:44
Noter et commenter ce tutoriel
Imprimer ce tutoriel
Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Bien le bonjour, amis zéros !

Comme vous le remarquerez si vous revenez ici, ce tuto a été édité. Il est désormais bien plus juste et complet que la version précédente (mais j'ai pas dit que c'était encore parfait, hein), je vous invite donc à le relire. Bonne (re)lecture ! ;)

A noter aussi qu'il est probable que le bug relaté dans ce topic s'applique, provoquant une erreur à la compilation de forme "stray\nombre in program". Évitez donc de copier-coller les codes, mieux vaut les réécrire.

Donc, ça c'est fait ; maintenant, passons aux choses sérieuses, histoire de réveiller un peu ceux qui se sont déjà endormis :p .

Dans ce tuto, vous allez voir :


Ce plan vous plaît ?

Si oui, c'est parfait ! Si non, ben tant pis... :euh:

Let's go and run ! :pirate:
Sommaire du chapitre :

Déclarer un pointeur de fonction

Bien. Passons-nous d'un long discours, plongeons !

La question est donc :

Comment déclarer un pointeur de fonction ?


Je crois savoir ce que la plupart de ceux qui lisent ce tuto se disent : héhé, fastoche !

D'abord, il faut déclarer un pointeur de type fonction... mouais, pas si simple.

Eurêka ! Utilisons le type void* !

Code : C
1
void* p = NULL;


Maintenant, il faut lui donner l'adresse de la... heu... Mais ça a une adresse, une fonction ? o_O

Oui, bien sûr, sinon on ne parlerait pas de pointeurs. En fait lorsqu'on crée un pointeur de fonction, il contiendra l'adresse de la première instruction de la fonction en question. Mais, franchement, en pratique ce n'est pas vraiment important de le savoir. ;)

Donc, on va mettre l'opérande '&' pour indiquer que c'est une adresse. Le programme final sera donc :

Code : C
1
2
void* p = NULL;
p = &fonction;


Mais ce code ne fonctionne pas. :( Vous pouvez le tester...

La réaction la plus fréquente, après moult essais sera, et c'est justifié :

Citation : ZérO énervé
Aaaaaaarrrrgggg !


Je la comprends, c'est aussi pourquoi je fais ce tuto ^^ .

Nous allons voir dans un premier temps comment créer un pointeur de fonction sans paramètre et ne renvoyant rien. Dans un second, on mettra des paramètres et on gérera le type de la variable retournée par la fonction. :)

Donc, imaginons qu'on ait une fonction nommée fonction (super original, n'est-ce pas ? :D ), définie ainsi :

Code : C
1
2
3
4
5
void fonction(void)
{
    printf("Le programme fonctionne. \n");
    getchar();
}


Certes, elle ne sert à rien, mais elle est parfaite pour la théorie.

Maintenant essayons d'imaginer pourquoi notre code de tout à l'heure ne fonctionnait pas. Tout simplement parce que lorsqu'on écrit :

Code : C
1
void* p;

...pourquoi p serait-il un pointeur sur une fonction et non pas autre chose ?

Il faut donc dans un premier temps indiquer que c'est un pointeur de fonction. Pour cela, on mettra des parenthèses à côté de lui, comme ceci :

Code : C
1
void (*p)();


...où devront être indiqués les paramètres effectifs de la fonction pointée.

Mais, au fait, pourquoi y a-t-il des parenthèses autour de *p ? o_O

C'est une question de priorité (merci rz0). Je me permets ici de citer le K&R :

Citation : K&R, 5.11
* est un opérande utilisé en préfixe et il a une priorité plus faible que () ; c'est pourquoi les parenthèses sont nécessaires pour forcer l'association correcte.


fonction ne prend pas d'arguments, c'est pourquoi on ne met rien dans les parenthèses.

Et voilà ! Notre pointeur est déclaré, manque plus qu'à le définir :

Code : C
1
p = &fonction;


Donc, le code final donne :

Code : C
1
2
void (*p)();
p = &fonction;


Ça y est, nous voilà arrivés au bout de nos peines. Je peux vous assurer qu'à partir de là le reste est vraiment simple ;) .

Par exemple, imaginons maintenant que fonction2 soit définie comme suit :

Code : C
1
2
3
4
int fonction2(int a, int b)
{
    return a += b;
}


Le code va un peu changer. On va devoir déclarer le pointeur de type int, puisque la fonction renvoie un int.

On aura donc :

Code : C
1
2
int (*p)(int, int); /* Avec les types des paramètres */
p = &fonction2;


Ça peut paraître bizarre au début mais on s'y fait vite. ;)

Voilà, vous savez donc créer un pointeur de fonction !

Mais avant de passer à la suite, il faut que je vous sachiez que :

Citation : rz0 dans le topic 'notion de C'
Un nom de fonction est automatiquement converti en pointeur sur cette fonction, il n'y a pas besoin du & mais c'est permis et il n'y a pas vraiment de différence.

...C'est on ne peut plus clair. Du coup, on peut enlever le & lors de la définition du pointeur.

Code : C
1
p = &fonction;

... est donc semblable à :

Code : C
1
p = fonction;


Cependant, je trouve que la présence du '&' permet de bien voir qu'il s'agit d'une adresse. C'est donc à vous de voir, mais je vous conseille de le mettre, ne serait-ce que par souci de compréhension.

Enfin maintenant, nous pouvons passer à la suite... :)

Manier notre pointeur

Dans cette sous-partie, nous verrons :

On aura à peu près fait le tour. :)

La déférenciation du pointeur



Vous allez voir que si vous avez compris la partie précédente, vous n'aurez aucun mal ici. ;)

Nous allons reprendre pour exemple nos deux fonctions précédentes fonction et fonction2.

Donc, pour déférencer un pointeur de variable, vous savez qu'on utilise l'opérateur d'indirection '*'. Pour un pointeur de fonction, que va-t-on utiliser, d'après vous ?

Allez, cherchez un peu quand même. :-°

Bien vu ! (ou pas :p ) La réponse était (*...).

Toujours question de la règle des priorités, à laquelle j'ai fait appel plus haut.

Reste encore que dans l'appel d'une fonction, on indique les variables qui seront passées en paramètre.

Le code de notre programme pour fonction pourrait donc être :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <stdio.h>
 
void fonction()
{
    printf("Le programme fonctionne. \n");
    getchar();
}
 
int main()
{
    void (*p)() = NULL;
    p = &fonction; 
 
    (*p)(); /* On appelle fonction via son pointeur. */
 
    return 0;
}


Résultat :

Code : Console
Le programme fonctionne.


Allelujah ! :)

C'est beau, ces moments là... ^^

Enfin nous avons gagné une bataille, mais pas encore la guerre. ;)
Alors à l'assaut !

Déférençons maintenant notre pointeur de fonction pour fonction2.

On aura alors, par exemple :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
 
int fonction2(int a, int b)
{
    return a * b;
}    
 
int main()
{
    int a = 10, b = 20;
    
    int (*p)(int, int) = NULL;
    
    p = &fonction2;
    
    printf("Resultat : %d", (*p)(a, b));
       
    getchar();   
    return 0;
}


Code : Console
Resultat : 200


Yeeaah !

Mais c'est trop simple ! (Hé ! Je vous avais prévenus ! :p )

Maintenant, je voudrais vous montrer (encore :-° ) une écriture possible.

Elle correspond tout simplement à celle acceptée par la norme ANSI, nous verrons le pourquoi de ce changement quand nous parlerons des structures. Elle stipule que cette ligne :

Code : C
1
(*p)();

... peut dorénavant s'écrire :

Code : C
1
p();


Le pourquoi est un peu dur à saisir au premier abord, alors accrochez-vous, relisez deux ou trois fois s'il le faut :p .

Citation : rz0, commentaires de ce mini-tuto
L'opérateur d'appel de fonction () prend pour opérande à sa gauche un pointeur de fonction.
C'est la propriété même d'une fonction de se convertir implicitement en pointeur sur fonction qui fait que lorsque l'on effectue un appel avec un nom de fonction ordinaire, on utilise réellement un pointeur pour l'appel.
Conceptuellement : (*f)() effectue une double conversion : de pointeur à fonction puis de fonction à pointeur avant d'être appelé.


Ce qui veut dire que lorsqu'on fait par exemple :

Code : C
1
f(a, b);


... f est un pointeur de fonction sur la fonction f.

C'est le même principe que pour les tableaux. ;)

Je crois que j'ai dit tout ce que j'avais à dire sur ce point, passons à un autre qui lui est directement lié.

Le passage d'une fonction dans une autre via un pointeur



Cette partie sera très courte : si vous avez compris la précédente, elle ne devrait pas poser problème.

Ici, nous allons avoir besoin pour mon exemple d'une troisième fonction (que l'on nommera... fonction3, pour changer). En fait, on ne se servira que de fonction2 et fonction3.

Imaginons donc que l'on veuille récupérer le résultat de notre fonction 2 et l'utiliser dans fonction 3, qui est définie ainsi :

Code : C
1
2
3
4
int fonction3(int (*p)(), int d)
{
    return p(a, b) / d;
}


Rien de plus simple, on enverra le pointeur de notre fonction déférencé.

Voici mon fameux code d'exemple :

Code : C
 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
#include <stdio.h>
 
int fonction2(int a, int b)
{
    return a * b;
}   
 
int fonction3(int (*p)(int, int), int a, int b, int d)
{
    return p(a, b) / d;
}
 
int main(void)
{
    int a = 20;
    int b = 20;
    int d = 100;
   
    int (*p)(int, int);
    p = &fonction2;
 
    printf("Resultat : %d \n", (*p)(a, b));
    printf("Resultat : %d \n", fonction3(p, a, b, d));
   
    getchar();
    return 0;
}


Vous devez avoir remarqué que j'ai mélangé les écritures pour vous familiariser aux deux. J'aurais tout de même pu écrire :

Code : C
1
printf("Resultat : %d \n"` fonction3((*p)(a` b)` d));

... mais ça fait beaucoup de parenthèses... ^^

Avouez que ce n'était pas vraiment dur... Mais dans la dernière partie, heureusement, ça va se corser un peu (comme on dit dans le nord de la France... Bon d'accord je sors :lol: ).

La création d'un tableau de pointeurs de fonctions



Ici, rien de bien compliqué non plus, à condition d'être un peu attentifs. Après ça et le point sur les structures, vous serez vite étonnés de ce que vous serez capables de faire, qui se précisera dans la prochaine sous-partie.

Je donne le code et l'explique après si besoin :

Code : C
 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
#include <stdio.h>
 
int fonction2(int a, int b)
{
    return a * b;
}   
 
int fonction3(int c, int d)
{
    return c / d;
}
 
int main()
{
    int a = 20;
    int b = 20;
    int d = 100;
   
    int (*p)(int, int) = NULL;
    p = &fonction2;
   
    int (*p2)(int, int) = NULL;
    p2 = &fonction3;
   
    int (*t[2])() = { p, p2 }; /* Tableau de pointeurs de fonctions. */
 
    printf("Resultat : %d \n", (*p)(a, b));
    printf("Resultat : %d \n", (*p2)(p(a, b), d));
   
    printf ("\n");
   
    printf("Resultat : %d \n", t[0](a, b)); /* On teste avec le tableau */
    printf("Resultat : %d \n", t[1](p(a, b), d));   
   
    getchar();
    return 0;
}


Code : Console
Resultat : 400
Resultat : 4
 
Resultat : 400
Resultat : 4


Toute la nouveauté est ici, même si en fait ce n'est qu'une application de la théorie décrite dans la première partie :

Code : C
1
int (*t[2])() = { p, p2 }; /* Tableau de pointeurs de fonctions. */


Que fait-on concrètement ?

D'abord, on indique que les fonctions retournent un entier : tableau de int.
Ensuite, on indique qu'il s'agit de pointeurs grâce à l'étoile '*'.
Enfin, on indique que ces pointeurs pointent sur des fonctions avec ().

Quant aux autres parenthèses, je crois que vous aurez compris qu'il est encore question de priorité. Je ne m'étendrai pas dessus cinquante fois. Il existe un tableau des priorités - s'y référer si besoin - mais ici, il s'agit encore de la faible priorité de '*' (cf tableau 2-1 du K&R).

Vous noterez tout de même que dans le cas où l'une des fonctions retourne autre chose que le type indiqué lors de la création du tableau, vous devrez faire un cast lors de la définition du pointeur. Par exemple : (long) p = f. Je ne m'y attarderai pas, à vous de faire les tests.

Les pointeurs de fonction et les structures



Au programme :

Soyez attentifs, ouvrez votre IDE (j'espère que vous l'avez déjà fait) et let's go ! :)

Une fonction dans une structure ? Mais c'est simple !

Pourtant, ce code ne fonctionne pas :

Code : C
 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
#include <stdlib.h>
#include <stdio.h>
 
typedef struct structure {
    int fonction2(int, int);
    int fonction3(int, int);
} structure;    
 
int fonction2(int a, int b)
{
    return a * b;
}
 
int fonction3(int c, int d)
{
    return c / d;
}
 
int main()
{
    int a = 20;
    int b = 10;
    int c = 30;
    int d = 3;
    
    structure operateur;
        
    printf("%d \n", operateur.fonction2(a, b));
    printf("%d \n"` operateur.fonction3(c, d));
    
    getchar();
    return 0;
}


Je sais, c'est un peu long mais ça illustre très bien le problème. Nous avons ces deux erreurs :

Citation : IDE
5 | "fichier" | field `fonction2' declared as a function
6 | "fichier" | field `fonction3' declared as a function


En gros : loupé, essaie encore !

Pour palier ce problème, nous allons devoir utiliser les pointeurs de fonctions. Je pense que vous vous en étiez doutés... :D

Nous allons donc déclarer nos pointeurs dans la structure, puis nous les affecterons dans le main.

Aussitôt dit, aussitôt fait :

Code : C
 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
#include <stdlib.h>
#include <stdio.h>
 
typedef struct structure {
    int (*p_f2)(int, int); /* On déclare nos pointeurs. */
    int (*p_f3)(int, int);
} structure;    
 
int fonction2(int a, int b)
{
    return a * b;
}
 
int fonction3(int c, int d)
{
    return c / d;
}
 
int main()
{
    int a = 20;
    int b = 10;
    int c = 30;
    int d = 3;
    
    structure operateur;
    
    operateur.p_f2 = &fonction2; 
    operateur.p_f3 = fonction3; /* Sans le '&' mais c'est pareil. */
 
    printf("%d \n", (*operateur.p_f2)(a` b));
    printf("%d \n", operateur.p_f3(c` d));
    
    getchar();
    return 0;
}


Ce code-ci compile (merci louisclem au passage, on se comprend ;) ).

Voilà, j'ai fait le tour de ce que je voulais vous enseigner ici de théorique, et il me semble que j'ai fait le tour en général des pointeurs de fonctions.

Pour ce qui est de la pratique, les deux travaux pratiques qui figuraient dans ma troisième partie sont obsolètes (comprendre : pas représentatifs des connaissances acquises) ; je vais tout de même tenter d'en trouver de nouveaux, vous pouvez donc revenir ici à vos moments perdus voir si un ou deux TP ne sont pas sortis ;) .

En espérant ne pas avoir dit trop de bêtises,
++ !

mleg.


À propos



Je voulais passer un mot à tomasc, qui a commencé un big-tuto sur les pointeurs, c'est pourquoi je lui ai envoyé un MP. Puis j'ai attendu quelques jours avant de me mettre au boulot, mais n'ayant pas reçu de réponse, ni favorable, ni négative, je me suis dit que je pouvais commencer. Donc voilà : tomasc, si tu comptais aborder ce point cet été, je m'en excuse.

De plus, je voudrais remercier Oli pour son bêta-test plus qu'utile et constructif... ;)

Cette fois, c'est fini... :p
Auteur : mleg
Noter et commenter ce tutoriel
Imprimer ce tutoriel

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | Fil RSS | XHTML 1.0 | CSS 2.0
Édité par Simple IT SARL : Nous contacter | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 562 Zéros connectés | Requêtes SQL 7 requêtes | Temps de génération de la page : Total (SQL) 0.0255s (0.0088s)