Множественный выбор: 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не нужен. - Диапазоны меток не должны пересекаться — это контролирует компилятор.
- Компилятор может построить таблицу переходов, что делает конструкцию эффективной.