35. Les énumérations (enum)▲
enum est la fonctionnalité qui permet de définir des valeurs constantes nommées.
35-1. Les effets des constantes magiques sur la qualité du code▲
Le code suivant apparaît dans les solutions des exercices du chapitre sur les entiers et les opérations arithmétiquesNombres entiers et opérations arithmétiques :
if
(
operation ==
1
) {
resultat =
premier +
second;
}
else
if
(
operation ==
2
) {
resultat =
premier -
second;
}
else
if
(
operation ==
3
) {
resultat =
premier *
second;
}
else
if
(
operation ==
4
) {
resultat =
premier /
second;
}
Les littéraux entiers 1, 2, 3 et 4 dans ce bout de code sont appelés constantes magiques. Il n'est pas facile de déterminer à quoi correspond chacun de ces littéraux dans le programme. Le code de chaque bloc doit être examiné pour voir que 1 désigne l'addition, 2 la soustraction, etc. Cette tâche est relativement aisée dans cet exemple parce que chaque bloc ne contient qu'une ligne. Il serait considérablement plus difficile de déchiffrer le sens des constantes magiques dans la plupart des autres programmes.
Les constantes magiques doivent être évitées parce qu'elles limitent les deux qualités les plus importantes des programmes : lisibilité et maintenabilité.
enum permet de donner des noms à de telles constantes et donc de rendre le code plus lisible et maintenable. Chaque condition est immédiatement compréhensible quand les valeurs énumérées suivantes sont utilisées :
if
(
operation ==
Operation.ajouter) {
resultat =
premier +
second;
}
else
if
(
operation ==
Operation.soustraire) {
resultat =
premier -
second;
}
else
if
(
operation ==
Operation.multiplier) {
resultat =
premier *
second;
}
else
if
(
operation ==
Operation.diviser) {
resultat =
premier /
second;
}
Le type énuméré Operation ci-dessus qui rend inutile le recours aux constantes magiques 1, 2, 3 et 4 peut être défini comme ceci :
enum
Operation {
ajouter =
1
, soustraire, multiplier, diviser }
35-2. La syntaxe d'enum ▲
La forme la plus simple de la définition d'une énumération est la suivante :
enum
NomDuType {
NomDeValeur_1, NomDeValeur_2, /* etc. */
}
Il est parfois nécessaire de spécifier également le vrai type (le type de base) des valeurs :
enum
NomDuType : type_de_base {
NomDeValeur_1, NomDeValeur_2, /* etc. */
}
Nous verrons comment ceci peut être utilisé dans la section suivante.
NomDuType définit la signification commune des valeurs. Toutes les valeurs d'un type énuméré sont listées entre accolades. Voici quelques exemples :
enum
PileOuFace {
pile, face }
enum
Couleur {
pique, cœur, carreau, trèfle }
enum
Voyageur {
ordinaire, enfant, etudiant, senior }
Chaque ensemble de valeur fait alors partie d'un type distinct. Par exemple, pile et face deviennent des valeurs du type PileOuFace. Le nouveau type peut être utilisé comme n'importe quel type lors de la définition de variables :
PileOuFace resultat; // initialisé à la valeur par défaut
auto
tq =
PileOuFace.pile; // type inféré
Comme nous l'avons vu dans les codes précédents, les valeurs des types énumérés sont toujours spécifiées au moyen du nom de leur type :
if
(
resultat ==
PileOuFace.pile) {
// ...
}
35-3. Valeurs réelles et types de base▲
Les valeurs des types énumérés sont normalement implémentées, en interne, par des valeurs int. Autrement dit, même si elles apparaissent comme des valeurs nommées comme pile et face dans le code, elles sont en réalité des valeurs int. (Note : il est possible de choisir un autre type que int lorsque c'est nécessaire.)
Sauf dans le cas où c'est explicitement indiqué par le programmeur, les valeurs int commencent par 0 et sont incrémentées de 1 pour chaque valeur énumérée. Par exemple, les deux valeurs de PileOuFace ont les valeurs 0 et 1 :
writeln
(
"pile vaut 0 : "
, (
PileOuFace.pile ==
0
));
writeln
(
"face vaut 1 : "
, (
PileOuFace.face ==
1
));
La sortie :
pile vaut 0
: true
face vaut 1
: true
Il est possible de réinitialiser manuellement les valeurs à n'importe quel endroit. Cela a été le cas quand on a donné la valeur 1 à Operation.ajouter. L'exemple suivant réinitialise les valeurs deux fois :
enum
Test {
a, b, c, ç =
100
, d, e, f =
222
, g, ğ }
writefln
(
"%d %d %d"
, Test.b, Test.ç, Test.ğ);
La sortie :
0
Si int n'est pas adapté en tant que type de base des valeurs énumérées, le type de base peut être indiqué de façon explicite après le nom de l'énumération :
enum
ConstanteNaturelle : double
{
pi =
3
.14
, e =
2
.72
}
enum
UnitéDeTempérature : string {
C =
"Celsius"
, F =
"Fahrenheit"
}
35-4. Des valeurs énumérées sans type énuméré▲
Nous avons vu qu'il était important d'éviter les constantes magiques et qu'il valait mieux se servir des énumérations.
Cependant, parfois, il peut ne pas être naturel d'utiliser les noms des types énumérés pour simplement utiliser des constantes nommées. Supposons que l'on ait besoin de représenter le nombre de secondes par jour. Il ne devrait pas être nécessaire de définir également un type énuméré pour cette constante. Tout ce dont on a besoin est une valeur constante à laquelle on peut se référer par son nom. Dans de tels cas, le type de l'énumération et les accolades ne sont pas écrits :
enum
secondesParJour =
60
*
60
*
24
;
Le type de la valeur peut être spécifié explicitement, ce qui est nécessaire si le type ne peut pas être inféré depuis la valeur :
enum
int
secondesParJour =
60
*
60
*
24
;
Comme il n'y a pas de type énuméré auquel se référer, de telles constantes nommées peuvent être simplement utilisées dans le code par leur nom :
nombreTotalDeSecondes =
nombreDeJours *
secondesParJour;
enum peut également être utilisé pour définir des constantes nommées d'autres types. Par exemple, le type de la constante suivante est string :
enum
nomDeFichier =
"list.txt"
;
35-5. Propriétés▲
Les propriétés .min et .max sont les valeurs minimale et maximale du type énuméré. Quand les valeurs du type énuméré sont consécutives, elles peuvent être itérées dans une boucle for entre ces limites :
enum
Couleur {
pique, cœur, carreau, trèfle }
for
(
auto
couleur =
Couleur.min; couleur <=
Couleur.max; ++
couleur) {
writefln
(
"%s : %d"
, couleur, couleur);
}
Les indicateurs de format "%s" et "%d" produisent des sorties différentes :
pique : 0
cœur : 1
carreau : 2
trèfle : 3
Notez qu'une boucle foreach sur cet intervalle ne considérerait pas la valeur .max :
foreach
(
couleur; Couleur.min .. Couleur.max) {
writefln
(
"%s : %d"
, couleur, couleur);
}
La sortie :
pique : 0
cœur : 1
carreau : 2
← il manque le trèfle
35-6. Conversion depuis le type de base▲
Comme nous l'avons vu dans la sortie formatée ci-dessus, une valeur énumérée peut être automatiquement convertie vers son type de base (par ex. vers int). La conversion inverse n'est pas automatique :
Couleur couleur =
1
; // ← ERREUR de compilation
La raison à cela est d'éviter de se retrouver avec des valeurs énumérées invalides :
couleur =
100
; // ← cela serait une valeur énumérée invalide
Les valeurs que l'on sait pouvoir correspondre à des valeurs énumérées valides d'un type énuméré particulier peuvent quand même être converties vers ce type par un cast explicite :
couleur =
cast
(
Couleur)1
; // devient cœur
Il est à la charge du programmeur de s'assurer de la validité des valeurs quand un cast explicite est utilisé. Nous verrons les conversions de type et les casts dans des chapitres ultérieurs.
35-7. Exercice▲
Modifiez le programme de calculatrice des exercices du chapitre sur les entiers et les opérations arithmétiquesNombres entiers et opérations arithmétiques en faisant choisir à l'utilisateur l'opération arithmétique dans un menu.
Ce programme doit différer du précédent par au moins ces points :
- utilisez des valeurs énumérées, pas des constantes magiques ;
- utilisez double à la place d'int ;
- utilisez une instruction switch à la place de la chaîne « if, else if, else ».