Перейти к содержимому

Enum class c что это

  • автор:

Enum class c что это

Перечисления (enum) представляют еще один способ определения своих типов. Их отличительной особенностью является то, что они содержат набор числовых констант. Перечисление имеет следующую форму:

enum class имя_перечисления < константа_1, константа_2, . константа_N>;

После ключевых enum class идет название перечисления, и затем в фигруных скобках перечисляются через запятую константы перечисления.

Определим простейшее перечисление:

enum class Day

В данном случае перечисление называется Day и представляет дни недели. В фигурных скобках заключены все дни недели. Фактически они представляют числовые константы.

Каждой константе сопоставляется некоторое числовое значение. По умолчанию первая константа получает в качестве значения 0, а остальные увеличиваются на единицу. Так, в примере выше Monday будет иметь значение 0, Tuesday — 1 и так далее. Таким образом, последняя константа — Sunday будет равна 6.

После создания перечисления мы можем определить его переменную и присвоить ей одну из констант:

Day today ; // или так //Day today = Day::Thursday;

В данном случае определяется переменная today , которая равна Day::Thursday , то есть четвертой константе перечисления Day.

Чтобы вывести значение переменной на консоль, можно использовать преобразование к типу целочисленному типу:

#include enum class Day ; int main() < Day today ; std::cout (today)

То есть в данном случае на консоль будет выведено Today: 3 , так как константа Thursday имеет значение 3.

Мы также можем управлять установкой значений в перечислении. Так, мы можем задать начальное значение для одной контанты, тогда у последуюших констант значение увеличивается на единицу:

enum class Day ;

В данном случае Tuesday будет равно 2, а Sunday — 7.

Можно назначить каждой константе индивидуальное значение или сочетать этот подход с автоустановкой:

enum class Day ;

В данном случае Saturday будет равно 7, а Sunday — 1.

Можно даже назначать двум константам одно и то же значение:

enum class Day ;

Здесь константы Monday и Mon имеют одно и то же значение.

Можно присвоить константам значение уже имеющихся констант:

enum class Day ;

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

enum class Operation: char ;

Если мы захотим вывести значения этих констант на консоль в виде символов, то необходимо преобразовать их к типу char :

#include enum class Operation: char ; int main() < std::cout (Operation::Add) (Operation::Subtract) (Operation::Multiply)

Применение перечислений

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

#include enum class Operation ; void calculate(int n1, int n2, Operation op) < switch (op) < case Operation::Add: std::cout > int main() < calculate(10, 6, Operation::Add); // 16 calculate(10, 6, Operation::Subtract); // 4 calculate(10, 6, Operation::Multiply); // 60 >

В данном случае все арифметические операции хранятся в перечислении Operation. В функции calculate зависимости от значения третьего параметра — применяемой операции выполняются определенные действия с двумя первыми параметрами.

Подключение констант перечисления

При обращении к контантам перечисления по умолчанию необходимо указывать название перечисления, например, Day::Monday . Но начиная со стандарта C++20 мы можем подключить константы перечисления в текущий контекст с помощью оператора using .

using enum Day;

И в дальнейшем использовать только имя констант:

#include enum class Day ; using enum Day; // подключаем константы перечисления в текущую область видимости int main() < Day today ; // используем только имя константы // или так //Day today = Thursday; std::cout (today) << std::endl; // 3 // выводим значение констаты Sunday std::cout (Sunday) 

Также мы можем подключить только одну константу:

using Day::Monday; // подключаем только Monday . Day today ;

В данном случае подключаем только константу Day::Monday . Для обращения к дргуим константам по прежднему необходимо использовать имя перечисления.

Поскольку такая возможность добавлена лишь начиная со стандарта С++20, то при компиляции с g++ или clang++ добавляется соответствующий флаг - -std=c++20

Перечисления в С-стиле

Стоит отметить, что раньше в С++ использовалась другая форма перечислений, которые пришли из языка С и определяются без ключевого слова class :

#include enum Day ; int main() < Day today = Tuesday; std::cout 

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

Перечисления в C++

