在C中使用枚举types的variables作为string的简单方法?

这是我想要做的:

typedef enum { ONE, TWO, THREE } Numbers; 

我正在尝试编写一个function,可以做类似于以下的开关情况:

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

在每种情况下,而不是定义,有没有办法使用枚举variables来设置它,就像我想要做的那样?

没有内置的解决scheme。 最简单的方法是使用char *数组,其中枚举的int值索引到包含该枚举的描述性名称的string。 如果你有一个稀疏的枚举(一个不是从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标识符和一个string的东西的技术? 可以在这里使用。

像往常一样使用这种预处理器的东西,编写和理解预处理器部分可能很难,并且包括将macros传递给其他macros,并涉及使用#和##运算符,但使用它是很容易的。 我发现这种风格对于长时间的枚举来说非常有用,两次维护同样的列表可能会非常麻烦。

工厂代码 – 只input一次,通常隐藏在标题中:

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) 

该技术可以很容易地扩展,以便XXmacros接受更多的参数,并且您也可以准备更多的macros来替代XX以满足不同的需求,类似于本示例中提供的三个macros。

使用#include / #define / #undef比较X-Macros

虽然这与其他人提到的X-Macros很相似,但我认为这个解决scheme更加优雅,因为它不需要#undefing任何东西,这可以让你隐藏更多复杂的东西,就是在工厂的头文件 – 头文件当你需要定义一个新的枚举时,你根本不感兴趣,因此新的枚举定义要短得多,更清晰。

肯定有办法做到这一点 – 使用X()macros 。 这些macros使用C预处理器从源数据列表构造枚举,数组和代码块。 您只需要将新项目添加到包含X()macros的#define。 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; 

有更有效的方法(即使用Xmacros创build一个string数组和枚举索引),但这是最简单的演示。

我知道你有一些很好的答案,但是你知道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 ++不提供这个function,虽然我经常需要它。

下面的代码工作,虽然它最适合非稀疏枚举。

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

非稀疏,我的意思不是forms

 typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers; 

因为这有很大的差距。

这种方法的优点是把枚举和string的定义放在一起, 在一个函数中有一个switch语句来指明它们。 这意味着你不太可能改变一个没有另一个。

吻。 你会用你的枚举做各种其他的开关/事件,为什么要打印不同呢? 当你考虑有大约100个其他的地方可以忘记一个案例的时候,在你的印刷例程中遗忘一个案例并不是什么大问题。 只是编译–Wall,它会警告非详尽的大小写匹配。 不要使用“默认”,因为这将使开关彻底,你不会得到警告。 相反,让交换机退出,并处理像这样的默认情况下…

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

尝试将C ++枚举转换为string 。 当枚举项具有任意值时, 注释的改进可以解决问题。

boost :: preprocessor的使用使得像下面这样的优雅的解决scheme成为可能:

第1步:包含头文件:

 #include "EnumUtilities.h" 

第2步:用下面的语法声明枚举对象:

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

第3步:使用您的数据:

获取元素的数量:

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

获取关联的string:

 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; 

从关联string获取枚举值:

 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 :: preprocessor。 在这种情况下,常量列表不能超过64个元素。

遵循相同的逻辑,你也可以考虑创build稀疏的枚举:

 #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上testing过,但是要注意boost :: preprocessor可能不是完全可移植的。

通过在这里合并一些技术,我想出了最简单的forms:

 #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] 

看看穆动力研究实验室的博客档案 。 今年早些时候我发现了这一点 – 我忘记了我遇到的确切上下文 – 并且已经将它应用到了这个代码中。 我们可以辩论前面加E的好处, 它适用于所解决的具体问题,但不是通用解决scheme的一部分。 我把它藏在我的“小插曲”文件夹中 – 在那里我保留有趣的代码碎片,以备日后使用。 我很尴尬地说,我当时并没有记下这个想法的来源。

标题: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 <stdio.h> 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标识符和一个string

如果枚举索引是基于0的,则可以将这些名称放在char *数组中,并使用枚举值对它们进行索引。

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

进一步讨论这种方法

预处理器指令技巧为新人

我创build了一个简单的模板类streamable_enum ,它使用stream操作符<<>> ,并基于std::map<Enum, std::string>

 #ifndef STREAMABLE_ENUM_HPP #define STREAMABLE_ENUM_HPP #include <iostream> #include <string> #include <map> template <typename E> class streamable_enum { public: typedef typename std::map<E, std::string> tostr_map_t; typedef typename std::map<std::string, E> fromstr_map_t; streamable_enum() {} streamable_enum(E val) : Val_(val) {} operator E() { return Val_; } bool operator==(const streamable_enum<E>& 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<E>()); 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 <typename E> streamable_enum<E> stream_enum(E e) { return streamable_enum<E>(e); } template <typename E> typename streamable_enum<E>::tostr_map_t get_enum_strings() { // \todo throw an appropriate exception or display compile error/warning return {}; } template <typename E> std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) { auto& mp = streamable_enum<E>::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 <typename E> std::istream& operator>>(std::istream& is, streamable_enum<E>& e) { std::string str; is >> str; if (str.empty()) { is.setstate(std::ios_base::failbit); } auto& mp = streamable_enum<E>::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<Animal>::tostr_map_t get_enum_strings<Animal>() { 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<Animal>::to_string_map()) { // Use from_string_map() and pr.first instead cout << " " << pr.second << endl; // to have them sorted in alphabetical order } streamable_enum<Animal> 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; } 

以下是使用具有以下function的macros的解决scheme:

  1. 只写一次枚举的每个值,所以没有双重列表来维护

  2. 不要将枚举值保存在一个单独的文件中,这个文件稍后会被包含,所以我可以在任何我想要的地方写入它

  3. 不要取代枚举本身,我仍然想要定义枚举types,但除此之外,我想能够将每个枚举名称映射到相应的string(不影响遗留代码)

  4. search应该是快速的,所以最好是没有开关的情况下,为那些巨大的枚举

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

我认为像Boost.Fusion这样的解决scheme适用于结构和类将是很好的,他们甚至有一些使用枚举作为融合序列。

所以我做了一些小的macros来生成打印枚举的代码。 这不是完美的,没有什么可以看到与Boost.Fusion生成的样板代码,但可以像使用Boost Fusionmacros一样使用。 我真的想要生成Boost.Fusion需要的types来集成到允许打印结构成员名称的基础结构中,但是这会在以后发生,现在这只是macros:

 #ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP #define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP #include <swissarmyknife/detail/config.hpp> #include <string> #include <ostream> #include <boost/preprocessor/cat.hpp> #include <boost/preprocessor/stringize.hpp> #include <boost/preprocessor/seq/for_each.hpp> #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 

下面的答案很糟糕,请不要使用它。 🙂

老答案:

我一直在寻找一种解决这个问题的方式,而不用改变太多的枚举声明语法。 我来到一个解决scheme,它使用预处理器从string化的枚举声明中检索一个string。

我可以像这样定义非稀疏枚举:

 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<enumTypeArg>(#__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 <typename EnumType> 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<EnumType>(count); } ++count; } while (found != std::string::npos); throw std::runtime_error("No valid enum value for the provided string"); } }} 

当我需要稀疏枚举的支持,当我有更多的时间,我会改进to_stringfrom_string实现与boost :: xpressive,但这将花费在编译时间,因为执行重要的模板和生成的可执行文件可能会更大。 但是这样做的好处是它比这个丑陋的手工string操作代码更具可读性和可维护性。 :d

否则,我总是使用boost :: bimap在枚举值和string之间执行映射,但必须手动维护。