Есть ли возможность преобразовать имена перечислителей в строку на C?
Один из способов - заставить препроцессор делать всю работу. Это также обеспечивает синхронизацию перечислений и строк.
#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)
Затем сделайте:
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 для получения дополнительной информации.
#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
В ситуации, когда у вас есть это:
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]; }
источник
enumToString(apple)
чем печатать"apple"
? Это не похоже на безопасность типов. Если мне не чего-то не хватает, то, что вы предлагаете здесь, бессмысленно и просто преуспевает в обфускации кода.Нет простого способа добиться этого напрямую. Но в P99 есть макросы, которые позволяют автоматически создавать такие функции:
в файле заголовка и
в одном модуле компиляции (файл .c) должен сделать трюк, в этом примере тогда будет вызвана функция
color_getname
.источник
Я нашел трюк с препроцессором 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 ); }
Непоследовательные перечисления
Поскольку я хотел сопоставить определения кодов ошибок с строкой массива, чтобы я мог добавить исходное определение ошибки к коду ошибки (например
"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", };
Это приводит к следующим возможностям реализации:
источник
Вам не нужно полагаться на препроцессор, чтобы обеспечить синхронизацию перечислений и строк. На мой взгляд, использование макросов затрудняет чтение кода.
Использование Enum и массива строк
enum fruit { APPLE = 0, ORANGE, GRAPE, BANANA, /* etc. */ FRUIT_MAX }; const char * const fruit_str[] = { [BANANA] = "banana", [ORANGE] = "orange", [GRAPE] = "grape", [APPLE] = "apple", /* etc. */ };
Примечание: строки в
fruit_str
массиве необязательно объявлять в том же порядке, что и элементы перечисления.Как это использовать
printf("enum apple as a string: %s\n", fruit_str[APPLE]);
Добавление проверки времени компиляции
Если вы боитесь забыть одну строку, можете добавить следующую проверку:
#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);
Во время компиляции будет сообщено об ошибке, если количество элементов перечисления не соответствует количеству строк в массиве.
источник
Такая функция без проверки enum немного опасна. Я предлагаю использовать оператор 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_ могут быть значениями ресурсов.
(При использовании C ++ все функции могут иметь одно и то же имя.
string EnumToString(fruit e);
)
источник
Более простая альтернатива ответу Hokyo "Non-Sequential enums", основанная на использовании указателей для создания экземпляра массива строк:
#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 };
источник
Я обычно так делаю:
#define COLOR_STR(color) \ (RED == color ? "red" : \ (BLUE == color ? "blue" : \ (GREEN == color ? "green" : \ (YELLOW == color ? "yellow" : "unknown"))))
источник