Pointeurs sur fonction

Aujourd'hui j'ai eu envie de me faire plaisir et comme j'adore coder (à mon tout petit niveau bien sûr) je me suis dit que j'allais travailler un tout petit peu sur "les pointeurs sur fonction". Bon bien sûr les "pros" (sourire) n'y apprendrons rien :wink: mais on ne sait jamais, un débutant comme moi pourra tout à fait y trouver un interêt.
Toute fonction dispose d'une adresse du code binaire qui la constitue, bon là je ne rentre pas trop dans les détails, je reste simple :

Imaginons que je veuille déclarer un pointeur sur une fonction qui calcule la somme de deux entiers d'un octet non signé :

uint8_t somme (uint8_t x, uint8_t y)
{
  return x + y;
}

il faut que m'y prenne comme ceci :

type (*identificateur)(paramètres);

Pour la fonction somme ça donne ça :

uint8_t (*ptrFonct)(uint8_t, uint8_t); // pointeur sur fonction avec deux paramètres

Voici un exemple complet avec deux fonctions :

uint8_t (*ptrFonct)(uint8_t, uint8_t); // pointeur sur fonction avec deux paramètres

void setup() {
  Serial.begin(115200);
  ptrFonct = &somme; // affectation de l'adresse de la fonction somme
  Serial.println((*ptrFonct) (4,9));
  ptrFonct = &multiplication;  // affectation de l'adresse de la fonction multiplication
  Serial.println((*ptrFonct) (2,3));
}

void loop() {}

uint8_t somme (uint8_t x, uint8_t y)
{
  return x + y;
}

uint8_t multiplication (uint8_t x, uint8_t y)
{
  return x * y;
}

C'est simple non ?

Après voici un autre exemple pour les jeunes qui voudront se souvenir des tables de multiplications :

uint8_t (*ptrFonct)(uint8_t, uint8_t);

void setup() {
  Serial.begin(115200);
  ptrFonct = &multiplication;  // affectation de l'adresse de la fonction multiplication

}

void loop() {
  uint8_t quelleTable;
  Serial.println(F("Entrez un chiffre de 1 à 10 (vous pouvez dépasser mais n'oubliez pas que vous travaillez avec des octets non signés)"));
  quelleTable = entree();
  for (uint8_t x = 0; x < 10; x++) {
    Serial.print(quelleTable); Serial.print(" * "); Serial.print(x + 1); Serial.print(" = ");
    Serial.println((*ptrFonct) (quelleTable, x + 1));
  }
  Serial.print('\n');
}

uint8_t multiplication (uint8_t x, uint8_t y)
{
  return x * y;
}

uint8_t entree()
{
  char caractere;
  while (!Serial.available());
  uint8_t chiffre = Serial.parseInt();
  while (Serial.readBytes(&caractere, 1) != 0) {
    if (caractere == '\r') {
      break;
    }
  }
  return chiffre;
}

Voilà c'est plutôt du C ici mais on retrouve des pointeurs de fonctions quand on applique par exemple des méthodes à des objets (POO) mais là c'est une autre histoire.

PS : Je ne fais pas cette toute petite synthèse pour faire le malin mais juste pour apporter peut-être un petit plus à un débutant comme moi et surtout ça me permet de synthétiser mes connaissances et de mieux les retenir.

pour info, c'est comme pour les tableaux, le nom de la fonction est aussi un pointeur vers la fonction et le C++ sait ce qu'est un pointeur sur fonction, donc pas la peine de la forme alambiquée d'appel que vous utilisez

vous pouvez écrire simplement

void setup() {
  Serial.begin(115200);

  ptrFonct = somme;                // affectation à la fonction somme
  Serial.println(ptrFonct(4, 9));  // appel de la fonction somme

  ptrFonct = multiplication;      // affectation à la fonc tion multiplication
  Serial.println(ptrFonct(2, 3));  // appel de la fonction multiplication
}

Merci @J-M-L
C’est vrai que c’est plus simple comme ça :wink:
Merci

Du coup voici la correction pour le programme concernant les tables de multiplications :

uint8_t (*ptrFonct)(uint8_t, uint8_t);

void setup() {
  Serial.begin(115200);
  ptrFonct = multiplication;  // affectation de l'adresse de la fonction multiplication

}

void loop() {
  uint8_t quelleTable;
  Serial.println(F("Entrez un chiffre de 1 à 10 (vous pouvez dépasser mais n'oubliez pas que vous travaillez avec des octets non signés)"));
  quelleTable = entree();
  for (uint8_t x = 0; x < 10; x++) {
    Serial.print(quelleTable); Serial.print(" * "); Serial.print(x + 1); Serial.print(" = ");
    Serial.println((ptrFonct) (quelleTable, x + 1));
  }
  Serial.print('\n');
}

