Условный оператор if/else if/else

Конструкция if/else if/else — это способ научить программу принимать решения на основе логических условий.

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

Программа, которая всегда делает одно и то же, бесполезна — реальные вычисления требуют ветвления: если шаг сошёлся, остановиться; если значение отрицательно, скорректировать; если итераций слишком много, прервать. Fortran предлагает ясный и структурный синтаксис условий, выросший из блочного IF ещё FORTRAN 77. Этот урок разбирает все формы условного оператора — от однострочной до полной блочной цепочки с именами конструкций — и показывает, как избегать классических ошибок ветвления в численном коде.

Немного истории: от арифметического if к структурному

Чтобы понять, почему современный if в Fortran устроен именно так, полезно знать, от чего язык ушёл. В самом первом FORTRAN 1957 года ветвление выглядело пугающе: существовал так называемый арифметический IF вида IF (выражение) n1, n2, n3, который вычислял арифметическое выражение и прыгал на одну из трёх меток в зависимости от того, отрицательно оно, равно нулю или положительно. Никакого блока, никакого then — только три номера строк, на которые управление улетало через go to. Такой код было почти невозможно читать: логика «расползалась» по программе, а вернуться к структуре алгоритма по тексту было нельзя. Это и есть классический «спагетти-код», с которым позже воевал Дейкстра в своей знаменитой заметке о вреде go to.

Структурный, или логический блочный IF, который мы изучаем здесь, появился значительно позже — он был узаконен стандартом FORTRAN 77 и стал тем, что сегодня воспринимается как норма. Идея была проста и революционна для своего времени: условие теперь возвращает не число, а логическое значение, а тело ветви — это явно очерченный блок между then и end if, а не разбросанные по программе строки. Благодаря этому алгоритм снова читается сверху вниз, вложенность видна глазом, а сопровождение перестаёт быть лотереей. Арифметический IF формально дожил аж до стандарта 2008 года как «устаревшая возможность», а затем был объявлен удалённым — современный код им не пользуется, и вам встретить его доведётся разве что в очень старых библиотеках. Понимание этой эволюции объясняет дух всех конструкций управления потоком в Fortran: они сознательно вытесняют переходы по меткам структурными блоками.

Блочный if: основная форма

Сердце ветвления — блочная конструкция if ... then ... end if. Логическое условие в скобках, блок операторов между then и end if выполняется, только если условие истинно.

program block_if
  implicit none
  real :: temperature
  temperature = 38.5
  if (temperature > 37.0) then
    print *, "Повышенная температура"
    print *, "Значение:", temperature
  end if
end program block_if

Вывод:

 Повышенная температура
 Значение:   38.5000000

Условие temperature > 37.0 возвращает logical; если оно .true., выполняется весь блок. Скобки вокруг условия обязательны, then — тоже. Закрывающее end if завершает конструкцию; блок может содержать сколько угодно операторов.

Ветви else и else if

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

program grade
  implicit none
  integer :: score
  character(len=20) :: result
  score = 75

  if (score >= 90) then
    result = "Отлично"
  else if (score >= 70) then
    result = "Хорошо"
  else if (score >= 50) then
    result = "Удовлетворительно"
  else
    result = "Неудовлетворительно"
  end if

  print *, trim(result)
end program grade

Вывод:

 Хорошо

Логика важна: при score = 75 первое условие (>= 90) ложно, второе (>= 70) истинно — выбирается «Хорошо», и проверка останавливается. Порядок ветвей имеет значение: будь условия в обратном порядке, любое значение попало бы в первую же широкую ветвь. Завершающий else необязателен, но часто полезен как «во всех прочих случаях».

Однострочный и логический if

Для одного-единственного действия существует краткая форма без then и end if: оператор пишется прямо после условия. Она удобна, но применима только к одному оператору.

program logical_if
  implicit none
  real :: x
  x = -4.0
  if (x < 0.0) x = -x        ! модуль: одна строка, без then
  print *, "Модуль:", x
  if (x > 100.0) print *, "Большое"   ! ничего не печатает
end program logical_if

Вывод:

 Модуль:   4.0000000

Однострочный if (условие) оператор выполняет ровно один оператор. Это компактно для простых случаев вроде взятия модуля, но как только действий становится больше одного, переходите к блочной форме — иначе код станет нечитаемым и подверженным ошибкам.

Именованные конструкции и вложенность

Условия можно вкладывать друг в друга, но глубокая вложенность быстро запутывает. Fortran позволяет давать конструкциям имена — это улучшает читаемость и помогает в сложных случаях. Имя ставится перед if через двоеточие и повторяется в end if.

program named_if
  implicit none
  real :: x, y
  x = 3.0
  y = -1.0

  check_x: if (x > 0.0) then
    check_y: if (y > 0.0) then
      print *, "Оба положительны"
    else check_y
      print *, "x положителен, y нет"
    end if check_y
  end if check_x
end program named_if

Вывод:

 x положителен, y нет

Имена check_x и check_y делают очевидным, какой end if закрывает какую конструкцию. В коротких условиях это излишне, но во вложенных блоках и особенно в циклах (следующие уроки) именование заметно повышает надёжность сопровождения.

Логический тип и природа условия

