枚举声明
枚举 是一种独特类型,其值被限定在一定范围内(详见下文),其中可能包含多个显式命名的常量(“枚举量”)。
常量的值是称为枚举的底层类型 的整型类型的值。枚举与其底层类型具有相同的 大小、值表示 和 对齐要求。此外,枚举的每个值都与其底层类型的对应值具有相同的表示形式。
枚举使用以下语法(重新)声明
枚举关键字 特性 (可选) 枚举头名称 (可选) 枚举基 (可选){ 枚举量列表 (可选) } |
(1) | ||||||||
枚举关键字 特性 (可选) 枚举头名称 (可选) 枚举基 (可选){ 枚举量列表 , } |
(2) | ||||||||
枚举关键字 特性 (可选) 枚举头名称 枚举基 (可选) ; |
(3) | (C++11 起) | |||||||
枚举关键字 | - |
| ||||
特性 | - | (C++11 起) 可选的任意数量 特性 序列 | ||||
枚举头名称 | - |
| ||||
枚举基 | - | (C++11 起) 冒号 (: ),后跟一个 类型说明符序列,该序列命名一个整型类型(如果它是 cv 限定的,则忽略限定),该类型将作为此枚举类型的固定底层类型 | ||||
枚举量列表 | - | 以逗号分隔的枚举量定义列表,每个定义要么只是一个唯一的 标识符(成为枚举量的名称),要么是一个带有常量表达式的唯一标识符:标识符 = 常量表达式。 在任何一种情况下,标识符 后面都可以直接跟一个可选的 特性说明符序列。(C++17 起) |
有两种不同类型的枚举:非限定作用域枚举(使用 枚举关键字 enum
声明)和限定作用域枚举(使用 枚举关键字 enum class
或 enum struct
声明)。
目录 |
[编辑] 非限定作用域枚举
enum 名称 (可选) { 枚举量 = 常量表达式 , 枚举量 = 常量表达式 , ... } |
(1) | ||||||||
enum 名称 (可选) : 类型 { 枚举量 = 常量表达式 , 枚举量 = 常量表达式 , ... } |
(2) | (C++11 起) | |||||||
enum 名称 : 类型 ; |
(3) | (C++11 起) | |||||||
每个 枚举符 都成为枚举类型(即 名称)的命名常量,在封闭作用域中可见,并且可以在需要常量时使用。
每个枚举符都与底层类型的值相关联。当在 枚举符列表 中提供 =
时,枚举符的值由那些关联的 常量表达式 定义。如果第一个枚举符没有 =
,则关联值为零。对于任何其他定义中没有 =
的枚举符,关联值是前一个枚举符的值加一。
enum Foo { a, b, c = 10, d, e = 1, f, g = f + c }; //a = 0, b = 1, c = 10, d = 11, e = 1, f = 2, g = 12
非限定作用域枚举的 名称 可以省略:此类声明仅将枚举符引入封闭作用域
enum { a, b, c = 0, d = a + 2 }; // defines a = 0, b = 1, c = 0, d = 2
当非限定作用域枚举是类成员时,可以使用类成员访问运算符 .
和 ->
访问其枚举符
struct X { enum direction { left = 'l', right = 'r' }; }; X x; X* p = &x; int a = X::direction::left; // allowed only in C++11 and later int b = X::left; int c = x.left; int d = p->left;
始终被解析为枚举声明的一部分 struct S { enum E1 : int {}; enum E1 : int {}; // error: redeclaration of enumeration, // NOT parsed as a zero-length bit-field of type enum E1 }; enum E2 { e1 }; void f() { false ? new enum E2 : int(); // OK: 'int' is NOT parsed as the underlying type } |
(C++11 起) |
[编辑] 用于链接目的的枚举名称
对于 链接目的,没有 用于链接目的的 typedef 名称 且具有枚举符的未命名枚举,由其底层类型及其第一个枚举符表示;据说这样的枚举具有一个枚举符作为*用于链接目的的名称*。
[编辑] 限定作用域枚举
1) 声明底层类型为 int 的限定作用域枚举类型(关键字 class 和 struct 完全等效)
2) 声明底层类型为 类型 的限定作用域枚举类型
3) 底层类型为 int 的限定作用域枚举的不透明枚举声明
4) 底层类型为 类型 的限定作用域枚举的不透明枚举声明
每个 枚举符 都成为枚举类型(即 名称)的命名常量,该常量包含在枚举的范围内,并且可以使用作用域解析运算符访问。从限定作用域枚举符的值到整数类型没有隐式转换,尽管可以使用 运行此代码 #include <iostream> int main() { enum class Color { red, green = 20, blue }; Color r = Color::blue; switch(r) { case Color::red : std::cout << "red\n"; break; case Color::green: std::cout << "green\n"; break; case Color::blue : std::cout << "blue\n"; break; } // int n = r; // error: no implicit conversion from scoped enum to int int n = static_cast<int>(r); // OK, n = 21 std::cout << n << '\n'; // prints 21 } |
(C++11 起) |
如果满足以下所有条件,则可以使用 列表初始化 从整数初始化枚举,而无需强制转换
这使得引入新的整数类型(例如 enum byte : unsigned char {}; // byte is a new integer type; see also std::byte (C++17) byte b{42}; // OK as of C++17 (direct-list-initialization) byte c = {42}; // error byte d = byte{42}; // OK as of C++17; same value as b byte e{-1}; // error struct A { byte b; }; A a1 = {{42}}; // error (copy-list-initialization of a constructor parameter) A a2 = {byte{42}}; // OK as of C++17 void f(byte); f({42}); // error (copy-list-initialization of a function parameter) enum class Handle : std::uint32_t { Invalid = 0 }; Handle h{42}; // OK as of C++17 |
(自 C++17 起) |
using enum 声明
enum E { x }; void f() { int E; using enum E; // OK } using F = E; using enum F; // OK template<class T> using EE = T; void g() { using enum EE<E>; // OK } using enum 声明引入命名枚举的枚举符名称,就像对每个枚举符使用 using 声明 一样。当在类范围内时,using enum 声明将命名枚举的枚举符作为成员添加到作用域中,使其可用于成员查找。 enum class fruit { orange, apple }; struct S { using enum fruit; // OK: introduces orange and apple into S }; void f() { S s; s.orange; // OK: names fruit::orange S::orange; // OK: names fruit::orange } 两个引入相同名称的两个枚举符的 using enum 声明会冲突。 enum class fruit { orange, apple }; enum class color { red, orange }; void f() { using enum fruit; // OK // using enum color; // error: color::orange and fruit::orange conflict } |
(自 C++20 起) |
[编辑] 备注
enum color { red, yellow, green = 20, blue }; color col = red; int n = blue; // n == 21
整数、浮点数和枚举类型的值可以使用 static_cast
转换为任何枚举类型。请注意,此类转换后的值可能不一定等于为枚举定义的任何命名枚举符
enum access_t { read = 1, write = 2, exec = 4 }; // enumerators: 1, 2, 4 range: 0..7 access_t rwe = static_cast<access_t>(7); assert((rwe & read) && (rwe & write) && (rwe & exec)); access_t x = static_cast<access_t>(8.0); // undefined behavior since CWG 1766 access_t y = static_cast<access_t>(8); // undefined behavior since CWG 1766 enum foo { a = 0, b = UINT_MAX }; // range: [0, UINT_MAX] foo x = foo(-1); // undefined behavior since CWG 1766, // even if foo's underlying type is unsigned int
特性测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__cpp_enumerator_attributes |
201411L | (C++17) | 枚举符的 属性 |
__cpp_using_enum |
201907L | (C++20) | using enum
|
[编辑] 关键字
[编辑] 示例
#include <cstdint> #include <iostream> // enum that takes 16 bits enum smallenum: std::int16_t { a, b, c }; // color may be red (value 0), yellow (value 1), green (value 20), or blue (value 21) enum color { red, yellow, green = 20, blue }; // altitude may be altitude::high or altitude::low enum class altitude: char { high = 'h', low = 'l', // trailing comma only allowed after CWG 518 }; // the constant d is 0, the constant e is 1, the constant f is 3 enum { d, e, f = e + 2 }; // enumeration types (both scoped and unscoped) can have overloaded operators std::ostream& operator<<(std::ostream& os, color c) { switch(c) { case red : os << "red"; break; case yellow: os << "yellow"; break; case green : os << "green"; break; case blue : os << "blue"; break; default : os.setstate(std::ios_base::failbit); } return os; } std::ostream& operator<<(std::ostream& os, altitude al) { return os << static_cast<char>(al); } // The scoped enum (C++11) can be partially emulated in earlier C++ revisions: enum struct E11 { x, y }; // since C++11 struct E98 { enum { x, y }; }; // OK in pre-C++11 namespace N98 { enum { x, y }; } // OK in pre-C++11 struct S98 { static const int x = 0, y = 1; }; // OK in pre-C++11 void emu() { std::cout << (static_cast<int>(E11::y) + E98::y + N98::y + S98::y) << '\n'; // 4 } namespace cxx20 { enum class long_long_long_name { x, y }; void using_enum_demo() { std::cout << "C++20 `using enum`: __cpp_using_enum == "; switch (auto rnd = []{return long_long_long_name::x;}; rnd()) { #if defined(__cpp_using_enum) using enum long_long_long_name; case x: std::cout << __cpp_using_enum << "; x\n"; break; case y: std::cout << __cpp_using_enum << "; y\n"; break; #else case long_long_long_name::x: std::cout << "?; x\n"; break; case long_long_long_name::y: std::cout << "?; y\n"; break; #endif } } } int main() { color col = red; altitude a; a = altitude::low; std::cout << "col = " << col << '\n' << "a = " << a << '\n' << "f = " << f << '\n'; cxx20::using_enum_demo(); }
可能的输出
col = red a = l f = 3 C++20 `using enum`: __cpp_using_enum == 201907; x
[编辑] 缺陷报告
以下行为变更缺陷报告已追溯应用于以前发布的 C++ 标准。
DR | 应用于 | 发布时的行为 | 正确行为 |
---|---|---|---|
CWG 377 | C++98 | 当没有整数时,行为未指定 类型可以表示所有枚举值 |
在这种情况下,枚举是错误的- 形成的 |
CWG 518 | C++98 | 枚举列表后不允许有尾随逗号 | 允许 |
CWG 1514 | C++11 | 重新定义具有固定底层类型的枚举 可以解析为类成员声明中的位域 |
始终解析为重新定义 |
CWG 1638 | C++11 | 不透明枚举声明的语法 禁止用于模板特化 |
嵌套名称说明符 允许 |
CWG 1766 | C++98 | 将超出范围的值强制转换为枚举 没有固定底层类型的结果未指定 |
行为未定义 |
CWG 1966 | C++11 | CWG 问题 1514 的决议使得条件表达式的 : 成为 枚举基 的一部分 |
仅将决议应用于 成员声明说明符 |
CWG 2156 | C++11 | 枚举定义可以定义 使用声明的枚举类型 |
禁止 |
CWG 2157 | C++11 | CWG 问题 1966 的决议没有 涵盖限定枚举名称 |
涵盖 |
CWG 2530 | C++98 | 枚举列表可以包含多个 具有相同标识符的枚举器 |
禁止 |
CWG 2590 | C++98 | 枚举的大小、值表示和对齐要求 不依赖于其底层类型 |
它们都与 底层类型的相同 |
CWG 2621 | C++20 | 在以下位置使用的枚举名称查找 using enum 声明不清楚 |
已澄清 |
CWG 2877 | C++20 | 在以下位置使用的枚举名称查找 using enum 声明不是仅类型声明 |
已设为仅类型声明 |
[编辑] 参考文献
- C++23 标准 (ISO/IEC 14882:2024)
- 9.7.1 枚举声明 [dcl.enum]
- C++20 标准 (ISO/IEC 14882:2020)
- 9.7.1 枚举声明 [dcl.enum]
- C++17 标准 (ISO/IEC 14882:2017)
- 10.2 枚举声明 [dcl.enum]
- C++14 标准 (ISO/IEC 14882:2014)
- 7.2 枚举声明 [dcl.enum]
- C++11 标准 (ISO/IEC 14882:2011)
- 7.2 枚举声明 [dcl.enum]
- C++03 标准 (ISO/IEC 14882:2003)
- 7.2 枚举声明 [dcl.enum]
- C++98 标准 (ISO/IEC 14882:1998)
- 7.2 枚举声明 [dcl.enum]
[编辑] 另请参阅
(C++11) |
检查类型是否为枚举类型 (类模板) |
(C++23) |
检查类型是否为限定作用域枚举类型 (类模板) |
(C++11) |
获取给定枚举类型的底层整数类型 (类模板) |
(C++23) |
将枚举转换为其底层类型 (函数模板) |
C 文档 枚举
|