Cómo convertir nombres de enumeración a cadenas en c

92

¿Existe la posibilidad de convertir los nombres de los enumeradores a cadenas en C?

Misha
fuente

Respuestas:

185

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.

Terrence M
fuente
1
Esto es perfecto, pero no puedo entender lo que realmente está sucediendo. : O
p0lAris
Además, ¿cómo se convierte una cadena en una enumeración en el caso anterior?
p0lAris
Hay algunas formas de hacerlo, dependiendo de lo que esté tratando de lograr.
Terrence M
5
Si no desea contaminar el espacio de nombres con manzana y naranja ... puede prefijarlo con#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
jsaak
1
Para aquellos que se encuentran con esta publicación, este método de usar una lista de macros para enumerar varios elementos en un programa se llama informalmente "macros X".
Lundin
27

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];
}
Richard J. Ross III
fuente
4
Por mi vida, no puedo ver cómo esto ayuda. ¿Podría expandirse un poco para hacerlo más obvio?
David Heffernan
2
OK, ¿cómo ayuda eso? ¿Estás diciendo que es más fácil escribir 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.
David Heffernan
2
OK, ya veo. La macro es falsa en mi opinión y le sugiero que la elimine.
David Heffernan
2
los comentarios hablan de macro. ¿Dónde está?
mk ..
2
Esto también es un inconveniente de mantener. Si inserto una nueva enumeración, tengo que recordar duplicarla también en la matriz, en la posición correcta.
Fabio
14

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.

Jens Gustedt
fuente
¿Cómo introduzco esta biblioteca?
JohnyTex
14

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

Enumeraciones secuenciales

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.

Enumeraciones no secuenciales

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"

Maschina
fuente
Agradable. Esto es exactamente lo que estaba buscando y usando. Mismos errores :)
mrbean
5

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.

Usando Enum y una matriz de cadenas

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_strmatriz no tienen que declararse en el mismo orden que los elementos de enumeración.

Cómo usarlo

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

Agregar una verificación de tiempo de compilación

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.

jyvet
fuente
2

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

)

QuentinUK
fuente
2

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 };
Lars
fuente
-2

Yo suelo hacer esto:

#define COLOR_STR(color)                            \
    (RED       == color ? "red"    :                \
     (BLUE     == color ? "blue"   :                \
      (GREEN   == color ? "green"  :                \
       (YELLOW == color ? "yellow" : "unknown"))))   
gigilibala
fuente
1
Esto es simplemente ridículo
Massimo Callegari
Esta no es una mala respuesta. Es claro, simple y fácil de entender. Si está trabajando en sistemas donde otras personas necesitan leer y comprender su código rápidamente, la claridad es muy importante. No recomendaría usar trucos de preprocesador a menos que estén bien comentados o descritos en un estándar de codificación.
nielsen