Инструкция if-else
Разобравшись с отношениями и логическими операторами, можно переходить к изучению инструкции if - else.
Её основное предназначение состоит в том, чтобы программа имела возможность выполнять те или иные действия в зависимости от дополнительных условий.
В качестве примера рассмотрим программу сложения двух целых чисел, написанную в третьем уроке. Продублирую её код здесь.
Листинг 1. Программа sum2 v.1.0
#include <stdio.h>
int main(void)
{
int a = 0, b = 0, res;
scanf("%d %d", &a, &b);
res = a + b;
printf("%d + %d = %d\n", a, b, res);
return 0;
}Эта программа никак не проверяет данные, введённые пользователем. Поэтому, если ввести, например, числа 3.14 2.71, то получим следующий результат:

Рис.1 Результат работы программы sum2 v.1.0 при вводе некорректных данных
Проблема, как мы уже понимаем, возникает из-за того, что формат входных данных не соответствует формат-строке "%d %d". Поэтому функция scanf запишет значение в переменную a и завершится. Давайте это проверим.
Надеюсь, вы помните, что функция scanf в результате своей работы возвращает целое число — количество переменных, в которые она успешно записала значения из входного потока. Модифицируем наш код, чтобы программа выводила значение, возвращаемое функцией scanf, на экран.
Листинг 2. Программа sum2 v.1.1
#include <stdio.h>
int main(void)
{
int a = 0, b = 0, res, values_read = 0;
// значение, возвращаемое функцией scanf,
// присваиваем переменной values_read
values_read = scanf("%d%d", &a, &b);
printf("Number of values read: %d\n", values_read);
res = a + b;
printf("%d + %d = %d\n", a, b, res);
return 0;
}

Рис.2 Результат работы программы sum2 v.1.1
Как видите, функция scanf успешно считала только одно значение. Понятно, что в таком случае производить дальнейшие вычисления бесполезно, т.к. нет корректных входных данных.
Возникает вопрос, как научить программу выполнять различные действия в зависимости от значения переменной values_read: если values_read == 2, то производить сложение, а иначе выводить ошибку.
Вот здесь нам и пригодится инструкция if - else.
Базовый синтаксис if - else
Листинг 3. Синтаксис инструкции if - else
if (управляющее_выражение)
инструкция_1; // основная ветка
else
инструкция_2; // побочная ветка / else-веткаКстати, если перевести ключевые слова if и else на русский язык, то синтаксис становится ещё более понятным:
ЕСЛИ (управляющее_выражение)
инструкция_1;
ИНАЧЕ
инструкция_2;Как работает if - else
Тут всё довольно просто. Сперва вычисляется управляющее_выражение. Если получается любое ненулевое значение (т.е. ИСТИНА), то выполняется инструкция_1, а иначе — выполняется инструкция_2.
Управляющее выражение часто называют просто условием и говорят, что если условие истинно, то выполняется основная ветвь инструкции if-else, а если условие ложно, то — побочная ветвь.
Посмотрите на рисунок.

Рис 3. Блок-схема инструкции if-else
Инструкция if-else как бы создаёт развилку на которой поток выполнения программы может пойти по одной из двух ветвей. Важный момент состоит в том, что всегда будет выполнена либо основная, либо побочная ветка инструкции if-else.
Разберём парочку простых примеров.
Листинг 4.
#include <stdio.h>
int main(void)
{
if (1 < 2)
printf("TRUE!\n");
else
printf("FALSE!\n");
return 0;
}Эта программа выведет TRUE!, т.к. управляющее выражение 1 < 2 истинно.
Листинг 5.
#include <stdio.h>
int main(void)
{
if (3.14)
printf("TRUE!\n");
else
printf("FALSE!\n");
return 0;
}Эта программа тоже выведет TRUE!. В этом примере управляющее выражение вычислять не требуется, т.к. оно уже записано в виде числового значения 3.14. Т.к. это не нулевое значение (т.е. истина), то будет выполнена основная ветка конструкции if-else.
Возвращаемся к программе sum2. Добавим в неё инструкцию if-else, которая будет проверять значение переменной values_read.
Листинг 6. Программа sum2 v.1.2
#include <stdio.h>
int main(void)
{
int a = 0, b = 0, res, values_read = 0;
values_read = scanf("%d%d", &a, &b);
if (values_read == 2) {
res = a + b;
printf("%d + %d = %d\n", a, b, res);
} else {
printf("Error! Number of values read: %d\n", values_read);
}
return 0;
}В переменную values_read сохраняется количество переменных, которые записала функция scanf. Если scanf записала два значения, то управляющее выражение values_read == 2 будет истинным, а значит выполнится основная ветка условной конструкции if-else. Иначе будет выполняться инструкция из ветки else (например, если считано всего одно значение).
Прежде чем рассказать вам о том, зачем в программу добавлены фигурные скобки, убедимся, что теперь программа правильно обрабатывает случай неправильного ввода.

