枚举 (C++)
枚举是用户定义的类型,其中包含一组称为“枚举器”的命名的整型常量。
注意
本文包含 ISO 标准 C++ 语言 enum
类型和 C++11 中引入的范围(或强类型)enum class
类型。 有关 C++/CLI 和 C++/CX 中 public enum class
或 private enum class
类型的详细信息,请参阅 enum class
(C++/CLI and C++/CX)。
语法
enum-name
?
identifier
enum-specifier
?
enum-head
{
enumerator-list
opt }
enum-head
{
enumerator-list
,
}
enum-head
?
enum-key
attribute-specifier-seq
opt enum-head-name
opt enum-base
opt
enum-head-name
?
nested-name-specifier
opt identifier
opaque-enum-declaration
?
enum-key
attribute-specifier-seq
optenum-head-name
enum-base
opt ;
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
opt
使用情况
// 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、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 { };
新类型是基础类型的精确复制,因此具有相同的调用约定,这意味着它可以跨 ABI 使用,而不会造成任何性能损失。 使用直接列表初始化初始化类型变量时,无需强制转换。 以下示例演示如何在各种上下文中初始化不用枚举器的枚举:
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;
}