枚举
我之前讲了一些整数类型,如 int
long long
等等,它们的取值范围都比较大。比如 int
可以取到数十亿的范围,long long
则高达 。即便是最小的 char
,取值范围也有几百。(布尔类型暂且不考虑。)但在一些场合,某个值的取值范围可能只有数十个甚至几个,那么采用过大取值范围的数据类型来表示它就不太合适了。
比如我想描述一个小人目前正在前进的方向。它可以前进的方向只有东、南、西、北四种。如果用 int
来表示这个数据,那么就需要规定 0 表示东、1 表示西……,然后还需要规定大于 3 的值是错误的。这中额外的编码工作会让代码看上去更费劲了。此时我们可以选用枚举类型作为一个更合适的数据类型来表示它。
枚举类型(Enumeration)是规定了一个有限取值范围的数据类型。它的所有可能取值称为这个类型的枚举项(Enumerator)。声明枚举类型的语法是这样的:
enum 枚举类型名 : 基 { 枚举项列表 };
先抛开 :基
不管;枚举项列表
是若干个逗号分隔的名字,表示这个类型的所有可能取值。刚刚小人前进方向的例子中,如果用枚举类型来表示它的话就写成这样:
enum Direction {
East,
South,
West,
North
};
这段代码的意思是,声明一个枚举类型 Direction
,它的取值范围只能是 East
South
West
和 North
。下面给出了枚举类型的使用例子:
// 声明并定义一个枚举
Direction d{East};
// 赋值
d = North;
// 判断
if (d == West) {
cout << "He is going to west." << endl;
}
// d = 3; // 编译错误:不能将 int 类型变量赋值给 Direction 类型
需要注意的是,这种声明不仅引入了类型名 Direction
,还向全局命名空间引入了四个枚举项(East
South
West
和 North
)的名字,从而你能在任何地方使用这些枚举项。如果不希望这种名字的污染,可以使用带作用域的枚举类型:
enum classstruct 枚举类型名 : 基 { 枚举项列表 }
在 enum
关键字后加上 class
或者 struct
,这个枚举便成为了带有作用域的枚举类型。当枚举类型带有作用域时,其枚举项需要带 枚举类型名::
前缀使用:
enum class Direction {
East,
South,
West,
North
};
Direction d{Direction::East};
d = Direction::North;
// d = North; // 编译错误:North 未定义
我们推荐在 C++ 中使用带有作用域的枚举,这样能有助于避免命名冲突。
枚举类型的底层实现
关于枚举类型,我们还需要了解其底层是如何实现的。对于一个枚举类型来说,其实际上定义了若干个整数类型常量作为其枚举项。这些常量的值是从 0
开始的连续整数。比如在最初的 Direction
例子中,编译器定义了四个常量 East
South
West
和 North
,它们的取值分别为 0
1
2
3
。也就是说,
Direction d{East};
d = North;
会被编译器翻译成
int d{0};
d = 3;
正因为枚举项底层实现是整数类型,所以 C++ 允许从枚举项到整数类型的转换。(但反过来不行,如果反过来就违背了枚举类型“有限个取值”的初衷。)
你可以手动指定一个枚举项底层的值。
enum Mode {
Execute = 1,
Write = 2,
Read = 4
};
在这个例子中,枚举 Mode
的三个枚举项以 = 常量
的后缀的形式给出了对应枚举项底层的值。所以,此时如果写下 int a{Read};
则代表了 int a{4};
。
如果没有显式给出一个枚举项底层的值,则它的值为上一个枚举项的取值 +1。第一个枚举项底层的值默认为 0。
枚举项底层的类型被称为这个枚举类型的基类型(Enum base type),简称基。默认一个枚举的基是 int
,所以在上面所有例子中见到的枚举或枚举项都会被翻译成 int
类型的变量或值的操作。当然,你可以更改一个枚举的基,方法就是指定最初提到的 :基
:
enum class Direction: short {
East,
South,
West,
North
};
int main() {
Direction d{Direction::East};
sizeof(d); // sizeof(d) 的值等于 sizeof(short)
}
更改枚举的基会影响枚举类型的变量所占用的存储空间,以及其所能取到的枚举项的最大个数。