Jak przekonwertować nazwy enum na ciąg znaków w c

Czy jest możliwość konwersji nazw enumeratorów na ciąg znaków w C?

Author: Boann, 2012-03-28

8 answers

W jeden sposób, zmuszając preprocesor do pracy. Zapewnia również synchronizację enum i ciągów.

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

Po wykonaniu preprocesora będziesz miał:

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

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

Wtedy możesz zrobić coś takiego:

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

Jeśli przypadek użycia jest dosłownie tylko wydrukowaniem nazwy enum, dodaj następujące makra:

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

To zrób:

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

W tym przypadku może się wydawać, że dwupoziomowe makro jest zbędne, jednak ze względu na to, jak działa stringification w C, jest konieczne w niektórych przypadkach. Na przykład, powiedzmy, że chcemy użyć # define z enum:

#define foo apple

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

Wyjście będzie:

foo
apple

Dzieje się tak dlatego, że str będzie ciągnąć dane wejściowe foo zamiast rozszerzać je na apple. Za pomocą xstr najpierw zostanie wykonane rozszerzenie makra, a następnie wynik zostanie stringified.

Zobacz Stringification aby uzyskać więcej informacji.

 190
Author: Terrence M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-09 15:43:50

W sytuacji, gdy masz to:

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

Lubię umieścić to w pliku nagłówkowym, gdzie enum jest zdefiniowane:

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

    return strings[f];
}
 29
Author: Richard J. Ross III,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-06 11:42:49

Znalazłem trick preprocesora C, który wykonuje to samo zadanie Bez deklarowania dedykowanego ciągu tablicy (Źródło: http://userpage.fu-berlin.de/ ~ ram / pub / pub_jf47ht81Ht / c_preprocessor_applications_en).

Enemy sekwencyjne

Po wynalezieniu Stefana Ram, sekwencyjne liczby (bez wyraźnego wskazania indeksu, np. enum {foo=-1, foo1 = 1}) mogą być zrealizowane w ten sposób:

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

Daje to następujący wynik:

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

The kolor czerwony.
Są 3 kolory.

Nie-sekwencyjne enumery

Ponieważ chciałem odwzorować definicje kodów błędów na ciąg tablicy, aby móc dołączyć surową definicję błędu do kodu błędu (np. "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."), rozszerzyłem kod w ten sposób, że można łatwo określić wymagany indeks dla odpowiednich wartości 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];

W tym przykładzie, preprocesor C wygeneruje następujący kod :

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

Wynika to z następujące możliwości implementacji:

LC_errors_ _ strings[-1] = = > LC_Error_ _ strings[LC_RESPONSE_GENERIC_ERROR] = = >"LC_RESPONSE_GENERIC_ERROR"

 15
Author: Maschina,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-17 13:39:59

Nie ma prostego sposobu, aby osiągnąć to bezpośrednio. Ale P99 ma makra, które pozwalają na automatyczne tworzenie tego typu funkcji:

 P99_DECLARE_ENUM(color, red, green, blue);

W pliku nagłówkowym, oraz

 P99_DEFINE_ENUM(color);

W jednej jednostce kompilacyjnej (.plik c) powinien wtedy wykonać trick, w tym przykładzie funkcja zostanie wywołana color_getname.

 14
Author: Jens Gustedt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-28 12:25:14

Nie musisz polegać na preprocesorze, aby upewnić się, że twoje liczby i ciągi są zsynchronizowane. Dla mnie używanie makr sprawia, że kod jest trudniejszy do odczytania.

Używanie Enum I Tablicy Łańcuchów

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

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

Uwaga: łańcuchy w tablicy fruit_str nie muszą być deklarowane w tej samej kolejności co elementy enum.

Jak Go Używać

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

Dodawanie Sprawdzania Czasu Kompilacji

Jeśli boisz się zapomnieć jednego ciągu, możesz dodać następujące sprawdzić:

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

Błąd zostanie zgłoszony podczas kompilacji, jeśli ilość pozycji enum nie odpowiada ilości łańcuchów w tablicy.

 7
Author: jyvet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-10-22 11:12:20

Taka funkcja bez sprawdzania enum jest trochę niebezpieczna. Sugeruję użycie polecenia switch. Kolejną zaletą jest to, że można to wykorzystać dla enum, które mają zdefiniowane wartości, na przykład dla FLAG, gdzie wartości są 1,2,4,8,16 itd.

Również umieść wszystkie swoje łańcuchy enum razem w jednej tablicy: -

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

Definiowanie indeksów w pliku nagłówkowym:-

#define ID_undefined       0
#define ID_fruit_apple     1
#define ID_fruit_orange    2
/* etc */
Dzięki temu łatwiej jest produkować różne wersje, na przykład jeśli chcesz stworzyć międzynarodową wersje programu z innymi językami.

Użycie makra, również w pliku nagłówkowym:-

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

Make a function with a switch statement, this should return a const char * because the strings 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];
}

Jeśli programowanie z Windows To wartości ID_ mogą być wartościami zasobów.

(Jeśli używasz C++, wszystkie funkcje mogą mieć tę samą nazwę.

string EnumToString(fruit e);

)

 2
Author: QuentinUK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-09 10:50:37

Prostsza alternatywa dla odpowiedzi Hokyo "non-Sequential enums", oparta na użyciu desygnatorów do tworzenia instancji tablicy łańcuchów:

#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
Author: Lars,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-19 12:51:13

Zwykle robię to:

#define COLOR_STR(color)                            \
    (RED       == color ? "red"    :                \
     (BLUE     == color ? "blue"   :                \
      (GREEN   == color ? "green"  :                \
       (YELLOW == color ? "yellow" : "unknown"))))   
 -2
Author: gigilibala,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-14 02:10:14