Як перетворити імена перерахувань на рядок у c


92

Чи є можливість перетворити імена перечислювача на рядок у C?

Відповіді:


185

Один із способів - змусити препроцесор виконати роботу. Це також забезпечує синхронізацію перерахувань та рядків.

#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, а не розширювати його як apple. За допомогою xstr спочатку робиться розширення макросу, а потім цей результат стригується.

Додаткову інформацію див. У розділі Стрингіфікація .


1
Це ідеально, але я не можу зрозуміти, що насправді відбувається. : O
p0lAris

Також як перетворити рядок у перелік у наведеному вище випадку?
p0lAris

Є кілька способів це зробити, залежно від того, чого ви намагаєтесь досягти?
Терренс М

5
Якщо ви не хочете забруднювати простір імен яблуком та апельсином ... ви можете #define GENERATE_ENUM(ENUM) PREFIX##ENUM,
додати до

1
Для тих, хто стикається з цим повідомленням, цей спосіб використання списку макросів для перерахування різних елементів у програмі неофіційно називається "X макроси".
Лундін,

27

У ситуації, коли у вас таке:

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

4
За своє життя я не бачу, як це допомагає. Не могли б ви трохи розширити, щоб зробити це більш очевидним.
Девід Хеффернан,

2
Добре, як це допомагає? Ви хочете сказати, що набирати текст легше, enumToString(apple)ніж друкувати "apple"? Це не схоже на те, що де-небудь існує будь-яка безпека типу. Якщо я чогось не пропускаю, те, що ви пропонуєте тут, безглуздо і мені вдається лише заплутати код.
Девід Хеффернан,

2
Добре, я бачу зараз. На мій погляд, макрос неправдивий, і я пропоную вам його видалити.
Девід Хеффернан,

2
коментарі говорять про макрос. Де це?
mk ..

2
Це також незручно підтримувати. Якщо я вставляю нове перерахування, я повинен запам'ятати, щоб дублювати його також у масиві, у правильній позиції.
Фабіо

14

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

 P99_DECLARE_ENUM(color, red, green, blue);

у файлі заголовка та

 P99_DEFINE_ENUM(color);

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


Як втягнути цю лібу?
JohnyTex

14

Я знайшов трюк препроцесора 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"


Приємно. Це саме те, що я шукав і використовував для цього. Ті самі помилки :)
mrbean

5

Вам не потрібно покладатися на препроцесор, щоб забезпечити синхронізацію перерахувань та рядків. Для мене використання макросів, як правило, ускладнює читання коду.

Використання 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);

Про помилку буде повідомлено під час компіляції, якщо кількість елементів переліку не збігається з кількістю рядків у масиві.


2

Така функція без перевірки переліку є дрібницею небезпечною. Я пропоную використовувати оператор 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, це повинно повернути a, 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);

)


2

Простіша альтернатива відповіді Хокіо "Непослідовні перерахування", заснована на використанні означників для створення екземпляра масиву рядків:

#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

Я зазвичай роблю це:

#define COLOR_STR(color)                            \
    (RED       == color ? "red"    :                \
     (BLUE     == color ? "blue"   :                \
      (GREEN   == color ? "green"  :                \
       (YELLOW == color ? "yellow" : "unknown"))))   

1
Це просто смішно
Массімо Каллеґарі

Це не погана відповідь. Це зрозуміло, просто і легко зрозуміти. Якщо ви працюєте над системами, де іншим людям потрібно швидко прочитати та зрозуміти ваш код, дуже важлива чіткість. Я б не рекомендував використовувати трюки препроцесора, якщо вони не будуть детально прокоментовані або описані в стандарті кодування.
nielsen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.