Есть ещё один способ создания именованных констант - 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 байт
Также компилятор может сам повысить тип при указании больших значений, например на 8-битной архитектуре
enum Foo {
foo1 = 123456,
foo2,
};
Foo станет 32-битным вместо 16-битного.
Указание значения #
Значения констант в enum можно указывать явно, например кодов цветов:
- Если константа не задана явно, её значение будет равно значению предыдущей константы
+ 1 - Имена должны быть уникальными в рамках перечисления
- Имена подчиняются правилам создания имён
enum Colors {
Red = 0xcb2839,
Orange = 0xd55f30,
Yellow = 0xd69d27,
color1, // 0xd69d28 = Yellow + 1
color2, // 0xd69d29 = color1 + 1
color3 = 1234,
color4, // 1235
//color4, // ошибка, такое имя уже есть
//for, // ошибка, ключевое слово
};
Подсказки значений #
К константе можно обратиться как напрямую - константа, так и через имя списка при помощи оператора :: - имя_списка::константа:
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; // присвоение к экземпляру Colors
Если нужно указать конкретный тип такому перечислению - это делается так:
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);
}
Дополнительно #
Дополнительный контент доступен владельцам набора GyverKIT и по подписке, подробнее читай здесь. Блок содержит:
- Тезисы, Примеры (Arduino)
- 1 блоков кода
Полезные страницы #
- Набор GyverKIT – наш большой стартовый набор Arduino, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
- Поддержать автора за работу над уроками