C'è la possibilità di convertire i nomi degli enumeratori in stringhe in C?
Risposte:
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.
#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
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];
}
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.
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
.
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 ).
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.
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"
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.
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_str
nell'array non devono essere dichiarate nello stesso ordine degli elementi enum.
printf("enum apple as a string: %s\n", fruit_str[APPLE]);
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.
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);
)
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 };
Di solito lo faccio:
#define COLOR_STR(color) \
(RED == color ? "red" : \
(BLUE == color ? "blue" : \
(GREEN == color ? "green" : \
(YELLOW == color ? "yellow" : "unknown"))))