Enumeration declaration
一个 枚举 是一种独特类型,其值被限制在一定范围内(详见下文),该范围可能包含若干显式命名的常量(" 枚举项 ")。
常量的值是被称为枚举 底层类型 的整数类型的值。枚举具有与其底层类型相同的 大小 、 值表示 和 对齐要求 。此外,枚举的每个值都具有与底层类型对应值相同的表示形式。
枚举通过以下语法(重新)声明:
枚举键
属性
(可选)
枚举头名称
(可选)
枚举基类型
(可选)
{
枚举列表
(可选)
}
|
(1) | ||||||||
枚举键
属性
(可选)
枚举头名称
(可选)
枚举基类型
(可选)
{
枚举列表
, }
|
(2) | ||||||||
枚举键
属性
(可选)
枚举头名称
枚举基类型
(可选)
;
|
(3) | (自 C++11 起) | |||||||
| enum-key | - |
|
||||
| attr | - | (C++11 起) 任意数量的 属性 的可选序列 | ||||
| enum-head-name | - |
|
||||
| enum-base | - |
(C++11 起)
冒号 (
:
),后跟命名整数类型的
type-specifier-seq
(若具有 cv 限定符,则忽略限定符),该类型将作为此枚举类型的固定底层类型
|
||||
| enumerator-list | - |
以逗号分隔的枚举器定义列表,每个定义要么是唯一的
identifier
(成为枚举器的名称),要么是带有常量表达式的唯一标识符:
identifier
=
constant-expression
。
在任一情况下,
identifier
可直接后接可选的
属性说明符序列
。
(C++17 起)
|
枚举有两种不同的类型:
无作用域枚举
(使用
enum-key
enum
声明)和
有作用域枚举
(使用
enum-key
enum class
或
enum struct
声明)。
目录 |
无作用域枚举
enum
名称
(可选)
{
枚举项
=
常量表达式
,
枚举项
=
常量表达式
,
...
}
|
(1) | ||||||||
enum
名称
(可选)
:
类型
{
枚举项
=
常量表达式
,
枚举项
=
常量表达式
,
...
}
|
(2) | (C++11 起) | |||||||
enum
名称
:
类型
;
|
(3) | (C++11 起) | |||||||
每个 enumerator 都会成为枚举类型的命名常量(即 name ),在封闭作用域中可见,并且可以在需要常量时使用。
每个枚举项都与底层类型的一个值相关联。当在
枚举列表
中提供
=
时,枚举项的值由这些关联的
常量表达式
定义。如果第一个枚举项没有
=
,其关联值为零。对于任何其他定义中没有
=
的枚举项,其关联值为前一个枚举项的值加一。
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 }; // 定义 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; // 仅允许在 C++11 及之后版本中使用 int b = X::left; int c = x.left; int d = p->left;
始终被解析为枚举声明的一部分: struct S { enum E1 : int {}; enum E1 : int {}; // 错误:枚举重声明, // 不会被解析为 enum E1 类型的零长度位域 }; enum E2 { e1 }; void f() { false ? new enum E2 : int(); // 正确:'int' 不会被解析为基础类型 } |
(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; // 错误:无法从作用域枚举隐式转换为 int int n = static_cast<int>(r); // 正确,n = 21 std::cout << n << '\n'; // 输出 21 } |
(C++11 起) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
枚举类型在满足以下所有条件时,可通过 列表初始化 从整数值直接初始化而无需显式转换:
这使得引入新型整数类型(例如
enum byte : unsigned char {}; // byte是新型整数类型;另见std::byte (C++17) byte b{42}; // C++17起合法(直接列表初始化) byte c = {42}; // 错误 byte d = byte{42}; // C++17起合法;值与b相同 byte e{-1}; // 错误 struct A { byte b; }; A a1 = {{42}}; // 错误(构造函数参数的拷贝列表初始化) A a2 = {byte{42}}; // C++17起合法 void f(byte); f({42}); // 错误(函数参数的拷贝列表初始化) enum class Handle : std::uint32_t { Invalid = 0 }; Handle h{42}; // 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: 将 orange 和 apple 引入 S }; void f() { S s; s.orange; // OK: 指代 fruit::orange S::orange; // OK: 指代 fruit::orange } 两个引入同名枚举器的 using enum 声明会产生冲突。 enum class fruit { orange, apple }; enum class color { red, orange }; void f() { using enum fruit; // OK // using enum color; // 错误: color::orange 与 fruit::orange 冲突 } |
(since 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 }; // 枚举项:1, 2, 4 范围: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); // 自 CWG 1766 起为未定义行为 access_t y = static_cast<access_t>(8); // 自 CWG 1766 起为未定义行为 enum foo { a = 0, b = UINT_MAX }; // 范围:[0, UINT_MAX] foo x = foo(-1); // 自 CWG 1766 起为未定义行为, // 即使 foo 的底层类型是 unsigned int
| 功能测试宏 | 值 | 标准 | 功能特性 |
|---|---|---|---|
__cpp_enumerator_attributes
|
201411L
|
(C++17) | 枚举器属性 |
__cpp_using_enum
|
201907L
|
(C++20) |
using enum
|
关键词
示例
#include <cstdint> #include <iostream> // 占用16位的枚举 enum smallenum: std::int16_t { a, b, c }; // color 可以是红色(值0)、黄色(值1)、绿色(值20)或蓝色(值21) enum color { red, yellow, green = 20, blue }; // altitude 可以是 altitude::high 或 altitude::low enum class altitude: char { high = 'h', low = 'l', // 尾随逗号仅在 CWG 518 后允许 }; // 常量 d 为 0,常量 e 为 1,常量 f 为 3 enum { d, e, f = e + 2 }; // 枚举类型(包括作用域和非作用域)可以拥有重载运算符 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); } // 作用域枚举(C++11)可以在早期 C++ 修订版中部分模拟: enum struct E11 { x, y }; // 自 C++11 起 struct E98 { enum { x, y }; }; // 在 C++11 之前版本中可用 namespace N98 { enum { x, y }; } // 在 C++11 之前版本中可用 struct S98 { static const int x = 0, y = 1; }; // 在 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++ 标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 正确行为 |
|---|---|---|---|
| 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
的解决方案使条件表达式的
:
成为 enum-base 的一部分 |
仅将解决方案应用于
成员声明说明符 |
| CWG 2156 | C++11 |
枚举定义可通过using声明
定义枚举类型 |
禁止 |
| 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 文档
关于
枚举
|
|