Menu

Оператор switch в C: case, break и проваливание

Как оператор switch в C выбирает одну ветку из списка констант: метки case, зачем нужен break, намеренное и случайное проваливание, default, switch по символам и перечислениям и когда switch лучше цепочки else if.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Одно значение, много констант

Цепочка 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, когда в проверках участвуют диапазоны, несколько переменных или что угодно, кроме равенства константе времени компиляции.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