到目前为止,我们处理的主要是数字和字符串。 为了给现实世界建模,我们可能希望某个变量只能取有限的一组值。 你可能想要一个专门的类型,它包含少数几个名称各不相同的值。 例如,在滑板工厂里,板面材料只能在枫木、竹子或塑料中选择。
你可以用整数来编码这些值,但还得额外写代码,检查系统传来的材料值是否无效。
这些魔法数字的含义在源码中很难追溯,而且容易混淆。
enumerations可以用来鼓励写出更富表现力的代码,并减少无意的比较错误。
这种枚举的专门术语是scoped enumeration。
下面的代码片段展示了如何写一个DeckMaterial enumeration。
注意enum class关键字以及定义末尾的;:
enum class DeckMaterial {
maple,
bamboo,
plastic
};
现在,看看滑板店里的一个定价函数,注意作用域解析运算符(::)是如何从enumeration中指定enumerator的:
double deck_price(double base_price, DeckMaterial material) {
if(material == DeckMaterial::plastic) {
return base_price * 0.9;
}
return base_price * 1.3;
}
想象你还有第二个用于轮子材料的enumeration:
enum class WheelMaterial {
steel,
clay,
plastic
};
虽然轮子和板面都可以用_塑料_制成,但这两者并不会混淆。
它们是不同的_类型_:DeckMaterial的塑料和WheelMaterial的塑料。
每个enumeration的enumerators都在自己的作用域里,也就是自己的namespace里。
这正是它们被称为scoped enumerations的原因。
既然名字里带scoped,你可能会想,是不是也有_unscoped枚举?没错,确实有。
Unscoped enumerations越来越不受欢迎,因为它们全都共享同一个全局命名空间。
正因为共享,你无法像上面的例子那样,让两个unscoped enumerations拥有相同的enumerators(比如_塑料)。
另外,unscoped enumerations会隐式地转换成整数。
看看下面这个例子,结果可能会让你意外:
enum CitrusFruits {
lemons, // 0
oranges, //1
};
enum IceCream {
walnut, // 0
apples, // 1
};
bool comparison{apples == oranges};
// => true
// Example from above:
bool comparison{DeckMaterial::plastic == WheelMaterial::plastic};
// => Does not compile!
如果想把这些scoped enumerations转换成整数,可以使用static_cast<int>。
和其他语言一样,C++ 也提供了switch语句。
switch 语句可以更简短地写出很长的if ... else if语句。
要写一个 switch,我们先用关键字switch,后面跟一个整数。
然后用case关键字声明每一个分支。
我们还可以声明一个default分支,当之前的所有case条件都不匹配时,它就会执行。
每个分支都应该以break(或return)语句结束。
int price{0};
int adults{3};
int kids{2};
switch (int group_size{adults + kids}) {
case 1:
price = 50;
break;
case 2:
price = 70;
break;
default:
price = group_size * 30;
}
switch 结构有一点很重要:代码会一直继续执行,直到被break(或return)语句中止。
这可能导致意料之外的行为。
int adults{1};
int kids{0};
switch (int group_size{adults + kids}) {
case 1:
price = 50;
case 2:
price = 70;
default:
price = group_size * 30;
}
// price will be 30!
这种继续执行的特性主要用在一个语句带有多个标签的时候。 多个 switch 结果可以映射到同一段要执行的代码。 这样一来,比如在预订应用里,人数为 2 和 3 时调用的函数就可以是同一个:
switch (group_size) {
case 1:
book_room();
break;
case 2:
case 3:
book_apartment(group_size);
break;
default:
book_house(group_size);
}
// book_apartment happens when group_size is 2 or 3
你的朋友 Helma 做了一款小型的网络游戏,很快就火了起来。 它叫做 HellMath。 这个小社区引来了一些喷子,把游戏和论坛搞得乌烟瘴气。 Helma 请你开发一套新的权限系统,把这些捣乱的人隔离开。
论坛支持 3 种不同的操作:
账户有 4 种类型,每种类型有不同的默认权限:
Helma 发现封禁喷子账号没有用。 她的策略是让他们以为自己花的时间“很值得”,但他们的帖子只会展示给其他喷子。 凡是要按优先级排序的地方,喷子在任何序列中都排在最后。 他们进入游戏时,可匹配的玩家池也被限制为其他喷子。
这种做法叫做影子封禁。
首先,定义一个AccountStatus枚举,表示 4 种账户类型:troll、guest、user 和 mod。
接下来,定义一个Action枚举,表示 3 种权限类型:read、write 和 remove。
论坛上的每个帖子都会在自己的元数据里保存发帖人的AccountStatus。
确保喷子发的帖子只展示给其他喷子。
Helma 需要一个display_post函数,它接收 2 个AccountStatus类型的实参,并返回一个bool。
第一个实参是发帖人的状态,第二个是浏览者的状态。
using namespace hellmath;
display_post(AccountStatus::troll, AccountStatus::user);
// => false
display_post(AccountStatus::mod, AccountStatus::guest);
// => true
Helma 需要一种方法,来检查某个用户是否被允许执行某个操作。
请实现一个permission_check函数,它的第一个实参是Action,第二个是要检查的AccountStatus。
它应该根据引言中列出的权限返回一个bool。
permission_check(Action::remove, AccountStatus::guest);
// => false
permission_check(Action::write, AccountStatus::mod);
// => true
为了让游戏里的真实玩家为自己的行为负责,Hellmath 不允许游客用户进入。 如上所述,Helma 想让喷子只去喷其他喷子。 其他用户之间的游戏匹配不受限制。
实现valid_player_combination函数,检查 2 个玩家能否加入同一局游戏。
这个函数有 2 个AccountStatus类型的形参,并返回一个bool。
valid_player_combination(AccountStatus::guest, AccountStatus::mod);
// => false
valid_player_combination(AccountStatus::troll, AccountStatus::troll);
// => true
随着游戏和论坛的快速增长,Helma 现在必须在用户之间分配计算能力和带宽。 为了应对紧急情况,版主被赋予最高的优先级。 游客排在普通用户后面,而喷子排在其他所有人后面。
实现has_priority函数,它接收 2 个AccountStatus实参,当且仅当第一个账户的优先级严格高于第二个时返回true。
has_priority(AccountStatus::guest, AccountStatus::mod);
// => false
has_priority(AccountStatus::user, AccountStatus::troll);
// => true