C++ 如何将枚举类型变量转换为字符串?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5093460/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 17:21:56  来源:igfitidea点击:

How to convert an enum type variable to a string?

c++cpreprocessoransi-c

提问by psihodelia

How to make printf to show the values of variables which are of an enum type? For instance:

如何让 printf 显示枚举类型的变量的值?例如:

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

and what I need is something like

我需要的是类似的东西

printenum(OS_type, "My OS is %s", myOS);

which must show a string "Linux", not an integer.

它必须显示一个字符串“Linux”,而不是一个整数。

I suppose, first I have to create a value-indexed array of strings. But I don't know if that is the most beautiful way to do it. Is it possible at all?

我想,首先我必须创建一个值索引的字符串数组。但我不知道这是否是最美丽的方式。有可能吗?

采纳答案by Bo Persson

There really is no beautiful way of doing this. Just set up an array of strings indexed by the enum.

真的没有漂亮的方法可以做到这一点。只需设置一个由枚举索引的字符串数组。

If you do a lot of output, you can define an operator<< that takes an enum parameter and does the lookup for you.

如果您进行大量输出,则可以定义一个运算符<<,它接受一个枚举参数并为您进行查找。

回答by James McNellis

The naive solution, of course, is to write a function for each enumeration that performs the conversion to string:

当然,最简单的解决方案是为每个枚举编写一个函数来执行到字符串的转换:

enum OS_type { Linux, Apple, Windows };

inline const char* ToString(OS_type v)
{
    switch (v)
    {
        case Linux:   return "Linux";
        case Apple:   return "Apple";
        case Windows: return "Windows";
        default:      return "[Unknown OS_type]";
    }
}

This, however, is a maintenance disaster. With the help of the Boost.Preprocessor library, which can be used with both C and C++ code, you can easily take advantage of the preprocessor and let it generate this function for you. The generation macro is as follows:

然而,这是一场维护灾难。借助可用于 C 和 C++ 代码的 Boost.Preprocessor 库,您可以轻松利用预处理器并让它为您生成此函数。生成宏如下:

#include <boost/preprocessor.hpp>

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem)    \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                \
    enum name {                                                               \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    inline const char* ToString(name v)                                       \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,          \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }

The first macro (beginning with X_) is used internally by the second. The second macro first generates the enumeration, then generates a ToStringfunction that takes an object of that type and returns the enumerator name as a string (this implementation, for obvious reasons, requires that the enumerators map to unique values).

第一个宏(以 开头X_)由第二个在内部使用。第二个宏首先生成枚举,然后生成一个ToString函数,该函数采用该类型的对象并将枚举器名称作为字符串返回(出于显而易见的原因,此实现要求枚举器映射到唯一值)。

In C++ you could implement the ToStringfunction as an operator<<overload instead, but I think it's a bit cleaner to require an explicit "ToString" to convert the value to string form.

在 C++ 中,您可以将该ToString函数实现为operator<<重载,但我认为需要显式“ ToString”来将值转换为字符串形式会更简洁一些。

As a usage example, your OS_typeenumeration would be defined as follows:

作为使用示例,您的OS_type枚举将定义如下:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows))

While the macro looks at first like it is a lot of work, and the definition of OS_typelooks rather foreign, remember that you have to write the macro once, then you can use it for every enumeration. You can add additional functionality to it (e.g., a string-form to enum conversion) without too much trouble, and it completely solves the maintenance problem, since you only have to provide the names once, when you invoke the macro.

虽然宏乍一看好像工作量很大,而且定义OS_type看起来很陌生,但请记住,您必须编写一次宏,然后才能在每次枚举时使用它。您可以向它添加附加功能(例如,将字符串形式转换为枚举)而不会带来太多麻烦,并且它完全解决了维护问题,因为您只需在调用宏时提供一次名称。

The enumeration can then be used as if it were defined normally:

然后可以像正常定义一样使用枚举:

#include <iostream>

int main()
{
    OS_type t = Windows;
    std::cout << ToString(t) << " " << ToString(Apple) << std::endl;
}

The code snippets in this post, beginning with the #include <boost/preprocessor.hpp>line, can be compiled as posted to demonstrate the solution.

这篇文章中的代码片段,从#include <boost/preprocessor.hpp>行开始,可以按照发布的方式进行编译以演示解决方案。

This particular solution is for C++ as it uses C++-specific syntax (e.g., no typedef enum) and function overloading, but it would be straightforward to make this work with C as well.

