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, в которых есть автодополнение кода, подскажут список констант при вводе имя_списка::
:
Пересечение имён #
Константы обычного 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);
}