Содержание
Оператор switch выбирает одну из нескольких веток кода по значению выражения. Он заменяет длинную цепочку if ... else if ... else, когда одна и та же переменная сравнивается с набором заранее известных значений: пункт меню, день недели, код команды.
Синтаксис switch
switch (выражение) {
case значение1:
// код для значения1
break;
case значение2:
// код для значения2
break;
default:
// код для всех остальных значений
}
Программа вычисляет выражение в скобках один раз, находит метку case с таким же значением и продолжает выполнение с неё. Если подходящей метки нет, выполняется ветка default. Если нет и её, switch не делает ничего.
Пример: день недели
#include <iostream>
int main() {
int day;
std::cin >> day;
switch (day) {
case 1:
std::cout << "Понедельник\n";
break;
case 2:
std::cout << "Вторник\n";
break;
case 3:
std::cout << "Среда\n";
break;
default:
std::cout << "Другой день\n";
}
}
Для числа 2 программа выведет «Вторник», для числа 7 — «Другой день».
Зачем нужен break
break завершает switch. Без него выполнение не останавливается на следующей метке, а идёт дальше — в код соседних веток. Это называют «проваливанием» (fallthrough).
int mark = 4;
switch (mark) {
case 5:
std::cout << "Отлично\n";
case 4:
std::cout << "Хорошо\n";
case 3:
std::cout << "Удовлетворительно\n";
}
Программа напечатает две строки: «Хорошо» и «Удовлетворительно». Метка case 4 — это только точка входа, а не отдельный блок. Забытый break — самая частая ошибка при работе со switch.
Если проваливание задумано, скажите об этом явно атрибутом [[fallthrough]] — предупреждение исчезнет, а читателю кода будет понятно, что break пропущен намеренно:
switch (level) {
case 3:
unlockBonus();
[[fallthrough]];
case 2:
unlockLevel();
break;
}
Несколько значений для одной ветки
Метки можно ставить подряд. Так несколько значений обрабатываются одним кодом:
switch (month) {
case 12:
case 1:
case 2:
std::cout << "Зима\n";
break;
case 3:
case 4:
case 5:
std::cout << "Весна\n";
break;
default:
std::cout << "Лето или осень\n";
}
Здесь проваливание работает на Вас: между метками нет кода, поэтому атрибут [[fallthrough]] не нужен.
Ветка default
default выполняется, когда ни одна метка не подошла. Ветку можно не писать, но лучше оставить: в ней удобно сообщить о неожиданном значении.
switch (command) {
case 'q':
quit();
break;
default:
std::cout << "Неизвестная команда\n";
}
default принято ставить последним, хотя язык разрешает любое место.
Что можно проверять в switch
Выражение в скобках должно быть целого типа или перечислением: int, char, long long, bool, enum. После каждой метки case должна стоять константа, известная во время компиляции.
| Можно | Нельзя |
|---|---|
case 3: | case x:, если x — обычная переменная |
case 'a': | case "строка": |
case Color::red: | case 2.5: |
case 2 + 3: | две одинаковые метки в одном switch |
Строки std::string и дробные числа в switch проверить нельзя — для них остаётся if ... else if. Диапазон вроде «от 1 до 10» одной меткой тоже не записать.
Переменные внутри case
Объявить переменную с инициализацией прямо после метки не получится: компилятор сообщит, что переход к следующей метке «перепрыгивает» через инициализацию.
switch (shape) {
case 1:
double radius = 2.0; // ошибка компиляции, если ниже есть другие метки
std::cout << 3.14159 * radius * radius << '\n';
break;
case 2:
std::cout << "Квадрат\n";
break;
}
Заключите код ветки в фигурные скобки — у переменной появится своя область видимости:
switch (shape) {
case 1: {
double radius = 2.0;
std::cout << 3.14159 * radius * radius << '\n';
break;
}
case 2:
std::cout << "Квадрат\n";
break;
}
switch с инициализатором
Начиная с C++17 в скобках можно объявить переменную, которая нужна только внутри switch:
switch (int code = readCode(); code) {
case 0:
std::cout << "Успех\n";
break;
default:
std::cout << "Ошибка " << code << '\n';
}
После закрывающей скобки switch переменной code уже не существует.
switch и перечисления
switch особенно удобен вместе с enum class: значения имеют имена, а компилятор следит, чтобы Вы ничего не забыли.
enum class Light { red, yellow, green };
void show(Light light) {
switch (light) {
case Light::red:
std::cout << "Стойте\n";
break;
case Light::yellow:
std::cout << "Ждите\n";
break;
case Light::green:
std::cout << "Идите\n";
break;
}
}
Если убрать одну из веток и не писать default, GCC и Clang с флагом -Wall предупредят: enumeration value 'green' not handled in switch. Это защищает от ошибки, когда в перечисление добавили новое значение, а обработать его забыли.
switch или if-else
| Ситуация | Что выбрать |
|---|---|
| Одна переменная сравнивается с набором констант | switch |
Значения — имена из enum | switch |
Условия с диапазонами: x > 10, x <= 0 | if ... else if |
| Сравниваются строки или дробные числа | if ... else if |
| В условиях участвуют разные переменные | if ... else if |
Частые ошибки
- Забыли
break. Выполняются лишние ветки. Проверяйте каждую ветку и включайте предупреждения о проваливании. - Написали условие в метке. Запись
case x > 5:не проверяет диапазон: послеcaseдолжна стоять константа, и компилятор ответит ошибкойcase value is not a constant expression. - Объявили переменную без фигурных скобок. Компилятор сообщает, что переход к метке обходит инициализацию:
jump bypasses variable initialization. - Сравнивают строки.
switchработает только с целыми значениями и перечислениями.
Коротко
switchвыбирает ветку по значению целого выражения или перечисления.- Каждую ветку заканчивайте
break, иначе выполнение «провалится» дальше. defaultобрабатывает все значения, для которых нет метки.- Для переменных внутри ветки нужны фигурные скобки.
- Для диапазонов, строк и дробных чисел используйте
if ... else if.
Все условные конструкции вместе — if, else if, switch и тернарный оператор — разобраны в уроке Условные конструкции в C++, там же есть задачи с автоматической проверкой.