В дополнение к классам C++ поддерживает перечисления. В современном стандарте C++ поддерживаются как перечисления без области видимости, которые были введены в ранних версиях C++, а также C.
enum ColorTypes { Green, Yellow, Red };
Так и перечисления с областью видимости
enum class ColorTypes { Green, Yellow, Red };
Отличие перечислений с областью видимости от перечислений без области видимости
Отличие перечисление с областью видимостью отличается от перечислении без области видимости тем, что переменные перечислений с областью видимости не могут неявно преобразовываться в целочисленные переменные и обратно. Для такого преобразования следует использовать
static_cast
enum class ColorTypes { Green, Yellow, Red }; ColorTypes currentType_1 = ColorTypes::Green; // Ok ColorTypes currentType_2 = 2; // Error, преобразование невозможно int currentType_3 = ColorTypes::Red; // Error, преобразование невозможно int currentType_4 = Red; // Error, Red не существует в данной области видимости int currentType_5 = static_cast<int>(ColorTypes::Green);
Подобный контроль областей видимости позволяет объявлять в классе перечисления, члены которых имеют одинаковые имена. Также предоставляет больший контроль над кодом, хотя и накладывает ряд ограничений.
Как задаются значения перечислений
По умолчанию перечиление начинается с 0, и дальше происходит инкремент членов перечисления...
enum class ColorTypes { Green, // 0 Yellow, // 1 Red, // 2 Black, // 3 Blue // 4 };
но имеется возможность задавать собственные значения для перечислений при объявлении.
enum class ColorTypes { Green = 15, // 15 Yellow, // 16 Red = 24, // 24 Black = ColorTypes::Red, // 24 Blue // 25 };
Использование switch case для перечислений
Перечисления как с областью видимости, так и без области видимости поддерживают операторы условий и ветвления switch/case :
ColorTypes currentType = ColorTypes::Green; switch (currentType) { case ColorTypes::Green: std::cout << "Green"; break; case ColorTypes::Yellow: std::cout << "Yellow"; break; case ColorTypes::Black: std::cout << "Black"; break; case ColorTypes::Blue: std::cout << "Blue"; break; default: std::cout << "Unknown Type"; break; }
Если рассуждать о контроле выполнения кода и накладывающихся ограничениях при использовании перечислений с областью видимости и без области видимости, то можно смоделировать ситуацию, когда в switch/case по какой-то причине (опечатка, копипаста, нуб, напортачили при разрешении конфликтов при мердже веток) попадают перечисления разных типов. Тогда перечисления без области видимости будут неявно преобразованы в нужный тип компилятором и код выполнится, хотя и будет ошибочен, а в случае с перчислениями с областью видимости компилятор сообщит об ошибке и прекратит сборку программы.
То есть, ниже следующий код, будучи ошибочным, скомпилируется:
enum SideTypes { Top, Bottom, Right, Left }; enum ColorTypes { Green = 8, Yellow, Red, Blue }; int main(int argc, char *argv[]) { ColorTypes currentType = ColorTypes::Green; switch (currentType) { case SideTypes::Top: std::cout << "Top Side"; break; case ColorTypes::Green: std::cout << "Green"; break; case ColorTypes::Yellow: std::cout << "Yellow"; break; case ColorTypes::Red: std::cout << "Red"; break; case ColorTypes::Blue: std::cout << "Blue"; break; default: std::cout << "Unknown Type"; break; } return 0; }
В лучшем случае компилятор выкинет Warning.
warning: case value ‘0’ not in enumerated type ‘ColorTypes’ [-Wswitch] case SideTypes::Top: std::cout << "Top Side"; break; ^
Но бывает же так, что программист "лучше компилятора знает и понимает С++", и отключает предупреждения.
Тогда как следующий код просто не скомпилируется:
enum class SideTypes { Top, Bottom, Right, Left }; enum class ColorTypes { Green = 8, Yellow, Red, Blue }; int main(int argc, char *argv[]) { ColorTypes currentType = ColorTypes::Green; switch (currentType) { case SideTypes::Top: std::cout << "Top Side"; break; case ColorTypes::Green: std::cout << "Green"; break; case ColorTypes::Yellow: std::cout << "Yellow"; break; case ColorTypes::Red: std::cout << "Red"; break; case ColorTypes::Blue: std::cout << "Blue"; break; default: std::cout << "Unknown Type"; break; } return 0; }
Копилятор выдаст ошибку компиляции:
error: could not convert ‘Top’ from ‘SideTypes’ to ‘ColorTypes’ case SideTypes::Top: std::cout << "Top Side"; break; ^
Как задать конкретный целочисленный тип для перечисления
Перечисления также могут иметь более конкретизированный тип, который должен быть определённым целочисленным типом:
- unsigned char;
- char;
- int;
- long int;
- и т.д.
Это позволяет выделить определённый объём памяти под переменные, которые имеют значения перечислений. Пожалуй, это может быть актуально для embedded разработки. В зависимости от целевой платформы будет выделяться определённый объём памяти.
enum class SideTypes : short int { Top, Bottom, Right, Left };
Итератор для перечисления
И напоследок сделаем итератор для перечислений, с помощью которых можно использовать range-based цикл for .
#include <iostream> enum class ColorTypes { Blue, Red, Green, Purple, First=ColorTypes::Blue, // участник перечисления для первого элемента Last=ColorTypes::Purple // участник перечисления для последнего элемента }; ColorTypes operator++(ColorTypes& x) { // std::underlying_type преобразовывает тип ColorTypes в целочисленный тип, под которым данный enum был объявлен return x = static_cast<ColorTypes>(std::underlying_type<ColorTypes>::type(x) + 1); } ColorTypes operator*(ColorTypes c) { return c; } ColorTypes begin(ColorTypes r) { return ColorTypes::First; } ColorTypes end(ColorTypes r) { ColorTypes l=ColorTypes::Last; return ++l; } int main(int argc, char *argv[]) { // Используем круглые скобки для инстанцирования перечисления for(const auto& c : ColorTypes()) { std::cout << static_cast<int>(c) << std::endl; } return 0; }
классная статья! Большое спасибо. Хотел бы добавить для тех, кто будет использовать это в другом классе- перед операторами и методами begin(), end() нужно поставить friend.