Условные операторы: if, else, switch

Урок разбирает ветвление в C: конструкцию if/else if/else, компактный тернарный оператор и многоветочный switch, где забытый break вызывает коварное «проваливание» вниз.
В switch без break выполнение «проваливается» в следующую ветку и идёт дальше, пока не встретит break или конец. Это поведение по умолчанию — и самая частая ошибка при работе со switch.

Базовое ветвление в C выглядит знакомо. Помните: условие истинно, если оно не равно нулю.

int score = 75;

if (score >= 90) {
    printf("Отлично\n");
} else if (score >= 60) {
    printf("Зачёт\n");
} else {
    printf("Незачёт\n");
}

Для коротких выборов есть тернарный оператор условие ? а : б — он возвращает одно из двух значений:

int a = 5, b = 8;
int max = (a > b) ? a : b;   // если a>b, то a, иначе b
printf("Максимум: %d\n", max);

Когда вариантов много и все сравниваются с одной переменной, удобен switch:

switch (grade) {
    case 'A': printf("Превосходно\n"); break;
    case 'B': printf("Хорошо\n");     break;
    case 'C': printf("Нормально\n");  break;
    default:  printf("Неизвестно\n");
}

Как работает под капотом

Ключевое слово в switch — break. Без него выполнение не останавливается на сработавшей ветке, а «проваливается» в следующую. Сравните два сценария:

С break (правильно):           Без break (проваливание):
  grade = 'B'                    grade = 'B'
    |                              |
    v                              v
  case 'A' -- нет                case 'A' -- нет
  case 'B' -> "Хорошо"           case 'B' -> "Хорошо"
    |                              |  (нет break!)
  break -> ВЫХОД                   v
                                 case 'C' -> "Нормально"  (тоже!)
                                   |
                                   v
                                 default -> "Неизвестно"  (и это!)

Иногда проваливание используют намеренно — например, чтобы несколько вариантов делали одно и то же: case 'a': case 'A': .... Но в 99% случаев забытый break — это баг.

Частые ошибки

  • Забытый break в switch. Выполнение проваливается в соседние ветки, и программа делает лишнее.
  • Присваивание вместо сравнения. if (x = 5) присваивает 5 и всегда истинно; нужно ==.
  • Лишняя точка с запятой. if (x > 0); — точка с запятой завершает if пустым телом, и следующий блок выполнится всегда.
  • switch только по целым. Нельзя написать case для строки или дробного числа — только целые и символы (которые тоже целые).

Best practices

  • Всегда добавляйте default в switch — он ловит непредусмотренные случаи.
  • Ставьте фигурные скобки даже для однострочного if: if (x) { ... }. Это спасает от ошибок при дописывании кода.
  • Если намеренно опускаете break ради проваливания — оставьте комментарий /* fall through */, чтобы было видно, что это не забывчивость.

Логику switch с проваливанием удобно прочувствовать на Python, где такого механизма нет — придётся смоделировать его явно через список веток.

# Моделируем switch с break и без него
def run_switch(grade, use_break):
    branches = ['A', 'B', 'C']
    started = False
    for b in branches:
        if b == grade:
            started = True
        if started:
            print("Ветка", b, "сработала")
            if use_break:
                break   # как break в C — выходим сразу

print("С break:")
run_switch('B', use_break=True)
print("Без break (проваливание):")
run_switch('B', use_break=False)

Та же логика на Python ▶ — видно, как без break выполнение «течёт» через все ветки ниже сработавшей. Именно это происходит в C при забытом break.

Истинность в C

За компактностью условий C скрывается простое правило, которое стоит усвоить раз и навсегда: истинно всё, что не равно нулю. Это значит, что if (n) срабатывает для любого ненулевого n, а if (!n) — только когда n равно нулю. Отсюда идиомы: проверка указателя if (p) вместо if (p != NULL), проверка строки на пустоту через первый символ. Но эта же гибкость порождает коварную ошибку: if (x = 5) присваивает 5 и, поскольку 5 ненулевое, всегда истинно. Многие компиляторы предупреждают о таком присваивании в условии — ещё один довод компилировать с -Wall. Современный стандарт добавил тип bool из <stdbool.h> со значениями true и false, и для ясности кода его стоит использовать.

Итоги

Ветвление в C — это if/else if/else, компактный тернарный оператор ? : и switch для множественного выбора по целому значению. Главная особенность switch — обязательный break: без него выполнение проваливается в соседние ветки. Защита от ошибок — фигурные скобки везде, обязательный default и явные комментарии при намеренном проваливании.

Проверьте себя
1. Что произойдёт в switch, если в ветке case забыть break?
AПрограмма не скомпилируется
BВыполнение остановится на этой ветке
CВыполнение «провалится» в следующие ветки и выполнит их код тоже
DSwitch перезапустится с начала
2. Что делает тернарный оператор (a > b) ? a : b?
AСкладывает a и b
BВозвращает большее из двух значений: a, если a > b, иначе b
CСравнивает строки
DВсегда возвращает a