Рис 4. Проверка работы программы sum2 v.1.2 при разных входных данных
А теперь разберёмся с фигурными скобками.
Составные инструкции
Важно!
В каждой ветке if-else можно записать только одну инструкцию.
Но что делать, если внутри ветки требуется выполнить несколько инструкций? В таких случаях эти несколько инструкций нужно "обернуть" в фигурные скобки {} и тогда компилятор будет их воспринимать как одну составную инструкцию (как единый блок инструкций).
Так как в основной ветке нам нужно было выполнить две инструкции, то пришлось их "обернуть" в фигурные скобки, чтобы сделать из них составную инструкцию.
Листинг 7.
// две отдельные инструкции
res = a + b;
printf("%d + %d = %d\n", a, b, res);
// одна единая составная инструкция
{
res = a + b;
printf("%d + %d = %d\n", a, b, res);
}Чувствую, что у вас возник немой вопрос: "В ветке else всего одна инструкция. Зачем её оборачивать в фигурные скобки?"
С формальной точки зрения, конечно, скобки в этой ветке не нужны. Программа будет корректно работать и без них. Но я всё-таки рекомендую вам всегда использовать фигурные скобки в конструкции if - else, даже если в них нет необходимости.
Рекомендация!
Всегда используйте {} в ветках if-else, даже если внутри будет всего лишь одна инструкция.
Во-первых, вам не придётся их писать потом, когда вам вдруг потребуется добавить ещё несколько инструкций в ветку. Во-вторых, так получается более наглядный и понятный код, который убережёт вас от случайных ошибок (см. далее Листинг 12).
Сокращённая форма if - else
Ветка else в конструкции if-else не является обязательной. И если она нам не нужна, то её можно убрать. Получится сокращённая форма инструкции if-else или инструкция if.
Базовый синтаксис инструкции if представлен ниже:
Листинг 8.
if (управляющее_выражение)
инструкция_1; // основная ветка Эта инструкция работает аналогично if-else, но с небольшим изменением, связанным с отсутствием побочной ветки else.
Вычисляется значение управляющего выражения. Если получается ИСТИНА, то выполняется инструкция_1 из основной ветки, а если ложь, то инструкции из основной ветки игнорируются. А выполнение переходит к следующей инструкции, расположенной после инструкции if.
Посмотрите на рисунок.

