Evgenii Legotckoi
02 вересня 2017 р. 17:25

C++ - Підручник 008. Перерахування

Крім класів C++ підтримує перерахування. У сучасному стандарті C++ підтримуються як перерахування без видимості, які були введені в ранніх версіях C++, а також C.

  1. enum ColorTypes {
  2. Green,
  3. Yellow,
  4. Red
  5. };

Так і перерахування з областю видимості

  1. enum class ColorTypes {
  2. Green,
  3. Yellow,
  4. Red
  5. };

Відмінність перерахувань з областю видимості від перерахувань без області видимості

Відмінність перерахування з областю видимістю відрізняється від перерахунку без області видимості тим, що змінні перерахувань з областю видимості не можуть неявно перетворюватися на цілі змінні і назад. Для такого перетворення слід використовувати static_cast ().

  1. enum class ColorTypes { Green, Yellow, Red };
  2.  
  3. ColorTypes currentType_1 = ColorTypes::Green; // Ok
  4. ColorTypes currentType_2 = 2; // Error, преобразование невозможно
  5. int currentType_3 = ColorTypes::Red; // Error, преобразование невозможно
  6. int currentType_4 = Red; // Error, Red не существует в данной области видимости
  7. int currentType_5 = static_cast<int>(ColorTypes::Green);

Подібний контроль областей видимості дозволяє оголошувати у класі перерахування, члени яких мають однакові імена. Також надає більший контроль над кодом, хоч і накладає низку обмежень.

Як задаються значення перерахувань

За замовчуванням перерахування починається з 0, і далі відбувається інкремент членів перерахування.

  1. enum class ColorTypes {
  2. Green, // 0
  3. Yellow, // 1
  4. Red, // 2
  5. Black, // 3
  6. Blue // 4
  7. };

Проте є можливість ставити власні значення для перерахувань під час оголошення.

  1. enum class ColorTypes {
  2. Green = 15, // 15
  3. Yellow, // 16
  4. Red = 24, // 24
  5. Black = ColorTypes::Red, // 24
  6. Blue // 25
  7. };

Використання switch case для перерахувань

Перерахування як із областю видимості, так і без області видимості підтримують оператори умов та розгалуження switch/case :

  1. ColorTypes currentType = ColorTypes::Green;
  2.  
  3. switch (currentType)
  4. {
  5. case ColorTypes::Green: std::cout << "Green"; break;
  6. case ColorTypes::Yellow: std::cout << "Yellow"; break;
  7. case ColorTypes::Black: std::cout << "Black"; break;
  8. case ColorTypes::Blue: std::cout << "Blue"; break;
  9. default: std::cout << "Unknown Type"; break;
  10. }

Якщо міркувати про контроль виконання коду і обмеження, що накладаються при використанні перерахувань з областю видимості і без області видимості, то можна змоделювати ситуацію, коли в switch/case з якоїсь причини (друкарська помилка, копіпаста, нуб, напортачили при вирішенні конфліктів при мердже гілок) потрапляють перерахування різних типів. Тоді перерахування без області видимості будуть неявно перетворені на потрібний тип компілятором і код виконається, хоча і буде помилковим, а у випадку з перерахуваннями з областю видимості компілятор повідомить про помилку і припинить складання програми.

Тобто нижче наступний код, будучи помилковим, скомпілюється:

  1. enum SideTypes {
  2. Top,
  3. Bottom,
  4. Right,
  5. Left
  6. };
  7.  
  8. enum ColorTypes {
  9. Green = 8,
  10. Yellow,
  11. Red,
  12. Blue
  13. };
  14.  
  15. int main(int argc, char *argv[])
  16. {
  17. ColorTypes currentType = ColorTypes::Green;
  18.  
  19. switch (currentType)
  20. {
  21. case SideTypes::Top: std::cout << "Top Side"; break;
  22. case ColorTypes::Green: std::cout << "Green"; break;
  23. case ColorTypes::Yellow: std::cout << "Yellow"; break;
  24. case ColorTypes::Red: std::cout << "Red"; break;
  25. case ColorTypes::Blue: std::cout << "Blue"; break;
  26. default: std::cout << "Unknown Type"; break;
  27. }
  28.  
  29. return 0;
  30. }

