Есть ли возможность конвертировать имена перечислителей в строку в C?
Как преобразовать имена переименований в строку в c
Ответ 1
Один из способов сделать препроцессор выполненным. Он также обеспечивает синхронизацию ваших перечислений и строк.
#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)
};
После выполнения препроцессора вы получите:
enum FRUIT_ENUM {
    apple, orange, grape, banana,
};
static const char *FRUIT_STRING[] = {
    "apple", "orange", "grape", "banana",
};
Тогда вы можете сделать что-то вроде:
printf("enum apple as a string: %s\n",FRUIT_STRING[apple]);
Если прецедент буквально просто печатает имя перечисления, добавьте следующие макросы:
#define str(x) #x
#define xstr(x) str(x)
Тогда do:
printf("enum apple as a string: %s\n", xstr(apple));
В этом случае может показаться, что двухуровневый макрос лишний, однако, из-за того, как работает строка в C, в некоторых случаях это необходимо. Например, допустим, мы хотим использовать #define с перечислением:
#define foo apple
int main() {
    printf("%s\n", str(foo));
    printf("%s\n", xstr(foo));
}
Вывод будет:
foo
apple
Это связано с тем, что str будет вытеснять входной файл foo, а не расширять его, чтобы быть яблоком. Используя xstr, сначала выполняется макрораспределение, затем этот результат стробируется.
Подробнее см. Stringification.
Ответ 2
В ситуации, когда у вас есть это:
enum fruit {
    apple, 
    orange, 
    grape,
    banana,
    // etc.
};
Мне нравится помещать это в заголовочный файл, где определено перечисление:
static inline char *stringFromFruit(enum fruit f)
{
    static const char *strings[] = { "apple", "orange", "grape", "banana", /* continue for rest of values */ };
    return strings[f];
}
Ответ 3
Нет простого способа добиться этого напрямую. Но P99 имеет макросы, которые позволяют автоматически создавать такой тип функции:
 P99_DECLARE_ENUM(color, red, green, blue);
в файле заголовка и
 P99_DEFINE_ENUM(color);
в одном блоке компиляции (файл .c) затем должен выполнить трюк, в этом примере функция затем будет называться color_getname.
Ответ 4
Я нашел трюк препроцессора C, который выполняет одно и то же задание, не объявляя выделенную строку массива (Источник: http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_en).
Последовательные перечисления
После изобретения Стефана Рама последовательные перечисления (без явного указания индекса, например enum {foo=-1, foo1 = 1}) могут быть реализованы, как этот гениальный трюк:
#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 };
Это дает следующий результат:
int main( void )  { 
    printf( "The color is %s.\n", color_name[ RED ]);  
    printf( "There are %d colors.\n", TOP ); 
}
Цвет КРАСНЫЙ.
Есть 3 цвета.
Непоследовательные перечисления
Так как я хотел сопоставить определения кодов ошибок как строку массива, так что я могу добавить исходное определение ошибки в код ошибки (например, "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."), я расширил код таким образом, чтобы вы могли легко определить требуемый индекс для соответствующих значений перечисления:
#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];
В этом примере препроцессор C будет генерировать следующий код:
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", };
Это приводит к следующим возможностям реализации:
LC_errors__strings [-1] == > LC_errors__strings [LC_RESPONSE_GENERIC_ERROR] == > "LC_RESPONSE_GENERIC_ERROR"
Ответ 5
Функция, подобная этой, без проверки перечисления, является мелочью. Я предлагаю использовать оператор switch. Другим преимуществом является то, что это можно использовать для перечислений, которые имеют определенные значения, например, для флагов, где значения равны 1,2,4,8,16 и т.д.
Также поместите все свои строки перечисления в один массив: -
static const char * allEnums[] = {
    "Undefined",
    "apple",
    "orange"
    /* etc */
};
определить индексы в файле заголовка: -
#define ID_undefined       0
#define ID_fruit_apple     1
#define ID_fruit_orange    2
/* etc */
Это упрощает создание разных версий, например, если вы хотите сделать международные версии вашей программы на других языках.
Использование макроса, также в файле заголовка: -
#define CASE(type,val) case val: index = ID_##type##_##val; break;
Создайте функцию с помощью оператора switch, это должно вернуть const char *, потому что строки 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];
}
При программировании с Windows значения ID_ могут быть значениями ресурсов.
(При использовании С++ все функции могут иметь одно и то же имя.
string EnumToString(fruit e);
)
Ответ 6
Я обычно делаю это:
#define COLOR_STR(color)                            \
    (RED       == color ? "red"    :                \
     (BLUE     == color ? "blue"   :                \
      (GREEN   == color ? "green"  :                \
       (YELLOW == color ? "yellow" : "unknown"))))   
