整理一下关于C/C++中枚举类的用法。

概述

C和C++中提供了枚举类型,用于定义一组相关的命名离散常量, 通常直接使用非负整数实现,并且枚举可以很方便地和非负整数进行相互转换。 但是这不符合C++类型系统的设计要求,C++将之前的枚举称为弱枚举类型,并且提供了更加安全的强枚举类型, 强枚举相比弱枚举有如下优点:

  • 作用域限制:枚举成员不会污染所在作用域的命名空间。
  • 类型安全:强类型枚举不允许隐式转换为整数,必须显式进行转换。
  • 明确的基础类型:可以指定枚举成员的基础类型,默认是int

弱枚举类型

使用enum定义枚举类型和枚举变量,例如

1
2
3
4
5
enum DAY
{
MON, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;

也可以将它们合在一起简写

1
2
3
4
enum DAY
{
MON, TUE, WED, THU, FRI, SAT, SUN
} day1, day2;

使用枚举类例如

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

enum DAY { MON, TUE, WED, THU, FRI, SAT, SUN } day1, day2;

int main() {
enum DAY day = WED;
printf("%d\n", day);

day1 = THU;
day2 = FRI;
printf("%d %d", day1, day2);
return 0;
}

输出

1
2
2
3 4

对于C语言,我们可以直接使用枚举成员(MON);对于C++,我们也可以加上枚举类型(DAY::MON),并没有什么区别。

枚举成员的实质就是某个自动对应的非负整数,我们也可以手动更改某个枚举成员对应的整数值,具体规则为:

  • 默认第一个成员的值为0
  • 默认当前成员为前一个成员的值+1
  • 显式指定某个成员的整数值的优先级最高

例如

1
2
3
4
5
6
7
enum season { spring, summer=3, autumn=10, winter };
/*
spring = 0
summer = 3
autumn = 10
winter = 11
*/

枚举类型可以和整数类型相互之间进行隐式转换,例如

1
2
3
4
5
6
7
8
enum DAY { MON, TUE, WED, THU, FRI, SAT, SUN };

enum DAY day = WED;
int d1 = day;
int d2 = day + 2;

int d3 = 2;
enum DAY day2 = d3;

虽然枚举类型的成员仅仅对应有限的几个整数值,但是编译器在把整数转换过去的过程中并不会进行范围检查,这很可能导致程序BUG, 尤其是程序中需要基于枚举类型的合法值进行判断并进入不同分支时,很可能出现意料之外的值,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

enum TrafficLight { RED = 1, YELLOW, GREEN };

void func(enum TrafficLight light) {
switch (light) {
case RED: printf("Stop!\n"); break;
case YELLOW: printf("Caution!\n"); break;
case GREEN: printf("Go!\n"); break;
default: printf("Invalid light color!\n"); break;
}
}

int main() {
func(RED); // Stop!
func(2); // Caution!
func(5); // Invalid light color!
return 0;
}

编译器实际上就是将枚举成员自动替换为对应的整数,因此实际上还需要禁止不同枚举类拥有同样名称的枚举成员, 因为编译器无法区分它属于哪一个枚举类,它们可能对应不同的整数,例如

1
2
3
enum DAY { MON, TUE, WED, THU, FRI, SAT, SUN };

enum TMP { TUE, UNE }; // compile error

虽然C++尽量地兼容C语言的弱枚举类型,但是实际上它们在处理弱枚举时采用的语法规则并不一致, 有可能出现C语言可以成功编译,但是C++编译报错的语句,反之同理。

强枚举类型

使用enum class定义强枚举类型,例如

1
2
3
4
enum class DAY
{
MON, TUE, WED, THU, FRI, SAT, SUN
};

定义强枚举类型的变量和普通类型类似

1
2
DAY day1;
DAY day2 = DAY::MON;

在使用枚举成员时必须加上枚举类名前缀(DAY::MON),这也允许了不同的枚举类具有同名的枚举成员。

使用强枚举类例如

1
2
3
4
5
6
7
8
9
10
#include <iostream>

enum class DAY { MON, TUE, WED, THU, FRI, SAT, SUN };

int main() {
DAY day = DAY::WED;
std::cout << static_cast<int>(day);

return 0;
}

虽然强枚举类型仍然是基于整数实现的,但是C++禁止了强枚举类型和整数类型的自动转换, 我们必须使用强制类型转换实现,例如

1
2
3
4
5
6
7
8
enum class DAY { MON, TUE, WED, THU, FRI, SAT, SUN };

DAY day = DAY::WED;
int d1 = static_cast<int>(day);
int d2 = static_cast<int>(day) + 2;

int d3 = 2;
DAY day2 = static_cast<DAY>(d3);

即使是强枚举类型,在强制类型转换时编译期仍然不会进行合法范围检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <cstdio>

enum class TrafficLight { RED = 1, YELLOW, GREEN };

void func(enum TrafficLight light) {
switch (light) {
case TrafficLight::RED: printf("Stop!\n"); break;
case TrafficLight::YELLOW: printf("Caution!\n"); break;
case TrafficLight::GREEN: printf("Go!\n"); break;
default: printf("Invalid light color!\n"); break;
}
}

int main() {
func(TrafficLight::RED); // Stop!
func(static_cast<TrafficLight>(2)); // Caution!
func(static_cast<TrafficLight>(5)); // Invalid light color!
return 0;
}

强枚举类型的一个特点是可以明确指定枚举所基于的整数类型,默认是int,也可以改成其它的,例如

1
enum class Type : char { General, Light, Medium, Heavy };

这是非常有用的,尤其在某些成员对应的常数需要设置的特别大的时候。