Рис.5 Блок-схема инструкции if
Здесь, как и в полной форме, тоже есть развилка, но только побочная ветка пустая (не содержит инструкций).
Напишем программу, которая выводит на экран модуль введённого числа.
Листинг 9. Программа вычисления модуля числа
#include <stdio.h>
int main(void)
{
double x;
scanf("%lf", &x);
if (x < 0) {
x = -x;
}
printf("%f\n", x);
return 0;
}Эта программа — яркая иллюстрация того случая, когда сокращённая форма более удобна, нежели полная форма if-else.
Если пользователь ввёл отрицательное число, то у него надо изменить знак, что и происходит в основной ветке if. А вот если он ввёл нуль или положительное число, то с числом ничего делать не нужно, а значит ветвь else в данном конкретном случае нам вовсе не нужна. Программа же в этом случае пропускает всё, что записано в основной ветке, и переходит к выполнению следующей инструкции.
Можно было бы использовать и полную форму, но получилась бы менее изящная программа.
Например, такая:
Листинг 10.
#include <stdio.h>
int main(void)
{
double x;
scanf("%lf", &x);
if (x < 0) {
printf("%f\n", -x);
} else {
printf("%f\n", x);
}
return 0;
}Кстати, сокращённую форму довольно часто используют для проверки корректности входных данных. Например, программу из sum2 v.1.2 (Листинг 6) можно переписать следующим образом:
Листинг 11. Программа sum2 v.2.0
#include <stdio.h>
int main(void)
{
int a = 0, b = 0, res, values_read = 0;
values_read = scanf("%d%d", &a, &b);
if (values_read != 2) {
printf("Error! Number of values read: %d\n", values_read);
return -1;
}
res = a + b;
printf("%d + %d = %d\n", a, b, res);
return 0;
}Здесь мы проверяем количество значений, считанных функцией scanf, и если оно не равно двум, то выводим сообщение об ошибке. Затем сразу вызываем инструкцию return -1;, чтобы завершить выполнение функции main, а значит и выполнение всей программы. При этом, чтобы сообщить операционной системе, что программа завершилась из-за ошибки, мы возвращаем значение -1, а не 0 как мы обычно делаем. Такой подход называется ранние возраты (early returns). Его суть заключается в том, чтобы проверить необходимые условия сразу в начале программы/функции и если они не выполнены, то завершить программу/функцию. Здесь инструкция if выступает как бы в роли охранника на входе в здание. Если ему что-то не нравится (условия не выполняются), он тебя сразу выпроваживает.
В программе sum2 v.1.2 (Листинг 6) основные вычисления были спрятаны где-то внутри инструкции if, что не очень наглядно. Программа sum2 v.2.0 (Листинг 11) выглядит более изящно и логично. В ней сразу видны три основных блока:
- объявление переменных и ввод данных;
- проверка корректности введённых данных;
- основные вычисления и вывод результата;
Вложенные инструкции if-else
Важно!
В ветках if-else можно использовать любые инструкции языка Си, в том числе и if-else.
Для демонстрации этой возможности, давайте решим задачу про Квадранты из прошлого урока.
По координатам точки на плоскости определить, какому квадранту она принадлежит. Для определённости предполагаем, что ни одна из координат не равна нулю.
Листинг 12. Программа для определения квадранта
#include <stdio.h>
int main(void)
{
double x, y;
scanf("%lf%lf", &x, &y);
if (x > 0) {
// вложенный if-else
if (y > 0) {
printf("%d\n", 1);
} else {
printf("%d\n", 4);
}
} else {
// ещё один вложенный if-else
if (y > 0) {
printf("%d\n", 2);
} else {
printf("%d\n", 3);
}
}
return 0;
}Здесь внешняя инструкция if-else проверяет справа или слева от нуля находится точка по оси Oх, а вложенные if-else проверяют выше или ниже нуля расположена точка по оси Oy.
Рекомендация!
Старайтесь не увлекаться вложенными конструкциями if-else. Они делают код менее понятным. Если внутри вложенной конструкции if-else вы хотите добавить ещё одну конструкцию if/if-else, то вам надо подумать о том, как сделать код проще и понятнее.
Говоря о вложенных инструкциях if-else, необходимо упомянуть про один важный нюанс.
Важно!
Ветка else всегда относится к ближайшему if, у которого нет своего else.
Разберём следующую демонстрационную программу, которая вычисляет стоимость покупки с учётом скидок по следующим правилам:
- для покупок до
10000рублей применяется скидка, накопленная на клубной карте покупателя (значение хранится в переменнойdiscount); - если клубная карта отсутствует, то в переменной
discountбудет значение0; - для покупок от
10000рублей применяется фиксированная скидка в8%.
Листинг 13.
#include <stdio.h>
int main(void)
{
double critical_sum = 10000,
fix_discount = 0.08,
discount = 0.05;
double price;
scanf("%lf", &price);
if (price < critical_sum)
if (discount > 0)
price = price * (1 - discount);
else
price = price * (1 - fix_discount);
printf("Final purchase price: %.2f!\n", price);
return 0;
}Давайте проанализируем этот код. Согласно указанному выше нюансу, ветка else относится к ближайшей инструкции if, т.е. к if (discount > 0), а не к if (price < critical_sum), как того хотел программист (судя по отступам), написавший такой код.
А значит, если мы введём цену больше или равную 10000, то выражение price < critical_sum окажется ложным. И так как у этого if отсутствует else-ветка, то все дальнейшие проверки будут пропущены и мы перейдём к следующей инструкции, где будет выведена итоговая цена товара и никакие скидки не будет применены.
Понятно, что для исправления этой программы нужно сделать так, чтобы else относилась к первой инструкции if, а не ко второй. Для этого мы можем с помощью фигурных скобок явно указать вложенность конструкций:
Листинг 14.
#include <stdio.h>
int main(void)
{
double critical_sum = 10000,
fix_discount = 0.08,
discount = 0.05;
double price;
scanf("%lf", &price);
if (price < critical_sum) {
if (discount > 0) {
price = price * (1 - discount);
}
} else {
price = price * (1 - fix_discount);
}
printf("Final purchase price: %.2f!\n", price);
return 0;
}Теперь программа работает корректно, а мы с вами получили ещё один пример того, что с фигурными скобками лучше перебдеть, чем недобдеть. Добавление фигурных скобок вам ничем не грозит, а вот их отсутствие может привести к случайным ошибкам. Заметить такую ошибку будет довольно сложно.
Разберём ещё один сценарий использования вложенных инструкций if-else.
Лесенка из вложенных if-else
Давайте напишем программу для перевода отметки из 100 балльной шкалы в пятибалльную. На вход программы поступает число от 0 до 100.
Правила соответствия между шкалами:
90—100баллов — пятёрка;70—89баллов — четвёрка;50—69баллов — тройка;0—49баллов — двойка.
Мы могли бы написать несколько отдельных конструкций if. Это самый "лобовой" способ.
Листинг 15.
#include <stdio.h>
int main(void)
{
int score, new_score;
scanf("%d", &score);
if (score >= 90) {
new_score = 5;
}
if (score >= 70 && score < 90) {
new_score = 4;
}
if (score >= 50 && score < 70) {
new_score = 3;
}
if (score <= 49) {
new_score = 2;
}
printf("score: %d\n", new_score);
return 0;
}Такой способ требует от нас шесть сравнений и четыре инструкции if. Но если задействовать ветку else, то мы сможем сократить количество сравнений.
Смотрите, если условие из первого if не выполнены (т.е. score не больше и не равно 90), то во втором if условие score < 90 заведомо верное.
Аналогично, если условия и в первом if и втором if ложны, то в третьей инструкции if условие score < 70 заведомо истинно. Такое же рассуждение будет верно и для оставшейся инструкций if.
Поэтому код из Листинга 15 можно переписать в виде следующей лесенки (каскада) из вложенных конструкций if-else.
Листинг 16.
#include <stdio.h>
int main(void)
{
int score, new_score;
scanf("%d", &score);
if (score >= 90) {
new_score = 5;
} else {
if (score >= 70) {
new_score = 4;
} else {
if (score >= 50) {
new_score = 3;
} else {
new_score = 2;
}
}
}
printf("score: %d\n", new_score);
return 0;
}В этом варианте всего три сравнения и три инструкции if. Экономия налицо. =)
Обычно такие каскады записывают в более компактном виде:
Листинг 17.
#include <stdio.h>
int main(void)
{
int score, new_score;
scanf("%d", &score);
if (score >= 90) {
new_score = 5;
} else if (score >= 70) {
new_score = 4;
} else if (score >= 50) {
new_score = 3;
} else {
new_score = 2;
}
printf("score: %d\n", new_score);
return 0;
}Такой код выглядит более наглядно и понятно. И это редкий случай, когда лучше не ставить лишние фигурные скобки.
Посмотрите на блок-схему получившейся конструкции:

