Множественный выбор: select case

Когда вариантов много и они дискретны, select case читается яснее длинной цепочки else if.

Конструкция select case выбирает один из блоков кода, сравнивая значение выражения с набором констант или диапазонов; работает с целыми, символьными и логическими значениями.

Длинная лестница else if, проверяющая одну и ту же переменную на разные значения, выглядит громоздко и плохо читается. Для таких ситуаций Fortran предлагает select case — конструкцию множественного выбора, аналог switch в C, но безопаснее и выразительнее. Она не только короче, но и эффективнее: компилятор может построить таблицу переходов вместо череды сравнений. Этот урок разбирает все возможности select case: одиночные значения, списки, диапазоны и важное отличие от сишного switch — отсутствие «проваливания».

Зачем понадобилась отдельная конструкция

Возникает законный вопрос: если есть if с цепочкой else if, зачем языку вторая конструкция выбора? Ведь всё, что делает select case, можно записать через else if. Ответ складывается из трёх причин — читаемости, безопасности и производительности, — и каждая по отдельности уже весома.

Во-первых, читаемость намерения. Когда вы видите select case (day), сразу ясно: дальше идёт разбор одной величины по дискретным вариантам. Цепочка else if такого обещания не даёт — каждое условие может проверять что угодно, и читателю приходится вглядываться, действительно ли это разбор одной переменной или хитрая последовательность независимых проверок. Конструкция select case кодирует замысел программиста прямо в синтаксисе, и это уменьшает когнитивную нагрузку при чтении чужого кода. Во-вторых, безопасность: компилятор знает, что метки — это константы, и может предупредить о пересечении диапазонов или о дублировании значений; в цепочке else if логическая ошибка вида «второе условие никогда не достижимо, потому что его поглотило первое» останется молчаливой. В-третьих, скорость: о ней подробно ниже, но суть в том, что выражение вычисляется единожды, а переход к нужной ветви может произойти за один шаг. Конструкция select case появилась в стандарте Fortran 90 именно для того, чтобы дать этим трём свойствам отдельную, чистую форму, не нагружая универсальный if.

Базовый синтаксис

Выражение в select case (...) вычисляется один раз, затем его значение сопоставляется с метками case. Выполняется блок совпавшей метки; case default ловит всё остальное.

program day_type
  implicit none
  integer :: day
  day = 6
  select case (day)
  case (1, 2, 3, 4, 5)
    print *, "Будний день"
  case (6, 7)
    print *, "Выходной"
  case default
    print *, "Неверный номер дня"
  end select
end program day_type

Вывод:

 Выходной

Метка case (1, 2, 3, 4, 5) — это список значений: блок выполнится, если day совпадёт с любым из них. case (6, 7) ловит выходные. case default срабатывает, если ни одна метка не подошла — хорошая привычка всегда его указывать для обработки неожиданных значений.

Диапазоны значений

Сила select case — в диапазонах. Двоеточие задаёт интервал: case (low:high) ловит все значения от low до high включительно. Можно открыть диапазон с одной стороны: (:0) — всё до нуля, (100:) — всё от ста.

program classify
  implicit none
  integer :: score
  score = 85
  select case (score)
  case (:0)
    print *, "Ноль или меньше — ошибка"
  case (1:49)
    print *, "Низкий балл"
  case (50:79)
    print *, "Средний балл"
  case (80:100)
    print *, "Высокий балл"
  case default
    print *, "Больше 100 — ошибка"
  end select
end program classify

Вывод:

 Высокий балл

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

Символьные и логические выборы

select case работает не только с целыми. Символьные значения сравниваются по алфавиту (по кодам символов), что позволяет элегантно обрабатывать команды или коды.

program command
  implicit none
  character(len=1) :: cmd
  cmd = "w"
  select case (cmd)
  case ("w", "W")
    print *, "Движение вверх"
  case ("s", "S")
    print *, "Движение вниз"
  case ("a", "A", "d", "D")
    print *, "Движение в сторону"
  case default
    print *, "Неизвестная команда"
  end select
end program command

Вывод:

 Движение вверх

Обратите внимание: поскольку Fortran различает регистр внутри строковых значений (в отличие от имён переменных), приходится перечислять и "w", и "W". С логическим выражением select case тоже работает: метками служат .true. и .false., хотя для двух вариантов обычный if привычнее.

Ключевое отличие от switch в C

Программисты на C знают про коварное «проваливание» (fall-through): без break выполнение продолжается в следующий case. В Fortran этой проблемы нет: после выполнения блока совпавшей метки управление автоматически переходит за end select. Никакого break не нужно и не существует. Это делает select case заметно безопаснее: невозможно случайно «провалиться» в соседнюю ветвь, забыв служебное слово.

Стоит осознать масштаб этого решения. Fall-through в C — один из самых живучих источников ошибок за всю историю языка: пропущенный break компилируется без единого предупреждения и приводит к тому, что после нужной ветви молча исполняется следующая. Самый знаменитый пример — уязвимость «goto fail» в библиотеке безопасности Apple, где сама механика провала через одну строку привела к тому, что проверка сертификата фактически пропускалась. Чтобы как-то сдержать класс этих багов, в C предусмотрены отдельные предупреждения компилятора (-Wimplicit-fallthrough) и даже специальные атрибуты, которыми приходится явно помечать намеренный провал. Fortran решает проблему на уровне дизайна, а не заплатками: модель «один блок выбран — он один и выполнен» не оставляет места для провала в принципе. Это характерный для Fortran подход — закрывать опасные возможности в самой семантике языка, а не полагаться на бдительность программиста.

