¿Existe la posibilidad de convertir los nombres de los enumeradores a cadenas en C?
Respuestas:
De una forma, hacer que el preprocesador haga el trabajo. También asegura que sus enumeraciones y cadenas estén sincronizadas.
#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)
};
Una vez finalizado el preprocesador, tendrá:
enum FRUIT_ENUM {
apple, orange, grape, banana,
};
static const char *FRUIT_STRING[] = {
"apple", "orange", "grape", "banana",
};
Entonces podrías hacer algo como:
printf("enum apple as a string: %s\n",FRUIT_STRING[apple]);
Si el caso de uso es simplemente imprimir el nombre de la enumeración, agregue las siguientes macros:
#define str(x) #x
#define xstr(x) str(x)
Entonces hazlo:
printf("enum apple as a string: %s\n", xstr(apple));
En este caso, puede parecer que la macro de dos niveles es superflua, sin embargo, debido a cómo funciona la cadena de caracteres en C, es necesaria en algunos casos. Por ejemplo, digamos que queremos usar un #define con una enumeración:
#define foo apple
int main() {
printf("%s\n", str(foo));
printf("%s\n", xstr(foo));
}
La salida sería:
foo
apple
Esto se debe a que str convertirá en cadena la entrada foo en lugar de expandirla para que sea apple. Al usar xstr, la expansión de la macro se realiza primero, luego ese resultado se secuencia.
Consulte Cadena de caracteres para obtener más información.
#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
En una situación en la que tiene esto:
enum fruit {
apple,
orange,
grape,
banana,
// etc.
};
Me gusta poner esto en el archivo de encabezado donde se define la enumeración:
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)
que escribir "apple"
? No es que haya ningún tipo de seguridad en ninguna parte. A menos que me esté perdiendo algo, lo que sugieres aquí no tiene sentido y solo logra ofuscar el código.
No hay una forma sencilla de lograr esto directamente. Pero P99 tiene macros que le permiten crear este tipo de función automáticamente:
P99_DECLARE_ENUM(color, red, green, blue);
en un archivo de encabezado, y
P99_DEFINE_ENUM(color);
en una unidad de compilación (archivo .c) debería funcionar, en ese ejemplo se llamaría a la función color_getname
.
Encontré un truco del preprocesador de C que está haciendo el mismo trabajo sin declarar una cadena de matriz dedicada (Fuente: http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_en ).
Después de la invención de Stefan Ram, las enumeraciones secuenciales (sin indicar explícitamente el índice, por ejemplo enum {foo=-1, foo1 = 1}
) se pueden realizar como este truco 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 };
Esto da el siguiente resultado:
int main( void ) {
printf( "The color is %s.\n", color_name[ RED ]);
printf( "There are %d colors.\n", TOP );
}
El color es ROJO.
Hay 3 colores.
Como quería asignar las definiciones de los códigos de error a una cadena de matriz, para poder agregar la definición de error sin procesar al código de error (por ejemplo "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."
), extendí el código de esa manera que puede determinar fácilmente el índice requerido para los respectivos valores de enumeración :
#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];
En este ejemplo, el preprocesador de C generará el siguiente 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", };
Esto da como resultado las siguientes capacidades de implementación:
LC_errors__strings [-1] ==> LC_errors__strings [LC_RESPONSE_GENERIC_ERROR] ==> "LC_RESPONSE_GENERIC_ERROR"
No necesita depender del preprocesador para asegurarse de que sus enumeraciones y cadenas estén sincronizadas. Para mí, el uso de macros tiende a hacer que el código sea más difícil de leer.
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: las cadenas de la fruit_str
matriz no tienen que declararse en el mismo orden que los elementos de enumeración.
printf("enum apple as a string: %s\n", fruit_str[APPLE]);
Si tiene miedo de olvidar una cadena, puede agregar la siguiente verificación:
#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);
Se informará un error en el momento de la compilación si la cantidad de elementos de enumeración no coincide con la cantidad de cadenas de la matriz.
Una función como esa sin validar la enumeración es un poco peligrosa. Sugiero usar una declaración de cambio. Otra ventaja es que esto se puede utilizar para enumeraciones que tienen valores definidos, por ejemplo, para banderas donde los valores son 1,2,4,8,16, etc.
También coloque todas sus cadenas de enumeración juntas en una matriz: -
static const char * allEnums[] = {
"Undefined",
"apple",
"orange"
/* etc */
};
definir los índices en un archivo de encabezado: -
#define ID_undefined 0
#define ID_fruit_apple 1
#define ID_fruit_orange 2
/* etc */
Hacer esto facilita la producción de diferentes versiones, por ejemplo, si desea hacer versiones internacionales de su programa con otros idiomas.
Usando una macro, también en el archivo de encabezado: -
#define CASE(type,val) case val: index = ID_##type##_##val; break;
Haga una función con una declaración de cambio, esto debería devolver un const char *
porque las cadenas de caracteres estáticos const: -
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 se programa con Windows, los valores ID_ pueden ser valores de recursos.
(Si usa C ++, todas las funciones pueden tener el mismo nombre.
string EnumToString(fruit e);
)
Una alternativa más simple a la respuesta de "enumeraciones no secuenciales" de Hokyo, basada en el uso de designadores para instanciar la matriz de cadenas:
#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 };
Yo suelo hacer esto:
#define COLOR_STR(color) \
(RED == color ? "red" : \
(BLUE == color ? "blue" : \
(GREEN == color ? "green" : \
(YELLOW == color ? "yellow" : "unknown"))))