Перечисления в C++ (enum) — это один из способов определения типов. Их отличительная особенность заключается в том, что перечисления содержат набор числовых констант. Рассмотрим простейшее перечисление:

1-1801-cd239e.png

Чтобы определить перечисление, используют ключевое слово enum, после которого пишут название перечисления. Далее в фигурных скобках числовые константы перечисляют через запятую. При этом каждой константе по умолчанию присваивается числовое значение, которое начинается с 0. Таким образом, в нашем примере spring=0, a winter, соответственно, трем.

2-1801-c2d2fe.png

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

3-1801-4a28cf.png

В то же самое время перечисление — это отдельный тип, что означает, что мы не сможем присвоить нашей переменной числовое значение напрямую:

4-1801-bb76b4.png

Но что делать, если дефолтные значения для констант нас не устраивают? Есть возможность задать значения явным образом, к примеру, установив начальное значение:

5-1801-f1ef57.png

В таком случае значения 2-й и последующих констант станет повышаться на единицу.

Есть возможность задать значение и для каждой константы:

6-1801-65c92c.png

Когда вообще нужны перечисления? Например, когда мы имеем ряд логически связанных констант, которые лучше определять в одном общем типе данных. Посмотрите на код ниже:

7-1801-a06eb7.png

В нашем примере все арифметические операции сохраняются в перечислении operations. А уже с учетом выбранной операции в switch-конструкции выполняются определенные действия.

Перечисления (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-specifier :
enum-head enumerator-list необ. >
enum-head enumerator-list , >

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 необ.

Использование

// unscoped enum: // enum [identifier] [: type] ; // scoped enum: // enum [class|struct] [identifier] [: type] ; // 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
Разделенный запятыми список перечислителей в перечислении. Каждый перечислитель или имя переменной в области должны быть уникальными. Однако значения могут повторяться. В перечислении un область d, область является окружающим область; в область перечислении, область сам по enum-list себе. В перечислении область может быть пустой список, который фактически определяет новый целочисленный тип.

class
Используя этот ключевое слово в объявлении, вы указываете, что перечисление область и identifier необходимо указать. Вы также можете использовать struct ключевое слово вместо class них, так как они семантически эквивалентны в этом контексте.

Перечисление область

Перечисление предоставляет контекст для описания диапазона значений, представленных как именованные константы. Эти именованные константы также называются перечислителями. В исходных типах C и C++ enum перечислители неквалифицированных отображаются на протяжении область, в которой объявлен. enum В область d перечисления имя перечислителя должно быть квалифицировано по 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 определен (для не область d перечислений) или внутри enum себя (для перечислений область d). Значения, заданные именам, не должны быть уникальными. Например, рассмотрим это объявление перечисления Suit un область d:

enum Suit < Diamonds = 5, Hearts, Clubs = 4, Spades >; 

Значения Diamonds , Hearts Clubs и Spades имеют значение 5, 6, 4 и 5 соответственно. Обратите внимание, что 5 используется более одного раза; Это разрешено, даже если оно не может быть предназначено. Такие же правила распространяются на ограниченные перечисления.

Приведение правил

Константы перечисления un область d могут быть неявно 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(account_num); // OK, but probably a bug. account_num = Suit::Hearts; // error C2440: '=' : cannot convert from 'Suit' to 'int' account_num = static_cast(Suit::Hearts); // OK > > 

Обратите внимание, что в строке hand = account_num; по-прежнему содержится ошибка, которая происходит при использовании неограниченных перечислений, как показано выше. Допускается явное приведение. Однако при использовании ограниченных перечислений попытка преобразования в следующем операторе — account_num = Suit::Hearts; — больше не будет разрешена без явного приведения.

Перечисления без перечислителей

Visual Studio 2017 версии 15.3 и более поздних версий (доступных и /std:c++17 более поздних версий): определяя перечисление (обычный или область d) с явным базовым типом и без перечислителей, вы можете в действительности ввести новый целочисленный тип, который не имеет неявного преобразования в любой другой тип. Используя этот тип вместо встроенного базового типа, можно исключить вероятность тонких ошибок, вызванных непреднамеренно неявными преобразованиями.

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; > 

Перечисления "enum class"

Новый стандарт С++ дает возможность использовать новый тип перечислений, лично для меня это намного удобнее старых обычных enum "перечислений", но есть одна проблема - она очень не удобна лично для меня, вот пример и суть проблемы:

enum class Colors: DWORD

Начинаю использовать:

void draw2DBox(DWORD col) < . //Не важно что здесь >int main() < draw2DBox(Colors::RED )//Компилятор считает Colors::RED как какой-то объект а не член типа DWORD return 0; >

Приходится делать приведение типов:

int main() < draw2DBox((DWORD)Colors::RED )//Так компилируется - но это жутко не удобно return 0; >

Может можно как-то обойтись без приведения типов? UPD: Использую VS 2015
Отслеживать
28.8k 12 12 золотых знаков 59 59 серебряных знаков 118 118 бронзовых знаков
задан 11 авг 2016 в 18:00
1,973 3 3 золотых знака 17 17 серебряных знаков 34 34 бронзовых знака
@VladD - ах! забыл самое главное - использую VS 2015
11 авг 2016 в 18:09
@VladD, почему должно? Они же специально вводились, чтобы автоприведение НЕ работало.
11 авг 2016 в 18:19
enum Colors : DWORD будет работать. enum class Colors : DWORD - не будет. Почувствуйте разницу.
11 авг 2016 в 19:22
@Qwertiy: Тьфу, я прочитал условие как void draw2DBox(Colors col) .
11 авг 2016 в 19:23
@VladD, посмотрел историю - это было потом исправлено))
11 авг 2016 в 19:35

