18. Tableaux▲
Nous avons défini cinq variables dans un des exercices du dernier chapitre et nous les avons utilisés dans certains calculs. Les définitions de ces variables étaient les suivantes :
double
valeur_1;
double
valeur_2;
double
valeur_3;
double
valeur_4;
double
valeur_5;
Cette méthode de définir des variables individuellement n'est pas du tout efficace dans les cas où il y a besoin d'encore plus de variables. Imaginez qu'on ait besoin d'un millier de valeurs ; il est pratiquement impossible de définir 1000 variables de valeur_1 à valeur_1000.
Les tableaux sont utiles dans de tels cas : les tableaux permettent la définition de beaucoup de valeurs ensemble. Les tableaux sont aussi la structure de données la plus fréquente quand de multiples valeurs sont utilisées ensemble en tant que collection.
Ce chapitre couvre seulement quelques-unes des fonctionnalités des tableaux. Plus de fonctionnalités seront introduites dans un chapitre ultérieur.
18-1. Définition▲
La définition de tableaux est vraiment similaire à la définition de variables. La seule différence est que le nombre de variables qui sont définies en même temps est indiqué entre crochets. On peut distinguer les deux définitions suivantes :
int
uneSimpleVariable;
int
[10
] tableauDeDixVariables ;
La première ligne ci-dessus est la définition d'une simple variable, tout à fait comme les variables que nous avons définies jusqu'alors. La deuxième ligne est la définition d'un tableau consistant en dix variables.
De même, l'équivalent des cinq variables séparées de l'exercice peut être défini comme un tableau de cinq variables en utilisant la syntaxe suivante :
double
[5
] valeurs;
Cette définition peut être lue comme cinq valeurs double. Notez qu'un pluriel a été choisi pour le nom du tableau pour éviter de le confondre avec une variable simple.
En résumé, la définition d'un tableau se compose du type des variables, de leur nombre et du nom du tableau :
nom_du_type[nombre_de_variables] nom_du_tableau;
Le type des variables peut aussi être un type défini par le programmeur (nous verrons les types définis par le programmeur plus tard). Par exemple :
// Un tableau qui stocke l'information météorologique de
// toutes les villes. Ici, les valeurs booléennes veulent dire :
// false : couvert
// true : ensoleillé
bool[nombreDeVilles] ConditionsMeteorologiques;
// Un tableau qui stocke le poids de cent boîtes
double
[100
] poidsDesBoites;
// Information à propos des étudiants d'une école
DonneeEtudiant[NombredDEtudiants] DonneesEtudiants;
18-2. Éléments et conteneurs▲
Les structures de données qui rassemblent des éléments d'un certain type sont appelées conteneurs. Selon cette définition, les tableaux sont des conteneurs. Par exemple, un tableau qui stocke les températures de l'air des jours de juillet peut rassembler 31 variables double et forme un conteneur d'éléments de type double.
Les variables d'un conteneur sont appelées éléments. Le nombre d'éléments d'un tableau est appelé la longueur d'un tableau.
18-3. Accéder aux éléments▲
Pour différencier les variables dans l'exercice du chapitre précédent, nous devions ajouter un tiret du bas et un nombre à leur nom (par exemple, valeur_1). Ce n'est ni possible, ni nécessaire quand les variables sont définies ensemble comme un seul tableau avec un seul nom. On accède aux éléments en indiquant leur numéro entre crochets :
valeurs[0
]
Cette expression peut être lue comme « l'élément numéro 0 du tableau de nom « valeurs » ». En d'autres termes, au lieu de taper valeur_1, on tape valeurs[0] avec les tableaux. Il y a deux points importants qu'il vaut la peine de relever :
- les numéros commencent à 0 : même si les humains comptent à partir de 1, les indices de tableaux commencent à 0. Les valeurs que nous avons numérotées 1, 2, 3, 4 et 5 sont numérotées 0, 1, 2, 3 et 4 dans le tableau. Cette spécificité est une cause de nombreuses erreurs de programmation ;
- deux utilisations différentes des crochets : ne les confondez pas. Quand on définit un tableau, les crochets sont écrits après le type des éléments et indiquent le nombre d'éléments. Quand on accède aux éléments, les crochets sont écrits après le nom du tableau et indiquent le numéro de l'élément auquel on accède :
// Ceci est une définition. Elle définit un tableau qui consiste
// en 12 éléments. Ce tableau est utilisé pour stocker le nombre
// de jours de chaque mois.
int
[12
] joursDuMois;
// Ceci est un accès. On accède à l'élément qui
// correspond à décembre et on lui donne la valeur 31.
joursDuMois[11
] =
31
;
// Ceci est un autre accès. On accède à l'élément qui
// correspond à janvier, sa valeur est passé à writeln.
writeln
(
"Janvier a "
, joursDuMois[0
], " jours."
);
Rappel : les numéros des éléments de janvier et décembre sont respectivement 0 et 11, non 1 et 12.
18-4. Indice▲
Le numéro d'un élément est appelé son indice.
Un indice n'a besoin d'être une valeur constante ; la valeur d'une variable peut aussi être utilisée comme un indice, ce qui rend les tableaux encore plus utiles. Par exemple, le mois est déterminé par la valeur de la variable IndiceMois ci-dessous :
writeln
(
"Ce mois a "
, joursDuMois[IndiceMois], " jours."
);
Quand la valeur de IndiceMois est 2, l'expression ci-dessus affiche la valeur de joursDuMois[2], le nombre de jours du mois de mars.
Seuls les indices entre 0 et la longueur du tableau moins 1 sont valides. Par exemple, les indices valides d'un tableau à 3 éléments sont 0, 1 et 2. Accéder à un tableau avec un mauvais indice entraîne l'arrêt du programme avec une erreur.
Les tableaux sont des conteneurs dans lesquels les éléments sont placés les uns à côté des autres dans la mémoire de l'ordinateur. Par exemple, les éléments du tableau qui stocke le nombre de jours dans chaque mois peuvent être vus comme ceci :
L'élément d'indice 0 a la valeur 31 (nombre de jours en janvier) ; l'élément d'indice 1 a la valeur 28 (nombre de jours en février), etc.
18-5. Tableaux de taille fixe vs tableaux dynamiques▲
Quand la taille du tableau est indiquée lorsque le programme est écrit, ce tableau est de taille fixe. Quand la longueur peut changer pendant l'exécution du programme, ce tableau est dynamique.
Les tableaux que nous avons définis au-dessus sont des tableaux à taille fixe parce que leur nombre d'éléments est indiqué lors de l'écriture du programme (5 et 12). Les longueurs de ces tableaux ne peuvent pas être changées pendant l'exécution du programme. Pour changer leur longueur, le code source doit être modifié et le programme doit être recompilé.
Définir un tableau dynamique est plus simple que définir un tableau à taille fixe ; il suffit d'omettre la taille et on obtient un tableau dynamique :
int
[] tableauDynamique;
La taille d'un tel tableau peut augmenter ou diminuer pendant l'exécution du programme.
18-6. .length pour récupérer ou changer la taille du tableau▲
Les tableaux ont également des attributs. Ici, nous ne verrons que .length, qui retourne le nombre d'éléments du tableau.
writeln
(
"Le tableau a"
, tableau.length, " éléments."
);
De plus, la taille des tableaux dynamiques peut être modifiée en affectant une valeur à cet attribut :
int
[] array; // initialement vide
tableau.length =
5
; // maintenant, il y a 5 éléments
Voyons maintenant comment on pourrait réécrire l'exercice avec les cinq valeurs en utilisant un tableau :
import
std.stdio;
void
main
(
)
{
// Cette variable est utilisée en tant que compteur dans une boucle
int
compteur;
// La définition d'un tableau à taille fixe de cinq
// éléments de type double
double
[5
] valeurs;
// récupérer les valeurs dans une boucle
while
(
compteur <
valeurs.length) {
write
(
"Valeur "
, compteur +
1
, " : "
);
readf
(
" %s"
, &
valeurs[compteur]);
++
compteur;
}
writeln
(
"Le double des valeurs :"
);
compteur =
0
;
while
(
compteur <
valeurs.length) {
writeln
(
valeurs[compteur] *
2
);
++
compteur;
}
// La boucle qui calcule le cinquième des valeurs
// serait écrite de façon similaire
}
Observations : la valeur de compteur détermine combien de fois les boucles sont répétées (itérées). Itérer la boucle tant que cette valeur est strictement inférieure à valeurs.length assure que la boucle est exécutée une fois par élément. Comme la valeur de cette variable est incrémentée à la fin de chaque itération, l'expression valeurs[compteur] fait référence à chaque élément du tableau un par un : valeurs[0], valeurs[1], etc.
Pour voir à quel point ce programme est meilleur que le précédent, imaginez que vous ayez besoin de 20 valeurs. Le programme ci-dessus nécessiterait une seule modification : remplacer 5 par 20 ; alors que le programme qui n'utilisait pas de tableau aurait eu besoin de 15 définitions de variables de plus, idem pour les lignes dans lesquelles elles sont utilisées.
18-7. Initialiser les éléments▲
Comme toute variable en D, les éléments des tableaux sont automatiquement initialisés. La valeur initiale des éléments dépend du type des éléments : 0 pour int, double.nan pour double, etc.
Tous les éléments du tableau valeurs ci-dessus sont initialisés à double.nan :
double
[5
] valeurs; // les éléments valent tous double.nan
Évidemment, les valeurs des éléments peuvent être modifiées plus tard dans le programme. On a déjà vu cela ci-dessus quand on a affecté une valeur à un élément de tableau :
joursDuMois[11
] =
31
;
Et aussi quand on récupérait une valeur depuis l'entrée :
readf
(
" %s"
, &
valeurs[compteur]);
Parfois, les valeurs souhaitées des éléments sont connues au moment où le tableau est défini. Dans de tels cas, les valeurs initiales des éléments peuvent être indiquées du côté droit de l'opérateur d'affectation, entre crochets. Voyons cela dans un programme qui demande le numéro du mois à l'utilisateur et qui affiche le nombre de jours de ce mois :
import
std.stdio;
void
main
(
)
{
int
[12
] joursDuMois =
[ 31
, 28
, 31
, 30
, 31
, 30
, 31
, 31
, 30
, 31
, 30
, 31
];
write
(
"Veuillez saisir le numéro du mois : "
);
int
numeroMois;
readf
(
" %s"
, &
numeroMois);
int
indice =
numeroMois -
1
;
writeln
(
"Le mois "
, numeroMois, " a "
,
joursDuMois[indice], " jours."
);
}
Comme vous pouvez le voir, le tableau joursDuMois est défini et initialisé au même moment. Notez aussi que le numéro du mois, qui est entre 1 et 12, est converti en un indice valide entre 0 et 11. Toute valeur entrée en dehors de l'intervalle 1-12 entraînerait l'arrêt du programme avec une erreur.
Quand on initialise des tableaux, il est possible d'utiliser une seule valeur sur le côté droit. Dans ce cas, tous les éléments du tableau sont initialisés à cette valeur :
int
[10
] tousUn =
1
; // Tous les éléments valent 1
18-8. Opérations basiques sur les tableaux▲
Les tableaux proposent des opérations pratiques qui s'appliquent à tous leurs éléments.
- Copier des tableaux à taille fixe : l'opérateur d'affectation copie tous les éléments du tableau de droite dans le tableau de gauche :
int
[5
] source =
[ 10
, 20
, 30
, 40
, 50
];
int
[5
] destination;
destination =
source;
La signification de l'opération d'affectation est complètement différente pour les tableaux dynamiques. Nous verrons cela dans un chapitre ultérieur.
- Ajouter des éléments aux tableaux : l'opérateur ~= ajoute un nouvel élément ou un nouveau tableau à la fin du tableau dynamique :
int
[] tableau; // vide
tableau ~=
7
; // un élément
tableau ~=
360
; // deux éléments
tableau ~=
[ 30
, 40
]; // 4 éléments
- Il n'est pas possible d'ajouter des éléments à un tableau à taille fixe :
int
[10
] tableau;
tableau ~=
7
; // ← ERREUR de compilation
- Combiner des tableaux : l'opérateur ~ crée un nouveau tableau en combinant deux tableaux. Son équivalent ~= combine deux tableaux et affecte le résultat au tableau de gauche :
import
std.stdio;
void
main
(
)
{
int
[10
] premier =
1
;
int
[10
] second =
2
;
int
[] resultat;
resultat =
premier ~
second;
writeln
(
resultat.length); // affiche 20
resultat ~=
premier;
writeln
(
resultat.length); // affiche 30
}
L'opérateur ~= ne peut pas être utilisé quand le tableau de gauche est de taille fixe :
int
[20
] resultat;
// ...
resultat ~=
premier; // ← ERREUR de compilation
Si le tableau de gauche n'a pas exactement la même taille que le tableau résultat, le programme se termine avec une erreur pendant l'affectation :
int
[10
] premier =
1
;
int
[10
] second =
2
;
int
[21
] resultat;
resultat =
premier ~
second;
Sortie :
object.Error: Array lengths don't match for copy: 21 != 20
Si on traduit : « les longueurs ne correspondent pas pour la copie de tableau. »
18-9. Trier les éléments▲
std.algorithm.sort trie les éléments de plages à accès directs. Dans le cas des entiers, les éléments sont triés du plus petit au plus grand. Pour utiliser sort(), il est nécessaire d'importer le module std.algorithm :
import
std.stdio;
import
std.algorithm;
void
main
(
)
{
int
[] tableau =
[ 4
, 3
, 1
, 5
, 2
];
sort
(
tableau);
writeln
(
tableau);
}
La sortie :
[1
, 2
, 3
, 4
, 5
]
18-9-1. Inverser les éléments▲
std.algorithm.reverse inverse les éléments sur place (le premier élément devient le dernier, etc.) :
import
std.stdio;
import
std.algorithm;
void
main
(
)
{
int
[] array =
[ 4
, 3
, 1
, 5
, 2
];
reverse
(
array);
writeln
(
array);
}
La sortie :
[2
, 5
, 1
, 3
, 4
]
18-9-2. Exercices▲
- Écrivez un programme qui demande à l'utilisateur combien de valeurs vont être entrées et ensuite les lit toutes. Le programme doit trier ces éléments en utilisant .sort et .reverse.
-
Écrivez un programme qui lit des nombres depuis l'entrée, et affiche les nombres pairs et impairs séparément, mais dans l'ordre. La valeur spéciale -1 termine la liste des nombres et ne fait pas partie de la liste.
Par exemple, quand les nombres suivants sont entrés :Sélectionnez1
4
7
2
3
8
11
-1
-
Le programme affiche ceci :
Sélectionnez1
3
7
11
2
4
8
Vous pouvez vouloir enregistrer les éléments dans des tableaux séparés. Vous pouvez déterminer si un nombre est pair ou impair en utilisant l'opérateur % (modulo).
- Ce qui suit est un programme qui ne marche pas comme attendu. Le programme est écrit pour lire cinq nombres depuis l'entrée et placer les carrés de ces nombres dans un tableau. Le programme essaie ensuite d'afficher les carrés dans la sortie. Au lieu de ça, le programme se termine avec une erreur.
Corrigez les bogues de ce programme et faites-le marcher comme ce qui est attendu :
import
std.stdio;
void
main
(
)
{
int
[5
] carres;
writeln
(
"Veuillez entrer 5 nombres"
);
int
i =
0
;
while
(
i <=
5
) {
int
nombre;
write
(
"Nombre "
, i +
1
, ": "
);
readf
(
" %s"
, &
nombre);
carres[i] =
nombre *
nombre;
++
i;
}
writeln
(
"=== Les carrés des nombres ==="
);
while
(
i <=
carres.length)
{
write
(
carres[i], " "
);
++
i;
}
writeln
(
);
}