一、 enum (传统枚举 / Unscoped Enum)

enum 是 C 语言就有的特性,在 C++ 中也得到了支持。它定义了一组具名的整数常量。

特点:

  1. 作用域污染 (Scope Pollution): enum 的枚举器(即具名常量)会泄露到定义它们的命名空间或全局作用域中。这意味着你不能在同一个作用域中定义同名的枚举器,即使它们属于不同的 enum 类型。

    1
    2
    3
    4
    5
    6
    enum Color { RED, GREEN, BLUE };
    enum TrafficLight { RED_LIGHT, YELLOW_LIGHT, GREEN_LIGHT }; // 编译错误:RED_LIGHT 与 Color::RED 冲突 (如果 RED_LIGHT 也叫 RED)
    // 实际上这里不会冲突,因为名字不同。但如果 TrafficLight 也有一个叫 RED 的枚举器,就会冲突。
    // 假设:
    // enum Color { RED, GREEN, BLUE };
    // enum Feeling { HAPPY, SAD, RED }; // 编译错误:RED 重定义
  2. 隐式类型转换 (Implicit Conversion): enum 的枚举器可以隐式转换为整数类型(如 int),也可以隐式地与其他整数类型进行比较或算术运算。这可能导致类型不安全的问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    enum Color { RED, GREEN, BLUE };
    Color c = RED;
    int i = c; // 隐式转换为 int,i = 0,合法
    if (c == 0) { // 隐式比较,合法
    // ...
    }
    Color another_c = BLUE;
    if (c == another_c) { // 合法
    // ...
    }
  3. 底层类型不确定 (Underlying Type): 编译器会为 enum 选择一个合适的整数类型来存储枚举值(例如 intchar 等),这个类型是实现定义的,通常是能够容纳所有枚举器值的最小整数类型。你也可以显式指定底层类型(C++11 引入),但这在传统 enum 中不常见。

    1
    2
    enum Color { RED, GREEN, BLUE };
    // sizeof(Color) 可能是 4 (int)
  4. 默认值: 如果没有显式赋值,第一个枚举器的值为 0,后续枚举器的值在前一个的基础上递增 1

    1
    2
    3
    4
    5
    6
    enum Status {
    OK, // 0
    ERROR, // 1
    WARNING = 5, // 5
    FATAL // 6
    };

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>

enum Direction {
NORTH, // 0
EAST, // 1
SOUTH, // 2
WEST // 3
};

enum Animal {
DOG = 10,
CAT, // 11
BIRD // 12
};

void print_direction(Direction d) {
switch (d) {
case NORTH: std::cout << "North" << std::endl; break;
case EAST: std::cout << "East" << std::endl; break;
case SOUTH: std::cout << "South" << std::endl; break;
case WEST: std::cout << "West" << std::endl; break;
default: std::cout << "Unknown Direction" << std::endl; break;
}
}

int main() {
Direction my_dir = NORTH;
print_direction(my_dir);

// 作用域污染:可以直接使用 NORTH,而不是 Direction::NORTH
if (my_dir == NORTH) {
std::cout << "It's North!" << std::endl;
}

// 隐式转换:
int dir_val = my_dir; // OK, dir_val = 0
std::cout << "Direction value: " << dir_val << std::endl;

// 隐式比较:
if (my_dir == 0) { // OK
std::cout << "North is 0" << std::endl;
}

// 类型不安全问题示例:
// Animal a = NORTH; // 编译错误,因为类型不匹配。但如果 Direction 和 Animal 的底层类型相同,且值匹配,可能可以通过强制转换绕过。
// int x = 1;
// Direction d2 = static_cast<Direction>(x); // 允许将任意整数转换为枚举类型,可能得到无效值
// print_direction(d2); // 如果 x=1,输出 East;如果 x=100,输出 Unknown Direction

return 0;
}

二、 enum class (枚举类 / Scoped Enum) (C++11)

enum class 是 C++11 引入的,旨在解决传统 enum 的主要缺点:作用域污染和类型不安全。

