Легкий способ использовать переменные типов enums как строку в C?

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

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, как я пытаюсь сделать выше?

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

 // 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 

Способ от создания чего-то как идентификатора C и строки? можно использовать здесь.

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

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

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) 

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

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

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

Существует определенно способ сделать это – использовать macros X () . Эти macros используют препроцессор 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 Макросов для создания строкового массива и индекса enums), но это простейшая демонстрация.

Я знаю, что у вас есть пара хороших надежных ответов, но знаете ли вы о # -операторе в препроцессоре 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]); } 

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 в функции, скопирует их. Это означает, что вы вряд ли измените одно без другого.

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

 const char *myenum_str(myenum e) { switch(e) { case ONE: return "one"; case TWO: return "two"; } return "invalid"; } 

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

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

Шаг 1: укажите файл заголовка:

 #include "EnumUtilities.h" 

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

 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; 

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

 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  #include  #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 элемента.

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

 #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 :: preprocessor может быть не полностью переносимым.

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

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

Если вы используете gcc, можно использовать:

 const char * enum_to_string_map[]={ [enum1]='string1', [enum2]='string2'}; 

Тогда просто позвоните, например

 enum_to_string_map[enum1] 

Ознакомьтесь с идеями в исследовательских лабораториях Mu Dynamics – Архив блога . Я нашел это в начале этого года – я забыл точный контекст, где я столкнулся с ним, – и адаптировал его в этот код. Мы можем обсудить достоинства добавления E на фронт; он применим к конкретной проблеме, но не является частью общего решения. Я спрятал это в своей папке «Виньетки», где я оставляю интересные fragmentы кода в случае, если я захочу их позже. Мне стыдно говорить, что я не помнил, откуда эта идея появилась в то время.

Заголовок: paste1.h

 /* @(#)File: $RCSfile: paste1.h,v $ @(#)Version: $Revision: 1.1 $ @(#)Last changed: $Date: 2008/05/17 21:38:05 $ @(#)Purpose: Automated Token Pasting */ #ifndef JLSS_ID_PASTE_H #define JLSS_ID_PASTE_H /* * Common case when someone just includes this file. In this case, * they just get the various E* tokens as good old enums. */ #if !defined(ETYPE) #define ETYPE(val, desc) E##val, #define ETYPE_ENUM enum { #endif /* ETYPE */ ETYPE(PERM, "Operation not permitted") ETYPE(NOENT, "No such file or directory") ETYPE(SRCH, "No such process") ETYPE(INTR, "Interrupted system call") ETYPE(IO, "I/O error") ETYPE(NXIO, "No such device or address") ETYPE(2BIG, "Arg list too long") /* * Close up the enum block in the common case of someone including * this file. */ #if defined(ETYPE_ENUM) #undef ETYPE_ENUM #undef ETYPE ETYPE_MAX }; #endif /* ETYPE_ENUM */ #endif /* JLSS_ID_PASTE_H */ 

Пример источника:

 /* @(#)File: $RCSfile: paste1.c,v $ @(#)Version: $Revision: 1.2 $ @(#)Last changed: $Date: 2008/06/24 01:03:38 $ @(#)Purpose: Automated Token Pasting */ #include "paste1.h" static const char *sys_errlist_internal[] = { #undef JLSS_ID_PASTE_H #define ETYPE(val, desc) desc, #include "paste1.h" 0 #undef ETYPE }; static const char *xerror(int err) { if (err >= ETYPE_MAX || err <= 0) return "Unknown error"; return sys_errlist_internal[err]; } static const char*errlist_mnemonics[] = { #undef JLSS_ID_PASTE_H #define ETYPE(val, desc) [E ## val] = "E" #val, #include "paste1.h" #undef ETYPE }; #include  int main(void) { int i; for (i = 0; i < ETYPE_MAX; i++) { printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i)); } return(0); } 

Не обязательно чистейшее использование в мире препроцессора C, но это не позволяет записывать материал несколько раз.

Создание как идентификатора C, так и строки

Если индекс enums основан на 0, вы можете поместить имена в массив char * и проиндексировать их с значением enums.

 #define stringify( name ) # name enum MyEnum { ENUMVAL1 }; ...stuff... stringify(EnumName::ENUMVAL1); // Returns MyEnum::ENUMVAL1 