Рис.6 Блок-схема каскада из инструкций if-else
Она вам ничего не напоминает? Да, это похоже на продвинутую версию инструкции switch-case, где вместо заранее заданного набора меток можно использовать условные выражения, которые будут проверяться последовательно.
Практика
Подумайте, всегда ли можно заменить
if (a != 0)наif (a), еслиaэто какая-либо переменная типаint,float,doubleилиchar.Перепишите программу определения квадранта (Листинг 12) с использованием каскада вложенных инструкций
if-else.Перепишите программу определения квадранта (Листинг 12) без использования вложенных инструкций
if-else.
Исследовательские задачи для хакеров:
В языке Си существует ещё одна конструкция, работающая с условными выражениями — это тернарный условный оператор
? :. Самостоятельно разберитесь, как он работает.Мы исправили только часть потенциальных проблем в программе сложения двух целых чисел. Подберите примеры входных данных, на которых программа из Листинга 11 будет выдавать неправильный результат. Свои варианты напишите в комментарии к этому уроку.
Подумайте, можно ли любой кусок кода с вложенными
if-elseпереписать без использования вложенности?
Дополнительные материалы
1. Дополнительные задачи на условный оператор из курса по С++ от Яндекса
2. Используя каскад из инструкций if-else можно заменить любую инструкцию switch-case. Вот пример, основанный на задаче из урока про switch-case:
switch (answer) {
case 'B':
case 'b':
printf("GOOD!\n");
break;
case 'A':
case 'a':
case 'C':
case 'c':
case 'D':
case 'd':
printf("BAD!\n");
break;
default:
printf("ERROR!\n");
break;
}
// аналог на if-else
if (answer == 'B' || answer == 'b') {
printf("GOOD!\n");
} else if (answer == 'A' || answer == 'D' || answer == 'C' ||
answer == 'a' || answer == 'd' || answer == 'c') {
printf("BAD!\n");
} else {
printf("ERROR!\n");
}И хотя мы можем переписать любой switch-case с помощью if-else, но так делать не рекомендуется. Во-первых, это выглядит менее красиво, во-вторых, это стрельба из пушки по воробьям.