Для создания ветвления в программе можно использовать конструкции на else if, но если нужно создать ветвление по значению переменной, которая принимает известный набор значений - лучше использовать оператор выбора - switch.
Оператор switch #
В общем виде он выглядит так:
switch (значение) {
case вариант1:
// код
break;
case вариант2:
// код
break;
case вариант3:
// код
break;
default:
// код
break;
}
Оператор перейдёт к коду под меткой с вариантом, который совпадает со значением. Если такого варианта нет - выполнится код под меткой default. Особенности и требования:
switchпринимает целочисленные значения и перечисленияenumcaseможет быть только целочисленной константой, известной на этапе компиляцииcaseне должны повторятьсяdefault(по умолчанию) будет выполнен, если не найдётся нужный вариант. Необязательная метка
break #
Оператор break (прерывать) позволяет покинуть тело switch в конце метки или в любом другом месте:
int var;
switch (var) {
case 0:
if (условие) break;
// ...
break;
case 1:
break;
}
Fallthrough #
Если не ставить break в конце кода метки - программа перейдёт к выполнению кода следующей по порядку метки, этот переход называется fallthrough (провал) - из одной метки в другую. Провал можно использовать для создания сложных конструкций:
int var;
switch (var) {
case 0:
// выполнить, если var == 0
break;
case 1:
// выполнить, если var == 1
break;
case 2:
// выполнить, если var == 2
// fall
case 3:
// выполнить, если var == 2 или var == 3
// fall
case 4:
// выполнить, если var == 2, var == 3 или var == 4
break;
}
Но если просто "забыть" поставить break - программа будет работать некорректно!
Область видимости #
Код между метками имеет общую область видимости, так как по сути находится внутри одного блока кода. Это означает, что нужно очень аккуратно работать с локальными переменными:
int var;
switch (var) {
case 0:
int a;
break;
case 1:
int a; // ошибка компиляции, такая переменная уже есть выше
break;
}
Более того, вот такой код некорректен:
int var = 1;
switch (var) {
case 0:
int a = 1;
break;
case 1:
// foo(a);
a = 2;
break;
}
Здесь в кейсе 1 используется переменная, объявленная в кейсе 0. Внутри кейса 1 она существует в памяти, но не инициализирована - содержит "случайное" значение. Данный код имеет неопределённое поведение - он может компилироваться/не компилироваться на разных версиях и настройках компилятора (в любом случае должен выдать предупреждение), может скомпилироваться и просто не работать - например кейс 1 вообще не будет выполняться или компилятор его просто вырежет.
Для решения всех проблем достаточно обернуть код под меткой в блок {} - это скроет локальные переменные от остальных кейсов:
switch (var) {
case 0: {
int a;
} break;
case 1: {
int a;
} break;
}
Если в коде case есть локальные переменные, обязательно оборачивайте его в блок кода!
Скорость работы #
Логика switch-case похожа на цепочку if-else, но компилятор может реализовать её по-разному: таблицей переходов, деревом сравнений или обычной последовательностью проверок. Выбор зависит от количества и плотности значений case, настроек оптимизации и платформы. Это приводит к тому, что конструкция на switch-case может выполняться сильно быстрее, чем голый if-else: в цепочке сравнений проверяется каждое условие (чем дальше верное условие - тем медленнее выполняется весь блок), а оптимизированный switch может буквально "выбрать" результат за одинаковое и фиксированное время.
Диапазон значений в case #
В GCC и Clang варианты для case можно задавать диапазоном значений. Это нестандартное расширение, которое может не работать в другом компиляторе:
switch (a) {
case 0 ... 100: b = 1; break;
case 101 ... 200: b = 2; break;
case 201 ... 300: b = 3; break;
case 301 ... 400: b = 4; break;
case 401 ... 500: b = 5; break;
case 501 ... 600: b = 6; break;
}
Этот пример равносилен конструкции на if-else:
if (a >= 0 && a <= 100) b = 1;
else if (a <= 200) b = 2;
else if (a <= 300) b = 3;
else if (a <= 400) b = 4;
else if (a <= 500) b = 5;
else if (a <= 600) b = 6;
Switch + enum #
Довольно частый сценарий использования switch - разбор и реакция на значение переменной, которая получила своё значение где-то в другом месте программы. Например - обработка текущего режима, который выбран кнопкой:
enum Modes {
Mode1,
Mode2,
Mode3,
};
Modes mode;
// ...
switch (mode) {
case Modes::Mode1:
break;
case Modes::Mode2:
break;
case Modes::Mode3:
break;
}
Здесь есть особенность: если в "кейсах" перечислены не все константы из enum, то компилятор может выдать предупреждение, что обработаны не все варианты. Чтобы скрыть предупреждение, достаточно указать default:
switch (mode) {
case Modes::Mode1:
break;
default:
break;
}
Дополнительно #
Дополнительный контент доступен владельцам набора GyverKIT и по подписке, подробнее читай здесь. Блок содержит:
- Тезисы, Вопросы, Задания
- 4 блоков кода
Полезные страницы #
- Набор GyverKIT – наш большой стартовый набор Arduino, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту (alex@alexgyver.ru)
- Поддержать автора за работу над уроками