В языках компьютерного программирования оператор switch - это тип механизма управления выбором, который позволяет значению переменной или выражения изменять поток управления выполнением программы через поиск и отображение.
Операторы switch работают if
примерно так же, как операторы, используемые в таких языках программирования, как C / C ++, C #, Visual Basic.NET, Java, и существуют в большинстве языков императивного программирования высокого уровня, таких как Pascal, Ada, C / C ++, C #, Visual Basic. NET, Java, и во многих других типах языка, используя такие ключевые слова, как switch
, case
, select
или inspect
.
Операторы switch бывают двух основных вариантов: структурированный переключатель, как в Паскале, который принимает ровно одну ветвь, и неструктурированный переключатель, как в C, который функционирует как тип перехода. Основными причинами использования переключателя являются повышение ясности за счет сокращения повторяющегося кодирования и (если позволяет эвристика ) также предложение потенциала для более быстрого выполнения во многих случаях за счет более простой оптимизации компилятора.
switch (age) { case 1: printf("You're one."); break; case 2: printf("You're two."); break; case 3: printf("You're three."); case 4: printf("You're three or four."); break; default: printf("You're not 1,2,3 or 4!"); } |
В своем тексте « Введение в метаматематику» 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.
В большинстве языков программисты пишут оператор switch во многих отдельных строках, используя одно или два ключевых слова. Типичный синтаксис включает:
select
, за которым следует выражение, которое часто называют управляющим выражением или управляющей переменной оператора switchbreak
оператор обычно следует за case
оператором, чтобы завершить указанное утверждение. [Уэллс]Каждая альтернатива начинается с конкретного значения или списка значений (см. Ниже), которым может соответствовать управляющая переменная и которые заставят элемент управления перейти к соответствующей последовательности операторов. Значение (или список / диапазон значений) обычно отделяется от соответствующей последовательности операторов двоеточием или стрелкой следования. Во многих языках перед каждым регистром также должно стоять ключевое слово, например case
или when
.
Дополнительный корпус по умолчанию, как правило, также допускаются, задается default
, otherwise
или else
ключевым словом. Это выполняется, когда ни один из других случаев не соответствует контрольному выражению. В некоторых языках, таких как C, если ни один регистр не соответствует и default
опущен, switch
инструкция просто завершается. В других, таких как PL / I, возникает ошибка.
Семантически существует две основные формы операторов switch.
Первая форма - это структурированные переключатели, как в Паскале, где берется ровно одна ветвь, а случаи рассматриваются как отдельные исключительные блоки. Это функционирует как обобщенное условие if-then-else, здесь с любым количеством ветвей, а не только с двумя.
Вторая форма - это неструктурированные переключатели, как в C, где варианты обрабатываются как метки в одном блоке, а переключатель функционирует как обобщенный переход. Это различие называется обработкой провала, которая подробно описывается ниже.
Во многих языках выполняется только соответствующий блок, а затем выполнение продолжается в конце оператора switch. К ним относятся семейство Паскаля (Object Pascal, Modula, Oberon, Ada и т. Д.), А также PL / I, современные формы диалектов Fortran и BASIC, на которые повлиял Паскаль, большинство функциональных языков и многие другие. Чтобы позволить нескольким значениям выполнять один и тот же код (и избежать необходимости дублировать код ), языки типа Pascal допускают любое количество значений для каждого случая, заданного в виде списка, разделенного запятыми, в виде диапазона или в виде комбинации.
Языки, производные от языка C, и, в более общем плане, те, на которые влияет вычисляемый GOTO Fortran, вместо этого имеют функцию провала, когда управление перемещается в соответствующий регистр, а затем выполнение продолжается ("проваливается") до операторов, связанных со следующим регистром в исходном тексте.. Это также позволяет нескольким значениям соответствовать одной и той же точке без какого-либо специального синтаксиса: они просто перечислены с пустыми телами. Значения могут быть специально обусловлены кодом в теле кейса. На практике провал обычно предотвращается с помощью break
ключевого слова в конце совпадающего тела, которое завершает выполнение блока переключения, но это может вызвать ошибки из-за непреднамеренного провала, если программист забывает вставить break
оператор. Таким образом, многие считают это языковой бородавкой и предостерегают от этого в некоторых инструментах lint. Синтаксически варианты интерпретируются как метки, а не блоки, а операторы switch и break явно изменяют поток управления. Некоторые языки, на которые влияет C, например JavaScript, сохраняют провал по умолчанию, в то время как другие удаляют провал или разрешают его только в особых случаях. Известные варианты этого в семействе C включают C #, в котором все блоки должны заканчиваться символом break
или, return
если блок не пуст (т. Е. Переход используется как способ указания нескольких значений).
В некоторых случаях языки обеспечивают необязательный откат. Например, Perl по умолчанию не работает, но случай может явно сделать это с помощью continue
ключевого слова. Это предотвращает непреднамеренное падение, но позволяет при желании. Точно так же Bash по умолчанию не проваливается при завершении с помощью ;;
, но разрешает провал с помощью ;amp;
или ;;amp;
вместо этого.
Примером оператора switch, который полагается на провал, является устройство Даффа.
Оптимизирующие компиляторы, такие как GCC или Clang, могут скомпилировать оператор switch либо в таблицу ветвлений, либо в двоичный поиск по значениям в случаях. Таблица переходов позволяет оператору switch определять с помощью небольшого постоянного числа инструкций, какую ветвь выполнять, не просматривая список сравнений, в то время как двоичный поиск требует только логарифмического числа сравнений, измеряемого числом наблюдений в оператор переключения.
Обычно единственный способ узнать, произошла ли эта оптимизация, - это посмотреть на результирующую сборку или вывод машинного кода, сгенерированный компилятором.
В некоторых языках и средах программирования использование оператора case
or switch
считается более предпочтительным по сравнению с эквивалентной серией операторов if else if, поскольку оно:
Кроме того, оптимизированная реализация может выполняться намного быстрее, чем альтернатива, поскольку она часто реализуется с использованием индексированной таблицы переходов. Например, принятие решения о выполнении программы на основе значения одного символа, если оно правильно реализовано, намного более эффективно, чем альтернатива, значительно сокращая длину пути команд. При реализации как таковой оператор switch по сути становится идеальным хешем.
В терминах графа потока управления оператор switch состоит из двух узлов (входа и выхода) плюс одно ребро между ними для каждого варианта. Напротив, последовательность операторов «if... else if... else if» имеет дополнительный узел для каждого случая, кроме первого и последнего, вместе с соответствующим ребром. Получающийся в результате граф потока управления для последовательностей «if», таким образом, имеет намного больше узлов и почти вдвое больше ребер, при этом они не добавляют никакой полезной информации. Однако простые ветви в операторах if по отдельности концептуально проще, чем сложная ветвь оператора switch. Что касается цикломатической сложности, оба этих варианта увеличивают ее на k −1, если заданы k случаев.
Выражения переключения представлены в Java SE 12, 19 марта 2019 г., в качестве предварительной версии. Здесь для возврата значения можно использовать целое выражение переключателя. Существует также новая форма метки case, case L-gt;
где правая часть представляет собой одно выражение. Это также предотвращает падение и требует, чтобы случаи были исчерпывающими. В Java SE 13 представлен yield
оператор, а в Java SE 14 выражения переключения становятся стандартной функцией языка. Например:
int ndays = switch(month) { case JAN, MAR, MAY, JUL, AUG, OCT, DEC -gt; 31; case APR, JUN, SEP, NOV -gt; 30; case FEB -gt; { 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 == 'hello'): foo(); break; case ($z == 'howdy'): break; } switch (5) { case $x: break; case $y: break; }
Эта функция также полезна для проверки нескольких переменных по одному значению, а не одной переменной по нескольким значениям. COBOL также поддерживает эту форму (и другие формы) в EVALUATE
заявлении. PL / I имеет альтернативную форму SELECT
оператора, в которой контрольное выражение полностью опускается, а выполняется первое, WHEN
которое оценивается как истинное.
В Ruby, благодаря обработке ===
равенства, оператор может использоваться для проверки класса переменной:
case input when Array then puts 'input is an Array!' when Hash then puts 'input is a Hash!' end
Ruby также возвращает значение, которое может быть присвоено переменной, и на самом деле не требует case
наличия каких-либо параметров (действует как else if
оператор):
catfood = case when cat.age lt;= 1 junior when cat.age gt; 10 senior else normal end
Оператор switch на языке ассемблера :
switch: cmp ah, 00h je a cmp ah, 01h je b jmp swtend ; No cases match or "default" code here a: push ah mov al, 'a' mov ah, 0Eh mov bh, 00h int 10h pop ah jmp swtend ; Equivalent to "break" b: push ah mov al, 'b' mov ah, 0Eh mov bh, 00h int 10h pop ah jmp swtend ; Equivalent to "break"... swtend:
Ряд языков реализуют форму оператора switch при обработке исключений, где, если исключение возникает в блоке, в зависимости от исключения выбирается отдельная ветвь. В некоторых случаях также присутствует ветвь по умолчанию, если исключение не возникает. Ранним примером является Modula-3, в котором используется синтаксис TRY
... EXCEPT
, каждый из которых EXCEPT
определяет регистр. Это также можно найти в Delphi, Scala и Visual Basic.NET.
Некоторые альтернативы операторам переключения могут быть:
case
значений и, как значения, в части под case
заявлением.switch
оператор. Подробнее см. В статье о контрольной таблице. на этом).switch
операторов на языке Lua, который не имеет встроенных функций switch
.switch
операторы, поскольку многие языки могут оптимизировать поиск в таблицах, в то время как операторы switch не оптимизируются, если диапазон значений невелик с небольшими пробелами. Однако неоптимизированный, небинарный поиск почти наверняка будет медленнее, чем неоптимизированный переключатель или эквивалентные множественные операторы if-else.