102 votes

Comment convertir les noms d'enum en chaîne de caractères en C

Existe-t-il une possibilité de convertir les noms des énumérateurs en chaîne de caractères en C ?

204voto

Terrence M Points 415

Une façon de faire, faire faire le travail par le préprocesseur. Cela permet également de s'assurer que vos enums et vos chaînes de caractères sont synchronisés.

#define FOREACH_FRUIT(FRUIT) \
        FRUIT(apple)   \
        FRUIT(orange)  \
        FRUIT(grape)   \
        FRUIT(banana)  \

#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,

enum FRUIT_ENUM {
    FOREACH_FRUIT(GENERATE_ENUM)
};

static const char *FRUIT_STRING[] = {
    FOREACH_FRUIT(GENERATE_STRING)
};

Une fois le préprocesseur terminé, vous aurez :

enum FRUIT_ENUM {
    apple, orange, grape, banana,
};

static const char *FRUIT_STRING[] = {
    "apple", "orange", "grape", "banana",
};

Alors vous pourriez faire quelque chose comme :

printf("enum apple as a string: %s\n",FRUIT_STRING[apple]);

Si le cas d'utilisation est littéralement juste l'impression du nom de l'enum, ajoutez les macros suivantes :

#define str(x) #x
#define xstr(x) str(x)

Alors, faites-le :

printf("enum apple as a string: %s\n", xstr(apple));

Dans ce cas, il peut sembler que la macro à deux niveaux est superflue, cependant, en raison de la façon dont la stringification fonctionne en C, elle est nécessaire dans certains cas. Par exemple, disons que nous voulons utiliser un #define avec un enum :

#define foo apple

int main() {
    printf("%s\n", str(foo));
    printf("%s\n", xstr(foo));
}

La sortie serait :

foo
apple

Ceci est dû au fait que str transformera l'entrée foo en chaîne plutôt que de l'étendre en pomme. En utilisant xstr, l'expansion de la macro est d'abord effectuée, puis le résultat est transformé en chaîne.

Voir Stringification pour plus d'informations.

2 votes

C'est parfait, mais je n'arrive pas à comprendre ce qui se passe vraiment. :O

0 votes

De plus, comment convertir une chaîne de caractères en un enum dans le cas ci-dessus ?

0 votes

Il y a plusieurs façons de procéder, en fonction de ce que vous essayez d'obtenir ?

30voto

Richard J. Ross III Points 33152

Dans une situation où vous avez ceci :

enum fruit {
    apple, 
    orange, 
    grape,
    banana,
    // etc.
};

J'aime mettre cela dans le fichier d'en-tête où l'enum est défini :

static inline char *stringFromFruit(enum fruit f)
{
    static const char *strings[] = { "apple", "orange", "grape", "banana", /* continue for rest of values */ };

    return strings[f];
}

4 votes

Je ne vois pas en quoi cela peut aider. Pourriez-vous développer un peu pour rendre cela plus évident.

2 votes

OK, en quoi cela aide-t-il ? Tu veux dire que c'est plus facile de taper enumToString(apple) que de taper "apple" ? Ce n'est pas comme s'il y avait une sécurité de type quelque part. À moins que je ne manque quelque chose, ce que vous suggérez ici est inutile et ne sert qu'à obscurcir le code.

0 votes

@DavidHeffernan et c'est là que la fonction entre en jeu. Vous obtiendrez une chaîne de caractères invalide si vous invoquez la fonction stringFromFruit() avec une valeur invalide, la macro est simplement là parce que c'est ce que j'aime utiliser. Rien de plus. Je peux comprendre que l'OP ne considère pas cela comme pratique, c'est pourquoi la deuxième réponse est faite.

16voto

Maschina Points 559

J'ai trouvé une astuce du préprocesseur C qui fait le même travail sans déclaration d'une chaîne de tableau dédiée (Source : http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_fr ).

Enums séquentiels

Suite à l'invention de Stefan Ram, les enums séquentiels (sans indication explicite de l'indice, par ex. enum {foo=-1, foo1 = 1} ) peut être réalisé grâce à cette astuce géniale :

#include <stdio.h>

#define NAMES C(RED)C(GREEN)C(BLUE)
#define C(x) x,
enum color { NAMES TOP };
#undef C

