Après les simples pointeurs, voyons des usages plus rares, mais toujours aussi puissants...
Les pointeurs
Les pointeurs (2)
Suite et fin
.
Par :
DIFFICULTÉ :★★★
Les poupées Russes : les pointeurs de pointeur.
Un pointeur est en réalité un entier qui représente une adresse, mais c’est avant tout un entier. Et la conséquence immédiate de cette observation, c’est que tout ce qui est applicable à un entier l’est aussi à un pointeur. En particulier les opérations arithmétiques, comme on l’a vu précédemment. Cet entier a aussi besoin d’occuper un espace mémoire pour stocker sa valeur. On retombe sur le contenant et le contenu ! L’adresse d’un pointeur est un pointeur, et son contenu est une adresse
int val = 123;
int *pVal = &val;
int **ppVal = &pVal;
int ***pppVal = &ppVal;
Serial.println(val, DEC); // Affiche 123
Serial.println(*pVal, DEC); // Affiche 123 aussi !
Serial.println(**ppVal, DEC); // Affiche 123 aussi !!
Serial.println(***pppVal, DEC); // Affiche 123 aussi !!!
Et ainsi de suite... Le nombre d’étoiles devant le nom détermine la profondeur du pointeur. pppVal est un pointeur de pointeur de pointeur d’entier ! Le contenu de pppVal est un pointeur. Le contenu de *pppVal est encore un pointeur . Le contenu de **pppVal est encore (encore !) un pointeur. Enfin le contenu de ***pppVal est bien l’entier !
Et vous allez me dire, "mais à quoi ça sert ?", et vous aurez raison de poser la question. Prenons un cas simple. Je veux allouer une zone de mémoire, et modifier un pointeur passé en argument d’une fonction :
void Allocate(int **ppTableau, int size)
{
*ppTableau = new int[size];
}
setup()
{
int *pTableau;
Allocate(&pTableau, 100);
...
}
Je passe ainsi l’adresse de mon pointeur pTableau comme argument de type int ** (pointeur de pointeur d’entier). L’allocation se fait par un new qui remplit le contenu de ce pointeur. Une étoile en moins donne le contenu de l’argument. A la fin du setup, le pointeur pTableau a bien été alloué de 100 entiers.
Encore pire : les pointeurs de fonction
On a vu que toutes les données nécessitant un contenu, types de base, tableaux, structures, classes sont adressées par un pointeur. Même si aucune donnée n’est transportée, les fonctions sont aussi des pointeurs. Elles ne représentent pas une adresse dans la SRAM puisqu’il n’y a aucun contenu, mais ce sont des adresses dans la mémoire Flash. Il est ainsi possible de stocker une adresse de fonction, de la passer en argument, et d’appeler la fonction qui est derrière ce pointeur.
Déclaration d’un pointeur de fonction
Un pointeur de fonction est différent d’un entier. Il doit tenir compte de la signature de la fonction à pointer. C’est à dire que le type de retour et les arguments doivent être rigoureusement les mêmes pour que le pointeur accepte de stocker l’adresse. Il faut donc déclarer le format de ce pointeur :
byte (*pFonction)(int argument);
Cette ligne précise que le pointeur nommé pFonction
peut être l’adresse d’une fonction dont le type de retour est un byte
, et doit posséder un argument entier.
L’affectation est toute simple :
int fonction(int value)
{
// fait des choses avec la valeur...
return 128;
}
byte (*pFonction)(int val);
setup()
{
pFunction = &fonction;
}
pFunction
contient l’adresse de fonction
. Reste à utiliser le pointeur pour appeler la fonction :
if ( (*pFunction) (10) > 64)
...
Mais alors...
Ceux qui ont suivi les articles sur Le Monde des Objets ont peut être fait le rapprochement. Lorsqu’une fonction est virtuelle, c’est à dire qu’elle peut être redéfinie dans une classe dérivée, c’est un pointeur de fonction qui est utilisé...
Et maintenant...
Que vais je faire ? C’est plutôt à vous de vous poser la question. Le domaine des pointeurs est très vaste. C’est une notion très puissante et très pratique, mais elle est aussi difficile à appréhender pour les non informaticiens. Vous trouverez sur le Net quantité de tutoriaux sur le sujet. et tout ce qui est applicable au langage C est transposable à l’Arduino.