这个特殊的解决方案适用于 C++,因为它使用 C++ 特定的语法(例如, no typedef enum)和函数重载,但也可以直接使用 C 来实现这一点。

回答by Reno

This is the pre processor block

这是预处理器块

#ifndef GENERATE_ENUM_STRINGS
    #define DECL_ENUM_ELEMENT( element ) element
    #define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME
    #define END_ENUM( ENUM_NAME ) ENUM_NAME; \
            char* GetString##ENUM_NAME(enum tag##ENUM_NAME index);
#else
    #define DECL_ENUM_ELEMENT( element ) #element
    #define BEGIN_ENUM( ENUM_NAME ) char* gs_##ENUM_NAME [] =
    #define END_ENUM( ENUM_NAME ) ; char* GetString##ENUM_NAME(enum \
            tag##ENUM_NAME index){ return gs_##ENUM_NAME [index]; }
#endif

Enum definition

枚举定义

BEGIN_ENUM(Os_type)
{
    DECL_ENUM_ELEMENT(winblows),
    DECL_ENUM_ELEMENT(hackintosh),
} END_ENUM(Os_type)

Call using

呼叫使用

GetStringOs_type(winblows);

Taken from here. How cool is that ? :)

取自这里。多么酷啊 ?:)

回答by Nawaz

Use std::map<OS_type, std::string>and populate it with enum as key, and string representation as values, then you can do these:

使用std::map<OS_type, std::string>枚举作为键并填充它,并将字符串表示作为值,然后您可以执行以下操作:

printf("My OS is %s", enumMap[myOS].c_str());
std::cout << enumMap[myOS] ;

回答by Andrew

The problem with C enums is that it's not a type of it's own, like it is in C++. An enum in C is a way to map identifiers to integral values. Just that. That's why an enum value is interchangeable with integer values.

C 枚举的问题在于它不是它自己的类型,就像在 C++ 中一样。C 中的枚举是一种将标识符映射到整数值的方法。只是。这就是枚举值可以与整数值互换的原因。

As you guess correctly, a good way is to create a mapping between the enum value and a string. For example:

正如您猜对的那样,一个好方法是在枚举值和字符串之间创建映射。例如:

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};

回答by PolarBear

I have combined the James', Howard'sand éder'ssolutions and created a more generic implementation:

我结合了JamesHowardéder 的解决方案,并创建了一个更通用的实现:

  • int value and custom string representation can be optionally defined for each enum element
  • "enum class" is used
  • 可以为每个枚举元素选择定义 int 值和自定义字符串表示
  • 使用“枚举类”

The full code is written bellow (use "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" for defining an enum) (online demo).

完整代码如下(使用“DEFINE_ENUM_CLASS_WITH_ToString_METHOD”定义枚举)(在线演示)。

#include <boost/preprocessor.hpp>
#include <iostream>

// ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ implementation is taken from:
// http://lists.boost.org/boost-users/2012/09/76055.php
//
// This macro do the following:
// input:
//      (Element1, "Element 1 string repr", 2) (Element2) (Element3, "Element 3 string repr")
// output:
//      ((Element1, "Element 1 string repr", 2)) ((Element2)) ((Element3, "Element 3 string repr"))
#define HELPER1(...) ((__VA_ARGS__)) HELPER2
#define HELPER2(...) ((__VA_ARGS__)) HELPER1
#define HELPER1_END
#define HELPER2_END
#define ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(sequence) BOOST_PP_CAT(HELPER1 sequence,_END)


// CREATE_ENUM_ELEMENT_IMPL works in the following way:
//  if (elementTuple.GetSize() == 4) {
//      GENERATE: elementTuple.GetElement(0) = elementTuple.GetElement(2)),
//  } else {
//      GENERATE: elementTuple.GetElement(0),
//  }
// Example 1:
//      CREATE_ENUM_ELEMENT_IMPL((Element1, "Element 1 string repr", 2, _))
//  generates:
//      Element1 = 2,
//
// Example 2:
//      CREATE_ENUM_ELEMENT_IMPL((Element2, _))
//  generates:
//      Element1,
#define CREATE_ENUM_ELEMENT_IMPL(elementTuple)                                          \
BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 4),                       \
    BOOST_PP_TUPLE_ELEM(0, elementTuple) = BOOST_PP_TUPLE_ELEM(2, elementTuple),        \
    BOOST_PP_TUPLE_ELEM(0, elementTuple)                                                \
),

