Du code plein de nombres nus est du code que personne ne peut lire. if (state == 2) ne vous dit rien ; if (state == STATE_RUNNING) vous dit tout. Une enum est la façon dont le C donne de vrais noms à un petit ensemble fixe d'entiers - et, contrairement à un tas de #define, ils arrivent groupés dans un type que le compilateur connaît.
Définir une énumération
La déclaration nomme une étiquette et liste les énumérateurs entre accolades :
Deux choses à noter d'emblée. D'abord, la numérotation commence à 0 et augmente de un, donc RED vaut 0, GREEN 1, BLUE 2. Ensuite, les noms d'énumérateurs sont visibles partout dans la portée englobante - vous écrivez GREEN, pas Color.GREEN. Ils vivent dans le même espace de noms que vos variables, c'est pourquoi le code C les écrit conventionnellement en MAJUSCULES ou les préfixe par le type (COLOR_RED) pour éviter les collisions.
Afficher une enum se fait avec %d, car une valeur d'énumération est une valeur entière.
Valeurs explicites
Donnez à n'importe quel énumérateur une constante explicite, et ceux qui suivent continuent à partir de là :
Les valeurs n'ont pas à être uniques ni ordonnées - deux énumérateurs peuvent partager un nombre, ce qui sert parfois pour des alias (COLOR_DEFAULT = COLOR_BLACK). Ce doivent être des constantes entières de compilation, vous ne pouvez donc pas en calculer une depuis une variable.
Un idiome courant s'en sert pour enregistrer le compte :
enum Suit {
CLUBS, DIAMONDS, HEARTS, SPADES,
SUIT_COUNT /* automatiquement 4 - reste correct si vous ajoutez une couleur */
};
SUIT_COUNT n'est pas une vraie couleur ; c'est leur nombre, maintenu gratuitement par la numérotation automatique. Boucles et tailles de tableaux peuvent s'en servir sans jamais devenir périmées.
typedef enum
Comme pour les structures, le nom de type complet en C est enum Color, et un typedef fait tomber le mot-clé :
Parce que les énumérateurs sont des entiers ordinaires partant de 0, ils servent aussi d'indices de tableau - dx[DIR_EAST] fonctionne sans aucune conversion. C'est la propriété la plus utile d'une enum indexée à partir de zéro.
Les enums dans un switch
Associer une enum à un switch est là où le type commence à payer. Compilez avec -Wall et le compilateur vous dira quand vous oubliez un cas.
Ajoutez un STATE_FAILED à l'enum et GCC avertit : enumeration value 'STATE_FAILED' not handled in switch. Cet avertissement est ce que le C a de plus proche d'un contrôle d'exhaustivité, et c'est une raison forte d'écrire ces switch sans cas default: - un default fait taire l'avertissement et laisse de nouveaux états passer sans traitement. Mettez plutôt le repli après le switch, comme le fait describe.
Convertir une enum en chaîne
Le C ne conserve aucun nom à l'exécution ; printf("%s", GREEN) ne peut pas fonctionner parce que GREEN n'est que 1. Deux motifs résolvent cela.
La version switch, que le compilateur vérifie :
Et la version table de correspondance, plus courte mais non vérifiée - si le tableau et l'enum divergent, rien n'avertit :
Les indices désignés ([RED] = "RED") gardent au moins les noms attachés à leurs cases, de sorte que réordonner l'enum ne peut pas mélanger silencieusement les chaînes. La vérification des bornes compte parce que rien n'empêche un appelant de passer une valeur hors de l'enum - voir la section suivante.
Les enums ne sont pas étanches
Une variable enum est un entier en dessous, et le C ne surveille pas la plage :
L'arithmétique sur les énumérateurs fonctionne, les comparaisons avec des int simples fonctionnent, et un cast hors plage est accepté. Traitez donc une enum comme une excellente documentation et un bon indice pour le compilateur, pas comme une garantie. Validez toute valeur venant de l'extérieur de votre programme - un fichier, un argument, un message réseau - avant d'en faire un switch.
Taille et stockage
En C17, chaque énumérateur doit tenir dans un int - enum Huge { BIG = 3000000000 }; est une violation de contrainte qu'un compilateur strict (-pedantic-errors) rejette, même si gcc et clang l'acceptent comme extension dans leur mode par défaut. Les énumérateurs eux-mêmes ont le type int, tandis que la taille du type enum est définie par l'implémentation (en pratique, int) :
(C23 lève la restriction : un énumérateur peut dépasser la plage d'int et vous pouvez même choisir le type sous-jacent avec enum E : unsigned long { ... }. Tant que votre chaîne d'outils ne vise pas C23, gardez les énumérateurs dans int.)
Parce que le choix exact est défini par l'implémentation, n'écrivez jamais une enum directement dans un fichier binaire ou un paquet réseau en espérant qu'une autre machine la relise. Convertissez à la frontière vers un type de largeur fixe comme uint8_t ou uint32_t de <stdint.h>.
Enum contre #define
Les deux donnent un nom à un nombre. L'enum est généralement meilleure :
enum | #define | |
|---|---|---|
| Traitée par | le compilateur | le préprocesseur |
| Regroupement | les valeurs liées partagent un type | chaque constante est indépendante |
| Numérotation automatique | oui | non, vous numérotez à la main |
| Visible dans un débogueur | souvent, par son nom | non, seulement le nombre |
Avertissement d'exhaustivité de switch | oui | non |
| Portée | respecte la portée de bloc | textuelle, jusqu'à #undef |
Prenez #define quand la constante n'est pas un entier (une chaîne, un flottant) ou quand elle doit être utilisable par le préprocesseur lui-même, comme dans la compilation conditionnelle. Pour un ensemble d'états, de drapeaux ou de genres entiers liés, utilisez une enum.
Conventions de nommage
Les enums entrent facilement en collision car leurs noms siègent dans la portée ordinaire. Les conventions qui gardent les grandes bases de code C saines :
- Préfixez chaque énumérateur avec le type :
COLOR_RED,STATE_IDLE,HTTP_OK. UnREDsans préfixe dans un programme graphique finira par rencontrer leREDde quelqu'un d'autre. - Écrivez les énumérateurs en MAJUSCULES, comme les autres constantes.
- Nommez le type au singulier en PascalCase ou en
snake_case_t:Color,Stateoucolor_t, de façon cohérente dans tout le projet. - Ajoutez une sentinelle
_COUNTen dernier quand vous allez boucler ou indexer avec l'enum.
Questions fréquentes
Comment définit-on une enum en C ?
enum Color { RED, GREEN, BLUE }; définit un type dont les valeurs sont les trois constantes nommées. Par défaut elles sont numérotées à partir de 0, donc RED vaut 0, GREEN 1 et BLUE 2. Déclarez une variable avec enum Color c = GREEN;, ou ajoutez un typedef pour écrire simplement Color c.
Peut-on attribuer des valeurs précises à une enum en C ?
Oui : enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };. Vous pouvez en fixer certaines et laisser les autres suivre - tout nom sans valeur explicite continue depuis le précédent, donc enum { A = 10, B, C }; donne B = 11 et C = 12.
Comment convertir une enum en chaîne en C ?
Le C n'a aucun moyen intégré ; les noms d'énumérateurs n'existent pas à l'exécution. L'approche habituelle est une petite fonction avec un switch renvoyant un littéral de chaîne par valeur, ou un tableau de chaînes indexé par l'enum. La version switch est plus sûre car -Wall avertit quand un nouvel énumérateur n'a pas de cas.
Quelle est la taille d'une enum en C ?
Définie par l'implémentation, mais en pratique sizeof(int) - 4 octets sur la plupart des systèmes. Le compilateur choisit un type entier capable de contenir chaque énumérateur. Ne supposez pas une taille précise pour disposer un fichier binaire ou un paquet réseau ; utilisez-y un type de largeur fixe de <stdint.h>.