Синтаксис си/с++

Управляющие конструкции: if/else, switch, тернарный оператор

Введение

После операторов переходим к конструкциям, управляющим порядком выполнения кода в зависимости от условий — if/else, switch, и тернарный оператор как компактная альтернатива простому if/else для выражений (а не операторов).

Концепция

if/else выполняет один из двух (или более, через цепочку else if) блоков кода в зависимости от истинности условного выражения — единственный оператор после if без явных фигурных скобок технически допустим синтаксисом, но менее безопасен при последующем редактировании кода. switch сравнивает значение выражения с несколькими константными вариантами (case), выполняя соответствующий блок кода — важная особенность switch в C/C++ — «проваливание» (fall-through), при котором выполнение продолжается в следующий case блок, если явно не прервано оператором break. Тернарный оператор (условие ? значение_если_истина : значение_если_ложь) — единственный в C/C++ оператор с тремя операндами, дающий значение (а не выполняющий блок операторов, как if), что делает его удобным для компактного выражения условного значения внутри более крупного выражения.

Пример кода

#include <iostream>

int main()
{
    int score = 75;

    if (score >= 90) {
        std::cout << "Отлично" << std::endl;
    } else if (score >= 70) {
        std::cout << "Хорошо" << std::endl;
    } else if (score >= 50) {
        std::cout << "Удовлетворительно" << std::endl;
    } else {
        std::cout << "Неудовлетворительно" << std::endl;
    }

    return 0;
}
// switch с явным break и демонстрацией намеренного fall-through
#include <iostream>

void describeDay(int dayNumber)
{
    switch (dayNumber) {
        case 6:
        case 7: // ОБА варианта (6 и 7) выполняют ОДИН И ТОТ ЖЕ код — намеренное "проваливание"
            std::cout << "Выходной день" << std::endl;
            break;
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
            std::cout << "Рабочий день" << std::endl;
            break;
        default: // выполняется, если значение не совпало НИ с одним явным case
            std::cout << "Некорректный номер дня" << std::endl;
            break;
    }
}
// Тернарный оператор — компактное выражение, а не оператор (используется ВНУТРИ выражения)
#include <iostream>

int main()
{
    int a = 5, b = 10;
    int max = (a > b) ? a : b; // компактная альтернатива четырёхстрочному if/else для получения значения

    std::cout << "Максимум: " << max << std::endl;

    // Вложенные тернарные операторы — формально допустимо, но снижает читаемость при чрезмерном использовании
    int grade = 75;
    std::string letter = (grade >= 90) ? "A" : (grade >= 70) ? "B" : (grade >= 50) ? "C" : "F";

    return 0;
}

Пояснения к коду

Цепочка if/else if/else показывает типичный, идиоматичный паттерн обработки нескольких взаимоисключающих диапазонов условий — каждое условие проверяется последовательно, и выполняется только первый блок, чьё условие оказалось истинным, с финальным else как запасным вариантом для случаев, не покрытых ни одним предыдущим условием. switch с двумя последовательными case 6:/case 7: без break между ними показывает намеренное, идиоматичное использование fall-through — оба значения должны привести к одному и тому же результату, и отсутствие break между этими двумя метками case (но не после них, где break явно присутствует) выражает это намерение компактно, без необходимости дублировать одинаковый код для обоих случаев. Тернарный оператор (a > b) ? a : b показывает его типичное использование — компактное вычисление одного из двух значений непосредственно в контексте присваивания, что было бы значительно более многословным при использовании полноценного if/else.

Подводные камни

  • Забытый break в блоке case switch, где fall-through НЕ является намеренным поведением — это одна из самых частых, классических ошибок в C/C++ коде, приводящая к непреднамеренному выполнению кода последующего case блока, и многие современные компиляторы выдают предупреждение для подозрительных случаев отсутствия break (особенно если последующий код не пуст), но это предупреждение легко проигнорировать без внимательной проверки каждого конкретного случая.
  • Написание if без фигурных скобок для единственного оператора, с последующим, неаккуратным добавлением второго оператора в «тот же» блок без добавления скобок при последующем редактировании кода — классическая ошибка, известная как «ошибка goto fail» по имени реального, печально известного случая в коде Apple, где отсутствие скобок привело к тому, что важная проверка безопасности фактически не выполнялась, и явное использование фигурных скобок для любого if/else/циклов независимо от количества операторов внутри — распространённая практика предотвращения именно этой категории ошибок.
  • Избыточное вложение тернарных операторов, создающее трудночитаемые, «нагруженные» выражения (как пример с letter для нескольких диапазонов оценок) — хотя такая конструкция формально корректна и компилируется, читаемость значительно страдает по сравнению с эквивалентной, более многословной, но понятной цепочкой if/else if, и тернарный оператор лучше подходит именно для простых, единичных условных выражений, а не для замены сложной, многоступенчатой условной логики.
  • Использование switch для значений, не являющихся целочисленными константами или перечислениями (например, попытка использовать switch для строк в чистом C/C++ без специальных библиотек) — switch в C/C++ принципиально ограничен сравнением с константными целочисленными значениями (включая enum, статья 523) известными на этапе компиляции, и для условной логики, основанной на сравнении строк или других, не подходящих для switch типов, нужна цепочка if/else if с явными вызовами сравнения (например, strcmp для C-строк или == для std::string).