// we have to add a dummy element at the end of a tuple in order to make 
// BOOST_PP_TUPLE_ELEM macro work in case an initial tuple has only one element.
// if we have a tuple (Element1), BOOST_PP_TUPLE_ELEM(2, (Element1)) macro won't compile.
// It requires that a tuple with only one element looked like (Element1,).
// Unfortunately I couldn't find a way to make this transformation, so
// I just use BOOST_PP_TUPLE_PUSH_BACK macro to add a dummy element at the end
// of a tuple, in this case the initial tuple will look like (Element1, _) what
// makes it compatible with BOOST_PP_TUPLE_ELEM macro
#define CREATE_ENUM_ELEMENT(r, data, elementTuple)                                      \
    CREATE_ENUM_ELEMENT_IMPL(BOOST_PP_TUPLE_PUSH_BACK(elementTuple, _))

#define DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, element)                                        \
    case enumName::element : return BOOST_PP_STRINGIZE(element);
#define DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, element, stringRepresentation)  \
    case enumName::element : return stringRepresentation;

// GENERATE_CASE_FOR_SWITCH macro generates case for switch operator.
// Algorithm of working is the following
//  if (elementTuple.GetSize() == 1) {
//      DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, elementTuple.GetElement(0))
//  } else {
//      DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, elementTuple.GetElement(0), elementTuple.GetElement(1))
//  }
//
// Example 1:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element1, "Element 1 string repr", 2))
//  generates:
//      case EnumName::Element1 : return "Element 1 string repr";
//
// Example 2:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element2))
//  generates:
//      case EnumName::Element2 : return "Element2";
#define GENERATE_CASE_FOR_SWITCH(r, enumName, elementTuple)                                                                                                 \
    BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 1),                                                                                       \
        DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple)),                                                          \
        DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple), BOOST_PP_TUPLE_ELEM(1, elementTuple))     \
    )


// DEFINE_ENUM_CLASS_WITH_ToString_METHOD final macro witch do the job
#define DEFINE_ENUM_CLASS_WITH_ToString_METHOD(enumName, enumElements)          \
enum class enumName {                                                           \
    BOOST_PP_SEQ_FOR_EACH(                                                      \
        CREATE_ENUM_ELEMENT,                                                    \
        0,                                                                      \
        ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)                     \
    )                                                                           \
};                                                                              \
inline const char* ToString(const enumName element) {                           \
        switch (element) {                                                      \
            BOOST_PP_SEQ_FOR_EACH(                                              \
                GENERATE_CASE_FOR_SWITCH,                                       \
                enumName,                                                       \
                ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)             \
            )                                                                   \
            default: return "[Unknown " BOOST_PP_STRINGIZE(enumName) "]";       \
        }                                                                       \
}

DEFINE_ENUM_CLASS_WITH_ToString_METHOD(Elements,
(Element1)
(Element2, "string representation for Element2 ")
(Element3, "Element3 string representation", 1000)
(Element4, "Element 4 string repr")
(Element5, "Element5", 1005)
(Element6, "Element6 ")
(Element7)
)
// Generates the following:
//      enum class Elements {
//          Element1, Element2, Element3 = 1000, Element4, Element5 = 1005, Element6,
//      };
//      inline const char* ToString(const Elements element) {
//          switch (element) {
//              case Elements::Element1: return "Element1";
//              case Elements::Element2: return "string representation for Element2 ";
//              case Elements::Element3: return "Element3 string representation";
//              case Elements::Element4: return "Element 4 string repr";
//              case Elements::Element5: return "Element5";
//              case Elements::Element6: return "Element6 ";
//              case Elements::Element7: return "Element7";
//              default: return "[Unknown " "Elements" "]";
//          }
//      }

int main() {
    std::cout << ToString(Elements::Element1) << std::endl;
    std::cout << ToString(Elements::Element2) << std::endl;
    std::cout << ToString(Elements::Element3) << std::endl;
    std::cout << ToString(Elements::Element4) << std::endl;
    std::cout << ToString(Elements::Element5) << std::endl;
    std::cout << ToString(Elements::Element6) << std::endl;
    std::cout << ToString(Elements::Element7) << std::endl;

    return 0;
}

回答by dgmz

This simple example worked for me. Hope this helps.

这个简单的例子对我有用。希望这可以帮助。

#include <iostream>
#include <string>

#define ENUM_TO_STR(ENUM) std::string(#ENUM)

enum DIRECTION{NORTH, SOUTH, WEST, EAST};

