Les énumérations

Jusqu’à présent, nous avons toujours employé le préprocesseur pour définir des constantes au sein de nos codes. Toutefois, une solution un peu plus commode existe pour les constantes entières : les énumérations.

Définition

Une énumération se définit à l’aide du mot-clé enum suivi du nom de l’énumération et de ses membres.

enum naturel { ZERO, UN, DEUX, TROIS, QUATRE, CINQ };

La particularité de cette définition est qu’elle crée en vérité deux choses : un type dit « énuméré » enum naturel et des constantes dites « énumérées » ZERO, UN, DEUX, etc. Le type énuméré ainsi produit peut être utilisé de la même manière que n’importe quel autre type. Quant aux constantes énumérées, il s’agit de constantes entières.

Certes me direz-vous, mais que valent ces constantes ? Eh bien, à défaut de préciser leur valeur, chaque constante énumérée se voit attribuer la valeur de celle qui la précède augmentée de un, sachant que la première constante est mise à zéro. Dans notre cas donc, la constante ZERO vaut zéro, la constante UN un et ainsi de suite jusque cinq.

L’exemple suivant illustre ce qui vient d’être dit.

#include <stdio.h>

enum naturel { ZERO, UN, DEUX, TROIS, QUATRE, CINQ };


int main(void)
{
	enum naturel n = ZERO;

	printf("n = %d.\n", (int)n);
	printf("UN = %d.\n", UN);
	return 0;
}
Résultat
n = 0.
UN = 1.

Notez qu’il n’est pas obligatoire de préciser un nom lors de la définition d’une énumération. Dans un tel cas, seules les constantes énumérées sont produites.

enum { ZERO, UN, DEUX, TROIS, QUATRE, CINQ };

Toutefois, il est possible de préciser la valeur de certaines constantes (voire de toutes les constantes) à l’aide d’une affectation.

enum naturel { DIX = 10, ONZE, DOUZE, TREIZE, QUATORZE, QUINZE };

Dans un tel cas, la règle habituelle s’applique : les constantes sans valeur se voient attribuer celle de la constante précédente augmentée de un et celle dont la valeur est spécifiée sont initialisées avec celle-ci. Dans le cas ci-dessus, la constante DIX vaut donc dix, la constante ONZE onze et ainsi de suite jusque quinze. Notez que le code ci-dessous est parfaitement équivalent.

enum naturel { DIX = 10, ONZE = 11, DOUZE = 12, TREIZE = 13, QUATORZE = 14, QUINZE = 15 };
Types entiers sous-jacents

Vous aurez sans doute remarqué que, dans notre exemple, nous avons converti la variable n vers le type int. Cela tient au fait qu’un type énuméré est un type entier (ce qui est logique puisqu’il est censé stocker des constantes entières), mais que le type sous-jacent n’est pas déterminé (cela peut donc être _Bool, char, short, int ,long ou long long) et dépend entre autres des valeurs devant être contenues. Ainsi, une conversion s’impose afin de pouvoir utiliser un format d’affichage correct.

Pour ce qui est des constantes énumérées, c’est plus simple : elles sont toujours de type int.

Utilisation

Dans la pratique, les énumérations servent essentiellement à fournir des informations supplémentaires via le typage, par exemple pour les retours d’erreurs. En effet, le plus souvent, les fonctions retournent un entier pour préciser si leur exécution s’est bien déroulée. Toutefois, indiquer un retour de type int ne fournit pas énormément d’information. Un type énuméré prend alors tout son sens.

La fonction vider_tampon() du dernier TP s’y prêterait par exemple bien.

enum erreur { E_OK, E_ERR };


static enum erreur vider_tampon(FILE *fp)
{
    int c;

    do
        c = fgetc(fp);
    while (c != '\n' && c != EOF);

    return ferror(fp) ? E_ERR : E_OK;
}

De cette manière, il est plus clair à la lecture que la fonction retourne le statut de son exécution.

Dans la même idée, il est possible d’utiliser un type énuméré pour la fonction statut_jeu() (également employée dans la correction du dernier TP) afin de décrire plus amplement son type de retour.

enum statut { STATUT_OK, STATUT_GAGNE, STATUT_EGALITE };


static enum statut statut_jeu(struct position *pos, char jeton)
{
    if (grille_complete())
        return STATUT_EGALITE;
    else if (calcule_nb_jetons_depuis(pos, jeton) >= 4)
        return STATUT_GAGNE;

    return STATUT_OK;
}

Dans un autre registre, un type enuméré peut être utilisé pour contenir des drapeaux. Par exemple, la fonction traitement() présentée dans le chapitre relatif aux opérateurs de manipulation des bits peut être réecrite comme suit.

enum drapeau {
	PAIR = 0x01,
	PUISSANCE = 0x02,
	PREMIER = 0x04
};


void traitement(int nombre, enum drapeau drapeaux)
{
    if (drapeaux & PAIR) /* Si le nombre est pair */
    {
        /* ... */
    }
    if (drapeaux & PUISSANCE) /* Si le nombre est une puissance de deux */
    {
        /* ... */
    }
    if (drapeaux & PREMIER) /* Si le nombre est premier */
    {
        /* ... */
    }
}

En résumé
  1. Sauf si le nom de l’énumération n’est pas renseigné, une définition d’énumération crée un type énuméré et des constantes énumérées ;
  2. Sauf si une valeur leur est attribuée, la valeur de chaque constante énumérée est celle de la précédente augmentée de un et celle de la première est zéro.
  3. Le type entier sous-jacent à un type énuméré est indéterminé ; les constantes énumérées sont de type int.