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;
}
打印出来的内容:

由于运算符<<无法直接打印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;
}
这会输出

在上例中,我们根据传递的枚举值对 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;
}

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

虽然这种方法可行,但有点别扭。另外需要注意的是,只有static_cast
从字符串中获取枚举值
如果用户能够输入一个代表枚举值的字符串(例如“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;
}

在上述解决方案中,我们使用一系列 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;
}
此函数遍历中的每个字符,使用(借助 lambda)将其
std::string_view sv转换为小写字符,然后将该小写字符附加到std::tolower()。low
我们将在第 20.6 课——lambda 表达式(匿名函数)介绍中介绍 lambda 表达式。
与输出情况类似,如果我们能直接这样std::cin >> pet做就更好了。我们将在接下来的第13.5 课——I/O 运算符重载介绍中介绍这一点。


浙公网安备 33010602011771号