3 ответа 3

Сортировка: Сброс на вариант по умолчанию

Не работает, не должно работать и не будет работать.

Идеей введения enum class была изолированность его значений и видимость особого типа. Неявное приведение для него недоступно by design:

enum class test : int < value = 0 >; int main()
prog.cpp: In function 'int main()': prog.cpp:11:11: error: cannot convert 'int' to 'test' in initialization test t = 0; ^ prog.cpp:12:15: error: cannot convert 'test' to 'int' in return return test::value; ^ 

Отслеживать
28.8k 12 12 золотых знаков 59 59 серебряных знаков 118 118 бронзовых знаков
ответ дан 11 авг 2016 в 18:23
123k 24 24 золотых знака 126 126 серебряных знаков 303 303 бронзовых знака

Запрет неявного приведения типа - это одно из следствий использования ключевого слова class в определении enum. Эффект этого ключевого слова двояк: во-первых, оно объявляет scoped enum (в отличие от "обычного" unscoped enum); во-вторых, оно запрещает неявное приведение к целочисленному типу.

В то же время в С++ вы можете "базировать" enum на выбранном вами целочисленном типе и без указания ключевого слова class

enum Colors : DWORD < RED = 0xFFFF0000, GREEN = 0xFF00FF00 . >; 

Вот такой enum-тип будет свободно приводится к целочисленному типу неявно, именно так как вы хотели.

Однако, убрав слово class , вы вместе с ограничениями на приведения еще и сделаете свой enum unscoped, т.е. теперь константы изнутри enum будут видны в глобальном пространстве имен как RED , GREEN и т.п. Т.е. нет необходимости использовать Colors::RED , Colors::GREEN . Это не очень приятно, но это уж вам судить, устраивает вас такой вариант или нет.

(При этом при желании вы можете продолжать обращаться к этим идентификаторам используя квалифицированные имена - Colors::RED , Colors::GREEN и т.п. Эта возможность появилась в С++11 вообще для всех enum-типов. Но фактически это не более чем syntactic sugar: для всех enum-типов, объявленных без ключевого слова class , имена констант скопом вываливаются в охватывающее пространство имен.)

Если же вам все таки очень хочется пользоваться неявным приведением, но при этом получить полный аналог scoped enum, то требуемое поведение можно "эмулировать" при помощи обертки из struct

struct Colors < enum : DWORD < RED = 0xFFFF0000, GREEN = 0xFF00FF00 . >; >; 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *