3.7 枚举类型
除了之前我们介绍的数值数据和文字数据之外,在现实世界中,常常还会遇到这样一类数据:一道单选题的答案只能是A、B、C、D四个选项中的某一个;红绿灯的颜色只能是红色,绿色和黄色中的某一种;一个人的性别只能是男性或者女性。这种数据都只有有限的几种可能值,其值也只能是这个范围内的某一个。为了抽象和表达这种特殊数据,C++提供了枚举机制。
使用C++的枚举机制,我们可以通过列举某种数据的所有可能值来定义一种新的数据类型,这种数据类型通常被称为枚举类型。反过来,当使用枚举类型来定义变量时,这个变量的取值也就限定在了枚举类型所列举出的可能取值范围内。定义一个枚举类型的语法格式如下:
enum 枚举类型名 { // 可能的枚举值列表 }; // 注意,这里必须以分号表示结束
其中,enum是定义枚举类型的关键字;枚举类型名是所要创建的新数据类型的名字,完成枚举类型的定义后,我们可以用它作为数据类型来定义变量;而在枚举类型的定义中,可以逐个列出这个枚举类型的所有可能值。例如,可以将描述交通灯颜色的数据抽象成一个枚举类型:
// 交通灯颜色 enum TrafficLight { RED, // 红 GERRN, // 绿 YELLOW // 黄 };
有了这样的TrafficLight枚举类型的定义,我们就可以用它做为数据类型来定义一个变量,用以表示交通灯的颜色:
// 定义一个变量light表示交通灯的颜色 // 将枚举值RED赋值给它,表示当前交通灯是红色 TrafficLight light = RED;
因为枚举类型所表达数据的特殊性,使我们在应用枚举类型时需要注意以下几点。
1. 枚举类型中的枚举值有对应的默认整数值
在本质上,枚举类型数据是一个整型数值,每个枚举类型的可选值其实是一个整数值。在默认情况下,第一个枚举值对应的是整数值0,第二个是1,以此类推。例如,上面TrafficLight枚举类型中的RED可选值其实就是整数值0,而GREEN就是1,YELLOW自然就是2了。如果认为枚举值的默认对应整数值不合适。比如,在某些情况下,我们希望某个枚举值拥有特殊的整数值以表示特殊含义,这时也可以在定义的时候另行指定各个枚举值对应的整数值。例如:
// 另行指定各个枚举值对应的整数值 enum TrafficLight { RED = 1, // 指定对应的整数值,不再是从0开始 GERRN, // 2 YELLOW = 0 // 指定对应的整数值为0,表示特殊含义 };
经过人为地指定枚举值对应的数值后,RED对应的整数值就变为了1,其后的GREEN增加一个自然也就随之而变为2,而对于最后的YELLOW,因为其特殊意义,我们又人为地将其指定为0。
2. 枚举类型变量的赋值
如果变量是枚举类型,那就只能使用这种枚举类型的某个枚举值对其进行赋值。例如:
// 红灯 TrafficLight light = RED; // 变为绿灯 light = GREEN; // 如果是用枚举值之外的数据对其进行赋值,会导致编译错误 // 即使这个值是某个枚举值对应的整数值 light = 1;
换句话说,如果想把某个变量的取值限定在某几个可选值范围之内,则可以把这个变量定义为枚举类型变量。
3. 枚举值是常量,定义后不可改变其对应整数值
在定义枚举类型时,就已经完成了枚举值的定义,其对应的数值不是默认数值就是给定的特殊数值。定义完成后,各个枚举值的数值就成为了常量,要按照常量来处理,不能对其进行赋值。也就是说,不能在完成定义后再改变其中某个枚举值对应的整数值。例如,下面的语句是非法的:
RED = 4; // 尝试改变枚举值,导致编译错误
枚举类型中的枚举值实质是一些整型常量。在上面的例子中,完全可以使用3个整型常量来表示交通灯的三种颜色。但是,枚举类型允许我们用描述性的名称来表示某个有限范围内的整数值,而不是直接使用含义模糊的整数值,这有助于确保给变量指定合法的期望值,同时这也使得我们的代码意义明确,更具可读性,也更易于维护。所以,如果要表达的数据“只有有限的几种可能值”时,我们应该优先选择使用枚举类型。
知道更多:带作用域的枚举类
虽然枚举类型可以方便地定义某个范围内的枚举值。但是,因为传统的枚举类型在本质上是一个int类型的整数,因此在使用中,也常常存在各种各样的问题。例如,传统枚举类型的所有枚举值在其定义后的代码范围内都是可以使用的,而这可能会造成名字污染,使得其后的代码就不能再把这个枚举值名字用作其他用途;不可以指定枚举类型的底层数据类型,这可能会浪费内存资源,同时也使得枚举类型不可以进行前向声明。所谓前向声明,就是在某个元素(函数或者类)尚未定义的时候,为了提前使用它而进行的声明,向编译器表明源文件中有这个元素的定义,现在就可以放心大胆地使用,而具体的定义会在稍后给出。正是为了解决传统枚举类型所存在的这些问题,C++11提出了新的具有作用域,并可以指定底层数据类型的枚举类。
定义枚举类的语法形式跟定义一个传统的枚举类型的语法形式十分相似:
enum class 枚举类名:数据类型 { // 可能的枚举值列表 };
枚举类的定义以“enum class”开始,后面跟上枚举类名,其后还可以用冒号“:”指定枚举类的底层数据类型。枚举类的底层数据类型必须是有符号或无符号的整型数,如果不指定,其默认的数据类型是int类型。例如:
// 定义一个枚举类TrafficLight, // 并指定其底层数据类型为char类型 enum class TrafficLight : char { RED = 1, // 红 GERRN, // 绿 YELLOW // 黄 };
虽然枚举类的定义同传统的枚举类型的定义十分相似,但是因为两者内在机制的不同,他们已经是完全不同的两个概念。枚举类具有作用域,其枚举值只在其作用域内可见,这就很好地解决了枚举值可能引起的名字污染问题。例如:
// 枚举值RED属于TrafficLight作用域 // 所以我们必须在其前面加上TrafficLight才能访问 TrafficLight light = TrafficLight::RED; // 定义一个名为RED的变量,虽然与枚举值RED同名, // 但是两者不会产生冲突,也就是枚举值RED的定义 // 没有引起名字污染 bool RED = true;
除了解决名字污染问题之外,因为可以指定枚举的底层数据类型,这使得前向声明成为可能,同时,根据枚举值的多少,选择合适的底层数据类型,也可以在一定程度上避免资源的浪费。例如:
// 使用前的前向声明,只是声明了枚举类的名字, // 没有定义具体的枚举值 enum class TrafficLight : char; // 使用前向声明的枚举类 void foo(TrafficLight* light) { // ... } // … // 补充完成枚举类的定义 enum class TrafficLight : char { RED = 1, // 红 // … };
在这段代码中,我们完成枚举类TrafficLight枚举类的前向声明后,就可以直接使用了。而在最后,我们只需要补充上它的具体定义即可。另外,我们在这里指定了枚举值的底层数据类型为char,这要比默认的int更加节省资源。
从上面的例子我们可以看到,枚举类的使用,很好地解决了传统的枚举类型在使用中所遇到的各种问题,所以,在以后的编程实践中,我们应该优先选择使用枚举类。