特点:

  1. 强作用域 (Strongly Scoped): enum class 的枚举器只在其枚举类型的作用域内可见。你需要使用 EnumTypeName::EnumeratorName 的方式来引用它们。这完全消除了作用域污染问题。

    1
    2
    3
    4
    5
    6
    enum class Color { RED, GREEN, BLUE };
    enum class TrafficLight { RED, YELLOW, GREEN }; // 合法!两个 RED 不冲突

    Color c = Color::RED; // 必须使用作用域限定符
    // TrafficLight tl = RED; // 编译错误:RED 未定义
    TrafficLight tl = TrafficLight::RED; // 合法
  2. 强类型 (Strongly Typed): enum class 的枚举器不会隐式转换为整数类型。你也不能将整数隐式赋值给 enum class 类型。这大大提高了类型安全性,避免了许多潜在的错误。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    enum class Color { RED, GREEN, BLUE };
    Color c = Color::RED;

    // int i = c; // 编译错误:不能隐式转换为 int
    int i = static_cast<int>(c); // 必须显式转换

    // if (c == 0) { // 编译错误:不能隐式比较
    // // ...
    // }
    if (c == Color::RED) { // 合法,同类型比较
    // ...
    }

    // Color another_c = TrafficLight::RED; // 编译错误:不同枚举类型不能直接比较或赋值
  3. 默认底层类型为 int 如果不显式指定,enum class 的底层类型默认为 int。你可以显式指定底层类型,这在传统 enum 中也是可行的,但对于 enum class 来说,这种控制更为重要。

    1
    2
    enum class Color : unsigned char { RED, GREEN, BLUE }; // 显式指定底层类型为 unsigned char
    // sizeof(Color) 会是 1

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>

enum class Direction {
NORTH, // 0
EAST, // 1
SOUTH, // 2
WEST // 3
};

enum class Animal {
DOG = 10,
CAT, // 11
BIRD // 12
};

void print_direction_class(Direction d) {
switch (d) {
case Direction::NORTH: std::cout << "North" << std::endl; break;
case Direction::EAST: std::cout << "East" << std::endl; break;
case Direction::SOUTH: std::cout << "South" << std::endl; break;
case Direction::WEST: std::cout << "West" << std::endl; break;
default: std::cout << "Unknown Direction" << std::endl; break;
}
}

int main() {
Direction my_dir = Direction::NORTH;
print_direction_class(my_dir);

// 强作用域:必须使用 Direction::NORTH
if (my_dir == Direction::NORTH) {
std::cout << "It's North!" << std::endl;
}

// 强类型:不能隐式转换为 int
// int dir_val = my_dir; // 编译错误
int dir_val = static_cast<int>(my_dir); // 必须显式转换
std::cout << "Direction value: " << dir_val << std::endl;

// 强类型:不能隐式比较
// if (my_dir == 0) { // 编译错误
// std::cout << "North is 0" << std::endl;
// }

// 强类型:不同枚举类型不能直接比较或赋值
// Animal a = Direction::NORTH; // 编译错误
// if (Direction::NORTH == Animal::DOG) { // 编译错误
// // ...
// }

// 允许将整数转换为枚举类型,但仍需显式转换
int x = 1;
Direction d2 = static_cast<Direction>(x); // 合法,但仍需小心无效值
print_direction_class(d2); // 如果 x=1,输出 East;如果 x=100,输出 Unknown Direction

return 0;
}

三、 总结与选择

特性 enum (传统枚举) enum class (枚举类 / 作用域枚举)
C++版本 C++98 及更高版本 C++11 及更高版本
作用域 枚举器泄露到父作用域,可能导致命名冲突 强作用域,枚举器只在枚举类型内部可见
引用方式 EnumeratorNameEnumTypeName::EnumeratorName 必须 EnumTypeName::EnumeratorName
类型安全性 弱类型,可隐式转换为整数,可与整数比较 强类型,不能隐式转换为整数,不能与整数比较
底层类型 编译器决定 (实现定义),默认为 int 默认为 int,可显式指定
隐式转换 enum -> int (是) enum class -> int (否,需要 static_cast)
不同枚举类型比较 如果底层值相同,可能隐式比较 (不安全) ,编译错误
主要优点 兼容 C 语言,在某些遗留代码中可能更方便 避免命名冲突,提高类型安全性,代码更清晰
主要缺点 作用域污染,类型不安全 需要显式作用域限定符和类型转换

何时选择哪种?

  • 推荐使用 enum class 在现代 C++ 编程中,几乎所有情况下都应该优先使用 enum class。它解决了传统 enum 的主要痛点,提供了更好的类型安全性和代码清晰度,减少了潜在的错误。
  • 使用 enum 的场景:
    • 与 C 语言代码兼容: 如果你需要与 C 语言代码进行接口交互,并且 C 代码期望接收或返回传统的 enum 类型,那么可能需要使用 enum
    • 作为位标志 (Bit Flags): 传统 enum 的隐式转换为整数的特性,有时在作为位标志使用时会显得“方便”(尽管仍然不安全)。然而,即使是位标志,也可以通过重载运算符等方式让 enum class 也能很好地工作,并且更安全。