13-4 枚举与字符串之间的转换

#include <iostream>

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

int main()
{
    Color shirt{ blue };

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

    return 0;
}

打印出来的内容:

image

由于运算符<<无法直接打印Color类型,编译器会隐式将Color转换为整数值并打印该值。

多数情况下,将枚举类型作为整数值(如2)输出并非我们所期望的。相反,我们通常希望输出枚举器所代表的名称(例如blue)。C++并未提供现成的解决方案,因此需要自行寻找方法。所幸实现起来并不困难。

获取枚举器的名称

获取枚举项名称的典型方法是编写一个函数,该函数允许传入枚举项并返回其名称字符串。但这需要某种机制来确定给定枚举项应返回哪个字符串。

实现此功能有两种常见方式。

第8.5课——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;
}

这会输出

image

在上例中,我们根据传递的枚举值对 color 进行 switch 处理。在切换语句内部,我们为Color枚举的每个成员设置了对应的case标签。每个case返回对应颜色的名称,以C风格字符串字面量形式呈现。该字符串字面量会被隐式转换为std::string_view,并返回给调用方。此外还设置了默认情况,当用户传入未预期的值时,该默认情况会返回“???”。

提醒:
由于C风格字符串常量在整个程序中都存在,因此返回一个查看C风格字符串常量的std::string_view是可行的。当std::string_view被复制回调用方时,被查看的C风格字符串常量仍将存在。

该函数是 constexpr,这样我们就可以在常量表达式中使用颜色名称。

相关内容
Constexpr 函数在 F.1 课中介绍——Constexpr 函数。

虽然这可以让我们获取枚举器的名称,但如果要将该名称打印到控制台,这样std::cout << getColorName(shirt)做就不如直接使用枚举器名称std::cout << shirt方便。我们将在接下来的第 13.5 课——I/O 运算符重载介绍std::cout中讲解如何打印枚举。

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

非作用域枚举器输入

现在我们来看一个输入示例。在下面的例子中,我们定义了一个Pet枚举类型。因为枚举Pet类型是程序自定义类型,所以编程语言不知道如何Pet使用std::cin 语句输入枚举值。

#include <iostream>

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

int main()
{
    Pet pet { pig };
    std::cin >> pet; // compile error: std::cin doesn't know how to input a Pet

    return 0;
}

image

一种简单的解决方法是读取一个整数,然后使用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; // input an integer

    if (input < 0 || input > 3)
        std::cout << "You entered an invalid pet\n";
    else
    {
        Pet pet{ static_cast<Pet>(input) }; // static_cast our integer to a Pet
        std::cout << "You entered: " << getPetName(pet) << '\n';
    }

    return 0;
}

image

虽然这种方法可行,但有点别扭。另外需要注意的是,只有static_cast(input)当我们确定input它在枚举器的范围内时才应该这样做。


从字符串中获取枚举值

如果用户能够输入一个代表枚举值的字符串(例如“pig”),而不是输入数字,然后我们可以将该字符串转换为相应的Pet枚举值,那就更好了。然而,要做到这一点,我们需要解决一些难题。

首先,我们不能直接用字符串作为判断条件,所以我们需要用其他方法来匹配用户输入的字符串。最简单的办法是使用一系列 if 语句。

其次,当用户传入无效字符串时,我们应该返回哪个Pet枚举器?一种方案是添加一个代表“无(none)/无效(invalid)”的枚举器并返回它。但更好的方案是使用std::optional。

相关内容
std::optional我们将在第 12.15 课中介绍——std::optional

#include <iostream>
#include <optional> // for 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)
{
    // We can only switch on an integral value (or enum), not a string
    // so we have to use if-statements here
    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;
}

image

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

适合高级读者:
请注意,上述方法仅匹配小写字母。如果需要匹配任意大小写字母,可以使用以下函数将用户输入转换为小写:

#include <algorithm> // for std::transform
#include <cctype>    // for std::tolower
#include <iterator>  // for std::back_inserter
#include <string>
#include <string_view>

// This function returns a std::string that is the lower-case version of the std::string_view passed in.
// Only 1:1 character mapping can be performed by this function
std::string toASCIILowerCase(std::string_view sv)
{
    std::string lower{};
    std::transform(sv.begin(), sv.end(), std::back_inserter(lower),
        [](char c)
        {
            return static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
        });
    return lower;
}

完整代码:

#include <iostream>
#include <algorithm>
#include <cctype>                                                                                                                                                                                                
#include <iterator>
#include <optional>
#include <string>
#include <string_view>

enum Pet
{
    cat,
    dog,
    pig,
    whale,
};

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)
{
    if (sv == "cat")         return cat;
    if (sv == "dog")         return dog;
    if (sv == "pig")         return pig;
    if (sv == "whale")         return whale;

    return {};
}

std::string toASCIILowerCase(std::string_view sv)
{
    std::string lower{};
    std::transform(first: sv.begin(), last: sv.end(), result: std::back_inserter(&x: lower),
        unary_op: [](char c)-> char
        {
            return static_cast<char>(std::tolower(c: static_cast<unsigned char>(c)));
        });
    return lower;
}
int main()
{
    std::cout << "Enter a pet: cat, dog, pig, or whale: ";
    std::string s{};
    std::cin >> s;

    std::optional<Pet> pet { getPetFromString(toASCIILowerCase(s)) };
    if (!pet)
        std::cout << "You entered an invalid pet\n";
    else
        std::cout << "You entered: " << getPetName(pet: *pet) << '\n';
                                                                                                                                                                                                                 
    return 0;
}

image

此函数遍历中的每个字符,使用(借助 lambda)将其std::string_view sv转换为小写字符,然后将该小写字符附加到std::tolower()。low

我们将在第 20.6 课——lambda 表达式(匿名函数)介绍中介绍 lambda 表达式。

与输出情况类似,如果我们能直接这样std::cin >> pet做就更好了。我们将在接下来的第13.5 课——I/O 运算符重载介绍中介绍这一点。

posted @ 2025-12-21 16:53  游翔  阅读(26)  评论(0)    收藏  举报