У кращому разі компілятор викине Warning.

  1. warning: case value 0 not in enumerated type ColorTypes [-Wswitch]
  2. case SideTypes::Top: std::cout << "Top Side"; break;
  3. ^

Але буває так, що програміст "краще компілятора знає і розуміє С++", і відключає попередження.

Тоді як наступний код просто не скомпілюється:

  1. enum class SideTypes {
  2. Top,
  3. Bottom,
  4. Right,
  5. Left
  6. };
  7.  
  8. enum class ColorTypes {
  9. Green = 8,
  10. Yellow,
  11. Red,
  12. Blue
  13. };
  14.  
  15. int main(int argc, char *argv[])
  16. {
  17. ColorTypes currentType = ColorTypes::Green;
  18.  
  19. switch (currentType)
  20. {
  21. case SideTypes::Top: std::cout << "Top Side"; break;
  22. case ColorTypes::Green: std::cout << "Green"; break;
  23. case ColorTypes::Yellow: std::cout << "Yellow"; break;
  24. case ColorTypes::Red: std::cout << "Red"; break;
  25. case ColorTypes::Blue: std::cout << "Blue"; break;
  26. default: std::cout << "Unknown Type"; break;
  27. }
  28. return 0;
  29. }

Копілятор видасть помилку компіляції:

  1. error: could not convert Top from SideTypes to ColorTypes
  2. case SideTypes::Top: std::cout << "Top Side"; break;
  3. ^

Як задати конкретний цілісний тип для перерахування

Перерахування також можуть мати більш конкретизований тип, який повинен бути певним цілим типом:

  • непідписаний символ;
  • char;
  • int;
  • довгий int;
  • і т.д.

Це дозволяє виділити певний обсяг пам'яті під змінні, які мають значення перерахувань. Мабуть, це може бути актуальним для embedded розробки. Залежно від цільової платформи виділятиметься певний обсяг пам'яті.

  1. enum class SideTypes : short int {
  2. Top,
  3. Bottom,
  4. Right,
  5. Left
  6. };

Ітератор для перерахування

І насамкінець зробимо ітератор для перерахувань, за допомогою яких можна використовувати range-based цикл for .

  1. #include <iostream>
  2.  
  3. enum class ColorTypes
  4. {
  5. Blue,
  6. Red,
  7. Green,
  8. Purple,
  9. First=ColorTypes::Blue, // участник перечисления для первого элемента
  10. Last=ColorTypes::Purple // участник перечисления для последнего элемента
  11. };
  12.  
  13. ColorTypes operator++(ColorTypes& x)
  14. {
  15. // std::underlying_type преобразовывает тип ColorTypes в целочисленный тип, под которым данный enum был объявлен
  16. return x = static_cast<ColorTypes>(std::underlying_type<ColorTypes>::type(x) + 1);
  17. }
  18.  
  19. ColorTypes operator*(ColorTypes c)
  20. {
  21. return c;
  22. }
  23.  
  24. ColorTypes begin(ColorTypes r)
  25. {
  26. return ColorTypes::First;
  27. }
  28.  
  29. ColorTypes end(ColorTypes r)
  30. {
  31. ColorTypes l=ColorTypes::Last;
  32. return ++l;
  33. }
  34.  
  35. int main(int argc, char *argv[])
  36. {
  37. // Используем круглые скобки для инстанцирования перечисления
  38. for(const auto& c : ColorTypes())
  39. {
  40. std::cout << static_cast<int>(c) << std::endl;
  41. }
  42.  
  43. return 0;
  44. }

Вам це подобається? Поділіться в соціальних мережах!

ДК
  • 26 листопада 2019 р. 20:38
  • (відредаговано)

классная статья! Большое спасибо. Хотел бы добавить для тех, кто будет использовать это в другом классе- перед операторами и методами begin(), end() нужно поставить friend.

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
  • Останні коментарі
  • Evgenii Legotckoi
    16 квітня 2025 р. 17:08
    Благодарю за отзыв. И вам желаю всяческих успехов!
  • IscanderChe
    12 квітня 2025 р. 17:12
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    01 квітня 2025 р. 11:41
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    09 березня 2025 р. 21:02
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    09 березня 2025 р. 16:14
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…