C++ 强类型枚举(enum class)

强类型枚举(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。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号