Поделиться через


Перечисления (C++)

Перечисление — это определяемый пользователем тип, состоящий из набора именованных целочисленных констант, которые называются перечислителями.

Примечание.

В этой статье рассматриваются тип языка enum C++ стандарта ISO и тип ограниченного (или строго типизированного), enum class который представлен в C++11. Сведения о public enum class типах private enum class или типах в C++/CLI и C++/CX см. в статьяхenum class (C++/CLI и C++/CX).

Синтаксис

enum-name:
identifier

enum-specifier:
enum-head { enumerator-listнеоб. }
enum-head { enumerator-list , }

enum-head:
enum-keyattribute-specifier-seqopt opt enum-baseenum-head-name

enum-head-name:
nested-name-specifierнеоб. identifier

opaque-enum-declaration:
enum-key attribute-specifier-seq необ.enum-head-name enum-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, HeartsClubsи 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 { };

Новый тип представляет собой точную копию базового типа и, следовательно, имеет то же соглашение о вызовах, что означает, что его можно использовать в 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;
}

См. также

Объявления перечисления C
Ключевые слова