Как преобразовать имена перечислений в строку в c

Есть ли возможность конвертировать имена перечислителей в строку в C?

10 ответов

Решение

Один из способов заставить препроцессор сделать работу. Это также гарантирует, что ваши перечисления и строки синхронизированы.

#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 для получения дополнительной информации.

В ситуации, когда у вас есть это:

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

Мне нравится помещать это в заголовочный файл, где определяется enum:

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

    return strings[f];
}

Вам не нужно полагаться на препроцессор, чтобы обеспечить синхронизацию перечислений и строк. На мой взгляд, использование макросов затрудняет чтение кода.

Использование 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 array не обязательно объявлять в том же порядке, что и элементы перечисления.

Как это использовать

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

Во время компиляции будет сообщено об ошибке, если количество элементов перечисления не соответствует количеству строк в массиве.

Я обнаружил уловку препроцессора 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"

Нет простого способа достичь этого напрямую. Но в P99 есть макросы, которые позволяют автоматически создавать функции такого типа:

 P99_DECLARE_ENUM(color, red, green, blue);

в заголовочном файле и

 P99_DEFINE_ENUM(color);

в одном модуле компиляции (файл.c) следует выполнить свое действие, в этом примере функция будет вызвана color_getname,

Более простая альтернатива ответу 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"))))   

Такая функция без проверки перечисления является немного опасной. Я предлагаю использовать оператор 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 * потому что строки статические константы:-

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

)

Я решил создать функцию, тело которой обновляется путем копирования перечисления и использования регулярного выражения в Vim. Я использую переключатель, потому что мое перечисление не является компактным, поэтому у нас есть максимальная гибкость. Я сохраняю регулярное выражение в качестве комментария в коде, так что это просто вопрос его копирования и вставки.

Мое перечисление (сокращенное, реальное намного больше):

      enum opcode
{
    op_1word_ops = 1024,
    op_end,

    op_2word_ops = 2048,
    op_ret_v,
    op_jmp,

    op_3word_ops = 3072,
    op_load_v,
    op_load_i,

    op_5word_ops = 5120,
    op_func2_vvv,
};

Функция перед копированием перечисления:

      const char *get_op_name(enum opcode op)
{
    // To update copy the enum and apply this regex:
    // s/\t\([^, ]*\).*$/\t\tcase \1:    \treturn "\1";
    switch (op)
    {
    }

    return "Unknown op";
}

Я вставляю содержимое перечисления в скобки переключателя:

      const char *get_op_name(enum opcode op)
{
    // To update copy the enum and apply this regex:
    // s/\t\([^, ]*\).*$/\t\tcase \1:    \treturn "\1";
    switch (op)
    {
    op_1word_ops = 1024,
    op_end,

    op_2word_ops = 2048,
    op_ret_v,
    op_jmp,

    op_3word_ops = 3072,
    op_load_v,
    op_load_i,

    op_5word_ops = 5120,
    op_func2_vvv,
    }

    return "Unknown op";
}

Затем с помощью Shift-V в Vim я выделяю строки, нажимаю :затем вставьте (Ctrl-V в Windows) регулярное выражение s/\t\([^, ]*\).*$/\t\tcase \1: \treturn "\1";и нажмите Enter:

      const char *get_op_name(enum opcode op)
{
    // To update copy the enum and apply this regex:
    // s/\t\([^, ]*\).*$/\t\tcase \1:    \treturn "\1";
    switch (op)
    {
        case op_1word_ops:      return "op_1word_ops";
        case op_end:        return "op_end";

        case op_2word_ops:      return "op_2word_ops";
        case op_ret_v:      return "op_ret_v";
        case op_jmp:        return "op_jmp";

        case op_3word_ops:      return "op_3word_ops";
        case op_load_v:     return "op_load_v";
        case op_load_i:     return "op_load_i";

        case op_5word_ops:      return "op_5word_ops";
        case op_func2_vvv:      return "op_func2_vvv";
    }

    return "Unknown op";
}

Регулярное выражение пропускает первый \tсимвол, затем помещает каждый следующий символ, который не является ни ,ни в и соответствует остальной части строки, чтобы удалить все. Затем с \1будучи меткой перечисления, он переделывает строки в case <label>: return "<label>";формат. Обратите внимание, что в этом посте он выглядит плохо выровненным только потому, что StackOverflow использует табуляцию с 4 пробелами, тогда как в Vim я использую табуляцию с 8 пробелами, поэтому вы можете отредактировать регулярное выражение для стиля.

Чтобы избежать путаницы между целыми числами и именами членов перечисления, я решил использовать в процессе отладки следующую команду:

enum {CC, CV, STOP, DESCONECTADO};

unsigned char mode;

Serial.print(" mode = "); Serial.println((mode == 0) ? "CC" : (mode == 1) ? "CV" : (mode == 2) ? "STOP" : "DESCONECTADO");

Таким образом, он печатает слово состояния вместо целочисленного значения.

Другие вопросы по тегам