Заметим и обратную сторону: в C иногда сознательно используют провал, чтобы несколько меток разделили один блок (несколько case подряд без кода между ними). В Fortran для этого есть честный и явный механизм — список значений в одной метке, case (1, 2, 3), или диапазон case (1:3). То, что в C достигается опасным умолчанием, в Fortran выражается прямо и читается без двусмысленности. Так конструкция оказывается не беднее сишной, а ровно настолько же выразительной, но без подводных камней.

Где это применяют на практике

Множественный выбор по дискретной величине — настолько частый узор, что его встречаешь почти в любой нетривиальной программе. Классический случай — разбор кода ошибки или статуса: процедура вернула целочисленный код, и по нему нужно либо напечатать понятное сообщение, либо выбрать стратегию восстановления. Здесь select case с case default для неизвестных кодов читается как небольшая таблица соответствий. Другой типичный сценарий из научного кода — выбор численного метода или граничного условия по целочисленному флагу из входного файла: case (1) — явная схема, case (2) — неявная, и так далее. В обработке простых текстовых форматов и команд символьный select case разбирает односимвольные ключи и опции. А в конечных автоматах состояние удобно кодировать целым числом или именованной константой и переключать поведение через select case по текущему состоянию — это делает логику автомата плоской и обозримой вместо лабиринта вложенных if.

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

Почему select case может быть быстрее цепочки else if? Потому что выражение вычисляется один раз, и компилятор знает, что все метки — это константы, известные на этапе компиляции, без перекрытий. Это позволяет ему построить таблицу переходов (jump table): по значению выражения сразу вычислить адрес нужного блока и перейти туда за один шаг, вместо последовательной проверки десятка условий. Для плотных диапазонов целых это особенно эффективно. Цепочка else if, напротив, вычисляет условия по очереди, и в худшем случае проверит их все. Требование непересекающихся меток — не каприз, а условие, делающее такую оптимизацию корректной: каждому значению соответствует ровно один блок. Отсутствие fall-through тоже вытекает из этой модели: выбран один блок — он и выполняется, без «перетекания».

Полезно понимать, что таблица переходов — это не единственная стратегия и что компилятор выбирает её не всегда. Если метки разбросаны по огромному диапазону с большими дырами (скажем, значения 1, 1000 и 1000000), плотная таблица потребовала бы слишком много памяти, и оптимизатор вместо неё может сгенерировать бинарный поиск по отсортированным меткам или вовсе вернуться к последовательным сравнениям. То есть select case не гарантирует таблицу переходов как языковое свойство — он разрешает её, передавая компилятору достаточно информации для разумного выбора. Это иллюстрирует общий принцип Fortran: язык формулирует намерение и ограничения (метки-константы, без перекрытий, без провала), а конкретную реализацию оставляет на усмотрение оптимизатора, который знает целевую архитектуру лучше. Та же философия объясняет, почему стандарт описывает семантику конструкции, а не предписывает машинный код.

Короткое сравнение двух конструкций выбора подытоживает сказанное:

Свойствоselect case (Fortran)switch (C)
Провал в соседнюю ветвьНевозможен по дизайнуПроисходит без break
Диапазоны метокЕсть: case (50:79)Нет (только отдельные значения)
Вещественные значенияЗапрещеныТоже запрещены
Проверка пересечения метокДелает компиляторНет

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

  • Ожидание fall-through. Кто пришёл из C, может думать, что нужен break; в Fortran блоки изолированы, проваливания нет.
  • Пересекающиеся диапазоны. case (1:50) и case (40:90) конфликтуют — это ошибка компиляции, а не «первый победил».
  • Использование real в case. select case не работает с вещественными значениями (из-за неточности); только integer, character, logical.
  • Забытый case default. Без него неожиданное значение молча не обработается; добавляйте default для надёжности.
  • Регистр в символьных метках. Строки чувствительны к регистру: "w" и "W" различны, перечисляйте оба варианта.

Итоги

  • select case выбирает блок по значению выражения; ясная замена длинной цепочки else if.
  • Метки бывают одиночные, списками (1, 2, 3) и диапазонами (low:high), в том числе открытыми.
  • Работает с integer, character и logical, но не с real.
  • В отличие от C, нет проваливания (fall-through) и break не нужен.
  • Диапазоны меток не должны пересекаться — это контролирует компилятор.
  • Компилятор может построить таблицу переходов, что делает конструкцию эффективной.
Проверьте себя
1. Чем select case в Fortran безопаснее switch в C?
AОн быстрее всегда
BВ Fortran нет проваливания (fall-through), break не нужен
CОн работает с real
DМетки могут пересекаться
2. С каким типом select case НЕ работает?
Ainteger
Bcharacter
Creal (вещественные)
Dlogical
3. Что означает метка case (50:79)?
AТолько значения 50 и 79
BВсе значения от 50 до 79 включительно
CЗначения, кратные 50 и 79
DОшибка синтаксиса