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

psi*_*lia 110 c c++ preprocessor ansi-c

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

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;
Run Code Online (Sandbox Code Playgroud)

而我需要的是类似的东西

printenum(OS_type, "My OS is %s", myOS);
Run Code Online (Sandbox Code Playgroud)

必须显示字符串"Linux",而不是整数.

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

Jam*_*lis 126

当然,天真的解决方案是为执行转换为字符串的每个枚举编写一个函数:

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]";
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,这是一场维护灾难.借助可与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) "]";         \
        }                                                                     \
    }
Run Code Online (Sandbox Code Playgroud)

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

在C++中,您可以将该ToString函数实现为operator<<重载,但我认为要求显式" ToString"将值转换为字符串形式更为清晰.

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

DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows))
Run Code Online (Sandbox Code Playgroud)

虽然宏首先看起来像是很多工作,而OS_type外观的定义相当陌生,但请记住你必须编写一次宏,然后你可以将它用于每个枚举.您可以添加其他功能(例如,字符串形式到枚举转换)而不会有太多麻烦,它完全解决了维护问题,因为您只需在调用宏时提供一次名称.

然后可以使用枚举,就好像它是正常定义的:

#include <iostream>

int main()
{
    OS_type t = Windows;
    std::cout << ToString(t) << " " << ToString(Apple) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

从帖子开始,这篇帖子中的代码片段#include <boost/preprocessor.hpp>可以编译为发布以演示解决方案.

这个特殊的解决方案适用于C++,因为它使用C++特定的语法(例如,没有typedef enum)和函数重载,但是使用C也可以直接使用它.

  • +1,实现机制是可怕的但终端界面很难打败优雅. (6认同)
  • 无论如何还有这个也允许你给出枚举整数值.例如,Windows将是3,Linux 5和Apple 7? (4认同)
  • 是的,您可以将`(Windows)`更改为`(Windows,3)`然后用适当写入的`BOOST_PP_SEQ_FOR_EACH`替换`BOOST_PP_SEQ_ENUM`.我没有那个方便的例子,但如果你愿意,我可以写一个. (4认同)
  • @JamesMcNellis我绝对想要一个完成Mark所要求的代码示例,你会如此善良地向我们展示道路吗?:) (2认同)
  • 注意:boost预处理器的硬限制为256个元素.对于较大的枚举,需要一个不同的解决方案. (2认同)

Bo *_*son 63

真的没有这样做的美妙方式.只需设置一个由枚举索引的字符串数组.

如果你做了很多输出,你可以定义一个运算符<<,它接受一个枚举参数并为你做查找.

  • @Syndog然后,你的生产代码中的56个枚举器长枚举被这个程序员更新,在很大的压力下发布了一个过期的功能,他忘记更新那个枚举索引的数组.它没有被注意到,因为相关的打印工具仅由应用程序调试代码使用.2个月后,您是第一个实际执行该调试代码的人:它会向您提供错误的信息,因此您在实现首先必须调试调试代码之前,会根据此错误信息丢失半天构建假设:设计依赖于明确的重复. (8认同)
  • @AdN您的场景是我更喜欢包含开关(没有默认子句)而不是数组的函数的原因,并且在构建文件中设置编译器开关,以便在未涵盖所有内容的枚举上发出错误可能的价值观 添加新的枚举条目而不更新相关的switch语句将导致编译错误. (8认同)
  • 您还可以在编译时检查您的数组中是否包含预期的字符串数. (2认同)
  • 我知道我在这方面占绝大多数,但对于像我这样的程序员来说,他们不希望依赖庞大的第三方库和/或marco-riddled代码来解决语言本身的缺点,我发现这是到目前为止,这是迄今为止最简单,最纯粹的解决方案.+1 (2认同)
  • @AdN 那个设计是错误的。从枚举到人类可读字符串的映射不应实现为由枚举值索引的字符串数组。您的经验(大概)说明了原因。映射应该是一个 (enum,string) 对的显式数组,所以如果你忘记为你的新枚举值添加一个条目,你会得到“???” 作为输出,但至少它不会搞砸所有其他枚举的名称。 (2认同)

Ren*_*eno 26

这是预处理器块

