Nous avons vu dans « Types, constantes et variables » comment déclarer des données, variables et constantes, et comment les utiliser dans « Calculer avec l’Arduino (1) » et « Calculer avec l’Arduino (2) ». Toutefois, ces données ne peuvent contenir qu’une seule valeur à la fois, ce sont des scalaires [1]. Nous allons maintenant examiner la manière de regrouper des données en paquets, on dit aussi collections, afin de pouvoir, d’une part, les manipuler plus aisément, et, d’autre part, d’améliorer la lisibilité des programmes.
Nous allons nous intéresser aux collections de données hétérogènes, c’est à dire les structures. Les structures permettent de regrouper plusieurs données, chacune ayant un type pour ensuite les manipuler d’un bloc. Les structures forment la base des classes qui sont une forme plus évoluées et qui ont été présentées dans « Le monde des objets (2) » mais, à la différence des classes, elles ne contiennent que des données.
Comment déclarer une structure
La déclaration d’une structure utilise le mot-clé struct
suivi éventuellement d’un nom. Comme la plupart du temps il est préférable de nommer les structures, nous allons le faire systématiquement.
Supposons que nous voulions gérer un feu tricolore composé de 3 DEL et nécessitant donc 3 broches de connexion. Plutôt que d’avoir 3 broches déclarées séparément, il est plus intéressant de regrouper ces 3 broches dans une struct
. Voici la déclaration de cette structure :
struct FeuTricolore {
byte vert;
byte jaune;
byte rouge;
};
Notez qu’à la différence des paires d’accolades employées pour délimiter les boucles, les if ... else
et les switch ... case
, celle utilisée ici est suivie d’un point-virgule.
Ceci n’est pas une déclaration de variable, pas encore. Nous avons juste décrit comment la structure FeuTricolore
est organisée. C’est une sorte de nouveau type de donnée. vert
, jaune
et rouge
sont les membres de la structure. Nous pouvons maintenant déclarer une variable de ce nouveau type, comme ceci :
struct FeuTricolore feuSortieGare;
Ce qui a pour effet de déclarer une variable feuSortieGare
qui a pour type struct FeuTricolore
.
Initialiser une structure
De même que pour une variable scalaire, il est possible de l’initialiser mais il faut pourvoir donner une valeur à chacun des membres. Ceci se fait en listant les valeurs appropriées entre accolades dans l’ordre où les membres apparaissent dans la structure. Comme ceci :
struct FeuTricolore feuSortieGare = { 5, 6, 7 };
La valeur 5 est ainsi attribuée au membre vert
, la valeur 6 au membre jaune
et la valeur 7 au membre rouge
. Ces valeurs étant des numeros de broche, il est logique de rendre la structure constante en ajoutant le mot-clé const
au début.
const struct FeuTricolore feuSortieGare = { 5, 6, 7 };
Utiliser une structure
Il ne suffit pas de déclarer des types structurés. Il faut aussi pouvoir les utiliser en accédant aux membres des variables et constantes de ce type. Pour accéder à un membre d’une structure, il suffit de suffixer le nom de la variable ou de la constante avec un point et le nom du membre. Ainsi pour accéder à chacun des membres de notre structure pour programmer les broches en sortie, on écrira :
pinMode(feuSortieGare.vert, OUTPUT);
pinMode(feuSortieGare.jaune, OUTPUT);
pinMode(feuSortieGare.rouge, OUTPUT);
On peut bien entendu affecter à une variable de type structure une variable ou une constante de même type sans avoir à le faire membre par membre. Supposons que nous ayons 2 variables, feu1
et feu2
déclarées comme ceci :
struct FeuTricolore feu1;
struct FeuTricolore feu2;
On peut ecrire :
feu1 = feu2;
Ce qui engendrera la copie de chacun des membres de feu2
dans les membres correspondant de feu1
.
Des structures dans des structures
Il est tout à fait légal qu’un membre d’une structure soit également une structure. Supposons que nous définissions une structure destinée à représenter un canton. On y trouvera, par exemple, une variable booléenne pour donner l’état d’occupation, une variable booléenne pour donner le sens de circulation et deux feux, un pour pour chaque sens de circulation. Comme nous avons déjà une structure pour les feux, il suffit de la réutiliser dans notre structure Canton
:
struct Canton {
boolean occupe;
boolean sens;
const struct FeuxTricolore feuSensNormal;
const struct FeuxTricolore feuSensInverse;
};
L’initialisation respecte les même règles. Comme les membres feuSensNormal
et feuSensInverse
sont des struct
, leur initialisation nécessitera une paire d’accolades supplémentaire :
struct Canton cantonVoiePrincipale1 = {
false, // inoccupé
true, // sens normal
{ 4, 5, 6 }, // broches du feu sens normal
{ 7, 8, 9 } // broches du feu sens inverse
};
Comme nous pouvons le voir, il ne faut pas hésiter à passer à la ligne et à ajouter des commentaires pour clarifier les initialisations.
L’accès aux membres d’une structure située à l’intérieur d’une structure respecte également les même règles. Il suffit de suffixer une fois de plus pour descendre à l’intérieur de la structure. Ainsi, l’initialisation des broches en sortie pour les deux feux du canton s’écrira de cette manière :
pinMode(cantonVoiePrincipale1.feuSensNormal.vert, OUTPUT);
pinMode(cantonVoiePrincipale1.feuSensNormal.jaune, OUTPUT);
pinMode(cantonVoiePrincipale1.feuSensNormal.rouge, OUTPUT);
pinMode(cantonVoiePrincipale1.feuSensInverse.vert, OUTPUT);
pinMode(cantonVoiePrincipale1.feuSensInverse.jaune, OUTPUT);
pinMode(cantonVoiePrincipale1.feuSensInverse.rouge, OUTPUT);
gagner de la place
Pour terminer avec les struct
, nous nous devons de mentionner une possibilité qui est peu utilisée mais qui, au regard de la faible capacité en mémoire de données des Arduino, se révèle particulièrement utile.
Normalement, il est impossible de déclarer des données d’une taille inférieure à 1 octets. Malgré cela, les structures permettent de déclarer des membres en spécifiant le nombre de bits utilisés. Pour chacun des membres, il suffit d’ajouter deux points suivi du nombre de bits voulus. Par exemple, dans notre exemple de structure, on sait que les deux membres booléens ne nécessitent qu’un seul bit car seules deux valeurs sont nécessaires [2]. Si par ailleurs les DEL des feux seront branchées sur les broches 0 à 13 de l’Arduino, seuls 4 bits sont nécessaires pour coder le numéro de broche [3]. Nous pouvons donc déclarer nos structures comme ceci :
struct FeuTricolore {
byte vert:4;
byte jaune:4;
byte rouge:4;
};
Pour indiquer que chacun des membres occupe 4 bits, soit 12 bits au total, soit 2 octets car la taille totale de la structure est arrondie à l’octet supérieur. La structure est compactée et nous avons donc gagné un octet par rapport à la déclaration originale.
Le programme n’a pas à être modifié pour utiliser ces structures plus compactes, le compilateur se charge de tout.
Ceci est à manier avec précaution. Si une valeur trop grande est écrite dans un membre, un débordement se produira silencieusement. Comme toujours, il est essentiel de veiller à ce que les tailles des variables, et donc des membres, soient suffisantes pour y stocker les informations que l’on souhaite traiter.De plus, si l’utilisation des structures compactées permet de gagner de la place en mémoire de données, elle produit un programme plus lent dans les portions où les membres des structures sont manipulés.
Un prochain article traitera des collections homogènes : les tableaux.