В языках программирования оператор switch представляет собой тип механизма управления выбором, который позволяет значению переменной или выражения изменять поток управления выполнением программы посредством поиска и отображения.
Операторы switch функционируют примерно так же, как if
оператор, используемый в таких языках программирования, как C / C++ , C# , Visual Basic .NET , Java , и существуют в большинстве императивных языков программирования высокого уровня, таких как Pascal , Ada , C / C++ , C# , [1] : 374–375, Visual Basic .NET , Java , [2] : 157–167 и во многих других типах языков, используя такие ключевые словаswitch
, как case
, select
, или inspect
.
Операторы switch существуют в двух основных вариантах: структурированный switch, как в Pascal, который принимает ровно одну ветвь, и неструктурированный switch, как в C, который функционирует как тип goto . Основные причины использования switch включают улучшение ясности за счет сокращения в противном случае повторяющегося кодирования и (если позволяет эвристика ) также предложение потенциала для более быстрого выполнения за счет более простой оптимизации компилятора во многих случаях.
В своей работе 1952 года « Введение в метаматематику » Стивен Клини формально доказал, что функция CASE (ее простейшей формой является функция IF-THEN-ELSE) является примитивной рекурсивной функцией , где он определяет понятие «определение по случаям» следующим образом:
"#F. Функция φ, определенная таким образом
- φ(x 1 , ... , x n ) =
- φ 1 (x 1 , ... , x n ), если Q 1 (x 1 , ... , x n ),
- . . . . . . . . . . . .
- φ m (x 1 , ... , x n ), если Q m (x 1 , ... , x n ),
- φ m+1 (x 1 , ... , x n ) в противном случае,
где Q 1 , ... , Q m — взаимоисключающие предикаты (или φ(x 1 , ... , x n ) будет иметь значение, заданное первым применимым предложением), является примитивно рекурсивным относительно φ 1 , ..., φ m+1 , Q 1 , ..., Q m+1 .
— Стивен Клини, [3]
Клини приводит доказательство этого в терминах рекурсивных функций типа булевых «знак-of» sg() и «не знак-of» ~sg() (Клини 1952:222-223); первая возвращает 1, если ее входные данные положительны, и −1, если ее входные данные отрицательны.
Булос-Берджесс-Джеффри делают дополнительное замечание, что «определение по случаям» должно быть как взаимоисключающим, так и коллективно исчерпывающим . Они также предлагают доказательство примитивной рекурсивности этой функции (Булос-Берджесс-Джеффри 2002:74-75).
IF-THEN-ELSE является основой формализма Маккарти : его использование заменяет как примитивную рекурсию, так и мю-оператор .
Самые ранние компиляторы Fortran поддерживали вычисляемый оператор GOTO для многоканального ветвления. Ранние компиляторы ALGOL поддерживали тип данных SWITCH, содержащий список «обозначающих выражений». Оператор GOTO мог ссылаться на переменную-переключатель и, предоставляя индекс, переходить к желаемому месту назначения. С опытом стало понятно, что нужна более формальная многоканальная конструкция с единой точкой входа и выхода. Такие языки, как BCPL , ALGOL-W и ALGOL-68, представили формы этой конструкции, которые сохранились в современных языках.
В большинстве языков программисты пишут оператор switch на многих отдельных строках, используя одно или два ключевых слова. Типичный синтаксис включает:
select
, за которым следует выражение, которое часто называют управляющим выражением или управляющей переменной оператора switchbreak
обычно следует фраза, case
завершающая фразу. [Уэллс]WHEN
предложения, содержащего логическое выражение, и совпадение происходит для первого случая, для которого это выражение оценивается как истинное. Такое использование похоже на структуры if/then/elseif/else в некоторых других языках, например, Perl .WHEN
предложения, содержащего логическое выражение, а сопоставление происходит для первого случая, для которого это выражение оценивается как истинное.Каждая альтернатива начинается с определенного значения или списка значений (см. ниже), с которыми может сопоставляться управляющая переменная, и которые заставят элемент управления перейти к соответствующей последовательности операторов. Значение (или список/диапазон значений) обычно отделяется от соответствующей последовательности операторов двоеточием или стрелкой импликации. Во многих языках каждому случаю также должно предшествовать ключевое слово, например case
или when
.
Обычно также допускается необязательный случай по умолчанию, указанный ключевым default
словом otherwise
, или else
. Он выполняется, когда ни один из других случаев не соответствует управляющему выражению. В некоторых языках, таких как C, если ни один из случаев не соответствует и default
опущено, switch
оператор просто ничего не делает. В других, таких как PL/I, возникает ошибка.
Семантически существуют две основные формы операторов switch.
Первая форма — это структурированные переключатели, как в Pascal, где берется ровно одна ветвь, а случаи рассматриваются как отдельные, исключающие блоки. Это работает как обобщенный if–then–else conditional , здесь с любым количеством ветвей, а не только с двумя.
Вторая форма — это неструктурированные переключатели, как в C, где случаи рассматриваются как метки в пределах одного блока, а переключатель функционирует как обобщенный goto. Это различие называется обработкой fallthrough, которая подробно описана ниже.
Во многих языках выполняется только соответствующий блок, а затем выполнение продолжается в конце оператора switch. К ним относятся семейство Pascal (Object Pascal, Modula, Oberon, Ada и т. д.), а также PL/I , современные формы Fortran и диалекты BASIC , на которые повлиял Pascal, большинство функциональных языков и многие другие. Чтобы разрешить нескольким значениям выполнять один и тот же код (и избежать необходимости дублировать код ), языки типа Pascal допускают любое количество значений на случай, заданных в виде списка, разделенного запятыми, в виде диапазона или в виде комбинации.
Языки, полученные из языка C, и, в более общем плане, те, которые повлияли на вычисляемый GOTO Fortran , вместо этого имеют fallthrough, где управление переходит к соответствующему случаю, а затем выполнение продолжается («проваливается») к операторам, связанным со следующим случаем в исходном тексте. Это также позволяет нескольким значениям соответствовать одной и той же точке без какого-либо специального синтаксиса: они просто перечислены с пустыми телами. Значения могут быть специально обусловлены с помощью кода в теле case. На практике fallthrough обычно предотвращается ключевым break
словом в конце совпадающего тела, которое завершает выполнение блока switch, но это может вызвать ошибки из-за непреднамеренного fallthrough, если программист забудет вставить оператор. Таким образом, многие [4]break
считают это языковой бородавкой, и о нем предостерегают в некоторых инструментах lint. Синтаксически case интерпретируются как метки, а не блоки, а операторы switch и break явно изменяют поток управления. Некоторые языки, на которые повлиял C, такие как JavaScript , сохраняют fallthrough по умолчанию, в то время как другие удаляют fallthrough или допускают его только в особых обстоятельствах. Известные вариации на эту тему в семействе языков C включают C# , в котором все блоки должны завершаться символом или , если только блок не является пустым (т. е. проход используется как способ указания нескольких значений).break
return
В некоторых случаях языки предоставляют опциональный проход. Например, Perl не проваливается по умолчанию, но случай может явно сделать это с помощью continue
ключевого слова. Это предотвращает непреднамеренный проход, но допускает его при желании. Аналогично, Bash по умолчанию не проваливается при завершении с помощью ;;
, но допускает проход [5] с помощью ;&
или ;;&
вместо этого.
Примером оператора switch, который полагается на проход, является устройство Даффа .
Оптимизирующие компиляторы, такие как GCC или Clang, могут компилировать оператор switch либо в таблицу ветвлений , либо в двоичный поиск по значениям в вариантах. [6] Таблица ветвлений позволяет оператору switch с помощью небольшого постоянного числа инструкций определять, какую ветвь выполнять, не проходя по списку сравнений, в то время как двоичный поиск требует только логарифмического числа сравнений, измеряемого числом случаев в операторе switch.
Обычно единственный способ выяснить, произошла ли эта оптимизация, — это фактически просмотреть полученный ассемблерный или машинный код , сгенерированный компилятором.
В некоторых языках и средах программирования использование оператора case
or switch
считается более предпочтительным, чем эквивалентная серия операторов if else if, поскольку оно:
Кроме того, оптимизированная реализация может выполняться намного быстрее альтернативы, поскольку она часто реализуется с использованием индексированной таблицы ветвлений . [7] Например, принятие решения о потоке программы на основе значения одного символа, если она правильно реализована, намного эффективнее альтернативы, значительно сокращая длину пути инструкции . При реализации таким образом оператор switch по сути становится идеальным хешем .
В терминах графа потока управления оператор switch состоит из двух узлов (входа и выхода) плюс одно ребро между ними для каждого варианта. Напротив, последовательность операторов "if...else if...else if" имеет дополнительный узел для каждого случая, кроме первого и последнего, вместе с соответствующим ребром. Результирующий граф потока управления для последовательностей "if" таким образом имеет гораздо больше узлов и почти вдвое больше ребер, при этом они не добавляют никакой полезной информации. Однако простые ветви в операторах if по отдельности концептуально проще, чем сложная ветвь оператора switch. В терминах цикломатической сложности оба этих варианта увеличивают ее на k −1, если задано k случаев.
Выражения switch введены в Java SE 12 19 марта 2019 года в качестве предварительной функции. Здесь целое выражение switch может быть использовано для возврата значения. Также есть новая форма метки case, case L->
где правая часть — это одно выражение. Это также предотвращает провалы и требует, чтобы case были исчерпывающими. В Java SE 13 yield
введен оператор, а в Java SE 14 выражения switch стали стандартной функцией языка. [8] [9] [10] Например:
int ndays = switch ( month ) { case JAN , MAR , MAY , JUL , AUG , OCT , DEC -> 31 ; case APR , JUN , SEP , NOV -> 30 ; case FEB -> { if ( year % 400 == 0 ) yield 29 ; else if ( year % 100 == 0 ) yield 28 ; else if ( year % 4 == 0 ) yield 29 ; else yield 28 ; } };
Многие языки оценивают выражения внутри switch
блоков во время выполнения, что позволяет использовать конструкцию в ряде менее очевидных вариантов. Это запрещает определенные оптимизации компилятора, поэтому чаще встречается в динамических и скриптовых языках, где повышенная гибкость важнее накладных расходов на производительность.
Например, в PHP константу можно использовать в качестве «переменной» для проверки, и будет выполнен первый оператор case, который вычисляется как эта константа:
switch ( true ) { case ( $ x == ' привет ' ) : foo (); перерыв ; case ( $ z == ' привет ' ) : перерыв ; } switch ( 5 ) { case $ x : перерыв ; case $ y : перерыв ; }
Эта функция также полезна для проверки нескольких переменных по одному значению, а не одной переменной по многим значениям. COBOL также поддерживает эту форму (и другие формы) в EVALUATE
операторе. PL/I имеет альтернативную форму оператора , в которой управляющее выражение вообще опускается, а выполняется SELECT
первое выражение WHEN
, которое оценивается как истинное .
В Ruby , благодаря обработке ===
равенства, этот оператор можно использовать для проверки класса переменной:
case input when Array then puts 'input is an Array!' when Hash then puts 'input is an Hash!' end
Ruby также возвращает значение, которое может быть присвоено переменной, и на самом деле не требует наличия case
каких-либо параметров (действуя немного как else if
оператор):
корм для кошек = случай , когда возраст кошки <= 1 младший , когда возраст кошки > 10 старший , в противном случае обычный конец
Оператор switch на языке ассемблера :
switch: cmp ah , 00h je a cmp ah , 01h je b jmp swtend ; Здесь нет совпадений по регистру или код "по умолчанию" a: push ah mov al , 'a' mov ah , 0Eh mov bh , 00h int 10h pop ah jmp swtend ; Эквивалентно "break" b: push ah mov al , 'b' mov ah , 0Eh mov bh , 00h int 10h pop ah jmp swtend ; Эквивалентно "break" ... swtend:
Для Python 3.10.6 были приняты PEP 634-636, которые добавили ключевые слова match
и case
. [11] [12] [13] [14] В отличие от других языков, Python, в частности, не демонстрирует поведение провалов.
letter = input ( "Введите одну букву: " ) . strip ()[ 0 ] . casefold () # первый непробельный символ ввода, нижний регистрсопоставьте букву : case 'a' | 'e' | 'i' | 'o' | 'u' : # В отличие от условий в операторах if, ключевое слово `or` не может быть использовано здесь для различения случаев print ( f "Буква { буква } — гласная!" ) случай «y» : print ( f "Буква { буква } может быть гласной." ) case _ : # `case _` эквивалентен `default` из C и других print ( f "Буква { буква } не является гласной!" )
В ряде языков реализована форма оператора switch в обработке исключений , где если исключение возникает в блоке, выбирается отдельная ветвь в зависимости от исключения. В некоторых случаях также присутствует ветвь по умолчанию, если исключение не возникает. Ранним примером является Modula-3 , который использует синтаксис TRY
... EXCEPT
, где каждый EXCEPT
определяет случай. Это также встречается в Delphi , Scala и Visual Basic .NET .
Альтернативами операторам switch могут быть:
case
значения и в качестве значений часть под case
оператором.switch
оператор. Более подробную информацию об этом см. в статье Таблица управления ).switch
операторов в языке Lua, в котором нет встроенных switch
. [15]switch
, поскольку многие языки могут оптимизировать поиск в таблицах, тогда как операторы switch не оптимизируются, если только диапазон значений не мал с небольшим количеством пробелов. Однако неоптимизированный, небинарный поиск почти наверняка будет медленнее, чем неоптимизированный switch или эквивалентные множественные операторы if-else . [ необходима цитата ]