Дальнейшее обсуждение этого метода

Предпроцессорные директивные трюки для новичков

Я создал простой шаблонный class streamable_enum который использует streamовые операторы << и >> и основан на std::map :

 #ifndef STREAMABLE_ENUM_HPP #define STREAMABLE_ENUM_HPP #include  #include  #include  template  class streamable_enum { public: typedef typename std::map tostr_map_t; typedef typename std::map fromstr_map_t; streamable_enum() {} streamable_enum(E val) : Val_(val) {} operator E() { return Val_; } bool operator==(const streamable_enum& e) { return this->Val_ == e.Val_; } bool operator==(const E& e) { return this->Val_ == e; } static const tostr_map_t& to_string_map() { static tostr_map_t to_str_(get_enum_strings()); return to_str_; } static const fromstr_map_t& from_string_map() { static fromstr_map_t from_str_(reverse_map(to_string_map())); return from_str_; } private: E Val_; static fromstr_map_t reverse_map(const tostr_map_t& eToS) { fromstr_map_t sToE; for (auto pr : eToS) { sToE.emplace(pr.second, pr.first); } return sToE; } }; template  streamable_enum stream_enum(E e) { return streamable_enum(e); } template  typename streamable_enum::tostr_map_t get_enum_strings() { // \todo throw an appropriate exception or display compile error/warning return {}; } template  std::ostream& operator<<(std::ostream& os, streamable_enum e) { auto& mp = streamable_enum::to_string_map(); auto res = mp.find(e); if (res != mp.end()) { os << res->second; } else { os.setstate(std::ios_base::failbit); } return os; } template  std::istream& operator>>(std::istream& is, streamable_enum& e) { std::string str; is >> str; if (str.empty()) { is.setstate(std::ios_base::failbit); } auto& mp = streamable_enum::from_string_map(); auto res = mp.find(str); if (res != mp.end()) { e = res->second; } else { is.setstate(std::ios_base::failbit); } return is; } #endif 

Применение:

 #include "streamable_enum.hpp" using std::cout; using std::cin; using std::endl; enum Animal { CAT, DOG, TIGER, RABBIT }; template <> streamable_enum::tostr_map_t get_enum_strings() { return { { CAT, "Cat"}, { DOG, "Dog" }, { TIGER, "Tiger" }, { RABBIT, "Rabbit" } }; } int main(int argc, char* argv []) { cout << "What animal do you want to buy? Our offering:" << endl; for (auto pr : streamable_enum::to_string_map()) { // Use from_string_map() and pr.first instead cout << " " << pr.second << endl; // to have them sorted in alphabetical order } streamable_enum anim; cin >> anim; if (!cin) { cout << "We don't have such animal here." << endl; } else if (anim == Animal::TIGER) { cout << stream_enum(Animal::TIGER) << " was a joke..." << endl; } else { cout << "Here you are!" << endl; } return 0; } 

Вот решение, использующее macros со следующими функциями:

  1. только записывайте каждое значение enums один раз, поэтому нет двойных списков для поддержки

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

  3. не заменяйте сам перечисление, я все же хочу иметь определенный тип enums, но в дополнение к нему я хочу иметь возможность сопоставлять каждое имя enums с соответствующей строкой (чтобы не влиять на устаревший код)

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

https://stackoverflow.com/a/20134475/1812866

Я думал, что такое решение, как Boost.Fusion для адаптации структур и classов, было бы неплохо, даже если бы в какой-то момент им понадобилось использовать enums как последовательность слияния.