#define C(x) #x,    
const char * const color_name[] = { NAMES };

Cela donne le résultat suivant :

int main( void )  { 
    printf( "The color is %s.\n", color_name[ RED ]);  
    printf( "There are %d colors.\n", TOP ); 
}

La couleur est ROUGE.
Il existe 3 couleurs.

Enums non séquentiels

Puisque je voulais faire correspondre les définitions des codes d'erreur à une chaîne de tableau, je peux ajouter la définition brute de l'erreur au code d'erreur (par ex. "The error is 3 (LC_FT_DEVICE_NOT_OPENED)." ), j'ai étendu le code de manière à ce que vous puissiez facilement déterminer l'index requis pour les valeurs respectives de l'enum :

#define LOOPN(n,a) LOOP##n(a)
#define LOOPF ,
#define LOOP2(a) a LOOPF a LOOPF
#define LOOP3(a) a LOOPF a LOOPF a LOOPF
#define LOOP4(a) a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP5(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP6(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP7(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP8(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP9(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF

#define LC_ERRORS_NAMES \
    Cn(LC_RESPONSE_PLUGIN_OK, -10) \
    Cw(8) \
    Cn(LC_RESPONSE_GENERIC_ERROR, -1) \
    Cn(LC_FT_OK, 0) \
    Ci(LC_FT_INVALID_HANDLE) \
    Ci(LC_FT_DEVICE_NOT_FOUND) \
    Ci(LC_FT_DEVICE_NOT_OPENED) \
    Ci(LC_FT_IO_ERROR) \
    Ci(LC_FT_INSUFFICIENT_RESOURCES) \
    Ci(LC_FT_INVALID_PARAMETER) \
    Ci(LC_FT_INVALID_BAUD_RATE) \
    Ci(LC_FT_DEVICE_NOT_OPENED_FOR_ERASE) \
    Ci(LC_FT_DEVICE_NOT_OPENED_FOR_WRITE) \
    Ci(LC_FT_FAILED_TO_WRITE_DEVICE) \
    Ci(LC_FT_EEPROM_READ_FAILED) \
    Ci(LC_FT_EEPROM_WRITE_FAILED) \
    Ci(LC_FT_EEPROM_ERASE_FAILED) \
    Ci(LC_FT_EEPROM_NOT_PRESENT) \
    Ci(LC_FT_EEPROM_NOT_PROGRAMMED) \
    Ci(LC_FT_INVALID_ARGS) \
    Ci(LC_FT_NOT_SUPPORTED) \
    Ci(LC_FT_OTHER_ERROR) \
    Ci(LC_FT_DEVICE_LIST_NOT_READY)

#define Cn(x,y) x=y,
#define Ci(x) x,
#define Cw(x)
enum LC_errors { LC_ERRORS_NAMES TOP };
#undef Cn
#undef Ci
#undef Cw
#define Cn(x,y) #x,
#define Ci(x) #x,
#define Cw(x) LOOPN(x,"")
static const char* __LC_errors__strings[] = { LC_ERRORS_NAMES };
static const char** LC_errors__strings = &__LC_errors__strings[10];

Dans cet exemple, le préprocesseur C générera le code suivant :

enum LC_errors { LC_RESPONSE_PLUGIN_OK=-10,  LC_RESPONSE_GENERIC_ERROR=-1, LC_FT_OK=0, LC_FT_INVALID_HANDLE, LC_FT_DEVICE_NOT_FOUND, LC_FT_DEVICE_NOT_OPENED, LC_FT_IO_ERROR, LC_FT_INSUFFICIENT_RESOURCES, LC_FT_INVALID_PARAMETER, LC_FT_INVALID_BAUD_RATE, LC_FT_DEVICE_NOT_OPENED_FOR_ERASE, LC_FT_DEVICE_NOT_OPENED_FOR_WRITE, LC_FT_FAILED_TO_WRITE_DEVICE, LC_FT_EEPROM_READ_FAILED, LC_FT_EEPROM_WRITE_FAILED, LC_FT_EEPROM_ERASE_FAILED, LC_FT_EEPROM_NOT_PRESENT, LC_FT_EEPROM_NOT_PROGRAMMED, LC_FT_INVALID_ARGS, LC_FT_NOT_SUPPORTED, LC_FT_OTHER_ERROR, LC_FT_DEVICE_LIST_NOT_READY, TOP };

