如何从枚举值获取枚举项名称

65

我声明了一个枚举类型如下,

enum WeekEnum
{
Mon = 0;
Tue = 1;
Wed = 2;
Thu = 3;
Fri = 4;
Sat = 5;
Sun = 6;
};

当我已经有项目值"0,1"时,如何获取项目名称 "Mon,Tue等"?

我已经有了这样的一个函数:

Log(今天是 "2",享受一下!);

现在我想要以下输出:

今天是 星期三,享受一下


可能是C++:将枚举值打印为文本的重复问题。 - Ciro Santilli OurBigBook.com
11个回答

50

你无法直接这样做,在C++中enum与Java的枚举不同。

通常的方法是创建一个std::map<WeekEnum,std::string>

std::map<WeekEnum,std::string> m;
m[Mon] = "Monday";
//...
m[Sun] = "Sunday";

34
在使用地图之前,我可能会使用数组。 - Mooing Duck
6
只要保留正确的大小,数组是更好的选择。读写时只需进行简单的索引操作,而且不会有插入或删除的情况。 - Mooing Duck
15
在哈希表中进行查找的时间复杂度是O(1),但由于哈希函数所需的计算,其常数项比较大。而在数组中进行查找是直接通过索引来访问,没有搜索或者运算。编译器会将m[Mon]视为m[1],这通常只需要单个操作码。你可能是在考虑按值而不是按索引在数组中查找的情况。 - Mooing Duck
2
@MooingDuck 你是对的,我误以为是按值而不是按索引查找。谢谢。 - Darwyn
8
使用数组可能意味着枚举值是连续的。尽管实际上声明的大多数枚举可能是连续的,但语言本身并没有强制要求。 - saxbophone
显示剩余3条评论

28

这里有另一个使用X Macro定义枚举的巧妙技巧:

#include <iostream>

#define WEEK_DAYS \
X(MON, "Monday", true) \
X(TUE, "Tuesday", true) \
X(WED, "Wednesday", true) \
X(THU, "Thursday", true) \
X(FRI, "Friday", true) \
X(SAT, "Saturday", false) \
X(SUN, "Sunday", false)

#define X(day, name, workday) day,
enum WeekDay : size_t
{
    WEEK_DAYS
};
#undef X

#define X(day, name, workday) name,
char const *weekday_name[] =
{
    WEEK_DAYS
};
#undef X

#define X(day, name, workday) workday,
bool weekday_workday[]
{
    WEEK_DAYS
};
#undef X

int main()
{
    std::cout << "Enum value: " << WeekDay::THU << std::endl;
    std::cout << "Name string: " << weekday_name[WeekDay::THU] << std::endl;
    std::cout << std::boolalpha << "Work day: " << weekday_workday[WeekDay::THU] << std::endl;

    WeekDay wd = SUN;
    std::cout << "Enum value: " << wd << std::endl;
    std::cout << "Name string: " << weekday_name[wd] << std::endl;
    std::cout << std::boolalpha << "Work day: " << weekday_workday[wd] << std::endl;

    return 0;
}

演示链接:https://ideone.com/bPAVTM

输出结果:

Enum value: 3
Name string: Thursday
Work day: true
Enum value: 6
Name string: Sunday
Work day: false

