Одно значение, много констант
Цепочка else if, которая раз за разом сравнивает одну и ту же переменную с константами, — это шаблон, под который в C есть отдельный оператор:
if (choice == 1) { /* ... */ }
else if (choice == 2) { /* ... */ }
else if (choice == 3) { /* ... */ }
else { /* ... */ }
switch говорит то же самое прямее. Он один раз вычисляет выражение и сразу прыгает к подходящей метке.
Поменяйте choice и запустите ещё раз. Структура делает набор допустимых значений видимым с одного взгляда — это и есть настоящая причина предпочесть здесь switch.
Синтаксис и правила для меток case
switch (целочисленноеВыражение) {
case КОНСТАНТА_1:
инструкции
break;
case КОНСТАНТА_2:
инструкции
break;
default:
инструкции
break;
}
Четыре ограничения, которые накладывает C:
- Выражение switch должно иметь целочисленный тип —
int,char,short,longилиenum. Неfloat, неdoubleи не строка. - Каждая метка
caseдолжна быть константным выражением, известным на этапе компиляции.case n:, гдеn— переменная, ошибка;case 3 + 4:иcase MAX:(константа из#defineили перечисления) допустимы. - Никакие два case не могут иметь одинаковое значение. Дубликаты — ошибка компиляции, и это небольшая, но настоящая страховка.
- Case не умеет выражать диапазон.
case 1 ... 5:— расширение GCC, а не стандартный C; для диапазонов используйтеif.
default необязателен и может стоять в любом месте блока, хотя почти всеобщее соглашение — ставить его последним. Без default несовпавшее значение просто пропускает весь switch.
break: почему он не по желанию
Самое неожиданное в switch на C — то, что метки case являются целями перехода, а не коробками. Как только выполнение попало на метку, оно идёт дальше по всем инструкциям ниже, сквозь другие метки case, до break или до закрывающей скобки.
Первый switch печатает один, два и три — он совпал с case 1, а затем выполнил всё, что за ним. Второй печатает только один. Такое поведение называется проваливанием, и забытый break — классический баг switch в C: программа молча делает не то, вместо того чтобы не собраться.
Собирайте с gcc -Wall -Wextra -Wimplicit-fallthrough, и компилятор укажет на каждый case, проваливающийся в следующий.
break в последнем case технически избыточен — блок там и так заканчивается. Пишите его всё равно, чтобы добавленный позже ниже новый case не создал тихого проваливания.
Намеренное проваливание
Проваливание — это возможность, когда оно сделано намеренно. Составить метки друг под другом без инструкций между ними — обычный способ сказать «эти значения делают одно и то же»:
Три метки делят одно тело, без дублирования кода и без длинного условия с ||. О такой форме компиляторы не предупреждают, потому что проваливаться тут не через что.
Проваливание с инструкциями между метками — когда case 1 выполняется и затем намеренно продолжается в case 2 — встречается реже и заслуживает явного комментария, ведь читателю нужно сообщить, что отсутствующий break был решением:
switch (level) {
case 3:
printf("verbose\n");
/* fall through */
case 2:
printf("info\n");
/* fall through */
case 1:
printf("errors\n");
break;
}
/* fall through */ — тот самый комментарий, который распознаёт предупреждение GCC, так что он заодно гасит диагностику.
switch по символам
Поскольку char — целочисленный тип, классификация символов ложится сюда естественно:
Обратите внимание: в case 'a': одинарные кавычки — это символьная константа, то есть попросту целочисленное значение этого символа. case "a": был бы строкой и не скомпилируется.
switch по перечислениям
Лучшая пара в C — switch вместе с перечислением. Метки case становятся читаемыми именами, а компиляторы умеют предупреждать, когда switch обрабатывает не все элементы перечисления:
Здесь стоит перенять две вещи. Каждый case выполняет return, поэтому break не нужен: return выходит из функции целиком, а значит, заканчивает и switch. И здесь нет default: с -Wswitch (входящим в -Wall) отсутствие default заставит компилятор предупредить в тот день, когда кто-то добавит пятое состояние и забудет про эту функцию. default это бы скрыл.
Объявление переменных внутри switch
Тело switch — один блок, поэтому объявление в одном case видно (хотя и не обязательно инициализировано) в последующих. Прыжок через инициализацию в C является ошибкой:
switch (n) {
case 1:
int x = 10; // ошибка в C: метка не может так предшествовать объявлению
printf("%d\n", x);
break;
case 2:
printf("%d\n", x); // x здесь существует, но никогда не инициализировался
break;
}
Заключайте case в собственные фигурные скобки, когда ему нужны локальные переменные:
switch (n) {
case 1: {
int x = 10;
printf("%d\n", x);
break;
}
case 2:
printf("два\n");
break;
}
switch или if-else?
Берите switch, когда верно всё сразу: вы проверяете одно выражение, оно целочисленного типа и вы сравниваете его с фиксированными константами. Обработка меню, конечные автоматы на перечислениях, классификация символов и диспетчеризация команд — канонические случаи. Выигрыш — в читаемости, проверке на дублирующиеся значения и компиляторе, который может предупредить о необработанных элементах перечисления.
Берите if-else, когда в проверках участвуют диапазоны (score >= 90), числа с плавающей точкой, строки (нужен strcmp), несколько разных переменных или условия, соединённые через && и ||. Ничто из этого нельзя выразить меткой case.
Производительность редко бывает решающим фактором. Компиляторы превращают плотный switch в таблицу переходов, а разреженный — в сравнения, так что на горстке вариантов обе формы неотличимы. Выбирайте ту, которая выражает намерение.
Часто задаваемые вопросы
Как работает оператор switch в C?
switch (expr) один раз вычисляет целочисленное выражение, переходит к метке case, чья константа совпала, и выполняет код оттуда до break или до конца switch. Если ничего не совпало, управление переходит к default, когда он есть, а иначе весь блок пропускается.
Зачем в switch на C нужен break?
Потому что метки case — это цели перехода, а не отдельные блоки. Без break выполнение продолжается прямо в инструкции следующего case; это называется проваливанием. Пропущенный break — самая частая ошибка в switch на C, и gcc -Wimplicit-fallthrough на неё укажет.
Можно ли сделать switch по строке в C?
Нет. switch в C требует целочисленного выражения, поэтому значения int, char и enum подходят, а строки, числа с плавающей точкой и диапазоны — нет. Чтобы ветвиться по строке, используйте strcmp в цепочке if-else или сначала отобразите строку в перечисление и переключайтесь по нему.
Когда в C лучше switch, а когда if-else?
Используйте switch, когда сравниваете одно целое число или символ со списком фиксированных констант: команды меню, состояния перечисления, классификация символов. Используйте цепочку if-else, когда в проверках участвуют диапазоны, несколько переменных или что угодно, кроме равенства константе времени компиляции.