Легкий способ использовать переменные типов 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, как я пытаюсь сделать выше?
- Метод расширения для enums, а не экземпляр enums
- Как реализованы значения () для перечислений Java 6?
- Как программно перечислять тип enums в TypeScript 0.9.5?
- Получить Enum из атрибута Description
- MVC 5.1 Razor DisplayFor не работает с Enum DisplayName
- Как добавить метод в Enumeration в Scala?
- Являются ли enums C ++ подписанными или неподписанными?
- Как установить перечисление в null
- что является значением по умолчанию для переменной enums
- Получить связанное значение из enums без переключателя / случая
- Кто-нибудь знает хороший способ обхода нехватки enum generic constraint?
- Строка C ++ для enums
- Почему я получаю ошибку типа «type not typeinfo» с типом enums
Там нет встроенного решения. Самый простой способ – с массивом 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
Применение:
#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 со следующими функциями:
-
только записывайте каждое значение enums один раз, поэтому нет двойных списков для поддержки
-
не сохраняйте значения enums в отдельном файле, который позже добавлен #, поэтому я могу писать его везде, где захочу
-
не заменяйте сам перечисление, я все же хочу иметь определенный тип enums, но в дополнение к нему я хочу иметь возможность сопоставлять каждое имя enums с соответствующей строкой (чтобы не влиять на устаревший код)
-
поиск должен быть быстрым, поэтому предпочтительно нет коммутационного футляра для этих огромных переписей
Я думал, что такое решение, как 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 для выполнения таких сопоставлений между значением перечислений и строкой, но его нужно поддерживать вручную.