最终我在共享头文件中采用了这个技巧来处理枚举。 但由于它是共享的,所以我不得不将数组更改为内联函数,如果值匹配,则返回该字符串(例如“如果参数== day则返回名称;”)。 这种方法非常丑陋,但可维护性非常高。 - Richard
你能以强类型的方式完成这个吗?类似于“枚举类”? - Weston McNamara
@WestonMcNamara 是的,就像 enum class WeekDay { WEEK_DAYS ... - Matthieu

18

不,你无法从C++中的数值中获取"名称",因为所有符号都在编译期间被丢弃。

你可能需要使用这种方式:X 宏


4
这个回答过于“绝对化”了。鉴于已经存在像 __FUNCTION____PRETTY_FUNCTION__ 等内容,为什么 __ENUM_NAME__(x) 不存在还没有解释。编译器的开发者可以为枚举类型提供类似的解决方案。Rust 中有类似 #[derive(Debug)]#[derive(Display)] 的东西。Haskell 中有 deriving Show 这样的编译器好处,也是一样的作用。 - BitTickler

9
您可以定义一个运算符来执行输出。
std::ostream& operator<<(std::ostream& lhs, WeekEnum e) {
    switch(e) {
    case Monday: lhs << "Monday"; break;
    .. etc
    }
    return lhs;
}

Brandon,如果你在MSVC中遇到了那个错误,这就是原因——我曾经有过完全有效的程序,在像Intel编译器这样昂贵的编译器中编译失败,但在Visual Studio中却惨不忍睹。它会在像一个数组中出现# define常量作为索引的地方出错,例如等于64,声称你不能将其作为索引。它是一个整数,不是浮点数、布尔值或其他任何东西。但它拒绝工作。我曾经有一些相同的项目在GCC、Mingw、Intel和旧的编译器(如Turbo C++)中构建,但是当涉及到枚举或常量时,MSVC不知道自己在做什么。 - Seth D. Fulmer

8
一个枚举类型是一种类似于反向数组的东西。我认为你需要的是这个:
const char * Week[] = { "", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };  // The blank string at the beginning is so that Sunday is 1 instead of 0.
cout << "Today is " << Week[2] << ", enjoy!";  // Or whatever you'de like to do with it.

9
枚举和数组是两个非常不同的东西。 - Master Yoda
2
没错。虽然枚举将(源)字符串与数字匹配,但数组可以将数字与字符串匹配。(看到关系了吗?)OP的标题不能直接实现,但她的核心问题可以解决。由于她的示例按数字顺序命名,因此她可以使用数组而不是std :: map(更重)。因此,数组可能正是OP所需的,尽管没有回答她的标题问题。 - Cosine
1
我给这个点踩了下去,因为如果枚举值和数组索引不匹配,它就没有用处。 - Elvis Dukaj

3

On GCC it may look like this:

const char* WeekEnumNames [] = {
    [Mon] = "Mon",
    [Tue] = "Tue",
    [Wed] = "Wed",
    [Thu] = "Thu",
    [Fri] = "Fri",
    [Sat] = "Sat",
    [Sun] = "Sun",
};

1

当我已经有“0, 1等”这样的项目值时,如何获取项目名称“Mon,Tue等”?

在一些旧的C代码中(相当久以前),我找到了类似于以下代码:

std::string weekEnumToStr(int n)
{
   std::string s("unknown");
   switch (n)
   {
   case 0: { s = "Mon"; } break;
   case 1: { s = "Tue"; } break;
   case 2: { s = "Wed"; } break;
   case 3: { s = "Thu"; } break;
   case 4: { s = "Fri"; } break;
   case 5: { s = "Sat"; } break;
   case 6: { s = "Sun"; } break;
   }
   return s;
}

缺点:这会在枚举值和函数之间建立一种“病态依赖”,这意味着如果您更改枚举,则必须更改与之匹配的函数。我想即使是对于 std::map,这也是正确的。
我依稀记得我们找到了一个实用程序,可以从枚举代码生成函数代码。枚举表的长度已经增长到几百个......在某些时候,编写代码来编写代码可能是一个明智的选择。
注意:在嵌入式系统增强方面,我的团队替换了许多用于将枚举 int 值映射到它们的文本字符串的空终止表(100+?)。
问题在于,由于许多这些表被聚集到代码/内存的一个区域中,因此往往注意不到超出范围的值,以至于超出范围的值会达到命名表的末尾,并从某个后续表返回空终止字符串。
使用带有 switch 语句的函数也允许我们在 switch 的默认子句中添加一个 assert。在测试期间,这些断言发现了更多的编码错误,并且我们的断言被链接到我们的现场技术人员可以搜索的静态 RAM 系统日志中。

该实用程序在编译时运行...make文件强制执行文件依赖关系。 - 2785528

1

可能不是最好的解决方案,但这个方案做得很好。

枚举名称是懒加载的,所以在第一次调用 to_string 后,它将被加载并保留在内存中。

from_string 方法没有实现,因为这里没有要求,但可以通过调用 get_enum_names,在向量中搜索名称,并将其位置强制转换为枚举类型来轻松实现。

请在 cpp 文件中添加 get_enum_names 的定义(只需在头文件中声明即可)。

它应该能够在 C++ >= 11 中正常工作
已在 gcc 和 MSVC 中测试过

实现:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <unordered_map>

// Add the definition of this method into a cpp file. (only the declaration in the header)
const std::vector<std::string>& get_enum_names(const std::string& en_key, const std::string& en_str)
{
    static std::unordered_map<std::string, std::vector<std::string>> en_names_map;
    const auto it = en_names_map.find(en_key);
    if (it != en_names_map.end())
        return it->second;

    constexpr auto delim(',');
    std::vector<std::string> en_names;
    std::size_t start{};
    auto end = en_str.find(delim);
    while (end != std::string::npos)
    {
        while (en_str[start] == ' ')
            ++start;
        en_names.push_back(en_str.substr(start, end - start));
        start = end + 1;
        end = en_str.find(delim, start);
    }
    while (en_str[start] == ' ')
        ++start;
    en_names.push_back(en_str.substr(start));
    return en_names_map.emplace(en_key, std::move(en_names)).first->second;
}

#define DECLARE_ENUM(ENUM_NAME, ...)   \
    enum class ENUM_NAME{ __VA_ARGS__ }; \
    inline std::string to_string(ENUM_NAME en) \
    { \
        const auto& names = get_enum_names(#ENUM_NAME #__VA_ARGS__, #__VA_ARGS__); \
        return names[static_cast<std::size_t>(en)]; \
    }

用法:

DECLARE_ENUM(WeekEnum, Mon, Tue, Wed, Thu, Fri, Sat, Sun);

int main()
{
    WeekEnum weekDay = WeekEnum::Wed;
    std::cout << to_string(weekDay) << std::endl; // prints Wed
    std::cout << to_string(WeekEnum::Sat) << std::endl; // prints Sat
    return 0;
}

1
@4LegsDrivenCat 友情提示,可能不需要在映射的键中附加 VA_ARGS 枚举项。 对于大多数实现来说是正确的,但是没有什么可以阻止在不同作用域(如命名空间)中声明具有相同名称的枚举。 因此,当前的解决方案确保具有相同名称的枚举只能共享其项目,如果两者具有相同的项目和相同的顺序。 话虽如此,ENUM_NAME 枚举的名称是键的冗余部分,但出于调试目的,最好保留。 - rflobao

0

我使用了一种类似于@RolandXu指向的X宏技术,取得了极大的成功。我们还广泛使用了字符串化运算符。这种技术可以缓解在应用程序域中,项目既以字符串形式出现,又以数字标记形式出现时所带来的维护噩梦。

当可用机器可读文档时,该技术特别方便,因为宏X(...)行可以自动生成。新文档将立即导致一致的程序更新,涵盖字符串、枚举和在两个方向之间转换它们的字典(我们正在处理PCL6标记)。

虽然预处理器代码看起来相当丑陋,但所有这些技术细节都可以隐藏在头文件中,而这些文件永远不需要再次修改,源文件也是如此。一切都是类型安全的。唯一变化的是包含所有X(...)行的文本文件,这可能是自动生成的。


2
有关这种基于宏的方法的有趣散文,但是代码呢? - Neil Justice
@NeilJustice 你读过X宏的文章了吗?我们在C++中用map和vector实现了类似的功能,可以在枚举值和字符串表示和标记值之间来回转换。 - Peter - Reinstate Monica

0

如果您知道实际枚举标签与其值的相关性,可以使用容器和C++17的std::string_view来快速访问值及其字符串表示,并自己跟踪。 []运算符。在创建时,std::string_view只分配内存。 如果您希望它们在运行时可用于更多的性能节省,则还可以使用static constexpr指定它们。这个小控制台应用程序应该相当快。

#include <iostream>
#include <string_view>
#include <tuple>    
int main() {
    enum class Weekdays { //default behavior starts at 0 and iterates by 1 per entry
        Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
    };

    static constexpr std::string_view Monday    = "Monday";
    static constexpr std::string_view Tuesday   = "Tuesday";
    static constexpr std::string_view Wednesday = "Wednesday";
    static constexpr std::string_view Thursday  = "Thursday";
    static constexpr std::string_view Friday    = "Friday";
    static constexpr std::string_view Saturday  = "Saturday";
    static constexpr std::string_view Sunday    = "Sunday";
    static constexpr std::string_view opener    = "enum[";
    static constexpr std::string_view closer    = "] is ";
    static constexpr std::string_view semi      = ":";

    std::pair<Weekdays, std::string_view> Weekdays_List[] = {
        std::make_pair(Weekdays::Monday,    Monday),
        std::make_pair(Weekdays::Tuesday,   Tuesday),
        std::make_pair(Weekdays::Wednesday, Wednesday),
        std::make_pair(Weekdays::Thursday,  Thursday),
        std::make_pair(Weekdays::Friday,    Friday),
        std::make_pair(Weekdays::Saturday,  Saturday),
        std::make_pair(Weekdays::Sunday,    Sunday)
    };

    for (int i=0;i<sizeof(Weekdays_List)/sizeof(Weekdays_List[0]);i++) {
        std::cout<<opener<<i<<closer<<Weekdays_List[(int)i].second<<semi\
        <<(int)Weekdays_List[(int)i].first<<std::endl;
    }    
    return 0;
}

输出:

enum[0] is Monday:0
enum[1] is Tuesday:1
enum[2] is Wednesday:2
enum[3] is Thursday:3
enum[4] is Friday:4
enum[5] is Saturday:5
enum[6] is Sunday:6

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接