Простой способ использовать переменные типов перечисления в виде строки в C? - PullRequest
83 голосов
/ 29 сентября 2008

Вот что я пытаюсь сделать:

typedef enum { ONE, TWO, THREE } Numbers;

Я пытаюсь написать функцию, которая будет выполнять переключение, похожее на следующее:

char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, num); //some way to get the symbolic constant name in here?
    } break;
    default:
      return 0; //no match
  return 1;
}

Вместо определения в каждом случае, есть ли способ установить его с помощью переменной enum, как я пытаюсь сделать выше?

Ответы [ 19 ]

63 голосов
/ 14 октября 2008

Техника из Здесь можно использовать что-то и идентификатор С, и строку? .

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

Заводской код - вводится только один раз, обычно скрывается в заголовке:

enumFactory.h:

// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,

// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;

// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;

/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
  enum EnumType { \
    ENUM_DEF(ENUM_VALUE) \
  }; \
  const char *GetString(EnumType dummy); \
  EnumType Get##EnumType##Value(const char *string); \

/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
  const char *GetString(EnumType value) \
  { \
    switch(value) \
    { \
      ENUM_DEF(ENUM_CASE) \
      default: return ""; /* handle input error */ \
    } \
  } \
  EnumType Get##EnumType##Value(const char *str) \
  { \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; /* handle input error */ \
  } \

Фабрика используется

someEnum.h:

#include "enumFactory.h"
#define SOME_ENUM(XX) \
    XX(FirstValue,) \
    XX(SecondValue,) \
    XX(SomeOtherValue,=50) \
    XX(OneMoreValue,=100) \

DECLARE_ENUM(SomeEnum,SOME_ENUM)

someEnum.cpp:

#include "someEnum.h"
DEFINE_ENUM(SomeEnum,SOME_ENUM)

Техника может быть легко расширена, так что макросы XX могут принимать больше аргументов, и вы также можете подготовить больше макросов для замены XX для различных нужд, аналогично трем, которые я предоставил в этом примере.

Сравнение с X-макросами с использованием #include / #define / # undef

Хотя это похоже на X-Macros, о которых упоминали другие, я думаю, что это решение более элегантно, так как не требует #undefing ничего, что позволяет скрыть более сложные вещи в заводском файле заголовка - файл заголовка - это то, к чему вы вообще не прикасаетесь, когда вам нужно определить новое перечисление, поэтому новое определение перечисления намного короче и чище.

61 голосов
/ 29 сентября 2008
// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
    ENUM(ONE),
    ENUM(TWO),
    ENUM(FOUR)
ENUM_END( Numbers )

// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };

// Now in one and only one .c file, redefine the ENUM macros and reinclude
//  the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED   // whatever you need to do to enable reinclusion
#include "numbers.h"

// Now you can do exactly what you want to do, with no retyping, and for any
//  number of enumerated types defined with the ENUM macro family
//  Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
    } break;
    default:
      return 0; //no match
  return 1;
}

// Sweet no ? After being frustrated by this for years, I finally came up
//  with this solution for my most recent project and plan to reuse the idea
//  forever
13 голосов
/ 29 сентября 2008

Нет встроенного решения. Самый простой способ - с массивом char*, где значение int перечисления указывается в строке, содержащей описательное имя этого перечисления. Если у вас есть разреженный enum (тот, который не начинается с 0 или имеет пробелы в нумерации), где некоторые из отображений int достаточно высоки, чтобы сделать отображение на основе массива непрактичным, вы можете использовать хеш-таблицу вместо этого.

13 голосов
/ 29 сентября 2008

Определенно есть способ сделать это - использовать макросы X () . Эти макросы используют препроцессор C для создания перечислений, массивов и блоков кода из списка исходных данных. Вам нужно только добавить новые элементы в #define, содержащий макрос X (). Оператор switch будет расширяться автоматически.

Ваш пример можно записать следующим образом:

 // Source data -- Enum, String
 #define X_NUMBERS \
    X(ONE,   "one") \
    X(TWO,   "two") \
    X(THREE, "three")

 ...

 // Use preprocessor to create the Enum
 typedef enum {
  #define X(Enum, String)       Enum,
   X_NUMBERS
  #undef X
 } Numbers;

 ...

 // Use Preprocessor to expand data into switch statement cases
 switch(num)
 {
 #define X(Enum, String) \
     case Enum:  strcpy(num_str, String); break;
 X_NUMBERS
 #undef X

     default: return 0; break;
 }
 return 1;

Существуют более эффективные способы (например, использование X-макросов для создания массива строк и индекса enum), но это самая простая демонстрация.

8 голосов
/ 29 сентября 2008

Я знаю, что у вас есть пара хороших твердых ответов, но знаете ли вы об операторе # в препроцессоре C?

Это позволяет вам сделать это:

#define MACROSTR(k) #k

typedef enum {
    kZero,
    kOne,
    kTwo,
    kThree
} kConst;

static char *kConstStr[] = {
    MACROSTR(kZero),
    MACROSTR(kOne),
    MACROSTR(kTwo),
    MACROSTR(kThree)
};

