枚举声明
枚举 是一种独立的类型,它的值被限制在一个取值范围内(细节见下文),它可以包含数个明确命名的常量(“枚举项”)。
各常量的值是某个整数类型(称为该枚举的底层类型)的值。枚举的大小、值表示和对齐要求与它的底层类型相同。而且枚举的每个值都与该枚举的底层类型的对应值有着相同的表示。
以下列语法(重复)声明枚举:
枚举关键词属性 (可选)枚举头名 (可选)枚举基 (可选){ 枚举项列表 (可选)} | (1) | ||||||||
枚举关键词属性 (可选)枚举头名 (可选)枚举基 (可选){ 枚举项列表, } | (2) | ||||||||
枚举关键词属性 (可选)枚举头名枚举基 (可选); | (3) | (C++11 起) | |||||||
枚举关键字 | - |
| ||||
属性 | - | (C++11 起) 任意数量的属性的可选序列 | ||||
枚举头名 | - |
| ||||
枚举基 | - | (C++11 起) 冒号 (: ),后随指名某个整数类型的类型说明符序列(忽略它的限定性),该类型将作为此枚举类型的固定底层类型 | ||||
枚举项列表 | - | 枚举项定义的逗号分隔列表,每项要么是简单的独一无二的标识符,它成为枚举项的名字,要么是带常量表达式的独一无二的标识符:标识符= 常量表达式。在任一情况下,标识符 都可以直接后随一个属性说明符序列。(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};// 定义 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();// OK:int 不会被解析成底层类型} | (C++11 起) |
[编辑]以链接为目的的枚举名
没有以链接为目的的 typedef 名且有枚举项的无名枚举会以链接为目的,通过它的底层类型和它的首个枚举项指名;这种枚举被视为以它的枚举项以链接为目的具有枚举名。
[编辑]有作用域枚举
1) 声明底层类型为 int 的有作用域枚举类型(关键词 class 与 struct 完全等价) 2) 声明底层类型为类型 的有作用域枚举类型 3) 底层类型为 int 的有作用域枚举类型的笼统枚举声明 4) 底层类型为类型 的有作用域枚举类型的笼统枚举声明 每个枚举项 都成为该枚举的类型(即名字)的具名常量,它被该枚举的作用域所包含,且可用作用域解析运算符访问。没有从有作用域枚举项到整数类型的隐式转换,尽管 运行此代码 #include <iostream> int main(){enumclass Color { red, green =20, blue }; Color r = Color::blue; switch(r){case Color::red:std::cout<<"红\n";break;case Color::green:std::cout<<"绿\n";break;case Color::blue:std::cout<<"蓝\n";break;} // int n = r; // 错误:不存在从有作用域枚举到 int 的隐式转换int n =static_cast<int>(r);// OK, n = 21std::cout<< n <<'\n';// prints 21} | (C++11 起) |
枚举在满足下列条件时都能用列表初始化从一个整数初始化而无需转型:
这使得我们能引入新的整数类型(例如 enum byte :unsignedchar{};// byte 是新的整数类型;参见 std::byte(C++17) byte b{42};// C++17 起 OK(直接列表初始化) byte c ={42};// 错误 byte d = byte{42};// C++17 起 OK;与 b 的值相同 byte e{-1};// 错误 struct A { byte b;}; A a1 ={{42}};// 错误(构造函数形参的复制列表初始化) A a2 ={byte{42}};// C++17 起 OK void f(byte); f({42});// 错误(函数形参的复制列表初始化) enumclass Handle :std::uint32_t{ Invalid =0}; Handle h{42};// C++17 起 OK | (C++17 起) |
usingenum 声明
enum E { x }; void f(){int E;usingenum E;// OK} using F = E;usingenum F;// OK template<class T>using EE = T; void g(){usingenum EE<E>;// OK} usingenum 声明引入它所指名的枚举的枚举项名字,如同用对每个枚举项的 using 声明。在类作用域中时,usingenum 声明将它指名的枚举的枚举项名字作为成员添加到作用域,使成员查找能访问它们。 enumclass fruit { orange, apple }; struct S {usingenum fruit;// OK:引入 orange 与 apple 到 S 中}; void f(){ S s; s.orange;// OK:指名 fruit::orange S::orange;// OK:指名 fruit::orange} 引入两个同名的枚举项的两个 usingenum 声明会冲突。 enumclass fruit { orange, apple };enumclass color { red, orange }; void f(){usingenum fruit;// OK// using enum color; // 错误:color::orange 与 fruit::orange 冲突} | (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) | usingenum |
[编辑]关键词
[编辑]示例
#include <cstdint>#include <iostream> // 采用 16 位的枚举enum smallenum:std::int16_t{ a, b, c }; // color 可以是 red(值为 0)、yellow(值为 1)、green(值为 20)或 blue(值为 21)enum color { red, yellow, green =20, blue }; // altitude 可为 altitude::high 或 altitude::lowenumclass altitude:char{ high ='h', low ='l', // CWG 518 之后才允许尾部的逗号}; // 常量 d 为 0,常量 e 为 1,常量 f 为 3enum{ d, e, f = e +2}; // 枚举类型(有作用域和无作用域)能有重载的运算符std::ostream& operator<<(std::ostream& os, color c){switch(c){case red : os <<"红";break;case yellow: os <<"黄";break;case green : os <<"绿";break;case blue : os <<"蓝";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++ 版本模拟: enumstruct E11 { x, y };// C++11 起 struct E98 {enum{ x, y };};// C++11 前也 OK namespace N98 {enum{ x, y };}// C++11 前也 OK struct S98 {staticconstint x =0, y =1;};// C++11 前也 OK void emu(){std::cout<<(static_cast<int>(E11::y)+ E98::y+ N98::y+ S98::y)<<'\n';// 4} namespace cxx20 {enumclass 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)usingenum long_long_long_name;case x:std::cout<< __cpp_using_enum <<";x\n";break;case y:std::cout<< __cpp_using_enum <<";y\n";break;#elsecase 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 = 红 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 的解决方案导致条件表达式中的 : 也会成为枚举基 的一部分 | 只将该解决方案应用到 成员声明说明符中 |
CWG 2156 | C++11 | 枚举定义可以通过 using 声明来定义枚举类型 | 已禁止 |
CWG 2157 | C++11 | CWG 问题 1966 的解决方案没有覆盖到有限定的枚举名 | 已覆盖 |
CWG 2530 | C++98 | 枚举项列表可以包含多个标识符相同的枚举项 | 已禁止 |
CWG 2590 | C++98 | 枚举的大小,值表示和对齐要求与它的底层类型无关 | 与底层类型的对应属性相同 |
CWG 2621 | C++20 | 不明确 usingenum 声明如何查找枚举声明 | 使之明确 |
CWG 2877 | C++20 | 在 usingenum 声明中会查找非类型声明 | 不会查找非类型声明 |
[编辑]参阅
- C++23 标准(ISO/IEC 14882:2024):
- 9.7.1 Enumeration declarations [dcl.enum]
- C++20 标准(ISO/IEC 14882:2020):
- 9.7.1 Enumeration declarations [dcl.enum]
- C++17 标准(ISO/IEC 14882:2017):
- 10.2 Enumeration declarations [dcl.enum]
- C++14 标准(ISO/IEC 14882:2014):
- 7.2 Enumeration declarations [dcl.enum]
- C++11 标准(ISO/IEC 14882:2011):
- 7.2 Enumeration declarations [dcl.enum]
- C++03 标准(ISO/IEC 14882:2003):
- 7.2 Enumeration declarations [dcl.enum]
- C++98 标准(ISO/IEC 14882:1998):
- 7.2 Enumeration declarations [dcl.enum]
[编辑]参阅
(C++11) | 检查类型是否为枚举类型 (类模板) |
(C++23) | 检查类型是否为有作用域枚举类型 (类模板) |
(C++11) | 获得给定枚举类型的底层整数类型 (类模板) |
(C++23) | 转换枚举为其底层类型 (函数模板) |
枚举的 C 文档 |