命名空间
变体
操作

枚举声明

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
常规主题
流程控制
条件执行语句
if
迭代语句 (循环)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (直到 C++17*)
noexcept 说明符 (C++11)
异常
命名空间
类型
基本类型
枚举类型
函数类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
 
 

枚举 是一种独特类型,其值被限定在一定范围内(详见下文),其中可能包含多个显式命名的常量(“枚举量”)。

常量的值是称为枚举的底层类型 的整型类型的值。枚举与其底层类型具有相同的 大小值表示对齐要求。此外,枚举的每个值都与其底层类型的对应值具有相同的表示形式。

枚举使用以下语法(重新)声明

枚举关键字 特性 (可选) 枚举头名称 (可选) 枚举基 (可选)
{ 枚举量列表 (可选) }
(1)
枚举关键字 特性 (可选) 枚举头名称 (可选) 枚举基 (可选)
{ 枚举量列表 , }
(2)
枚举关键字 特性 (可选) 枚举头名称 枚举基 (可选) ; (3) (C++11 起)
1) 枚举说明符,出现在 声明 语法的 声明说明符序列 中:定义枚举类型及其枚举量。
2) 枚举量列表 后面可以跟一个逗号。
3) 不透明枚举声明:定义枚举类型,但不定义其枚举量:在此声明之后,该类型是一个完整类型,并且其大小已知。
枚举关键字 -

enum

(截止 C++11)

enumenum classenum struct 之一

(C++11 起)
特性 - (C++11 起) 可选的任意数量 特性 序列
枚举头名称 -

要声明的枚举的名称,可以省略。

(截止 C++11)

要声明的枚举的名称,可以选择在其前面加上 嵌套名称说明符:名称和作用域解析运算符 :: 的序列,以作用域解析运算符结尾。它只能在非限定作用域的非不透明枚举声明中省略。
仅当存在枚举名称并且此声明是重新声明时,才能出现 嵌套名称说明符。对于不透明枚举声明,嵌套名称说明符 只能出现在 显式特化声明 中的枚举名称之前。
如果存在 嵌套名称说明符,则 枚举说明符 不能引用仅由 using 声明 继承或引入的枚举,并且 枚举说明符 只能出现在包含先前声明的命名空间中。在这种情况下,嵌套名称说明符 不能以 decltype 说明符开头。

(C++11 起)
枚举基 - (C++11 起) 冒号 (:),后跟一个 类型说明符序列,该序列命名一个整型类型(如果它是 cv 限定的,则忽略限定),该类型将作为此枚举类型的固定底层类型
枚举量列表 - 以逗号分隔的枚举量定义列表,每个定义要么只是一个唯一的 标识符(成为枚举量的名称),要么是一个带有常量表达式的唯一标识符:标识符 = 常量表达式在任何一种情况下,标识符 后面都可以直接跟一个可选的 特性说明符序列(C++17 起)

有两种不同类型的枚举:非限定作用域枚举(使用 枚举关键字 enum 声明)和限定作用域枚举(使用 枚举关键字 enum classenum struct 声明)。

目录

[编辑] 非限定作用域枚举

enum 名称 (可选) { 枚举量 = 常量表达式 , 枚举量 = 常量表达式 , ... } (1)
enum 名称 (可选) : 类型 { 枚举量 = 常量表达式 , 枚举量 = 常量表达式 , ... } (2) (C++11 起)
enum 名称 : 类型 ; (3) (C++11 起)
1) 声明一个底层类型未固定的非限定作用域枚举类型(在这种情况下,底层类型是实现定义的整型类型,可以表示所有枚举量值;此类型不大于 int,除非枚举量的值不适合 intunsigned int。如果 枚举量列表 为空,则底层类型就像枚举具有值为 0 的单个枚举量一样。如果没有整型类型可以表示所有枚举量值,则枚举是非法形成的)。
2) 声明一个底层类型固定的非限定作用域枚举类型。
3) 非限定作用域枚举的不透明枚举声明必须指定名称和底层类型。

每个 枚举符 都成为枚举类型(即 名称)的命名常量,在封闭作用域中可见,并且可以在需要常量时使用。

enum Color { red, green, blue };
Color r = red;
 
switch(r)
{
    case red  : std::cout << "red\n";   break;
    case green: std::cout << "green\n"; break;
    case blue : std::cout << "blue\n";  break;
}

每个枚举符都与底层类型的值相关联。当在 枚举符列表 中提供 = 时,枚举符的值由那些关联的 常量表达式 定义。如果第一个枚举符没有 =,则关联值为零。对于任何其他定义中没有 = 的枚举符,关联值是前一个枚举符的值加一。

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;

声明说明符成员声明 中,序列

enum 枚举头名称 :

始终被解析为枚举声明的一部分

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 名称 且具有枚举符的未命名枚举,由其底层类型及其第一个枚举符表示;据说这样的枚举具有一个枚举符作为*用于链接目的的名称*。

[编辑] 限定作用域枚举

enum struct|class 名称 { 枚举符 = 常量表达式 , 枚举符 = 常量表达式 , ... } (1)
enum struct|class 名称 : 类型 { 枚举符 = 常量表达式 , 枚举符 = 常量表达式 , ... } (2)
enum struct|class 名称 ; (3)
enum struct|class 名称 : 类型 ; (4)
1) 声明底层类型为 int 的限定作用域枚举类型(关键字 classstruct 完全等效)
2) 声明底层类型为 类型 的限定作用域枚举类型
3) 底层类型为 int 的限定作用域枚举的不透明枚举声明
4) 底层类型为 类型 的限定作用域枚举的不透明枚举声明

每个 枚举符 都成为枚举类型(即 名称)的命名常量,该常量包含在枚举的范围内,并且可以使用作用域解析运算符访问。从限定作用域枚举符的值到整数类型没有隐式转换,尽管可以使用 static_cast 获取枚举符的数值。

#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 起)

如果满足以下所有条件,则可以使用 列表初始化 从整数初始化枚举,而无需强制转换

  • 初始化是直接列表初始化。
  • 初始化列表只有一个元素。
  • 枚举是限定作用域的,或者是非限定作用域的且底层类型固定。
  • 转换是非窄化的。

这使得引入新的整数类型(例如 SafeInt)成为可能,这些类型与其底层整数类型享有相同的现有调用约定,即使在通过值传递/返回结构体受到 ABI 惩罚的情况下也是如此。

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 声明

using enum using-enum-声明符 ; (自 C++20 起)
声明符 - (可能限定的)标识符简单模板标识符


声明符 必须命名一个非 依赖 枚举类型。枚举声明是通过仅限类型的普通 限定非限定 查找找到的,具体取决于 声明符 是否限定。

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

[编辑] 关键字

enumstructclassusing

[编辑] 示例

#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 文档 枚举