Крім класів 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; ^
Як задати конкретний цілісний тип для перерахування
Перерахування також можуть мати більш конкретизований тип, який повинен бути певним цілим типом:
- непідписаний символ;
- char;
- int;
- довгий 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.