19.3. Перечисления

Перечисления (enumeration) позволяют группировать наборы целочисленных констант. Как и класс, каждое перечисление определяет новый тип. Перечисления — литеральные типы (см. раздел 7.5.6).

В языке С++ есть два вида перечислений: с ограниченной и с не ограниченной областью видимости. Перечисление с ограниченной областью видимости (scoped enumeration) вводит новый стандарт. Для определения перечисления с ограниченной областью видимости используются ключевые слова enum class (или enum struct), сопровождаемые именем перечисления и разделяемым запятыми списком перечислителей (enumerator), заключенным в фигурные скобки. За закрывающей фигурной скобкой следует точка с запятой:

enum class open_modes {input, output, append};

Здесь определен тип перечисления open_modes с тремя перечислителями: input, output и append.

В определении перечисления с не ограниченной областью видимости (unscoped enumeration) ключевое слово class (или struct) отсутствует. Имя перечисления с не ограниченной областью видимости не является обязательным:

enum color {red, yellow, green}; // перечисление с не ограниченной

                                 // областью видимости

// безымянное перечисление с не ограниченной областью видимости

enum {floatPrec = 6, doublePrec = 10, double_doublePrec = 10};

Если перечисление является безымянным, определить объекты его типа можно только в составе определения перечисления. Подобно определению класса, здесь можно предоставить разделяемый запятыми список объявлений между закрывающей фигурной скобкой и точкой с запятой, завершающей определение перечисления (см. раздел 2.6.1).

Перечислители

Имена перечислителей в перечислении с ограниченной областью видимости подчиняются обычным правилам областей видимости и недоступны вне области видимости перечисления. Имена перечислителей в перечислении с не ограниченной областью видимости находятся в той же области видимости, что и само перечисление:

enum color {red, yellow, green}; // перечисление с не ограниченной

                                 // областью видимости

enum stoplight {red, yellow, green}; // ошибка: переопределение

                                     // перечислителей

enum class peppers {red, yellow, green}; // ok: перечислители

                                         // скрываются

color eyes = green; // ok: перечислители находятся в области видимости

              // для перечисления с не ограниченной областью видимости

peppers p = green; // ошибка: перечислители из peppers не находятся в

                   // области видимости

                   // color::green находится в области видимости,

                   // но имеет неправильный тип

color hair = color::red; // ok: к перечислителям можно обратиться явно

peppers p2 = peppers::red; // ok: использование red из peppers

По умолчанию значения перечислителей начинаются с 0, и значение каждого последующего перечислителя на 1 больше предыдущего. Однако вполне можно предоставить инициализаторы для одного или нескольких перечислителей:

enum class intTypes {

 charTyp = 8, shortTyp = 16, intTyp = 16,

 longTyp = 32, long_longTyp = 64

};

Как можно заметить на примере перечислителей intTyp и shortTyp, значение перечислителя не обязано быть уникальным. Без инициализатора значение перечислителя будет на 1 больше, чем у предыдущего.

Перечислители являются константами, и их инициализаторы должны быть константными выражениями (см. раздел 2.4.4). Следовательно, каждый перечислитель сам является константным выражением. Поскольку перечислители — константные выражения, их можно использовать там, где необходимы константные выражения. Например, можно определить переменные constexpr типа перечисления:

constexpr intTypes charbits = intTypes::charTyp;

Точно так же перечисление можно использовать как выражение в операторе switch, а значения его перечислителей как метки разделов case (см. раздел 5.3.2). По той же причине тип перечисления можно также использовать как параметр значения шаблона (см. раздел 16.1.1) и инициализировать статические переменные-члены типа перечисления в определении класса (см. раздел 7.6).

Подобно классам, перечисления определяют новые типы

Поскольку перечисление имеет имя, можно определять и инициализировать объекты этого типа. Объект перечисления может быть инициализирован или присвоен только одному из своих перечислителей или другому объекту того же типа перечисления:

open_modes om = 2;      // ошибка: 2 не имеет типа open_modes

om = open_modes::input; // ok: input - перечислитель open_modes

Объекты или перечислители типа перечисления с не ограниченной областью видимости автоматически преобразовываются в целочисленный тип. В результате они применимы там, где требуется целочисленное значение:

int i = color::red; // ok: перечислитель перечисления с не ограниченной

                    // областью видимости неявно преобразован в тип int