static const char* __LC_errors__strings[] = { "LC_RESPONSE_PLUGIN_OK", "" , "" , "" , "" , "" , "" , "" , "" "LC_RESPONSE_GENERIC_ERROR", "LC_FT_OK", "LC_FT_INVALID_HANDLE", "LC_FT_DEVICE_NOT_FOUND", "LC_FT_DEVICE_NOT_OPENED", "LC_FT_IO_ERROR", "LC_FT_INSUFFICIENT_RESOURCES", "LC_FT_INVALID_PARAMETER", "LC_FT_INVALID_BAUD_RATE", "LC_FT_DEVICE_NOT_OPENED_FOR_ERASE", "LC_FT_DEVICE_NOT_OPENED_FOR_WRITE", "LC_FT_FAILED_TO_WRITE_DEVICE", "LC_FT_EEPROM_READ_FAILED", "LC_FT_EEPROM_WRITE_FAILED", "LC_FT_EEPROM_ERASE_FAILED", "LC_FT_EEPROM_NOT_PRESENT", "LC_FT_EEPROM_NOT_PROGRAMMED", "LC_FT_INVALID_ARGS", "LC_FT_NOT_SUPPORTED", "LC_FT_OTHER_ERROR", "LC_FT_DEVICE_LIST_NOT_READY", };

Il en résulte les capacités de mise en œuvre suivantes :

LC_errors__strings[-1] ==> LC_errors__strings[LC_RESPONSE_GENERIC_ERROR] ==> "LC_RESPONSE_GENERIC_ERROR" (EN ANGLAIS)

1 votes

Joli. C'est exactement ce que je cherchais et ce pour quoi je l'utilisais. Mêmes erreurs :)

1 votes

LOOP3...LOOP9 peuvent être définis en fonction de LOOP2 - LOOP8 respectivement.

0 votes

@Michael : Bon point. Même si cela rend la maintenance plus difficile, cela produit un code plus court.

14voto

Jens Gustedt Points 40410

Il n'existe pas de moyen simple d'y parvenir directement. Mais P99 dispose de macros qui vous permettent de créer automatiquement ce type de fonction :

 P99_DECLARE_ENUM(color, red, green, blue);

dans un fichier d'en-tête, et

 P99_DEFINE_ENUM(color);

dans une unité de compilation (fichier .c) devrait alors faire l'affaire, dans cet exemple la fonction serait alors appelée color_getname .

0 votes

Comment puis-je faire entrer cette librairie ?

9voto

jyvet Points 852

Vous n'avez pas besoin de compter sur le préprocesseur pour vous assurer que vos enums et vos chaînes de caractères sont synchronisés. Pour moi, l'utilisation de macros a tendance à rendre le code plus difficile à lire.

Utilisation de Enum et d'un tableau de chaînes de caractères

enum fruit                                                                   
{
    APPLE = 0, 
    ORANGE, 
    GRAPE,
    BANANA,
    /* etc. */
    FRUIT_MAX                                                                                                                
};   

const char * const fruit_str[] =
{
    [BANANA] = "banana",
    [ORANGE] = "orange",
    [GRAPE]  = "grape",
    [APPLE]  = "apple",
    /* etc. */  
};

_Remarque : les chaînes de caractères dans les fruit_str n'ont pas besoin d'être déclarés dans le même ordre que les éléments de l'énumération._

Comment l'utiliser

printf("enum apple as a string: %s\n", fruit_str[APPLE]);

Ajout d'une vérification en temps de compilation

Si vous avez peur d'oublier une chaîne, vous pouvez ajouter le contrôle suivant :

#define ASSERT_ENUM_TO_STR(sarray, max) \                                       
  typedef char assert_sizeof_##max[(sizeof(sarray)/sizeof(sarray[0]) == (max)) ? 1 : -1]

ASSERT_ENUM_TO_STR(fruit_str, FRUIT_MAX);

Une erreur serait signalée au moment de la compilation si le nombre d'éléments de l'énumération ne correspond pas au nombre de chaînes dans le tableau.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X