static void kConstPrinter(kConst k)
{
    printf("%s", kConstStr[k]);
}
6 голосов
/ 05 июля 2010

ПОЦЕЛУЙ. Вы будете делать всевозможные другие переключатели / кейсы со своими перечислениями, так почему же печать должна отличаться? Забыть дело в своей процедуре печати не так уж и сложно, если учесть, что есть еще около 100 мест, которые можно забыть. Просто скомпилируйте -Wall, который будет предупреждать о неисчерпывающих совпадениях. Не используйте «default», потому что это сделает переключение исчерпывающим, и вы не получите предупреждений. Вместо этого позвольте коммутатору выйти и разобраться со случаем по умолчанию, например ...

const char *myenum_str(myenum e)
{
    switch(e) {
    case ONE: return "one";
    case TWO: return "two";
    }
    return "invalid";
}
5 голосов
/ 29 сентября 2008

C или C ++ не предоставляют эту функциональность, хотя она мне часто нужна.

Следующий код работает, хотя лучше всего подходит для не разреженных перечислений.

typedef enum { ONE, TWO, THREE } Numbers;
char *strNumbers[] = {"one","two","three"};
printf ("Value for TWO is %s\n",strNumbers[TWO]);

Под не разреженным я имею в виду не форму

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

, поскольку в нем огромные пробелы.

Преимущество этого метода в том, что он помещает определения перечислений и строк рядом друг с другом; наличие оператора switch в функции приводит к их заклинанию. Это означает, что у вас меньше шансов изменить одно без другого.

4 голосов
/ 23 февраля 2012

Использование boost :: препроцессора делает возможным элегантное решение, подобное следующему:

Шаг 1: включить заголовочный файл:

#include "EnumUtilities.h"

Шаг 2: объявить объект перечисления со следующим синтаксисом:

MakeEnum( TestData,
         (x)
         (y)
         (z)
         );

Шаг 3: используйте ваши данные:

Получение количества элементов:

td::cout << "Number of Elements: " << TestDataCount << std::endl;

Получение связанной строки:

std::cout << "Value of " << TestData2String(x) << " is " << x << std::endl;
std::cout << "Value of " << TestData2String(y) << " is " << y << std::endl;
std::cout << "Value of " << TestData2String(z) << " is " << z << std::endl;

Получение значения enum из связанной строки:

std::cout << "Value of x is " << TestData2Enum("x") << std::endl;
std::cout << "Value of y is " << TestData2Enum("y") << std::endl;
std::cout << "Value of z is " << TestData2Enum("z") << std::endl;

Это выглядит чисто и компактно, без дополнительных файлов для включения. Код, который я написал в EnumUtilities.h, выглядит следующим образом:

#include <boost/preprocessor/seq/for_each.hpp>
#include <string>

#define REALLY_MAKE_STRING(x) #x
#define MAKE_STRING(x) REALLY_MAKE_STRING(x)
#define MACRO1(r, data, elem) elem,
#define MACRO1_STRING(r, data, elem)    case elem: return REALLY_MAKE_STRING(elem);
#define MACRO1_ENUM(r, data, elem)      if (REALLY_MAKE_STRING(elem) == eStrEl) return elem;


#define MakeEnum(eName, SEQ) \
    enum eName { BOOST_PP_SEQ_FOR_EACH(MACRO1, , SEQ) \
    last_##eName##_enum}; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_STRING, , SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    }; \
    static enum eName eName##2Enum(const std::string eStrEl) \
    { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_ENUM, , SEQ) \
        return (enum eName)0; \
    };

Есть некоторые ограничения, например, boost :: препроцессор. В этом случае список констант не может быть больше 64 элементов.

Следуя той же логике, вы также можете подумать о создании разреженного перечисления:

#define EnumName(Tuple)                 BOOST_PP_TUPLE_ELEM(2, 0, Tuple)
#define EnumValue(Tuple)                BOOST_PP_TUPLE_ELEM(2, 1, Tuple)
#define MACRO2(r, data, elem)           EnumName(elem) EnumValue(elem),
#define MACRO2_STRING(r, data, elem)    case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem));

#define MakeEnumEx(eName, SEQ) \
    enum eName { \
    BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \
    last_##eName##_enum }; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    };  

В этом случае синтаксис:

MakeEnumEx(TestEnum,
           ((x,))
           ((y,=1000))
           ((z,))
           );

Использование аналогично описанному выше (за исключением функции eName ## 2Enum, которую вы можете попытаться экстраполировать из предыдущего синтаксиса).

Я тестировал его на Mac и Linux, но учтите, что препроцессор boost :: не может быть полностью переносимым.

4 голосов
/ 29 сентября 2008

Попробуйте Преобразование перечислений C ++ в строки . комментарии имеют улучшения, которые решают проблему, когда элементы enum имеют произвольные значения.

3 голосов
/ 10 апреля 2015

Объединив некоторые приемы, я получил простейшую форму:

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}
...