int j = peppers::red; // ошибка: перечисления с ограниченной областью

                      // видимости неявно не преобразуются

Определение размера перечисления

Хотя каждое перечисление определяет уникальный тип, оно представляется одним из встроенных целочисленных типов. По новому стандарту можно указать, что следует использовать тип, заданный за именем перечисления и двоеточием:

enum intValues : unsigned long long {

 charTyp = 255, shortTyp = 65535, intTyp = 65535,

 longTyp = 4294967295UL,

 long_longTyp = 18446744073709551615ULL

};

Если базовый тип не задан, то по умолчанию перечисления с ограниченной областью видимости имеют базовый тип int. Для перечислений с не ограниченной областью видимости типа по умолчанию нет; известно только то, что базовый тип достаточно велик для содержания значения перечислителя. Когда базовый тип определяется (включая неявное определение для перечисления с ограниченной областью видимости), попытка создания перечислителя, значение которого превосходит заданный тип, приведет к ошибке.

Возможность определить базовый тип перечисления позволяет контролировать тип, используемый при разных реализациях компилятора. Это позволяет также гарантировать, что программа, откомпилированная на одной реализации, создаст тот же код при компиляции на другом.

Предварительные объявления для перечислений

По новому стандарту перечисление можно объявить предварительно. Предварительное объявление перечисления должно определить (неявно или явно) его базовый размер:

// предварительное объявление перечисления с не ограниченной областью

// видимости intValues

enum intValues : unsigned long long; // перечисление с не ограниченной

                           // областью видимости должно определять тип

enum class open_modes; // перечисление с ограниченной областью

          // видимости может использовать по умолчанию тип int

Поскольку для перечисления с не ограниченной областью видимости нет размера по умолчанию, каждое объявление должно включить его размер. Перечисление с ограниченной областью видимости можно объявить, не определяя размер, тогда размер неявно определяется как int.

Подобно любым объявлениям, все объявления и определения того же перечисления должны соответствовать друг другу. В случае перечислений это требование означает, что размер перечисления должен совпадать для всех объявлений и определений. Кроме того, нельзя объявить имя как перечисление с не ограниченной областью видимости в одном контексте, а затем повторно объявить его как перечисление с ограниченной областью видимости:

// ошибка: в объявлении и определении должно совпадать, ограничена ли

// область видимости перечисления

enum class intValues;

enum intValues; // ошибка: intValues ранее объявлено как перечисление с

                // ограниченной областью видимости

enum intValues : long; // ошибка: intValues ранее объявлено как int

Соответствие параметров и перечисления

Поскольку объект типа перечисления может быть инициализирован только другим объектом того же типа перечисления или одним из его перечислителей (см. раздел 19.3), целое число, значение которого случайно совпадает со значением перечислителя, не может использоваться при вызове функции, ожидающей перечислимый аргумент:

// перечисление с не ограниченной областью видимости;

// базовый тип зависит от машины

enum Tokens {INLINE = 128, VIRTUAL = 129};

void ff(Tokens);

void ff(int);

int main() {

 Tokens curTok = INLINE;

 ff(128);    // точно соответствует ff(int)

 ff(INLINE); // точно соответствует ff(Tokens)

 ff(curTok); // точно соответствует ff(Tokens)

 return 0;

}

Хоть и нельзя передать целочисленное значение параметру перечислимого типа, вполне можно передать объект или перечислитель перечисления с неограниченной областью видимости параметру целочисленного типа. При этом значение перечислителя преобразуется в тип int или больший целочисленный тип. Фактический тип преобразования зависит от базового типа перечисления:

void newf(unsigned char);

void newf(int);

unsigned char uc = VIRTUAL;

newf(VIRTUAL); // вызов newf(int)

newf(uc);      // вызов newf(unsigned char)

У перечисления Tokens только два перечислителя, больший из них имеет значение 129. Это значение может быть представлено типом unsigned char, и большинство компиляторов будут использовать для перечисления Tokens базовый тип unsigned char. Независимо от своего базового типа, объекты и перечислители перечисления Tokens преобразуются в тип int. Перечислители и значения перечислимого типа не преобразуются в тип unsigned char, даже если ему соответствуют значения перечислителей.

Более 800 000 книг и аудиокниг! 📚

Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением

ПОЛУЧИТЬ ПОДАРОК