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-listZdecydować}
enum-head { enumerator-list , }

enum-head:
enum-keyattribute-specifier-seqopt optenum-head-nameenum-base

enum-head-name:
nested-name-specifierZdecydowaćidentifier

opaque-enum-declaration:
enum-keyattribute-specifier-seqopt opt optenum-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:
identifierattribute-specifier-seqZdecydować

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 classzamiast , 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, , HeartsClubsi 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;
}

Zobacz też

Deklaracje wyliczenia języka C
Słowa kluczowe