if выбирает, какую инструкцию выполнить, а не какое значение получить. Условие в скобках преобразуется к bool, при истине выполняется первая ветвь, при лжи — ветвь после else.

Зачем нужно

Пока программа считает одно выражение, выбирать не из чего. Как только ответ зависит от данных — например, делить можно, а делить на ноль нельзя — нужен способ выполнить разные действия:

if (y != 0) {
  std::cout << x / y << '\n';
} else {
  std::cout << "Error\n";
}

Выбирать между числом и строкой внутри одного выражения не пришлось: выбор сделан на уровне инструкций.

Как работает

Ветка else необязательна:

if (x > 0) {
  std::cout << x << '\n';
}

После if и после else синтаксически стоит ровно один оператор. Фигурные скобки превращают в этот оператор целый блок. Даже для одной инструкции ставим скобки — так границы видны и человеку, и через месяц себе.

Несколько вариантов — цепочка:

if (value == 0) {
  std::cout << "Zero\n";
} else if (value == 1) {
  std::cout << "One\n";
} else {
  std::cout << "Other\n";
}

Условия проверяются сверху вниз, и как только выбрана ветвь, остальные ветви цепочки пропускаются. else if — не отдельное ключевое слово, а обычный if, поставленный в ветвь else.

Несколько независимых if означают другое:

int value = 0;
 
if (value == 0) {
  value = 1;
}
if (value == 1) {     // сработает тоже
  std::cout << "One\n";
}

Выполнятся оба блока: первый изменил значение, второй независимо проверил уже изменённое. Замените второй if на else if — и его условие после сработавшей первой ветви проверяться не будет.

Условие — это выражение типа bool

Любое значение приводится к bool: ноль даёт false, любое ненулевое true. Отсюда две распространённые записи:

if (n) { ... }        // то же, что n != 0
if (!n) { ... }       // то же, что n == 0

Сравнение само по себе выражение, его не обязано «съедать» условие — см. Логические операции и короткое замыкание.

Объявление внутри условия

Переменную можно определить прямо в условии:

if (int x = 0) {
  std::cout << x << '\n';
} else {
  std::cout << "Zero\n";   // выполнится это
}

Проверяется значение созданного x. Здесь оно равно нулю, ноль приводится к false, поэтому работает else. Переменная доступна в обеих ветвях, но после всего условного оператора её имя уже недоступно. Инициализатор в такой форме обязателен: if (int x) недопустимо.

Приём удобен, когда значение нужно проверить и тут же использовать, не оставляя лишнее имя жить дальше по функции.

Тонкости

  • Лишняя точка с запятой ломает всё молча:

    if (x > 0);
    {
      std::cout << "Positive\n";   // печатается всегда
    }

    ; образовала пустой оператор — именно его выполняет if. Блок ниже существует сам по себе и выполняется независимо от условия.

  • Потерявшийся else относится к ближайшему if, у которого его ещё нет:

    if (true)
      if (false) std::cout << "A\n";
    else std::cout << "B\n";        // выведет B

    Отступ синтаксис не меняет — else принадлежит внутреннему if. Со скобками такая двусмысленность невозможна.

  • = вместо == в условии компилируется и почти всегда означает не то: if (x = 0) присваивает ноль и всегда ложно.

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

  • Писать ветви без фигурных скобок и потом добавить вторую строку.
  • Ставить ; сразу после условия.
  • Городить цепочку независимых if там, где нужна цепочка else if, и получать срабатывание нескольких веток подряд.
  • Проверять if (x == true) вместо if (x).