web-dev-qa-db-fra.com

Comment convertir les noms enum en chaîne en c

Est-il possible de convertir les noms d’énumérateurs en chaîne en C?

78
Misha

Une façon, de faire en sorte que le préprocesseur fasse le travail. Cela garantit également la synchronisation de vos enums et de vos chaînes.

#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 pourriez faire quelque chose comme:

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

Si le cas d'utilisation n'imprime littéralement que le nom de l'énum, ​​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 de la façon dont la codification fonctionne en C, elle est nécessaire dans certains cas. Par exemple, supposons que nous voulions 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

Cela est dû au fait que str renforcera l'entrée foo plutôt que de l'étendre à Apple. En utilisant xstr, le développement de la macro est effectué en premier, puis ce résultat est stratifié.

Voir Stringification pour plus d'informations.

159
Terrence M

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'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];
}
22

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

 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 s'appellerait alors color_getname.

15
Jens Gustedt

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

Enums séquentiels

Suite à l’invention de Stefan Ram, des énumérations séquentielles (sans énoncer explicitement l’indice, par exemple enum {foo=-1, foo1 = 1}) Peuvent être réalisées comme suit:

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

Enums non séquentiels

Etant donné que je voulais mapper les définitions de codes d'erreur sur des chaînes de tableaux, afin de pouvoir 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 manière à ce que vous puissiez facilement déterminer le 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", };

Cela se traduit par les capacités d'implémentation suivantes:

LC_errors__strings [-1] ==> LC_errors__strings [LC_RESPONSE_GENERIC_ERROR] ==> "LC_RESPONSE_GENERIC_ERROR"

9
Maschina

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

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

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

définir les index 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.

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

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

Faire une fonction avec une instruction switch, cela devrait retourner un const char * _ parce que les chaînes de caractères statique 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 ressources.

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

string EnumToString(fruit e);

)

2
QuentinUK

Une alternative plus simple à la réponse "Non-Sequential enums" de Hokyo, basée sur l'utilisation de désignateurs 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 };
1
Lars