#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
Run Code Online (Sandbox Code Playgroud)

枚举定义

BEGIN_ENUM(Os_type)
{
    DECL_ENUM_ELEMENT(winblows),
    DECL_ENUM_ELEMENT(hackintosh),
}
Run Code Online (Sandbox Code Playgroud)

打电话使用

GetStringOs_type(winblows);
Run Code Online (Sandbox Code Playgroud)

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

  • 当您的枚举超过 256 个元素时,这是唯一有效的解决方案。 (3认同)
  • 这既美好又可怕。对不起,我去洗澡哭了几个小时。 (3认同)

the*_*oid 9

There are lots of good answers already, but magic_enum is worth a look.

It describes itself as -

Static reflection for enums (to string, from string, iteration) for modern C++, work with any enum type without any macro or boilerplate code.

Header-only C++17 library provides static reflection for enums, work with any enum type without any macro or boilerplate code.

Example usage

enum Color { RED = 2, BLUE = 4, GREEN = 8 };


Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"

std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name);
if (color.has_value()) {
  // color.value() -> Color::GREEN
}
Run Code Online (Sandbox Code Playgroud)


And*_*rew 7

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

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

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};
Run Code Online (Sandbox Code Playgroud)

  • 你有点不对劲,`enum` *是C中的*类型。积分枚举类型常量是`int`类型,而不是定义它们的`enum`类型,这也许是你想说的。但我根本不明白这与问题有什么关系。 (2认同)

小智 7

你试过这个吗:

#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);
  }
Run Code Online (Sandbox Code Playgroud)

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

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


Pol*_*ear 7

我结合了James',HowardÉder的解决方案,并创建了一个更通用的实现:

  • 可以为每个枚举元素选择性地定义int值和自定义字符串表示
  • 使用"枚举类"

完整代码如下所示(使用"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;
}
Run Code Online (Sandbox Code Playgroud)


Naw*_*waz 6

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

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

  • 每次都进行 logN 查找效率非常低 (3认同)

Jen*_*edt 5

对于 C99,P99P99_DECLARE_ENUM中可以让您简单地声明如下:enum

P99_DECLARE_ENUM(color, red, green, blue);
Run Code Online (Sandbox Code Playgroud)

然后使用color_getname(A)获取带有颜色名称的字符串。


ger*_*rdw 5

我自己的偏好是尽量减少重复输入和难以理解的宏,并避免将宏定义引入通用编译器空间。

所以,在头文件中:

enum Level{
        /**
        * zero reserved for internal use
        */
        verbose = 1,
        trace,
        debug,
        info,
        warn,
        fatal
    };

static Level readLevel(const char *);
Run Code Online (Sandbox Code Playgroud)

cpp的实现是:

 Logger::Level Logger::readLevel(const char *in) { 
 #  define MATCH(x) if (strcmp(in,#x) ==0) return x; 
    MATCH(verbose);
    MATCH(trace);
    MATCH(debug);
    MATCH(info);
    MATCH(warn);
    MATCH(fatal);
 # undef MATCH
    std::string s("No match for logging level ");
    s += in;
    throw new std::domain_error(s);
 }
Run Code Online (Sandbox Code Playgroud)

一旦我们完成了它,请注意宏的#undef。


Ph0*_*t0n 5

这里有很多很好的答案,但我认为有些人可能会发现我的有用。我喜欢它,因为用于定义宏的接口几乎可以实现。它也很方便,因为您不必包括任何额外的库-所有库都随C ++一起提供,甚至不需要真正的较新版本。我在网上从各个地方拉了一些东西,所以我不能一味地归功于它,但是我认为它的独特性足以保证一个新的答案。

首先创建一个头文件...将其命名为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; \
        } \
    }
Run Code Online (Sandbox Code Playgroud)

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

#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);
}
Run Code Online (Sandbox Code Playgroud)

输出将为>>的值“ Apple”的值是:2/4

请享用!


dgm*_*gmz 5

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

#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";
}
Run Code Online (Sandbox Code Playgroud)

  • 如果你有方向a = NORTH,就不会工作; 然后写ENUM_TO_STR(a) (8认同)

归档时间:

查看次数:

180787 次

最近记录:

5 年,11 月 前