Les tableaux
Ce que ce chapitre apporte
- Déclarer un tableau, l'initialiser, et accéder à ses éléments par leur indice.
- Parcourir un tableau sans jamais sortir de ses bornes.
- Calculer le nombre d'éléments d'un tableau avec sizeof, et savoir où ce calcul cesse de fonctionner.
- Piloter plusieurs sorties à partir d'un tableau de numéros de broches.
- Calculer une somme, une moyenne et un maximum en un seul parcours.
- Estimer la mémoire occupée par un tableau.
Une LED, c'est une variable. Huit LEDs, c'est huit variables, huit pinMode, huit digitalWrite recopiés, et autant d'occasions de se tromper de numéro. Le tableau range ces valeurs sous un seul nom et les rend parcourables par une boucle. Il apporte aussi un risque que le pseudo-code ne connaissait pas : en C, sortir d'un tableau ne provoque aucune erreur, seulement des résultats faux.
Un nom pour plusieurs valeurs
Un tableau est une suite de valeurs du même type, rangées côte à côte en mémoire sous un seul nom. Chaque élément est désigné par son indice, qui commence à 0.
type nom[taille]; // gabarit de déclaration
type nom[] = {valeur, valeur, ...}; // la taille est déduite du nombre de valeurs
Un tableau de n éléments a des indices de 0 à n − 1. Le dernier élément de seuils est seuils[2], pas seuils[3].
Sortir des bornes
En Python, lire un indice qui n'existe pas lève une erreur. En C, rien ne se produit : le programme lit ou écrit la case mémoire située juste après le tableau, qui appartient à une autre variable.
mesures[5] dans un tableau de cinq éléments rend une valeur quelconque, celle qui se trouve là. Écrire mesures[5] = 0 est pire : cela modifie silencieusement une autre variable du programme, et le défaut apparaît ailleurs, sans rapport visible avec la ligne fautive.La cause est presque toujours la même : une boucle écrite avec
<= au lieu de <.
La forme sûre d'un parcours est toujours celle-ci : partir de 0, s'arrêter strictement avant le nombre d'éléments.
Compter les éléments avec sizeof
sizeof(mesures) rend la place occupée par le tableau entier, en octets ; sizeof(mesures[0]) celle d'un élément. Leur quotient est le nombre d'éléments, et il se met à jour tout seul si l'on ajoute une valeur dans les accolades.
sizeof mesure cette adresse, et le quotient est faux. C'est pourquoi une fonction qui traite un tableau reçoit toujours sa taille en second paramètre, comme le montre le chapitre suivant.
Un tableau de broches
Le tableau prend tout son intérêt quand il contient des numéros de broches. Le programme ne dépend plus du nombre de LEDs : il suffit d'ajouter un numéro entre les accolades.
Résumer une série en un parcours
Somme, moyenne et maximum se calculent tous en parcourant le tableau une seule fois.
maximum à 0 paraît naturel, et fonctionne tant que toutes les valeurs sont positives. Sur des relevés tous négatifs, aucun ne dépasse 0, et le programme annonce un maximum de 0, une valeur qui n'est pas dans le tableau. Partir du premier élément règle le problème dans tous les cas.
Ce qu'un tableau coûte en mémoire
Un tableau occupe le nombre d'éléments multiplié par la taille d'un élément. Sur une carte qui dispose de 2 Ko de mémoire vive, le calcul se fait avant d'écrire la déclaration.
| Déclaration | Place occupée | Part des 2 Ko |
|---|---|---|
int lectures[200]; | 400 octets | environ 20 % |
byte niveaux[200]; | 200 octets | environ 10 % |
float temperatures[200]; | 800 octets | environ 40 % |
Une lecture analogique, de 0 à 1023, ne tient pas dans un byte. Un niveau de PWM, de 0 à 255, y tient : le ranger dans un int gaspille la moitié de la place.
Exercices type
Un programme calcule la moyenne de int lectures[5] = {10, 20, 30, 40, 50} avec for (int i = 0; i <= 5; i++). Qu'affiche-t-il ?
On ne peut pas le prévoir. La boucle fait six tours et additionne lectures[5], une case qui n'appartient pas au tableau. La somme contient donc une valeur quelconque, et la moyenne est fausse.
La correction : for (int i = 0; i < 5; i++), et une division qui garde les décimales, (float)somme / 5. Le programme affiche alors 30.00.
Sur les relevés {-3, -7, -1, -12}, un calcul de maximum initialisé à 0 affiche 0. Quelle est la bonne réponse, et comment l'obtenir ?
Le maximum est -1. Aucun relevé ne dépasse 0, si bien que la valeur initiale n'est jamais remplacée.
Initialiser le maximum avec le premier élément, int maximum = releves[0];, garantit que le résultat est toujours une valeur présente dans le tableau.
Un enregistreur doit conserver les 200 dernières lectures de analogRead. Quel type choisir, et quelle part de la mémoire vive de l'Uno cela représente-t-il ?
analogRead rend une valeur de 0 à 1023, qui ne tient pas dans un byte : il faut un int. Deux cents int occupent 400 octets, soit environ 20 % des 2 Ko de mémoire vive.
Si seules des valeurs ramenées de 0 à 255 étaient nécessaires, un tableau de byte diviserait cette place par deux.
Un chenillard pilote les broches {2, 3, 4, 5, 6, 7}, chaque LED restant allumée 150 ms. Combien dure un cycle complet ?
Six LEDs de 150 ms chacune : ms.
L'intérêt du tableau apparaît ici : passer de quatre à six LEDs n'a demandé que d'ajouter deux numéros entre les accolades. NB, calculé par sizeof, s'est mis à jour tout seul, et aucune boucle n'a été modifiée.
Vérification
1.Quels sont les indices valides d'un tableau de 8 éléments ?
2.Que se passe-t-il en C quand on lit un indice situé hors du tableau ?
3.Que calcule sizeof(t) / sizeof(t[0]) là où le tableau t est déclaré ?
4.Pourquoi une fonction qui traite un tableau reçoit-elle sa taille en paramètre ?
5.Un maximum initialisé à 0 donne 0 sur une série de relevés. Quelle peut en être la cause ?
La méthode
- Calculer la taille avec
sizeof(t) / sizeof(t[0]), là où le tableau est déclaré, et la ranger dans une constante. - Écrire tout parcours sous la forme
for (int i = 0; i < NB; i++), et se méfier de chaque<=. - Initialiser un maximum ou un minimum avec le premier élément, jamais avec une valeur arbitraire.
- Convertir en
floatavant de diviser quand une moyenne doit garder ses décimales. - Chiffrer la mémoire occupée avant de déclarer un grand tableau, et choisir le plus petit type qui contient toutes les valeurs.
- Passer la taille en paramètre à toute fonction qui reçoit un tableau.
Synthèse
- Un tableau range des valeurs du même type sous un nom ; ses indices vont de 0 à n − 1.
- Sortir des bornes ne provoque aucune erreur en C : la lecture est quelconque, l'écriture corrompt une autre variable.
for (int i = 0; i < NB; i++)est la forme sûre de parcours.sizeof(t) / sizeof(t[0])donne le nombre d'éléments, uniquement là où le tableau est déclaré.- Un tableau de broches rend le programme indépendant du nombre de composants.
- Un maximum part du premier élément ; une moyenne se calcule après conversion en
float. - La place occupée vaut le nombre d'éléments multiplié par la taille du type : sur l'Uno, elle se compte en pourcentage de 2 Ko.
Mettre en pratique
Bornes des indices, place occupée en mémoire, et les trois parcours utiles.
- Les bornes d'un tableauNiveau 1
- La place que prend un tableauNiveau 2
- Parcourir et résumer un tableauNiveau 2