Come convertire i nomi enum in una stringa in c


92

C'è la possibilità di convertire i nomi degli enumeratori in stringhe in C?

Risposte:


185

Un modo, fare in modo che il preprocessore faccia il lavoro. Assicura inoltre che le enumerazioni e le stringhe siano sincronizzate.

#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)
};

Dopo che il preprocessore ha finito, avrai:

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

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

Quindi potresti fare qualcosa come:

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

Se il caso d'uso consiste letteralmente solo nella stampa del nome dell'enumerazione, aggiungi le seguenti macro:

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

Quindi fa:

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

In questo caso, può sembrare che la macro a due livelli sia superflua, tuttavia, a causa di come funziona la stringa in C, in alcuni casi è necessaria. Ad esempio, supponiamo di voler utilizzare un #define con un'enumerazione:

#define foo apple

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

L'output sarebbe:

foo
apple

Questo perché str stringerà l'input pippo invece di espanderlo per essere apple. Usando xstr, l'espansione della macro viene eseguita per prima, quindi il risultato viene stringa.

Vedere Stringificazione per ulteriori informazioni.


1
È perfetto, ma non riesco a capire cosa stia realmente accadendo. : O
p0lAris

Inoltre come si converte una stringa in un enum nel caso precedente?
p0lAris

Ci sono alcuni modi in cui potrebbe essere fatto, a seconda di cosa stai cercando di ottenere?
Terrence M

5
Se non vuoi inquinare lo spazio dei nomi con mela e arancia ... puoi #define GENERATE_ENUM(ENUM) PREFIX##ENUM,
anteporlo

1
Per coloro che si imbattono in questo post, questo metodo di utilizzare un elenco di macro per enumerare vari elementi in un programma è chiamato in modo informale "macro X".
Lundin

27

In una situazione in cui hai questo:

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

Mi piace metterlo nel file di intestazione in cui è definito l'enum:

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

    return strings[f];
}

4
Per la vita di me non riesco a vedere come questo aiuta. Potresti espandere un po 'per renderlo più ovvio.
David Heffernan

2
OK, come ti aiuta? Stai dicendo che è più facile scrivere enumToString(apple)che digitare "apple"? Non è che ci sia alcuna protezione dai tipi da nessuna parte. A meno che non mi manchi qualcosa, quello che suggerisci qui è inutile e riesce solo ad offuscare il codice.
David Heffernan

2
OK, ora capisco. La macro è fasulla a mio modo di vedere e ti consiglio di eliminarla.
David Heffernan

2
i commenti parlano di macro. Dov'è?
mk ..

2
Anche questo è scomodo da mantenere. Se inserisco una nuova enum devo ricordarmi di duplicarla anche nell'array, nella posizione corretta.
Fabio

14

Non esiste un modo semplice per ottenere questo risultato direttamente. Ma P99 ha delle macro che ti permettono di creare questo tipo di funzione automaticamente:

 P99_DECLARE_ENUM(color, red, green, blue);

in un file di intestazione e

 P99_DEFINE_ENUM(color);

in un'unità di compilazione (file .c) dovrebbe quindi fare il trucco, in quell'esempio la funzione sarebbe quindi chiamata color_getname.


Come inserisco questa libreria?
JohnyTex

14

Ho trovato un trucco per il preprocessore C che sta facendo lo stesso lavoro senza dichiarare una stringa di array dedicata (Fonte: http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_en ).

Enumerazioni sequenziali

In seguito all'invenzione di Stefan Ram, le enumerazioni sequenziali (senza indicare esplicitamente l'indice, ad esempio enum {foo=-1, foo1 = 1}) possono essere realizzate come questo trucco geniale:

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

Questo dà il seguente risultato:

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

Il colore è ROSSO.
Ci sono 3 colori.

Enumerazioni non sequenziali

Poiché volevo mappare le definizioni dei codici di errore in una stringa di matrice, in modo da poter aggiungere la definizione di errore non elaborata al codice di errore (ad esempio "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."), ho esteso il codice in questo modo che puoi facilmente determinare l'indice richiesto per i rispettivi valori di 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];

In questo esempio, il preprocessore C genererà il codice seguente :

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

Ciò si traduce nelle seguenti capacità di implementazione:

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


Bello. Questo è esattamente quello che stavo cercando e lo usavo. Stessi errori :)
mrbean

5

Non è necessario fare affidamento sul preprocessore per assicurarsi che le enumerazioni e le stringhe siano sincronizzate. Per me l'utilizzo delle macro tende a rendere il codice più difficile da leggere.

Utilizzo di Enum e un array di stringhe

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

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

Nota: le stringhe fruit_strnell'array non devono essere dichiarate nello stesso ordine degli elementi enum.

Come usarlo

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

Aggiunta di un controllo in fase di compilazione

Se hai paura di dimenticare una stringa, puoi aggiungere il seguente controllo:

#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);

Viene segnalato un errore in fase di compilazione se la quantità di elementi enum non corrisponde alla quantità di stringhe nell'array.


2

Una funzione del genere senza convalidare l'enum è un po 'pericolosa. Suggerisco di utilizzare un'istruzione switch. Un altro vantaggio è che questo può essere utilizzato per enumerazioni che hanno valori definiti, ad esempio per i flag in cui i valori sono 1,2,4,8,16 ecc.

Metti anche tutte le tue stringhe enum in un array: -

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

definire gli indici in un file di intestazione: -

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

In questo modo è più semplice produrre versioni diverse, ad esempio se si desidera creare versioni internazionali del programma con altre lingue.

Utilizzando una macro, anche nel file di intestazione: -

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

Crea una funzione con un'istruzione switch, dovrebbe restituire un const char *perché le stringhe static 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];
}

Se si programma con Windows, i valori ID_ possono essere valori di risorsa.

(Se si utilizza C ++, tutte le funzioni possono avere lo stesso nome.

string EnumToString(fruit e);

)


2

Un'alternativa più semplice alla risposta "Non-Sequential enums" di Hokyo, basata sull'utilizzo di designatori per istanziare l'array di stringhe:

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

-2

Di solito lo faccio:

#define COLOR_STR(color)                            \
    (RED       == color ? "red"    :                \
     (BLUE     == color ? "blue"   :                \
      (GREEN   == color ? "green"  :                \
       (YELLOW == color ? "yellow" : "unknown"))))   

1
Questo è semplicemente ridicolo
Massimo Callegari

Questa non è una cattiva risposta. È chiaro, semplice e di facile comprensione. Se stai lavorando su sistemi in cui altre persone hanno bisogno di leggere e comprendere rapidamente il tuo codice, la chiarezza è molto importante. Non consiglierei di usare trucchi del preprocessore a meno che non siano accuratamente commentati o descritti in uno standard di codifica.
nielsen
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.