枚举是用来做什么的
结构体把几个相关的值组合成一个对象。enum 解决的是另一个问题:它给一小组固定的选项起名字。你不必再记住 0 表示“红色”、1 表示“绿色”、2 表示“蓝色”,而是直接写 Color::Red,由编译器来帮你把关。
只要一个变量只能是少数几种有名字的状态之一,比如红绿灯的颜色、扑克牌的花色、连接状态,就用 enum。这样代码能自己说明含义,编译器也能发现拼写错误和漏掉的情况,而这些是裸整数永远做不到的。
声明枚举
现代 C++ 有两种枚举。先从你几乎总是应该使用的那种开始:作用域枚举 enum class。你列出名字,然后通过枚举名加 :: 访问每个枚举值:
注意你写的是 Color::Green,而不是单独的 Green。这些值本身只是标签:你可以比较它们、给变量赋值、把它们传来传去,但很少关心它们背后的数字。默认情况下,Red 是 0,Green 是 1,Blue 是 2,从零开始往上数。
普通 enum 与 enum class
较老的无作用域 enum(不带 class 关键字)会把它的名字直接放进外围作用域,并且会自行转换成 int。听起来很方便,但它会带来两个实际问题:
enum Color { Red, Green, Blue };
enum Fruit { Apple, Banana, Red }; // error: 'Red' already declared
enum Status { Active, Inactive };
int x = Active; // compiles silently - is this what you meant?
if (Active == Banana) { // compares unrelated enums via int - allowed!
}
因为普通枚举值是全局名字,两个枚举只要有一个相同的标签就会冲突。又因为它们会退化成 int,编译器会毫无怨言地比较两个毫不相干的枚举的值。作用域枚举 enum class 把这两个问题都解决了:名字位于类型内部,而且这个类型不会悄悄变成 int:
经验法则:默认使用 enum class。只有当你确实需要隐式转换成 int 时,比如旧的 C 风格标志常量,才退回到普通 enum。
自定义值与底层类型
你可以给枚举值指定明确的数字。没有指定的会从前一个值继续往上数,这对 HTTP 状态码或位标志这类东西很方便:
每个枚举背后都有一个整数类型,默认是 int。当你在意大小时,可以把它固定为更小的类型,比如在紧凑的结构体中存放大量枚举,或者要匹配某种传输格式:
选定底层类型也就确定了值必须落在的范围:底层类型为 uint8_t 的枚举无法保存大于 255 的值。
枚举与 int 之间的转换
作用域枚举 enum class 从不隐式转换,这正是它的意义所在。当你确实需要那个数字时,比如打印它、用它作为数组下标,或者从文件中读取它,就使用 static_cast。从 enum 转成 int 总是安全的:
把 int 转回枚举才是危险的方向。这个转换不会检查数字是否对应一个真实的枚举值,它会给你一个严格来说落在枚举命名集合之外的值:
Suit s = static_cast<Suit>(2); // fine - that's Clubs
Suit bad = static_cast<Suit>(99); // compiles, but 99 is not a valid Suit
// using `bad` in a switch or as an array index is a lurking bug
如果整数来自用户输入或文件,在转换之前要自己校验范围,否则你会得到一个没有任何 case 能处理的值,这会在后面埋下未定义行为的隐患。
在 switch 中使用枚举
因为枚举表示“固定集合中的一个”,所以它和 switch 是完美的搭配。当你覆盖了每个枚举值后,如果以后新增了一个值却忘了处理,很多编译器会给出警告。这是裸整数无法免费提供的安全保障:
一个坑:C++ 没有内置的方法来打印枚举值的名字。对于作用域枚举,cout << TrafficLight::Red 无法编译;即使是普通枚举,它打印出来的也是数字,而不是“Red”。用一个小小的 switch,或者像上面那样的查找表,是把枚举转换成人能读懂的字符串的常用方法。
下一步:异常
枚举和结构体让你描述数据长什么样。但真实的程序还必须处理出错的情况:打不开的文件、解析不了的数字、超出范围的值。C++ 用异常来处理这些失败路径,这就是下一页的内容。
常见问题
C++ 中 enum 和 enum class 有什么区别?
普通 enum 会把它的名字泄漏到外围作用域,并且会隐式转换成 int,这会导致命名冲突和意外的比较。作用域枚举 enum class 把名字留在枚举内部(Color::Red),没有显式转换就拒绝转成 int。在现代 C++ 中优先使用 enum class:它类型安全,能避开那些经典的坑。
怎样把 C++ 枚举转换成 int?
普通 enum 会隐式转换,所以 int n = Red; 直接就能用。作用域枚举 enum class 需要显式转换:int n = static_cast<int>(Color::Red);。反过来,把 int 转回枚举:Color c = static_cast<Color>(2);。但要小心,运行时并不会检查这个值是否是一个有效的枚举值。
C++ 枚举的第一个值从几开始?
默认情况下,第一个枚举值是 0,之后每一个都比前一个大 1。所以在 enum class Level { Low, Mid, High }; 中,Low 是 0,Mid 是 1,High 是 2。你可以给其中任何一个指定明确的值来覆盖这个规则。