强类型枚举(enum class)是 C++11 标准引入的一种安全枚举语法,有时也称为 “强作用域枚举”。在实际项目开发中,enum class 是我们编写现代 C++ 代码时定义枚举的首选方式。
在上一节 “C++ 枚举” 中我们看到,传统 enum 的枚举值会直接暴露在外层作用域中,而且会自动转换成普通整数。强类型枚举则同时解决了这两个问题:它既把枚举值严格限制在自身作用域内,又禁止了向整数的隐式转换。
enum class 的定义与使用
在 C++ 中,enum class 的定义格式与传统 enum 非常相似。定义时我们只需要在 enum 后面多写一个 class 关键字,并在右花括号 } 后面保留英文分号(;)即可。
语法:
enum class 枚举类型名 {
枚举值1,
枚举值2,
...
枚举值n
};说明:
在传统 enum 中,我们可以直接写出枚举值的名字。而在 enum class 中,所有枚举值都被限制在当前枚举类型的作用域内部,访问时必须写成 枚举类型名::枚举值 的格式。
这里的双冒号 :: 是作用域运算符,用法和前面在命名空间中学过的 std::cout 一样。比如定义了强类型枚举 TrafficLight 后,写 TrafficLight::Green 就表示访问 TrafficLight 里面的 Green。
示例 1:定义并使用 enum class
#include <iostream>
// 定义交通信号灯强类型枚举
enum class TrafficLight {
Red, // 红灯
Yellow, // 黄灯
Green // 绿灯
};
int main() {
TrafficLight light = TrafficLight::Green;
switch (light) {
case TrafficLight::Red:
std::cout << "红灯停\n";
break;
case TrafficLight::Yellow:
std::cout << "黄灯等一等\n";
break;
case TrafficLight::Green:
std::cout << "绿灯行\n";
break;
}
return 0;
}运行结果如下。
绿灯行分析:
在这个例子中,我们使用 enum class TrafficLight { ... }; 定义了一个强类型枚举。在 main() 函数里,无论是给变量 light 赋值,还是在 switch 语句的 case 分支中匹配,我们都统一写成 TrafficLight::Green。这样一眼就能看清枚举值的来源。
需要注意的是,访问强类型枚举的值时,前面的 TrafficLight:: 前缀一定不能省略。
// 正确方式
TrafficLight light = TrafficLight::Green;
// 错误方式
TrafficLight light = Green;如果漏掉前缀直接写 Green,编译器在外层作用域里找不到这个名字,就会直接编译报错。
enum class 的独立作用域
在传统 enum 中,如果 “水平方向” 和 “鼠标按键” 两个枚举都包含 Left 和 Right,放在同一个作用域里就会报重定义错误。
改用 enum class 之后,每个枚举都拥有各自独立的作用域,访问时必须带上各自的枚举名前缀。因此就算多个枚举里包含完全相同的名字,也互不干扰。
示例 2:同名枚举值互不冲突
#include <iostream>
// 水平方向
enum class Direction {
Left,
Right
};
// 鼠标按键
enum class MouseButton {
Left,
Right
};
int main() {
Direction dir = Direction::Left;
MouseButton btn = MouseButton::Left;
if (dir == Direction::Left) {
std::cout << "方向:向左\n";
}
if (btn == MouseButton::Left) {
std::cout << "鼠标:点击左键\n";
}
return 0;
}运行结果如下。
方向:向左
鼠标:点击左键分析:
Direction 和 MouseButton 虽然都定义了 Left 和 Right,但使用时分别写作 Direction::Left 和 MouseButton::Left,编译器能够准确区分它们。
在实际开发中,像 Left、Right、Success、Unknown 这类通用单词出现频率非常高。使用 enum class 不仅能避免重名冲突,还能阻止我们不小心把两个不相干的枚举混在一起比较。比如上面代码中如果误写 dir == btn,由于两者属于不同的强类型枚举,编译器会直接报错。
enum class 与整数的转换
enum class 与传统 enum 的另一个重要区别,是它关闭了向整数的隐式转换。在传统 enum 中,枚举值可以直接放进 std::cout 打印或赋给 int 变量。但在 enum class 中,下面这两种写法都会编译报错:
enum class OrderStatus {
Pending,
Paid,
Shipped
};
// 下面两行代码都会编译报错
int num = OrderStatus::Shipped;
std::cout << OrderStatus::Shipped << '\n';这并不意味着 enum class 底层没有整数编号。实际上,它底层照样按整数存储,默认从 0 开始依次加 1,也支持通过 = 手动指定编号。只不过编译器禁止了自动把枚举当成普通数字使用。
那如果在调试程序或保存数据时,我们确实需要获取 enum class 对应的底层整数,应该怎么办呢?这时可以使用 static_cast 进行显式转换,格式为 static_cast<目标类型>(表达式)。
示例 3:获取 enum class 的底层整数
#include <iostream>
enum class Level {
Low = 1, // 指定为 1
Medium, // 自动变成 2
High = 10, // 指定为 10
Urgent // 自动变成 11
};
int main() {
Level lv = Level::Medium;
std::cout << "Medium 对应整数:" << static_cast<int>(lv) << '\n';
std::cout << "Urgent 对应整数:" << static_cast<int>(Level::Urgent) << '\n';
return 0;
}运行结果如下。
Medium 对应整数:2
Urgent 对应整数:11分析:
static_cast<int>(lv) 表示将强类型枚举变量 lv 显式转换为 int 整数,因此能够正常输出 Medium 对应的整数 2 和 Urgent 对应的整数 11。平时写条件判断如果用不到背后的编号,小伙伴们直接使用 Level::Medium 比较即可,不需要特意去做转换。
提示 定义强类型枚举时,enum class 默认使用 int 作为底层存储类型。如果有节省内存等特殊需求,我们也可以在枚举名后面加上冒号和整型关键字来指定底层类型,比如写成 enum class Level : char,这里简单了解一下即可。
反过来,如果我们从文件或网络中读取到了一个整数状态码,需要把它还原成对应的 enum class 枚举值,同样可以使用 static_cast 进行显式转换。
示例 4:将整数显式转换为 enum class
#include <iostream>
enum class OrderStatus {
Pending,
Paid,
Shipped,
Completed
};
int main() {
int code = 2;
OrderStatus status = static_cast<OrderStatus>(code);
if (status == OrderStatus::Shipped) {
std::cout << "当前状态:订单已发货\n";
}
return 0;
}运行结果如下。
当前状态:订单已发货分析:
在 OrderStatus 中,枚举值默认从 0 开始编号,整数 2 刚好对应第 3 项 OrderStatus::Shipped。通过 static_cast<OrderStatus>(code) 显式转换后,变量 status 的值就变成了 OrderStatus::Shipped。
需要注意的是,static_cast 并不会自动检查整数是否越界。比如传入 99 时,编译虽然不会报错,但会得到一个没有对应枚举名的无效状态值。因此我们在转换前最好先检查整数范围,或者在 switch 中配上 default 分支兜底。
在实际开发中,小伙伴们编写现代 C++ 新代码时统一使用 enum class 即可。只有在维护旧项目或对接 C 语言接口时,才需要用到传统 enum。
