将枚举类型转换为字符串及从字符串转换为枚举类型

在之前的课程(无作用域枚举的整数转换)中,我们展示了如下示例:

#include <iostream>

enum Color
{
    black, // 0
    red,   // 1
    blue,  // 2
};

int main()
{
    Color shirt{ blue };

    std::cout << "Your shirt is " << shirt << '\n';

    return 0;
}

该程序的输出为:

Your shirt is 2

由于 operator<< 不知道如何打印一个 Color 类型,编译器会隐式地将 Color 转换为整数值并打印该整数值。

大多数情况下,将枚举类型以整数值(如 2)的形式打印出来并不是我们所期望的。相反,我们通常希望打印出枚举器所代表的名称(例如 blue)。C++ 并没有提供直接将枚举类型转换为字符串的功能,因此我们需要自己寻找解决方案。幸运的是,这并不困难。

获取枚举器的名称

获取枚举器名称的典型方法是编写一个函数,允许我们传入一个枚举器,并返回该枚举器的名称作为字符串。但这需要某种方式来确定对于给定的枚举器应该返回哪个字符串。

有两种常见的方法可以实现这一点。

在第 8.5 课 – switch 语句基础中,我们提到 switch 语句可以基于整数值或枚举值进行分支选择。在以下示例中,我们使用 switch 语句选择枚举器,并返回与该枚举器对应的适当颜色字符串字面量:

#include <iostream>
#include <string_view>

enum Color
{
    black,
    red,
    blue,
};

constexpr std::string_view getColorName(Color color)
{
    switch (color)
    {
    case black: return "black";
    case red:   return "red";
    case blue:  return "blue";
    default:    return "???";
    }
}

int main()
{
    constexpr Color shirt{ blue };

    std::cout << "Your shirt is " << getColorName(shirt) << '\n';

    return 0;
}

该程序的输出为:

Your shirt is blue

在上述示例中,我们基于 color 进行分支选择,color 是我们传入的枚举器。在 switch 语句中,我们为 Color 的每个枚举器都提供了一个 case 标签。每个 case 返回对应颜色的名称作为 C 风格字符串字面量。该 C 风格字符串字面量会被隐式地转换为 std::string_view,并返回给调用者。我们还提供了一个 default 情况,用于返回 "???",以防用户传入我们未预期的值。

提醒:

由于 C 风格字符串字面量在整个程序的生命周期内都存在,因此返回一个查看 C 风格字符串字面量的 std::string_view 是可以的。当 std::string_view 被复制回调用者时,被查看的 C 风格字符串字面量仍然存在。

该函数被声明为 constexpr,因此我们可以在常量表达式中使用颜色的名称。

相关内容:

constexpr 函数将在第 F.1 课 – constexpr 函数中介绍。

虽然这种方法可以让我们获取枚举器的名称,但如果想将该名称打印到控制台,使用 std::cout << getColorName(shirt) 并不如直接使用 std::cout << shirt 那样简洁。我们将在后续的第 13.5 课 – 输入输出运算符的重载介绍中,教 std::cout 如何打印枚举类型。

将枚举器映射为字符串的第二种方法是使用数组。我们将在第 17.6 课 – std::array 和枚举类型中介绍这种方法。

无作用域枚举的输入

现在,我们来看一个输入的示例。在以下示例中,我们定义了一个 Pet 枚举类型。由于 Pet 是用户自定义的类型,语言并不知道如何使用 std::cin 输入一个 Pet 类型:

#include <iostream>

enum Pet
{
    cat,   // 0
    dog,   // 1
    pig,   // 2
    whale, // 3
};

int main()
{
    Pet pet { pig };
    std::cin >> pet; // 编译错误:std::cin 不知道如何输入一个 Pet

    return 0;
}

解决这个问题的一个简单方法是读取一个整数,然后使用 static_cast 将该整数转换为相应枚举类型的枚举器:

#include <iostream>
#include <string_view>

enum Pet
{
    cat,   // 0
    dog,   // 1
    pig,   // 2
    whale, // 3
};

constexpr std::string_view getPetName(Pet pet)
{
    switch (pet)
    {
    case cat:   return "cat";
    case dog:   return "dog";
    case pig:   return "pig";
    case whale: return "whale";
    default:    return "???";
    }
}

int main()
{
    std::cout << "Enter a pet (0=cat, 1=dog, 2=pig, 3=whale): ";

    int input{};
    std::cin >> input; // 输入一个整数

    if (input < 0 || input > 3)
        std::cout << "You entered an invalid pet\n";
    else
    {
        Pet pet{ static_cast<Pet>(input) }; // 将整数静态转换为 Pet
        std::cout << "You entered: " << getPetName(pet) << '\n';
    }

    return 0;
}

虽然这种方法可以工作,但显得有些笨拙。另外请注意,只有在确定 input 的值在枚举器范围内时,我们才应该使用 static_cast<Pet>(input)

从字符串获取枚举类型

与其让用户输入一个数字,不如让用户输入一个代表枚举器的字符串(例如 "pig"),然后我们将该字符串转换为相应的 Pet 枚举器。然而,实现这一点需要我们解决一些挑战。

首先,我们不能基于字符串使用 switch 语句,因此我们需要使用其他方法来匹配用户传入的字符串。这里最简单的办法是使用一系列的 if 语句。

其次,如果用户传入了一个无效的字符串,我们应该返回哪个 Pet 枚举器呢?一种选择是添加一个表示“无/无效”的枚举器,并返回它。然而,更好的选择是使用 std::optional

相关内容:

我们将在第 12.15 课 – std::optional 中介绍 std::optional

#include <iostream>
#include <optional> // 用于 std::optional
#include <string>
#include <string_view>

enum Pet
{
    cat,   // 0
    dog,   // 1
    pig,   // 2
    whale, // 3
};

constexpr std::string_view getPetName(Pet pet)
{
    switch (pet)
    {
    case cat:   return "cat";
    case dog:   return "dog";
    case pig:   return "pig";
    case whale: return "whale";
    default:    return "???";
    }
}

constexpr std::optional<Pet> getPetFromString(std::string_view sv)
{
    // 我们只能基于整数值(或枚举类型)使用 switch 语句,而不能基于字符串
    // 因此这里我们需要使用 if 语句
    if (sv == "cat")   return cat;
    if (sv == "dog")   return dog;
    if (sv == "pig")   return pig;
    if (sv == "whale") return whale;

    return {}; // 表示“无值”
}

int main()
{
    std::cout << "Enter a pet: cat, dog, pig, or whale: ";
    std::string s{};
    std::cin >> s;

    std::optional<Pet> pet { getPetFromString(s) };

    if (!pet)
        std::cout << "You entered an invalid pet\n";
    else
        std::cout << "You entered: " << getPetName(*pet) << '\n';

    return 0;
}

在上述解决方案中,我们使用一系列的 if-else 语句进行字符串比较。如果用户的输入字符串与某个枚举器的字符串匹配,我们就返回相应的枚举器。如果没有任何字符串匹配,我们就返回 {},表示“无值”。

对于高级读者:

请注意,上述解决方案只匹配小写字

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"