Respostas:
Uma maneira, fazer o pré-processador fazer o trabalho. Ele também garante que seus enums e strings estejam sincronizados.
#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)
};
Depois que o pré-processador estiver pronto, você terá:
enum FRUIT_ENUM {
apple, orange, grape, banana,
};
static const char *FRUIT_STRING[] = {
"apple", "orange", "grape", "banana",
};
Então você poderia fazer algo como:
printf("enum apple as a string: %s\n",FRUIT_STRING[apple]);
Se o caso de uso for literalmente apenas imprimir o nome enum, adicione as seguintes macros:
#define str(x) #x
#define xstr(x) str(x)
Então faça:
printf("enum apple as a string: %s\n", xstr(apple));
Nesse caso, pode parecer que a macro de dois níveis é supérflua, no entanto, devido ao modo como a estringificação funciona em C, ela é necessária em alguns casos. Por exemplo, digamos que queremos usar um #define com um enum:
#define foo apple
int main() {
printf("%s\n", str(foo));
printf("%s\n", xstr(foo));
}
O resultado seria:
foo
apple
Isso ocorre porque str irá restringir a entrada foo em vez de expandi-la para ser apple. Usando xstr, a expansão da macro é feita primeiro e, em seguida, o resultado é codificado.
Consulte Stringificação para obter mais informações.
#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
Em uma situação em que você tem:
enum fruit {
apple,
orange,
grape,
banana,
// etc.
};
Gosto de colocar isso no arquivo de cabeçalho onde o enum é definido:
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)
do que digitar "apple"
? Não é como se houvesse qualquer tipo de segurança em qualquer lugar. A menos que eu esteja perdendo algo, o que você sugere aqui é inútil e apenas consegue ofuscar o código.
Não existe uma maneira simples de fazer isso diretamente. Mas o P99 tem macros que permitem criar esse tipo de função automaticamente:
P99_DECLARE_ENUM(color, red, green, blue);
em um arquivo de cabeçalho, e
P99_DEFINE_ENUM(color);
em uma unidade de compilação (arquivo .c) deve, então, fazer o truque, nesse exemplo a função seria chamada color_getname
.
Eu encontrei um truque do pré-processador C que está fazendo o mesmo trabalho sem declarar uma string de array dedicada (Fonte: http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_en ).
Seguindo a invenção de Stefan Ram, enums sequenciais (sem declarar explicitamente o índice, por exemplo enum {foo=-1, foo1 = 1}
) podem ser realizados como este truque genial:
#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 };
Isso dá o seguinte resultado:
int main( void ) {
printf( "The color is %s.\n", color_name[ RED ]);
printf( "There are %d colors.\n", TOP );
}
A cor é VERMELHA.
Existem 3 cores.
Como eu queria mapear as definições dos códigos de erro para strings de array, para que eu pudesse anexar a definição de erro bruta ao código de erro (por exemplo "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."
), estendi o código de forma que você possa determinar facilmente o índice necessário para os respectivos valores 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];
Neste exemplo, o pré-processador C gerará o seguinte código :
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", };
Isso resulta nos seguintes recursos de implementação:
LC_errors__strings [-1] ==> LC_errors__strings [LC_RESPONSE_GENERIC_ERROR] ==> "LC_RESPONSE_GENERIC_ERROR"
Você não precisa depender do pré-processador para garantir que seus enums e strings estejam sincronizados. Para mim, o uso de macros torna o código mais difícil de ler.
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: as strings na fruit_str
matriz não precisam ser declaradas na mesma ordem que os itens enum.
printf("enum apple as a string: %s\n", fruit_str[APPLE]);
Se você tem medo de esquecer uma string, pode adicionar a seguinte verificação:
#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);
Um erro seria relatado em tempo de compilação se a quantidade de itens enum não corresponder à quantidade de strings na matriz.
Uma função como essa sem validar o enum é um pouco perigosa. Eu sugiro usar uma instrução switch. Outra vantagem é que isso pode ser usado para enums que possuem valores definidos, por exemplo, para sinalizadores onde os valores são 1,2,4,8,16 etc.
Além disso, coloque todas as strings de enum juntas em uma matriz: -
static const char * allEnums[] = {
"Undefined",
"apple",
"orange"
/* etc */
};
definir os índices em um arquivo de cabeçalho: -
#define ID_undefined 0
#define ID_fruit_apple 1
#define ID_fruit_orange 2
/* etc */
Isso facilita a produção de diferentes versões, por exemplo, se você deseja fazer versões internacionais de seu programa com outros idiomas.
Usando uma macro, também no arquivo de cabeçalho: -
#define CASE(type,val) case val: index = ID_##type##_##val; break;
Faça uma função com uma instrução switch, isso deve retornar um const char *
porque as strings consts estáticas: -
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 estiver programando com Windows, os valores ID_ podem ser valores de recursos.
(Se estiver usando C ++, todas as funções podem ter o mesmo nome.
string EnumToString(fruit e);
)
Uma alternativa mais simples para a resposta de "enums não sequenciais" de Hokyo, com base no uso de designadores para instanciar a matriz de string:
#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 };
Eu normalmente faço isso:
#define COLOR_STR(color) \
(RED == color ? "red" : \
(BLUE == color ? "blue" : \
(GREEN == color ? "green" : \
(YELLOW == color ? "yellow" : "unknown"))))