列舉是使用者定義型別,其中包含一組具名的整數常數,稱為列舉程式。
注意
本文包含 ISO Standard C++ 語言enum
型別和限定範圍 (或強型別) enum class
型別 (在 C++11 中推出)。 如需 C++/CLI 和 C++/CX 中public enum class
或private enum class
型別的詳細資訊,請參閱 enum class
(C++/CLI 和 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
opt enum-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
絕對不能隱含轉換為列舉值。 下列範例顯示,如果您嘗試將不是Suit
的值指派給hand
時,所發生的狀況:
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 { };
新型別是基礎型別的確切復本,因此具有相同的呼叫慣例,這表示它可以跨 ABIS 使用,而不會造成任何效能損失。 使用直接清單初始化初始化型別變數時,不需要轉換。 下列範例會示範如何在各種內容中初始化沒有列舉程式的列舉:
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;
}