В языках компьютерного программирования оператор переключения — это тип механизма управления выбором, который позволяет значению переменной или выражения изменять поток управления выполнением программы посредством поиска и отображения.
Операторы 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
Операторы переключения существуют в двух основных вариантах: структурированный переключатель, как в Паскале, который принимает ровно одну ветвь, и неструктурированный переключатель, как в C, который функционирует как тип goto . Основные причины использования переключателя включают повышение ясности за счет сокращения повторяющегося кодирования, а также (если эвристика позволяет ) также предоставление возможности более быстрого выполнения во многих случаях за счет более простой оптимизации компилятора .
В своем тексте 1952 года «Введение в метаматематику» Стивен Клини формально доказал, что функция CASE (функция IF-THEN-ELSE является ее простейшей формой) является примитивно-рекурсивной функцией , где он определяет это понятие definition by cases
следующим образом:
Клини предоставляет доказательство этого в терминах булевых рекурсивных функций «знак» sg( ) и «не знак» ~sg( ) (Kleene 1952:222-223); первый возвращает 1, если его входной сигнал положителен, и -1, если его входной сигнал отрицательный.
Булос-Берджесс-Джеффри делают дополнительное наблюдение, что «определение по случаям» должно быть как взаимоисключающим , так и коллективно исчерпывающим . Они также предлагают доказательство примитивной рекурсивности этой функции (Boolos-Burgess-Jeffrey 2002:74-75).
IF-THEN-ELSE является основой формализма Маккарти : его использование заменяет как примитивную рекурсию, так и mu-оператор .
В большинстве языков программисты пишут оператор переключения во многих отдельных строках, используя одно или два ключевых слова. Типичный синтаксис включает в себя:
select
, за которым следует выражение, которое часто называют управляющим выражением или управляющей переменной оператора переключения.break
оператор обычно следует за case
оператором, завершающим указанный оператор. [Уэллс]WHEN
предложения, содержащего логическое выражение, и совпадение происходит для первого случая, для которого это выражение имеет значение true. Такое использование аналогично структурам if/then/elseif/else в некоторых других языках, например Perl .WHEN
предложения, содержащего логическое выражение, и совпадение происходит для первого случая, для которого это выражение имеет значение true.Каждая альтернатива начинается с конкретного значения или списка значений (см. ниже), которому может соответствовать управляющая переменная и которые заставят элемент управления перейти к соответствующей последовательности операторов. Значение (или список/диапазон значений) обычно отделяется от соответствующей последовательности операторов двоеточием или стрелкой-значением. Во многих языках каждому регистру также должно предшествовать ключевое слово, например case
или when
.
Обычно также допускается необязательный регистр по умолчанию, определяемый ключевым словом default
, otherwise
или else
. Это выполняется, когда ни один из других случаев не соответствует управляющему выражению. В некоторых языках, таких как C, если ни один регистр не соответствует и оператор default
опущен, switch
оператор просто ничего не делает. В других, например PL/I, выдается ошибка.
Семантически существуют две основные формы операторов переключения.
Первая форма — это структурированные переключатели, как в Паскале, где берется ровно одна ветвь, а случаи рассматриваются как отдельные исключительные блоки. Это действует как обобщенный условный оператор if-then-else , здесь с любым количеством ветвей, а не только с двумя.
Вторая форма — это неструктурированные переключатели, как в C, где случаи рассматриваются как метки внутри одного блока, а переключатель функционирует как обобщенный переход. Это различие называется лечением провала, которое подробно описано ниже.
Во многих языках выполняется только соответствующий блок, а затем выполнение продолжается в конце оператора переключения. К ним относятся семейство Паскаль (Object Pascal, Modula, Oberon, Ada и т. д.), а также PL/I , современные формы диалектов Fortran и BASIC , на которые повлиял Паскаль, большинство функциональных языков и многие другие. Чтобы позволить нескольким значениям выполнять один и тот же код (и избежать необходимости дублировать код ), языки типа Паскаль допускают любое количество значений для каждого случая, заданное в виде списка, разделенного запятыми, диапазона или комбинации.
Языки, производные от языка C, и, в более общем смысле, те, на которые влияет вычисленный GOTO Фортрана , вместо этого имеют провал, когда управление переходит к соответствующему регистру, а затем выполнение продолжается («проваливается») до операторов, связанных со следующим регистром в исходном тексте. . Это также позволяет нескольким значениям соответствовать одной и той же точке без какого-либо специального синтаксиса: они просто перечисляются с пустыми телами. Значения могут быть специально обусловлены кодом в теле дела. На практике провал обычно предотвращается с помощью break
ключевого слова в конце соответствующего тела, которое завершает выполнение блока переключателя, но это может вызвать ошибки из-за непреднамеренного провала, если программист забудет вставить оператор break
. Таким образом, многие [4] рассматривают это как языковую бородавку, и некоторые инструменты lint предупреждают об этом. Синтаксически случаи интерпретируются как метки, а не блоки, а операторы переключения и прерывания явно изменяют поток управления. Некоторые языки, находящиеся под влиянием C, такие как JavaScript , сохраняют провал по умолчанию, в то время как другие удаляют провал или допускают его только в особых обстоятельствах. Известные варианты этого в семействе C включают C# , в котором все блоки должны заканчиваться знаком break
или return
, если блок не пуст (т. е. проходное соединение используется как способ указать несколько значений).
В некоторых случаях языки предусматривают необязательный отказ. Например, Perl по умолчанию не проваливается, но в некоторых случаях это можно сделать явно с помощью continue
ключевого слова. Это предотвращает непреднамеренный сбой, но допускает его при желании. Аналогично, Bash по умолчанию не проваливается при завершении с помощью ;;
, но допускает провал [5] с помощью ;&
или ;;&
вместо этого.
Примером оператора переключения, основанного на провале, является устройство Даффа .
Оптимизирующие компиляторы, такие как GCC или Clang , могут скомпилировать оператор переключения либо в таблицу ветвей , либо выполнить двоичный поиск по значениям в вариантах. [6] Таблица ветвей позволяет оператору переключения с помощью небольшого постоянного числа инструкций определить, какую ветвь выполнять, без необходимости проходить через список сравнений, в то время как двоичный поиск требует только логарифмического числа сравнений, измеряемого в количестве случаев в операторе переключения.
Обычно единственный способ узнать, произошла ли эта оптимизация, — это фактически просмотреть результирующий вывод сборки или машинного кода , сгенерированный компилятором.
В некоторых языках и средах программирования использование оператора case
or switch
считается более предпочтительным, чем эквивалентная серия операторов if else if, поскольку оно:
Кроме того, оптимизированная реализация может выполняться намного быстрее, чем альтернативная, поскольку она часто реализуется с использованием индексированной таблицы ветвей . [7] Например, определение потока программы на основе значения одного символа, если оно правильно реализовано, значительно более эффективно, чем альтернативный вариант, что значительно сокращает длину пути инструкции . При такой реализации оператор переключения по сути становится идеальным хешем .
С точки зрения графа потока управления , оператор переключения состоит из двух узлов (вход и выход) плюс одно ребро между ними для каждого варианта. Напротив, последовательность операторов «if...else if...else if» имеет дополнительный узел для каждого случая, кроме первого и последнего, вместе с соответствующим ребром. Таким образом, результирующий граф потока управления для последовательностей «if» имеет гораздо больше узлов и почти вдвое больше ребер, причем они не добавляют никакой полезной информации. Однако простые ветви оператора if по отдельности концептуально проще, чем сложная ветвь оператора переключателя. С точки зрения цикломатической сложности оба этих варианта увеличивают ее на k −1, если дано k случаев.
Выражения переключения представлены в Java SE 12 от 19 марта 2019 г. в качестве предварительной функции. Здесь для возврата значения можно использовать целое выражение переключателя. Существует также новая форма метки регистра, case L->
в которой правая часть представляет собой одно выражение. Однако это также предотвращает падение и требует, чтобы случаи были исчерпывающими. В Java SE 13 этот yield
оператор представлен, а в Java SE 14 выражения переключения становятся стандартной функцией языка. [8] [9] [10] Например:
int ndays = переключатель ( месяц ) { case ЯНВАРЬ , МАРТ , МАЙ , ИЮЛЬ , АВГУГ , ОКТЯБРЬ , ДЕКАБРЬ -> 31 ; случай АПР , ИЮНЬ , СЕНТ , НОЯ -> 30 ; случай FEB -> { if ( год % 400 == 0 ) выход 29 ; иначе, если ( год % 100 == 0 ) доходность 28 ; иначе , если ( год % 4 == 0 ) выход 29 ; иначе получим 28 ; } };
Многие языки оценивают выражения внутри switch
блоков во время выполнения, что позволяет использовать конструкцию менее очевидным образом. Это запрещает определенные оптимизации компилятора, поэтому чаще встречается в динамических языках и языках сценариев, где повышенная гибкость более важна, чем накладные расходы на производительность.
Например, в PHP константа может использоваться в качестве «переменной» для проверки, и будет выполнен первый оператор case, который вычисляет эту константу:
переключатель ( истина ) { case ( $x == 'привет' ) : foo (); перерыв ; случай ( $z == 'привет' ) : перерыв ; } Switch ( 5 ) { case $x : перерыв ; случай $y : перерыв ; }
Эта функция также полезна для проверки нескольких переменных по одному значению, а не по одной переменной по множеству значений. COBOL также поддерживает эту форму (и другие формы) в EVALUATE
операторе. В PL/I есть альтернативная форма оператора SELECT
, в которой управляющее выражение вообще опускается и выполняется первое WHEN
, имеющее значение true .
В Ruby , благодаря обработке ===
равенства, этот оператор можно использовать для проверки класса переменной:
ввод регистра , когда Array затем помещает «ввод - это массив!» когда Хэш затем вводит «ввод - это Хэш!» конец
Ruby также возвращает значение, которое можно присвоить переменной, и на самом деле не требует case
наличия каких-либо параметров (действуя немного как else if
оператор):
catfood = случай , когда cat . возраст <= 1 младший , когда кот . возраст > 10 лет , иначе нормальный конец
Оператор переключения на языке ассемблера :
переключатель: 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 ; Эквивалентно «брейку» … swtend:
Для Python 3.10.6 были приняты PEPmatch
634-636, в которые добавлены ключевые слова и case
. [11] [12] [13] [14] В отличие от других языков, Python не демонстрирует провалов.
Letter = input ( "Введите одну букву: " ) . полоса ()[ 0 ] . casefold () # первый непробельный символ ввода, строчная буква буква совпадения : случай «а» | 'е' | 'я' | 'о' | 'u' : # В отличие от условий в операторах if, здесь нельзя использовать ключевое слово `or` для различения случаев. print ( f "Буква { буква } — гласная!" ) случай 'y' : print ( f "Буква { буква } может быть гласной." ) case _ : # `case _` эквивалентен `default` из C и других print ( f "Буква { буква } не является гласной!" )
В ряде языков при обработке исключений реализована форма оператора переключения , где, если исключение возникает в блоке, в зависимости от исключения выбирается отдельная ветвь. В некоторых случаях также присутствует ветвь по умолчанию, если не возникает исключение. Ранним примером является Modula-3 , в котором используется синтаксис TRY
... EXCEPT
, где каждый EXCEPT
определяет регистр. Это также встречается в Delphi , Scala и Visual Basic .NET .
Некоторыми альтернативами операторам переключения могут быть:
case
значения и, в качестве значений, часть оператора case
.switch
оператор. Более подробную информацию см. в статье «Управляющая таблица». на этом).switch
операторов на языке Lua, который не имеет встроенного switch
. [15]switch
, поскольку многие языки могут оптимизировать поиск по таблицам, тогда как операторы переключения не оптимизируются, если диапазон значений невелик и с небольшим количеством пробелов. Однако неоптимизированный, недвоичный поиск почти наверняка будет медленнее, чем неоптимизированный переключатель или эквивалентные несколько операторов if-else . [ нужна цитата ]