轨道
/
C++
C++
/
练习
/
捉弄巨魔
捉弄巨魔

捉弄巨魔

学习练习

简介

枚举

到目前为止,我们处理的主要是数字和字符串。 为了给现实世界建模,我们可能希望某个变量只能取有限的一组值。 你可能想要一个专门的类型,它包含少数几个名称各不相同的值。 例如,在滑板工厂里,板面材料只能在枫木、竹子或塑料中选择。

你可以用整数来编码这些值,但还得额外写代码,检查系统传来的材料值是否无效。 这些魔法数字的含义在源码中很难追溯,而且容易混淆。 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的原因。

Advanced

无作用域枚举

既然名字里带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>。

switch 语句

和其他语言一样,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 发现封禁喷子账号没有用。 她的策略是让他们以为自己花的时间“很值得”,但他们的帖子只会展示给其他喷子。 凡是要按优先级排序的地方,喷子在任何序列中都排在最后。 他们进入游戏时,可匹配的玩家池也被限制为其他喷子。

这种做法叫做影子封禁。

1. 设置用户类型和权限。

首先,定义一个AccountStatus枚举,表示 4 种账户类型:troll、guest、user 和 mod。

接下来,定义一个Action枚举,表示 3 种权限类型:read、write 和 remove。

2. 只把喷子展示给其他喷子。

论坛上的每个帖子都会在自己的元数据里保存发帖人的AccountStatus。 确保喷子发的帖子只展示给其他喷子。

Helma 需要一个display_post函数,它接收 2 个AccountStatus类型的实参,并返回一个bool。 第一个实参是发帖人的状态,第二个是浏览者的状态。

using namespace hellmath;
display_post(AccountStatus::troll, AccountStatus::user);
// => false
display_post(AccountStatus::mod, AccountStatus::guest);
// => true

3. 检查不同用户的权限。

Helma 需要一种方法,来检查某个用户是否被允许执行某个操作。

请实现一个permission_check函数,它的第一个实参是Action,第二个是要检查的AccountStatus。 它应该根据引言中列出的权限返回一个bool。

permission_check(Action::remove, AccountStatus::guest);
// => false
permission_check(Action::write, AccountStatus::mod);
// => true

4. 允许进入游戏并匹配玩家。

为了让游戏里的真实玩家为自己的行为负责,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

5. 实现优先级排队。

随着游戏和论坛的快速增长,Helma 现在必须在用户之间分配计算能力和带宽。 为了应对紧急情况,版主被赋予最高的优先级。 游客排在普通用户后面,而喷子排在其他所有人后面。

实现has_priority函数,它接收 2 个AccountStatus实参,当且仅当第一个账户的优先级严格高于第二个时返回true。

has_priority(AccountStatus::guest, AccountStatus::mod);
// => false
has_priority(AccountStatus::user, AccountStatus::troll);
// => true
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
C++ Exercism

准备好开始 捉弄巨魔 了吗?

注册 Exercism,借助 19 个概念100 个练习 和真人导师指导,学习并掌握 C++,全部免费。