Ключевое отличие Fortran от языков семейства C кроется в типе условия. В C условие if — это любое целое выражение, и «истиной» считается любое ненулевое значение, а «ложью» — ноль. Поэтому в C компилируется (и работает) такая вещь, как if (n) или печально известная опечатка if (x = 5), где присваивание молча превращается в условие. В Fortran всё иначе: условие обязано иметь тип logical, у которого ровно два значения — .true. и .false.. Число в роли условия не пройдёт: if (n) для целого n — это ошибка компиляции, а не «истина при ненулевом». Точно так же присваивание внутри условия невозможно, потому что присваивание в Fortran — это оператор, а не выражение со значением. Целый класс ошибок, на которые в C ставят отдельные предупреждения компилятора, в Fortran просто не может возникнуть.

Сравнение значений тоже даёт результат типа logical. Операторы сравнения существуют в двух начертаниях: «символьном» (>, <, >=, <=, ==, /=), пришедшем со стандартом Fortran 90, и историческом «буквенном» (.gt., .lt., .ge., .le., .eq., .ne.), который вы встретите в старом коде. Оба варианта равноправны и означают одно и то же; в новых программах принято использовать символьную форму как более привычную глазу. Логические значения комбинируются операторами .and., .or., .not. и эквивалентностями .eqv./.neqv.. Обратите внимание на точки вокруг буквенных операторов — они не косметика, а часть синтаксиса: .and. без точек было бы воспринято как имя переменной. Эта особенность — наследие эпохи, когда пробелы в Fortran игнорировались, и язык нуждался в способе однозначно отделить ключевое слово-оператор от идентификатора.

Сравнение вещественных чисел: ловушка точности

Отдельного разговора заслуживает сравнение чисел типа real на равенство. Запись if (x == 0.3) выглядит безобидно, но почти наверняка сработает не так, как ожидается. Причина не в Fortran, а в самой природе чисел с плавающей точкой: большинство десятичных дробей, включая 0.1, 0.2 и 0.3, не представимы точно в двоичной форме, поэтому хранятся с микроскопической погрешностью. Классический пример — выражение 0.1 + 0.2, которое не равно 0.3 ни в Fortran, ни в C, ни в Python: результат отличается на величину порядка машинного эпсилон. Это означает, что прямое сравнение вещественных на == — источник коварных, плавающих от компилятора к компилятору багов.

Правильный подход — сравнивать не на точное равенство, а на близость: считать числа равными, если модуль их разности меньше некоторого малого порога. На псевдоуровне это условие вида «модуль разности меньше допуска». Выбор самого допуска — инженерное решение: слишком жёсткий порог отвергнет почти равные значения, слишком мягкий — спутает разные. В серьёзных численных кодах допуск часто привязывают к встроенной функции epsilon, дающей машинную точность выбранного типа, и масштабируют его по величине сравниваемых чисел. Эта дисциплина — не прихоть Fortran, а универсальное правило вычислительной математики, но именно в Fortran, где вещественные расчёты составляют суть программ, цена ошибки особенно высока.

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

Во что компилятор превращает if? В условные переходы на уровне машинного кода. Логическое выражение вычисляется в значение «истина/ложь», затем процессор по результату либо «проваливается» в блок, либо перепрыгивает через него инструкцией перехода. Цепочка else if разворачивается в последовательность таких проверок-переходов: как только одна оказалась истинной, выполняется её блок и происходит безусловный переход к end if, минуя остальные ветви. Именно поэтому выполняется только первая подходящая ветвь — это не магия, а структура переходов. Важная деталь для численного кода: Fortran не гарантирует короткого замыкания логических операторов так строго, как C. В выражении a .and. b компилятор вправе вычислить оба операнда, даже если a ложно. Поэтому нельзя полагаться, что (i > 0) .and. (array(i) > 0) защитит от обращения по индексу 0 — если важен порядок, разбивайте на вложенные if.

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

  • Расчёт на короткое замыкание. Fortran может вычислить оба операнда .and./.or.; защитная проверка индекса через .and. ненадёжна — вкладывайте if.
  • Однострочный if для нескольких действий. После однострочного if выполняется лишь один оператор; остальное выполнится всегда. Используйте блок.
  • Неверный порядок ветвей. Широкое условие выше узкого «съест» все случаи; упорядочивайте от частного к общему.
  • Сравнение real на == в условии. if (x == 0.3) ненадёжно; используйте порог.
  • Забытый then или end if. Блочная форма требует обоих; их отсутствие — ошибка компиляции.

Итоги

  • Блочная форма if (условие) then ... end if выполняет блок при истинном условии.
  • Цепочка else if выбирает первую истинную ветвь; else ловит остальные случаи.
  • Однострочный if (условие) оператор выполняет ровно один оператор — для простых случаев.
  • Конструкциям можно давать имена (name: if ...) для читаемости вложенных блоков.
  • Fortran не гарантирует короткого замыкания .and./.or. — не используйте его для защиты индекса.
  • Порядок ветвей важен: располагайте условия от частного к общему.
Проверьте себя
1. В цепочке if / else if / else сколько ветвей выполняется?
AВсе истинные
BРовно одна — первая истинная сверху вниз
CПоследняя истинная
DВсегда ветвь else
2. Почему нельзя полагаться на (i > 0) .and. (array(i) > 0) для защиты от индекса 0?
AЭто синтаксическая ошибка
BFortran не гарантирует короткого замыкания и может вычислить оба операнда
Carray(i) всегда равен нулю
DОператор .and. не существует