View Categories

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

enum #

Есть ещё один способ создания именованных констант - enum (enumeration, перечисление). Он позволяет не просто создать список констант, но и автоматически присвоить им численные значения с шагом 1, начиная с 0. Синтаксис такой:

enum имя_списка {
    константа1,     // значение 0
    константа2,     // значение 1
    константа3,     // значение 2
    // ...
};

Такая запись создаёт новый тип данных, никакие переменные в этот момент не создаются и память не выделяется. Объявить сам enum можно где угодно: глобально, внутри функции или внутри класса.

После объявления enum можно создавать переменные и константы этого нового типа по его названию. Эти переменные могут принимать указанные в списке значения:

имя_списка переменная = константа2;
const имя_списка константа = константа3;

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

Тип данных #

По умолчанию под капотом нашего нового типа данных находится int:

enum Foo {
    foo1,
    foo2,
};

sizeof(Foo);    // соответствует int (2/4 байта)

Foo foo = foo1;
sizeof(foo);    // соответствует int (2/4 байта)

Можно указать конкретный тип вручную как enum имя_списка : тип:

enum Foo : uint8_t {
    foo1,
    foo2,
};

sizeof(Foo);    // 1 байт

Foo foo = foo1;
sizeof(foo);    // 1 байт

Указание значения #

Значения констант в enum можно указывать явно, например кодов цветов:

  • Если константа не задана явно, её значение будет равно значению предыдущей константы + 1
  • Имена должны быть уникальными в рамках перечисления
  • Имена подчиняются правилам создания имён
enum Colors {
    Red = 0xcb2839,
    Orange = 0xd55f30,
    Yellow = 0xd69d27,

    color1,     // 0xd69d28 = Yellow + 1
    color2,     // 0xd69d29 = color1 + 1

    color3 = 1234,
    color4,     // 1235
    //color4,   // ошибка, такое имя уже есть
    //for,      // ошибка, ключевое слово
};

Компилятор автоматически повысит тип данных, если хоть одно значение выходит за int - тип Colors из примера выше является 4-байтным, т.к. 2 байта не хватило бы для хранения указанных чисел (для AVR, int 2 байта)

sizeof(Colors);     // 4 байта

Подсказки значений #

К константе можно обратиться как напрямую - константа, так и через имя списка при помощи оператора :: - имя_списка::константа:

enum Colors {
    Red = 0xcb2839,
    Orange = 0xd55f30,
    Yellow = 0xd69d27,
};

Colors color = Colors::Red;

IDE, в которых есть автодополнение кода, подскажут список констант при вводе имя_списка:::

file

Пересечение имён #

Константы обычного enum доступны глобально для всего кода как обычные числа, их имена могут пересекаться с остальными именами переменных в программе:

int Red;    // #1

enum Colors {
    Red,        // ошибка, имя уже занято переменной #1 выше
    Orange,
    Yellow,     // #2
};

int Yellow;     // ошибка, имя занято #2 в enum выше

Константы enum могут преобразовываться в соответствующие им числа:

int color1 = Orange;            // 1
int color2 = Colors::Orange;    // 1

В крупном проекте или при использовании в библиотеках очень сложно соблюдать уникальность имён констант - приходится добавлять им некрасивые префиксы. Для решения этой проблемы существует enum class.

Enum class #

Это тот же enum, но его константы:

  • "Спрятаны" (инкапсулированы) внутри имени типа, обратиться к ним можно только через ::
  • Не пересекаются с остальной программой
  • Не могут быть автоматически преобразованы и присвоены к обычным переменным. Принудительно - могут
int Red;

enum class Colors {
    Red,        // ошибки нет
    Orange,
    Yellow,
};

int Yellow;     // ошибки нет

int color1 = Orange;                // ошибка, такой константы нет
int color2 = Colors::Orange;        // ошибка, нельзя присвоить к int
int color3 = (int)Colors::Orange;   // так можно, но не нужно

Colors color3 = Colors::Orange;     // нужно так

Если нужно указать конкретный тип такому перечислению - это делается так:

enum class Colors : uint32_t {
    // ...
};

Рекомендуется использовать enum class - он удобнее и безопаснее, сводит количество возможных конфликтов имён практически в ноль

Трюки #

Количество констант #

В C/C++ невозможно узнать из программы количество констант, которое задано в enum или enum class. Но есть трюк, который работает, если все константы нумеруются автоматически с нуля (без задания вручную) - численное значение последней константы всегда равно количеству остальных констант! Просто создаём в конце константу с именем, которое олицетворяет длину списка, и можно пользоваться:

enum class MyConst {
    someConst,  // 0
    const2,     // 1
    Red,        // 2
    green,      // 3
    _len,       // 4 - количество констант (не включая эту)
};

int enumLen = (int)MyConst::_len;   // == 4

Инкремент и декремент #

enum нельзя использовать в математических выражениях - считается, что это не число, а другой тип. Тем не менее, бывает удобно переключать "режимы", сделанные через enum или enum class. Для этого можно просто приводить его к int, например e = Enum((int)e + 1). Можно реализовать инкремент и декремент через перегрузку операторов:

enum class State {
    Foo,
    Bar,
    FooBar,
};

void operator++(State& s) {
    s = State((int)s + 1);
}
void operator--(State& s) {
    s = State((int)s - 1);
}

/*
State operator++(State& s) {
    return s = State((int)s + 1);
}
State operator--(State& s) {
    return s = State((int)s - 1);
}
*/

int main() {
    State s;
    ++s;
    --s;
}

Переключение #

Инкремента и декремента мало для конструкций с переключением состояний - нужно проверять границы: между 0 и количеством констант. Есть два варианта: при выходе за границу переходить к противоположной, либо ничего не делать - ограничить переключение. Чтобы не хранить отдельно количество констант, можно добавить в конце ещё одну константу - маркер конца, как в примере с количеством констант выше.

Вот набор универсальных шаблонных функций для переключения enum:

// инкремент с ограничением
template <typename T>
T nextLim(const T& s) {
    return ((int)s + 1 == (int)T::_len) ? s : T((int)s + 1);
}

// инкремент с переполнением
template <typename T>
T nextOvf(const T& s) {
    return ((int)s + 1 == (int)T::_len) ? T(0) : T((int)s + 1);
}

// декремент с ограничением
template <typename T>
T prevLim(const T& s) {
    return (int)s ? T((int)s - 1) : s;
}

// декремент с переполнением
template <typename T>
T prevOvf(const T& s) {
    return (int)s ? T((int)s - 1) : T((int)T::_len - 1);
}
  • Работают с enum и enum class
  • Нумерация перечисления должна быть автоматической, без заданных вручную значений
  • Последней константой должна быть _len

Пример использования:

enum class State {
    Foo,
    Bar,
    State1,
    State2,
    _len,
};

int main() {
    State s = State::State2;
    s = nextLim(s);
    s = nextOvf(s);
    s = prevLim(s);
    s = prevOvf(s);
}
5 1 голос
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх