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

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

Крім класів 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;
}

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

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

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up