uint8_t multiplication (uint8_t x, uint8_t y)
{
  return x * y;
}

uint8_t entree()
{
  char caractere;
  while (!Serial.available());
  uint8_t chiffre = Serial.parseInt();
  while (Serial.readBytes(&caractere, 1) != 0) {
    if (caractere == '\r') {
      break;
    }
  }
  return chiffre;
}

Bonne soirée.

PS : Du coup c'est bien utile d'avoir des échanges avec un "PRO" :joy: et bien sûr c'est du second degré :wink:

1 Like

vous avez des parenthèses en trop encore :slight_smile:

et pour la lisibilité vous pourriez faire

  for (uint8_t x = 1; x <= 10; x++) {
    ...
  }

et ne pas faire x + 1 dans l'appel de fonction

mais bon, c'est du pinaillage

1 Like

Non ce n’est pas du pinaillage :wink:
Merci @J-M-L

Ce genre d'outil est très utile : je m'en étais servi il y a quelques années pour faire des animations aléatoires avec des leds. Un tableau de fonctions, un nombre aléatoire et ça lançait l'animation correspondant à la place de ce nombre dans le tableau.

Une info en plus : les déclarations (i.e. les prototypes) des fonctions doivent avoir la même forme (même nombre et mêmes types d'arguments et de sortie). C'est vrai dans ton cas (addition et multiplication de deux entiers).

Merci du partage.

2 Likes

Bonjour @lesept,
Bonjour à tous,

Pour illustrer l'utilisation de tableaux de pointeurs de fonction, je me suis amusé ce matin à réaliser une librairie ( il faut que je pense objet maintenant pour progresser dans ce domaine) qui permet à partir de deux chiffres d'effectuer des opérations basiques :

  • multiplications ;
  • sommes ;
  • division;
  • soustraction.

Cette fois-ci je travaille avec des "float". Il suffit d'entrer deux chiffres dans le moniteur série et de choisir le type d'opération et le moniteur série affiche le résultat.

INO :

#include "Operations.h"
float quoi[3];
void setup() {
  Serial.begin(115200);

}

void loop() {
  Serial.println(F("Pour un nombre décimal utilisez le POINT"));
  Serial.println(F("Entrez chiffre 1"));
  quoi[0] = entree();
  Serial.print(quoi[0]);Serial.print('\n');
  
  Serial.println(F("Entrez chiffre 2"));
  quoi[1] = entree();
  Serial.println(quoi[1]);Serial.print('\n');
  
  Serial.println(F("Entrez le type d'opération"));
  Serial.println(F("0 = multiplication"));
  Serial.println(F("1 = addition"));
  Serial.println(F("2 = division"));
  Serial.println(F("3 = soustraction"));
  Serial.print('\n');
  quoi[2] = entree();
  Operations Op(quoi[0],quoi[1], quoi[2]);
  Op.affiche();
}

float entree()
{
  char caractere;
  while (!Serial.available());
  float chiffre = Serial.parseFloat();
  while (Serial.readBytes(&caractere, 1) != 0) {
    if (caractere == '\r') {
      break;
    }
  }
  return chiffre;
}

header :

#include <arduino.h> 

#ifndef Operations_h
#define Operations_h

class Operations {
  public:
    // Constructeur
    Operations(float x, float y, uint8_t typeOperation);
    void affiche (void);

    // Destructeur
    ~Operations();
    
  private:
    float _x;
    float _y;
    uint8_t _typeOperation;
};

#endif

CPP :

#include "Operations.h"

typedef float (*ptrFonct)(float, float);
ptrFonct typeOperateur[4];


float multiplication (float x, float y)
{
  return x * y;
}

float somme (float x, float y)
{
  return x + y;
}

float division (float x, float y)
{
  return x / y;
}

float soustraction (float x, float y)
{
  return x - y;
}



// Initialisation de la classe
Operations::Operations(float x, float y, uint8_t typeOperation)
  : _x(x),
    _y(y),
    _typeOperation(typeOperation)

{}

// Destruction
Operations::~Operations() {}

void Operations::affiche()
{
  typeOperateur[0] = multiplication;
  typeOperateur[1] = somme;
  typeOperateur[2] = division;
  typeOperateur[3] = soustraction;
  
  Serial.print(_x);
  switch (_typeOperation) {
    case 0 :
    Serial.print(" * ");
    break;
    case 1 : 
    Serial.print(" + ");
    break;
    case 2 : 
    Serial.print(" / ");
    break;
    case 3 : 
    Serial.print(" - ");
    break;
  }


  Serial.print(_y); Serial.print(" = ");
  Serial.println(typeOperateur[_typeOperation] (_x, _y));

  Serial.print('\n');
}

Voilà c'est tout ce qu'il y a de plus basique mais ça illustre bien l'utilisation du concept de tableaux de pointeurs de fonction.

Bonne journée à tous.

PS : Excusez le manque d'optimisation, je suis passé du diagnostique de rhino-pharyngite à celui d'angine avec antibiotiques du coup mes neurones sont moins efficaces.

Si tu pense passer objet, il ne devrait plus être nécessaire de passer par des pointeur sur fonction :slight_smile:

Bonsoir @terwal
Du coup tu penses qu’il vaut mieux utiliser des méthodes de classe pour effectuer les différentes opérations (soustraction, multiplication…) ?
C’est pour m’entraîner avec la POO que j’ai mis en évidence ce tableau de pointeur de fonctions au travers d’une classe mais c’est vrai que c’est peut être un non sens ?

Ce sont des questions ?
Merci par avance

Du coup tu préfères ça ?

INO :

#include "Operations.h"
float quoi[3];
void setup() {
  Serial.begin(115200);

}

void loop() {
  Serial.println(F("Pour un nombre décimal utilisez le POINT"));
  Serial.println(F("Entrez chiffre 1"));
  quoi[0] = entree();
  Serial.print(quoi[0]); Serial.print('\n');

  Serial.println(F("Entrez chiffre 2"));
  quoi[1] = entree();
  Serial.println(quoi[1]); Serial.print('\n');

  Serial.println(F("Entrez le type d'opération"));
  Serial.println(F("0 = multiplication"));
  Serial.println(F("1 = addition"));
  Serial.println(F("2 = division"));
  Serial.println(F("3 = soustraction"));
  Serial.print('\n');
  quoi[2] = entree();
  Operations Op(quoi[0], quoi[1]);
  if ((int) quoi[2] == 0)  Op.multiplication();
  else if ((int) quoi[2] == 1)  Op.somme();
  else if ((int) quoi[2] == 2)  Op.division();
  else if ((int) quoi[2] == 3)  Op.soustraction();
}

float entree()
{
  char caractere;
  while (!Serial.available());
  float chiffre = Serial.parseFloat();
  while (Serial.readBytes(&caractere, 1) != 0) {
    if (caractere == '\r') {
      break;
    }
  }
  return chiffre;
}

header :

#include <arduino.h>

#ifndef Operations_h
#define Operations_h

class Operations {
  public:
    // Constructeur
    Operations(float x, float y);
    void multiplication (void);
    void somme (void);
    void division (void);
    void soustraction (void);

    // Destructeur
    ~Operations();

  private:
    float _x;
    float _y;
};

#endif

CPP :

#include "Operations.h"

// Initialisation de la classe
Operations::Operations(float x, float y)
  : _x(x),
    _y(y)
{}

// Destruction
Operations::~Operations() {}


void Operations::multiplication()
{
  Serial.print(_x); Serial.print(" * "); Serial.print(_y); Serial.print(" = "); Serial.print( _x * _y);
  Serial.print('\n');
}

void Operations::somme()
{
  Serial.print(_x); Serial.print(" + "); Serial.print(_y); Serial.print(" = "); Serial.print( _x + _y);
  Serial.print('\n');
}

void Operations::division()
{
  Serial.print(_x); Serial.print(" / "); Serial.print(_y); Serial.print(" = "); Serial.print( _x / _y);
  Serial.print('\n');
}

void Operations::soustraction()
{
  Serial.print(_x); Serial.print(" - "); Serial.print(_y); Serial.print(" = "); Serial.print( _x - _y);
  Serial.print('\n');
}

Ma démonstration est donc foireuse ?
:joy:

Bonjour Philippe,
Pas exactement, je ne suis pas sûre du pourquoi tu as besoin de passer par des pointeurs sur fonction, au delà de l'exercice pratique.
J'ai supposé que tu voulais pouvoir redéfinir les fonctions en fonction de tes objets, mais j'ai un gros doute.
Ce que je pensais c'est effectivement des fonctions de classes, voir d'instance, mais surtout que ton objet implémente une interface, ce qui te permettrais d''avoir pour chaque classe une implémentation différente des fonctions, mais ton code peut utiliser c'est objet en connaissant juste l'interface.
Mais que je l'ai dit, j'ai un gros doute que cela corresponde à ce que tu veux faire.

Bonsoir @terwal

En fait j’ai surtout cherché l’exercice pratique et comme en ce moment je suis en plein dans la POO. J’ai inclus un tableau de pointeurs de fonctions dans une méthode de classe, juste pour travailler avec une classe. J’aurai pu le faire sans classe…

Bonne soirée

Oui, c'est ce que j'avais l'impression.
Si tu veux faire de l'objet, tu peux par exemple, faire des fonctions d'instance, qui te permettrais d'avoir un intérêt de créer une instance, pour pouvoir cumuler tes opérations par exemple.
Histoire de mettre aussi en pratique l'orienté objet.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.