枚举是一种用户定义的类型,它由一组称为 枚举数的命名整型常量组成。
注释
本文介绍 ISO 标准C++语言 enum 类型和C++11 中引入的范围(或强类型) enum class 类型。 有关 C++/CLI 和 C++/CX 中的或private enum class类型的信息public enum class,请参阅enum class(C++/CLI 和 C++/CX)。
语法
enum-name:
identifier
enum-specifier:
enum-head
{
enumerator-list
选择}
enum-head
{
enumerator-list
,
}
enum-head:
enum-key
attribute-specifier-seq
选择enum-head-name选择enum-base选择
enum-head-name:
nested-name-specifier
选择identifier
opaque-enum-declaration:
enum-key
attribute-specifier-seq
选择enum-head-nameenum-base选择;
enum-key:
enum
enum class
enum struct
enum-base:
:
type-specifier-seq
enumerator-list:
enumerator-definition
enumerator-list
,
enumerator-definition
enumerator-definition:
enumerator
enumerator
=
constant-expression
enumerator:
identifier
attribute-specifier-seq
选择
用法
// unscoped enum:
// enum [identifier] [: type] {enum-list};
// scoped enum:
// enum [class|struct] [identifier] [: type] {enum-list};
// Forward declaration of enumerations (C++11):
enum A : int; // non-scoped enum must have type specified
enum class B; // scoped enum defaults to int but ...
enum class C : short; // ... may have any integral underlying type
参数
identifier
为枚举提供的类型名称。
type
枚举器的基础类型;所有枚举器具有相同的基础类型。 可以是任何整型。
enum-list
枚举中的枚举器的逗号分隔列表。 范围中的每个枚举器或变量名称都必须是唯一的。 但是,这些值可以重复。 在未范围枚举中,范围是周围的范围;在作用域枚举中,范围本身。enum-list 在作用域枚举中,列表可能为空,实际上定义了一个新的整型类型。
class
通过在声明中使用此关键字,可以指定枚举的范围,并且必须提供枚举 identifier 。 还可以使用 struct 关键字代替 class,因为它们在此上下文中具有语义上的等效性。
枚举器范围
枚举提供上下文来描述表示为命名常量的值范围。 这些命名常量也称为 枚举器。 在原始 C 和C++类型中,未限定的 enum 枚举器在整个声明范围 enum 中可见。 在作用域枚举中,枚举器名称必须由类型名称限定 enum 。 以下示例演示了这两种枚举之间的基本差异:
namespace CardGame_Scoped
{
enum class Suit { Diamonds, Hearts, Clubs, Spades };
void PlayCard(Suit suit)
{
if (suit == Suit::Clubs) // Enumerator must be qualified by enum type
{ /*...*/}
}
}
namespace CardGame_NonScoped
{
enum Suit { Diamonds, Hearts, Clubs, Spades };
void PlayCard(Suit suit)
{
if (suit == Clubs) // Enumerator is visible without qualification
{ /*...*/
}
}
}
枚举中的每个名称都分配一个整数值,该值对应于枚举中值的顺序。 默认情况下,第一个值分配为 0、下一个值分配 1 等,但可以显式设置枚举器的值,如下所示:
enum Suit { Diamonds = 1, Hearts, Clubs, Spades };
为枚举器 Diamonds 赋值 1。 如果后续枚举器未指定显式值,则接收上一个枚举器的值加上一个。 在前面的示例中, Hearts 值为 2、 Clubs 将具有 3 等。
每个枚举器都被视为常量,并且必须在定义范围(对于未作用域枚举)或自身范围内enum(对于作用域枚举)内具有唯一的名称enum。 提供给名称的值不必是唯一的。 例如,请考虑此未作用域枚举 Suit的声明:
enum Suit { Diamonds = 5, Hearts, Clubs = 4, Spades };
Diamonds
Hearts、、Clubs和 Spades 5 的值分别为 5、6、4 和 5。 请注意,5 多次使用;即使它可能不是预期,它也是允许的。 对于作用域枚举,这些规则是相同的。
强制转换规则
可以隐式转换为无作用域枚举常量,但int永远不会隐式转换为int枚举值。 以下示例演示了尝试分配 hand 非 Suit值时会发生什么情况:
int account_num = 135692;
Suit hand;
hand = account_num; // error C2440: '=' : cannot convert from 'int' to 'Suit'
强制转换需要转换为 int 作用域内或无作用域枚举器。 但是,可以将未范围枚举器提升为没有强制转换的整数值。
int account_num = Hearts; //OK if Hearts is in an unscoped enum
以这种方式使用隐式转换可能会导致意外的副作用。 为了帮助消除与未范围枚举关联的编程错误,范围枚举值是强类型化的。 作用域枚举器必须由枚举类型名称(标识符)限定,不能隐式转换,如以下示例所示:
namespace ScopedEnumConversions
{
enum class Suit { Diamonds, Hearts, Clubs, Spades };
void AttemptConversions()
{
Suit hand;
hand = Clubs; // error C2065: 'Clubs' : undeclared identifier
hand = Suit::Clubs; //Correct.
int account_num = 135692;
hand = account_num; // error C2440: '=' : cannot convert from 'int' to 'Suit'
hand = static_cast<Suit>(account_num); // OK, but probably a bug!!!
account_num = Suit::Hearts; // error C2440: '=' : cannot convert from 'Suit' to 'int'
account_num = static_cast<int>(Suit::Hearts); // OK
}
}
请注意,该行 hand = account_num; 仍然会导致出现未范围枚举的错误,如前所述。 允许使用显式强制转换。 但是,对于作用域枚举,下一个语句 account_num = Suit::Hearts;中的尝试转换不再允许在没有显式强制转换的情况下使用。
没有枚举器的枚举
Visual Studio 2017 版本 15.3 及更高版本 (可用于 /std:c++17 及更高版本):通过定义具有显式基础类型且没有枚举器的枚举(常规或作用域),实际上可以引入没有隐式转换到任何其他类型的新整型类型。 通过使用此类型而不是其内置基础类型,可以消除因无意隐式转换而导致的细微错误的可能性。
enum class byte : unsigned char { };
新类型是基础类型的精确副本,因此具有相同的调用约定,这意味着它可以在 API 之间使用,而不会造成任何性能损失。 使用直接列表初始化初始化类型变量时,不需要强制转换。 以下示例演示如何在各种上下文中初始化没有枚举器的枚举:
enum class byte : unsigned char { };
enum class E : int { };
E e1{ 0 };
E e2 = E{ 0 };
struct X
{
E e{ 0 };
X() : e{ 0 } { }
};
E* p = new E{ 0 };
void f(E e) {};
int main()
{
f(E{ 0 });
byte i{ 42 };
byte j = byte{ 42 };
// unsigned char c = j; // C2440: 'initializing': cannot convert from 'byte' to 'unsigned char'
return 0;
}