Comment convertir des noms d'énumérations en chaîne en C

92

Existe-t-il une possibilité de convertir les noms des énumérateurs en chaînes en C?

Misha
la source

Réponses:

185

Une façon de faire faire le travail au préprocesseur. Cela garantit également que vos énumérations et chaînes sont synchronisées.

#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",
};

Ensuite, vous pouvez faire quelque chose comme:

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

Si le cas d'utilisation imprime littéralement le nom d'énumération, ajoutez les macros suivantes:

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

Alors fais:

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

Dans ce cas, il peut sembler que la macro à deux niveaux soit superflue, cependant, en raison du fonctionnement de la stringification en C, elle est nécessaire dans certains cas. Par exemple, disons que nous voulons utiliser un #define avec une énumération:

#define foo apple

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

Le résultat serait:

foo
apple

C'est parce que str stringifiera l'entrée foo plutôt que de l'étendre pour qu'elle devienne apple. En utilisant xstr, le développement de la macro est effectué en premier, puis ce résultat est stringifié.

Voir Stringification pour plus d'informations.

Terrence M
la source
1
C'est parfait, mais je suis incapable de comprendre ce qui se passe réellement. : O
p0lAris
Aussi comment convertir une chaîne en une énumération dans le cas ci-dessus?
p0lAris
Il y a plusieurs façons de procéder, en fonction de ce que vous essayez d'accomplir?
Terrence M
5
Si vous ne voulez pas polluer l'espace de noms avec pomme et orange ... vous pouvez le préfixer avec#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
jsaak
1
Pour ceux qui rencontrent ce message, cette méthode d'utilisation d'une liste de macros pour énumérer divers éléments dans un programme est appelée de manière informelle "macros X".
Lundin
27

Dans une situation où vous avez ceci:

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

J'aime mettre ceci dans le fichier d'en-tête où l'énumération est définie:

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

    return strings[f];
}
Richard J. Ross III
la source
4
Pour la vie de moi, je ne vois pas comment cela aide. Pourriez-vous développer un peu pour le rendre plus évident.
David Heffernan
2
OK, comment ça aide? Êtes-vous en train de dire qu'il 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 n'importe où. À moins que je ne manque quelque chose, ce que vous suggérez ici est inutile et réussit simplement à obscurcir le code.
David Heffernan
2
OK, je vois maintenant. La macro est fausse à mon avis et je vous suggère de la supprimer.
David Heffernan
2
les commentaires parlent de macro. Où est-ce?
mk ..
2
Ceci est également peu pratique à entretenir. Si j'insère une nouvelle énumération, je dois me souvenir de la dupliquer également dans le tableau, dans la bonne position.
Fabio
14

Il n'y a pas de moyen simple d'y parvenir directement. Mais P99 a des 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.

Jens Gustedt
la source
Comment puis-je insérer cette bibliothèque?
JohnyTex
14

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

Énumérations séquentielles

Suite à l'invention de Stefan Ram, des énumérations séquentielles (sans énoncer explicitement l'index, par exemple enum {foo=-1, foo1 = 1}) peuvent être réalisées comme cette astuce de génie:

#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 y a 3 couleurs.

Énumérations non séquentielles

Puisque je voulais mapper les définitions de codes d'erreur à une chaîne de tableau, afin que je puisse ajouter la définition d'erreur brute au code d'erreur (par exemple "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."), j'ai étendu le code de cette manière que vous pouvez facilement déterminer l'index requis pour les valeurs d'énumération respectives :

#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"

Maschina
la source
Agréable. C'est exactement ce que je cherchais et je l'utilisais. Mêmes erreurs :)
mrbean
5

Vous n'avez pas besoin de vous fier au préprocesseur pour vous assurer que vos énumérations et chaînes sont synchronisées. Pour moi, l'utilisation de macros a tendance à rendre le code plus difficile à lire.

Utilisation d'énumérations et d'un tableau de chaînes

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 du fruit_strtableau n'ont pas à être déclarées dans le même ordre que les éléments d'énumération.

Comment l'utiliser

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

Ajout d'un contrôle de temps de compilation

Si vous avez peur d'oublier une chaîne, vous pouvez ajouter la vérification suivante:

#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 la quantité d'éléments d'énumération ne correspond pas à la quantité de chaînes dans le tableau.

jyvet
la source
2

Une fonction comme celle-là sans valider l'énumération est un peu dangereuse. Je suggère d'utiliser une instruction switch. Un autre avantage est que cela peut être utilisé pour les énumérations qui ont des valeurs définies, par exemple pour les indicateurs où les valeurs sont 1,2,4,8,16 etc.

Mettez également toutes vos chaînes enum ensemble dans un seul tableau: -

static const char * allEnums[] = {
    "Undefined",
    "apple",
    "orange"
    /* etc */
};

définir les indices dans un fichier d'en-tête: -

#define ID_undefined       0
#define ID_fruit_apple     1
#define ID_fruit_orange    2
/* etc */

Cela facilite la production de différentes versions, par exemple si vous souhaitez créer des versions internationales de votre programme avec d'autres langues.

En utilisant une macro, également dans le fichier d'en-tête: -

#define CASE(type,val) case val: index = ID_##type##_##val; break;

Créez une fonction avec une instruction switch, cela devrait renvoyer un const char *car les chaînes statiques consts: -

const char * FruitString(enum fruit e){

    unsigned int index;

    switch(e){
        CASE(fruit, apple)
        CASE(fruit, orange)
        CASE(fruit, banana)
        /* etc */
        default: index = ID_undefined;
    }
    return allEnums[index];
}

Si vous programmez avec Windows, les valeurs ID_ peuvent être des valeurs de ressources.

(Si vous utilisez C ++, toutes les fonctions peuvent avoir le même nom.

string EnumToString(fruit e);

)

QuentinUK
la source
2

Une alternative plus simple à la réponse "Enums non séquentiels" de Hokyo, basée sur l'utilisation d'indicateurs pour instancier le tableau de chaînes:

#define NAMES C(RED, 10)C(GREEN, 20)C(BLUE, 30)
#define C(k, v) k = v,
enum color { NAMES };
#undef C

#define C(k, v) [v] = #k,    
const char * const color_name[] = { NAMES };
Lars
la source
-2

Je fais généralement ceci:

#define COLOR_STR(color)                            \
    (RED       == color ? "red"    :                \
     (BLUE     == color ? "blue"   :                \
      (GREEN   == color ? "green"  :                \
       (YELLOW == color ? "yellow" : "unknown"))))   
gigilibala
la source
1
C'est tout simplement ridicule
Massimo Callegari
Ce n'est pas une mauvaise réponse. C'est clair, simple et facile à comprendre. Si vous travaillez sur des systèmes où d'autres personnes doivent lire et comprendre votre code rapidement, la clarté est très importante. Je ne recommanderais pas d'utiliser des astuces de préprocesseur à moins qu'elles ne soient complètement commentées ou décrites dans une norme de codage.
nielsen le