论坛权限管理

概念学习C++17

简介

枚举

到目前为止,我们主要使用数字和字符串。为现实世界建模时,有时希望变量只能取有限几个值,因此需要一种具有若干明确名称的专用类型。例如,滑板工厂的板面材料只能选择枫木、竹子或塑料。

可以用整数编码这些材料,但这需要额外检查系统是否传入非法数值。这样的魔数在代码中不易追踪,也容易混淆。枚举能使代码更易理解,并减少无意的比较错误。

使用示例

这种枚举称为有作用域枚举。下面展示了如何定义 DeckMaterial,注意 enum class 关键字及定义末尾的分号:

enum class DeckMaterial {
    maple,
    bamboo,
    plastic
};

下面是滑板商店的定价函数,注意使用作用域解析运算符 ::,指定枚举类型中的某个枚举项:

double deck_price(double base_price, DeckMaterial material) {
    if(material == DeckMaterial::plastic) {
        return base_price * 0.9;
    }
    return base_price * 1.3;
}

作用域

假设还有第二个枚举,表示轮子材料:

enum class WheelMaterial {
    steel,
    clay,
    plastic
};

虽然轮子和板面都可以由塑料制成,但两者不会混淆,因为 DeckMaterial 的塑料与 WheelMaterial 的塑料属于不同类型。每个枚举的枚举项都位于自己的作用域中,因此称为有作用域枚举。

进阶:无作用域枚举 既然存在有作用域枚举,也就有无作用域枚举。后者会把枚举项放入外围作用域,因此逐渐不如前者常用。如果两个无作用域枚举位于相同作用域,就不能同时具有上例中 plastic 这样的同名枚举项。

无作用域枚举还会隐式转换为整数。观察下面这个可能出乎意料的结果:

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!

若要把有作用域枚举转换为整数,可以使用 static_cast<int>。

switch 语句

与其他语言一样,C++ 提供 switch,可以简洁表达较长的 if ... else if 分支。使用 switch 后跟整型或枚举表达式,再用 case 列出各分支。还可提供 default,在没有其他分支匹配时执行。每个分支通常以 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!

这种继续执行的机制也可以让多个标签共用同一段代码。例如,在预订应用中,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

任务说明

朋友赫尔玛制作了一款迅速走红的小游戏 HellMath。社区吸引了一些捣乱者,使游戏和论坛氛围变差。她请你编写新的权限系统,把捣乱者与其他成员分开。

论坛支持三种操作:

  • 阅读
  • 发帖
  • 删除

共有四类账号,默认权限各不相同:

  • 访客:可以阅读帖子。
  • 普通用户与捣乱者:可以阅读和发帖。
  • 版主:可以阅读、发帖和删除,拥有全部权限。

赫尔玛发现直接封禁捣乱账号收效有限。她想让捣乱者以为自己的发言仍然有效,但帖子只对其他捣乱者可见。需要按优先级排序时,捣乱者始终排在最后;进入游戏时,也只能匹配其他捣乱者。

这种做法称为隐形封禁。

1. 定义账号类型与权限

定义 AccountStatus 枚举,包含四种账号类型:troll、guest、user、mod。

定义 Action 枚举,包含三种操作:read、write、remove。

2. 让捣乱者的帖子只对同类可见

论坛在每条帖子的元数据中记录发帖者的 AccountStatus。捣乱者的帖子只能显示给其他捣乱者。

实现 display_post,接收两个 AccountStatus 参数,分别表示发帖者和浏览者的账号状态,返回 bool。

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

3. 检查不同账号的权限

赫尔玛需要判断某个账号是否有权执行指定操作。

实现 permission_check,第一个参数是 Action,第二个参数是待检查的 AccountStatus,按前述权限规则返回 bool。

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

4. 检查游戏资格与玩家配对

为让玩家对自己的行为负责,HellMath 不允许访客进入游戏。捣乱者只能与其他捣乱者一起游戏,其他用户之间的连接不受限制。

实现 valid_player_combination,接收两个 AccountStatus 参数,判断两位玩家能否进入同一场游戏,返回 bool。

valid_player_combination(AccountStatus::guest, AccountStatus::mod);
// => false
valid_player_combination(AccountStatus::troll, AccountStatus::troll);
// => true

5. 建立优先队列

随着游戏与论坛规模扩大,赫尔玛需要分配算力和带宽。版主优先级最高,方便处理紧急情况;访客排在普通用户之后,捣乱者排在所有人之后。

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

has_priority(AccountStatus::guest, AccountStatus::mod);
// => false
has_priority(AccountStatus::user, AccountStatus::troll);
// => true
需要一点帮助?查看分步提示

提示

1. 定义账号类型与权限

  • 可以用 if 处理各个 AccountStatus,也可以选择 switch。

2. 限制帖子可见性

  • 和其他类型一样,可以用 !=、== 比较枚举值。

3. 检查权限

4. 检查游戏资格与配对

  • 重点考虑 guest 和 troll,其他组合不需要进一步拆分。

5. 建立优先队列

  • 如果恰当地排列 AccountStatus 的枚举项,可以用 static_cast<int> 简洁比较优先级。