Поэтому я создал несколько небольших макросов для генерации кода для печати перечислений. Это не идеально и не имеет ничего общего с Boost.Fusion сгенерированным шаблоном кода, но может использоваться как macros Boost Fusion. Я хочу действительно генерировать типы, необходимые Boost.Fusion для интеграции в эту инфраструктуру, которая позволяет печатать имена членов структуры, но это произойдет позже, так как теперь это всего лишь macros:

 #ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP #define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP #include  #include  #include  #include  #include  #include  #define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C( \ R, unused, ENUMERATION_ENTRY) \ case ENUMERATION_ENTRY: \ return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY); \ break; /** * \brief Adapts ENUM to reflectable types. * * \param ENUM_TYPE To be adapted * \param ENUMERATION_SEQ Sequence of enum states */ #define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ) \ inline std::string to_string(const ENUM_TYPE& enum_value) { \ switch (enum_value) { \ BOOST_PP_SEQ_FOR_EACH( \ SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C, \ unused, ENUMERATION_SEQ) \ default: \ return BOOST_PP_STRINGIZE(ENUM_TYPE); \ } \ } \ \ inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \ os << to_string(value); \ return os; \ } #endif 

Старый ответ ниже довольно плохой, пожалуйста, не используйте это. 🙂

Старый ответ:

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

Я могу определить нерезкие enums следующим образом:

 SMART_ENUM(State, enum State { RUNNING, SLEEPING, FAULT, UNKNOWN }) 

И я могу взаимодействовать с ними по-разному:

 // With a stringstream std::stringstream ss; ss << State::FAULT; std::string myEnumStr = ss.str(); //Directly to stdout std::cout << State::FAULT << std::endl; //to a string std::string myStr = State::to_string(State::FAULT); //from a string State::State myEnumVal = State::from_string(State::FAULT); 

Исходя из следующих определений:

 #define SMART_ENUM(enumTypeArg, ...) \ namespace enumTypeArg { \ __VA_ARGS__; \ std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) { \ os << swissarmyknife::enums::to_string(#__VA_ARGS__, val); \ return os; \ } \ \ std::string to_string(const enumTypeArg& val) { \ return swissarmyknife::enums::to_string(#__VA_ARGS__, val); \ } \ \ enumTypeArg from_string(const std::string &str) { \ return swissarmyknife::enums::from_string(#__VA_ARGS__, str); \ } \ } \ namespace swissarmyknife { namespace enums { static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) { size_t begin = completeEnumDeclaration.find_first_of('{'); size_t end = completeEnumDeclaration.find_last_of('}'); const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end ); size_t count = 0; size_t found = 0; do { found = identifiers.find_first_of(",}", found+1); if (enumVal == count) { std::string identifiersSubset = identifiers.substr(0, found); size_t beginId = identifiersSubset.find_last_of("{,"); identifiersSubset = identifiersSubset.substr(beginId+1); boost::algorithm::trim(identifiersSubset); return identifiersSubset; } ++count; } while (found != std::string::npos); throw std::runtime_error("The enum declaration provided doesn't contains this state."); } template  static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) { size_t begin = completeEnumDeclaration.find_first_of('{'); size_t end = completeEnumDeclaration.find_last_of('}'); const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end ); size_t count = 0; size_t found = 0; do { found = identifiers.find_first_of(",}", found+1); std::string identifiersSubset = identifiers.substr(0, found); size_t beginId = identifiersSubset.find_last_of("{,"); identifiersSubset = identifiersSubset.substr(beginId+1); boost::algorithm::trim(identifiersSubset); if (identifiersSubset == enumStr) { return static_cast(count); } ++count; } while (found != std::string::npos); throw std::runtime_error("No valid enum value for the provided string"); } }} 

Когда мне понадобится поддержка разреженного enums, и когда у меня будет больше времени, я улучшу реализацию to_string и from_string с boost :: xpressive, но это будет стоить во время компиляции из-за важного выполнения шаблона и сгенерированного исполняемого файла вероятно, будет действительно больше. Но это имеет то преимущество, что он будет более читабельным и управляемым, чем этот уродливый ручной код манипуляции строкой. : D

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

  • Получение атрибутов значения Enum
  • Ограничения типа Enum в C #
  • Что такое тильда (~) в определении enums?
  • .NET Enumeration позволяет запятую в последнем поле
  • Используете ли вы типы enums в своих веб-службах WCF?
  • Почему в java перечисление объявляется как Enum <E extends Enum >
  • Почему Enum реализует интерфейс?
  • Что такое enums и почему они полезны?
  • Выберите случайное значение из enums?
  • Неверный регистр
  • Enum String Name from Value
  • Давайте будем гением компьютера.