Wyliczenia (C++)
Wyliczenie to typ zdefiniowany przez użytkownika, który składa się z zestawu nazwanych stałych całkowitych, które są nazywane modułami wyliczania.
Uwaga
W tym artykule opisano typ języka enum
ISO Standard C++ oraz typ języka o określonym zakresie (lub silnie typie), enum class
który jest wprowadzany w języku C++11. Aby uzyskać informacje o typach public enum class
lub private enum class
w języku C++/CLI i C++/CX, zobaczenum class
(C++/CLI i C++/CX).
Składnia
enum-name
:
identifier
enum-specifier
:
enum-head
{
enumerator-list
Zdecydować }
enum-head
{
enumerator-list
,
}
enum-head
:
enum-key
attribute-specifier-seq
opt opt enum-head-name
enum-base
enum-head-name
:
nested-name-specifier
Zdecydować identifier
opaque-enum-declaration
:
enum-key
opt opt opt enum-base
enum-head-name
attribute-specifier-seq
;
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
Zdecydować
Użycie
// 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
Parametry
identifier
Nazwa typu nadana wyliczeniem.
type
Podstawowy typ modułów wyliczania; wszystkie moduły wyliczania mają ten sam typ bazowy. Może być dowolnym typem całkowitym.
enum-list
Rozdzielona przecinkami lista wyliczenia. Każda nazwa modułu wyliczającego lub zmiennej w zakresie musi być unikatowa. Można jednak zduplikować wartości. W wyliczenie niezakresowym zakres jest zakresem otaczającym; w wyliczenie o określonym zakresie zakres jest enum-list
sam. W wyliczenie o określonym zakresie lista może być pusta, co w efekcie definiuje nowy typ całkowity.
class
Używając tego słowa kluczowego w deklaracji, należy określić, że wyliczenie ma zakres i należy podać.identifier
Możesz również użyć struct
słowa kluczowego class
zamiast , ponieważ są one semantycznie równoważne w tym kontekście.
Zakres modułu wyliczającego
Wyliczenie zawiera kontekst opisujący zakres wartości reprezentowanych jako nazwane stałe. Te nazwane stałe są również nazywane modułami wyliczania. W oryginalnych typach C i C++ enum
niekwalifikowane moduły wyliczane są widoczne w całym zakresie, w którym enum
jest zadeklarowany. W wyliczeniach o określonym zakresie nazwa modułu wyliczającego musi być kwalifikowana enum
przez nazwę typu. W poniższym przykładzie pokazano tę podstawową różnicę między dwoma rodzajami wyliczenia:
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
{ /*...*/
}
}
}
Każda nazwa w wyliczeniu ma przypisaną wartość całkowitą odpowiadającą jej miejscu w kolejności wartości w wyliczeniu. Domyślnie pierwsza wartość jest przypisana 0, następna jest przypisana 1 itd., ale można jawnie ustawić wartość modułu wyliczającego, jak pokazano poniżej:
enum Suit { Diamonds = 1, Hearts, Clubs, Spades };
Moduł wyliczający Diamonds
ma przypisaną wartość 1
. Kolejne moduły wyliczania, jeśli nie mają jawnej wartości, otrzymają wartość poprzedniego modułu wyliczającego plus jeden. W poprzednim przykładzie Hearts
wartość 2 Clubs
będzie miała wartość 3 itd.
Każdy moduł wyliczający jest traktowany jako stała i musi mieć unikatową nazwę w zakresie, w którym enum
zdefiniowano (dla wyliczenia niezakresowego) lub w enum
obrębie samego (dla wyliczenia o określonym zakresie). Wartości podane nazw nie muszą być unikatowe. Rozważmy na przykład tę deklarację niezakresowego wyliczenia Suit
:
enum Suit { Diamonds = 5, Hearts, Clubs = 4, Spades };
Wartości Diamonds
, , Hearts
Clubs
i Spades
są odpowiednio 5, 6, 4 i 5. Zwróć uwagę, że 5 jest używane więcej niż raz; jest dozwolone, mimo że może nie być zamierzone. Te reguły są takie same w przypadku wyliczenia o określonym zakresie.
Reguły rzutów
Stałe wyliczenia unscoped mogą być niejawnie konwertowane na int
, ale int
nigdy nie jest niejawnie konwertowane na wartość wyliczeniową. W poniższym przykładzie pokazano, co się stanie, jeśli spróbujesz przypisać hand
wartość, która nie jest wartością Suit
:
int account_num = 135692;
Suit hand;
hand = account_num; // error C2440: '=' : cannot convert from 'int' to 'Suit'
Rzutowanie jest wymagane do przekonwertowania int
elementu na moduł wyliczający o określonym zakresie lub bez zakresu. Można jednak podwyższyć poziom niezakresowego modułu wyliczającego do wartości całkowitej bez rzutowania.
int account_num = Hearts; //OK if Hearts is in an unscoped enum
Użycie niejawnych konwersji w ten sposób może prowadzić do niezamierzonych skutków ubocznych. Aby pomóc wyeliminować błędy programowania skojarzone z wyliczeniami niezakresowymi, wartości wyliczenia o określonym zakresie są silnie typizowane. Wyliczenia o określonym zakresie muszą być kwalifikowane przez nazwę typu wyliczenia (identyfikator) i nie można ich niejawnie przekonwertować, jak pokazano w poniższym przykładzie:
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
}
}
Zwróć uwagę, że wiersz hand = account_num;
nadal powoduje błąd, który występuje z wyliczeniami niezakresowymi, jak pokazano wcześniej. Dozwolone jest jawne rzutowanie. Jednak w przypadku wyliczenia o określonym zakresie próba konwersji w następnej instrukcji account_num = Suit::Hearts;
, nie jest już dozwolona bez jawnego rzutowania.
Wyliczenia bez modułów wyliczania
Program Visual Studio 2017 w wersji 15.3 lub nowszej (dostępne w /std:c++17
systemach i nowszych): definiując wyliczenie (zwykłe lub ograniczone) z jawnym typem bazowym i bez modułów wyliczających, można wprowadzić nowy typ całkowity, który nie ma niejawnej konwersji na inny typ. Używając tego typu zamiast wbudowanego typu bazowego, można wyeliminować potencjał drobnych błędów spowodowanych niezamierzone konwersje niejawne.
enum class byte : unsigned char { };
Nowy typ jest dokładną kopią typu bazowego i dlatego ma tę samą konwencję wywoływania, co oznacza, że można go używać w interfejsach API bez żadnych kar za wydajność. Rzutowanie nie jest wymagane, gdy zmienne typu są inicjowane przy użyciu inicjowania listy bezpośredniej. W poniższym przykładzie pokazano, jak zainicjować wyliczenia bez wyliczenia w różnych kontekstach:
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;
}