int main()
{
  std::cout << "Hello, " << ENUM_TO_STR(NORTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(SOUTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(EAST) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(WEST) << "!\n";
}

回答by M.Ali

Did you try this:

你试过这个吗:

#define stringify( name ) # name

enum enMyErrorValue
  {
  ERROR_INVALIDINPUT = 0,
  ERROR_NULLINPUT,
  ERROR_INPUTTOOMUCH,
  ERROR_IAMBUSY
  };

const char* enMyErrorValueNames[] = 
  {
  stringify( ERROR_INVALIDINPUT ),
  stringify( ERROR_NULLINPUT ),
  stringify( ERROR_INPUTTOOMUCH ),
  stringify( ERROR_IAMBUSY )
  };

void vPrintError( enMyErrorValue enError )
  {
  cout << enMyErrorValueNames[ enError ] << endl;
  }

int main()
  {
  vPrintError((enMyErrorValue)1);
  }

The stringify()macro can be used to turn any text in your code into a string, but only the exact text between the parentheses. There are no variable dereferencing or macro substitutions or any other sort of thing done.

stringify()宏可用于将代码中的任何文本转换为字符串,但只能是括号之间的确切文本。没有变量解引用或宏替换或任何其他类型的事情。

http://www.cplusplus.com/forum/general/2949/

http://www.cplusplus.com/forum/general/2949/

回答by Ph0t0n

There are a lot of good answers here, but I thought some people might find mine useful. I like it because the interface that you use to define the macro is about as simple as it can get. It's also handy because you don't have to include any extra libraries - it all comes with C++ and it doesn't even require a really late version. I pulled pieces from various places online so I can't take credit for all of it, but I think it's unique enough to warrant a new answer.

这里有很多很好的答案,但我认为有些人可能会觉得我的有用。我喜欢它,因为您用来定义宏的接口非常简单。它也很方便,因为您不必包含任何额外的库 - 它都随 C++ 一起提供,甚至不需要非常晚的版本。我从网上的各个地方提取了一些作品,所以我不能把所有的功劳都归功于它,但我认为它的独特性足以保证一个新的答案。

First make a header file... call it EnumMacros.h or something like that, and put this in it:

首先制作一个头文件...将其命名为 EnumMacros.h 或类似的东西,然后将其放入其中:

// Search and remove whitespace from both ends of the string
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { rit++; }
    return std::string(it, rit.base());
}

static void SplitEnumArgs(const char* szArgs, std::string Array[], int nMax)
{
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        Array[nIdx] = TrimEnumString(strSub);
        nIdx++;
    }
};
// This will to define an enum that is wrapped in a namespace of the same name along with ToString(), FromString(), and COUNT
#define DECLARE_ENUM(ename, ...) \
    namespace ename { \
        enum ename { __VA_ARGS__, COUNT }; \
        static std::string _Strings[COUNT]; \
        static const char* ToString(ename e) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            return _Strings[e].c_str(); \
        } \
        static ename FromString(const std::string& strEnum) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            for (int i = 0; i < COUNT; i++) { if (_Strings[i] == strEnum) { return (ename)i; } } \
            return COUNT; \
        } \
    }

Then, in your main program you can do this...

然后,在您的主程序中,您可以执行此操作...

#include "EnumMacros.h"
DECLARE_ENUM(OsType, Windows, Linux, Apple)

void main() {
    OsType::OsType MyOs = OSType::Apple;
    printf("The value of '%s' is: %d of %d\n", OsType::ToString(MyOs), (int)OsType::FromString("Apple"), OsType::COUNT);
}

Where the output would be >> The value of 'Apple' is: 2 of 4

输出的位置 >> 'Apple' 的值是:2 of 4

Enjoy!

享受!

回答by Vladimir

Assuming that your enum is already defined, you can create an array of pairs:

假设您的枚举已经定义,您可以创建一个数组对:

std::pair<QTask::TASK, QString> pairs [] = {
std::pair<OS_type, string>(Linux, "Linux"),
std::pair<OS_type, string>(Windows, "Windows"),
std::pair<OS_type, string>(Apple, "Apple"),
};

Now, you can create a map:

现在,您可以创建地图:

std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0]));

Now, you can use the map. If your enum is changed, you have to add/remove pair from array pairs[]. I thinkk that it is the most elegant way to obtain a string from enum in C++.

现在,您可以使用地图了。如果您的枚举被更改,您必须从数组对 [] 中添加/删除对。我认为这是从 C++ 中的枚举中获取字符串的最优雅的方式。