В языках компьютерного программирования оператор switch - это тип механизма управления выбором, который позволяет значению переменной или выражения изменять поток управления выполнением программы через поиск и отображение.
Операторы switch работают if
примерно так же, как операторы, используемые в таких языках программирования, как C / C ++ , C # , Visual Basic .NET , Java, и существуют в большинстве языков императивного программирования высокого уровня, таких как Pascal , Ada , C / C ++ , C # , Visual Basic. NET , Java , и во многих других типах языка, используя такие ключевые слова , как switch
, case
, select
или inspect
.
Операторы switch бывают двух основных вариантов: структурированный переключатель, как в Pascal, который принимает ровно одну ветвь, и неструктурированный переключатель, как в C, который функционирует как тип goto . Основными причинами использования переключателя являются повышение ясности за счет сокращения повторяющегося кодирования и (если позволяет эвристика ) также предложение потенциала для более быстрого выполнения во многих случаях за счет более простой оптимизации компилятора .
switch ( возраст ) { case 1 : printf ( «Ты один.» ); перерыв ; case 2 : printf ( «Вас двое.» ); перерыв ; case 3 : printf ( «Вас трое.» ); case 4 : printf ( «Вас трое или четверо.» ); перерыв ; по умолчанию : printf ( «Вы не 1,2,3 или 4!» ); } |
История
В 1952 текст Введение в метаматематику , Стивен Клини формально доказано , что функция случая (IF-THEN-ELSE функция является самой простой формой) является примитивно рекурсивной функцией , где он определяет понятие definition by cases
следующим образом:
- "#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 ) в противном случае,
- φ (x 1 , ..., x n ) =
- где Q 1 , ..., Q m - взаимоисключающие предикаты (или φ (x 1 , ..., x n ) должен иметь значение, заданное первым применимым предложением) является примитивно рекурсивным в φ 1 , ... , φ m + 1 , Q 1 , ..., Q m + 1 . [1]
Клини предоставляет доказательство этого в терминах булевых рекурсивных функций «знак» sg () и «не знак» ~ sg () (Kleene 1952: 222-223); первый возвращает 1, если его вход положительный, и -1, если его вход отрицательный.
Булос-Берджесс-Джеффри делает дополнительное наблюдение, что «определение по случаям» должно быть как взаимоисключающим, так и исчерпывающим в совокупности. Они также предлагают доказательство примитивной рекурсивности этой функции (Boolos-Burgess-Jeffrey 2002: 74-75).
IF-THEN-ELSE является основой формализма Маккарти : его использование заменяет как примитивную рекурсию, так и оператор mu .
Типичный синтаксис
В большинстве языков программисты пишут оператор switch во многих отдельных строках, используя одно или два ключевых слова. Типичный синтаксис включает:
- первое
select
, за которым следует выражение, которое часто называют управляющим выражением или управляющей переменной оператора switch - последующие строки, определяющие фактические случаи (значения), с соответствующими последовательностями операторов для выполнения при обнаружении совпадения
- В языках с провальным поведением
break
оператор обычно следует за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
оператор. Таким образом, это рассматривается многими [2] как языковая бородавка и предостерегается от некоторых инструментов lint. Синтаксически варианты интерпретируются как метки, а не блоки, а операторы switch и break явно изменяют поток управления. Некоторые языки, на которые влияет C, например JavaScript , сохраняют провал по умолчанию, в то время как другие удаляют провал или разрешают его только в особых случаях. Известные варианты этого в семействе C включают C # , в котором все блоки должны заканчиваться символом break
или, return
если блок не пуст (т. Е. Переход используется как способ указания нескольких значений).
В некоторых случаях языки обеспечивают необязательный откат. Например, Perl по умолчанию не работает, но случай может явно сделать это с помощью continue
ключевого слова. Это предотвращает непреднамеренное падение, но позволяет при желании. Точно так же Bash по умолчанию не проваливается при завершении с помощью ;;
, но разрешает провал [3] с помощью ;&
или ;;&
вместо этого.
Примером оператора switch, который полагается на провал, является устройство Даффа .
Компиляция
Оптимизирующие компиляторы, такие как GCC или Clang, могут скомпилировать оператор switch либо в таблицу ветвлений, либо в двоичный поиск по значениям в случаях. [4] Таблица переходов позволяет оператору switch определять с помощью небольшого постоянного числа инструкций, какой переход выполнить без необходимости просматривать список сравнений, в то время как двоичный поиск требует только логарифмического числа сравнений, измеряемого числом случаев в операторе switch.
Обычно единственный способ узнать, произошла ли эта оптимизация, - это посмотреть на результирующую сборку или вывод машинного кода , сгенерированный компилятором.
Преимущества и недостатки
В некоторых языках и средах программирования использование оператора case
or switch
считается более предпочтительным по сравнению с эквивалентной серией операторов if else if, поскольку оно:
- Легче отлаживать (например, установка точек останова в коде по сравнению с таблицей вызовов, если отладчик не имеет возможности условной точки останова)
- Человеку легче читать
- Легче понять и, следовательно, проще поддерживать
- Фиксированная глубина: последовательность операторов «if else if» может привести к глубокой вложенности, что затрудняет компиляцию (особенно в автоматически сгенерированном коде)
- Легче проверить, обрабатываются ли все значения. Компиляторы могут выдавать предупреждение, если некоторые значения перечисления не обрабатываются.
Кроме того, оптимизированная реализация может выполняться намного быстрее, чем альтернатива, поскольку она часто реализуется с использованием индексированной таблицы переходов . [5] Например, принятие решения о выполнении программы на основе значения отдельного символа, если оно правильно реализовано, значительно более эффективно, чем альтернатива, значительно сокращая длину пути команд . При реализации как таковой оператор switch по сути становится идеальным хешем .
В терминах графа потока управления оператор switch состоит из двух узлов (входа и выхода) плюс одно ребро между ними для каждого варианта. Напротив, последовательность операторов «if ... else if ... else if» имеет дополнительный узел для каждого случая, кроме первого и последнего, вместе с соответствующим ребром. Получающийся в результате граф потока управления для последовательностей «if», таким образом, имеет намного больше узлов и почти вдвое больше ребер, при этом они не добавляют никакой полезной информации. Однако простые ветви в операторах if по отдельности концептуально проще, чем сложная ветвь оператора switch. Что касается цикломатической сложности , оба этих варианта увеличивают ее на k −1, если заданы k случаев.
Переключить выражения
Выражения переключения представлены в Java SE 12 , 19 марта 2019 г., в качестве предварительной версии. Здесь для возврата значения можно использовать целое выражение переключателя. Существует также новая форма метки case, case L->
где правая часть представляет собой одно выражение. Это также предотвращает падение и требует, чтобы случаи были исчерпывающими. В Java SE 13 представлен yield
оператор, а в Java SE 14 выражения переключения становятся стандартной функцией языка. [6] [7] [8] Например:
int ndays = переключатель ( месяц ) { case JAN , MAR , MAY , JUL , AUG , OCT , DEC -> 31 ; case APR , JUN , SEP , NOV -> 30 ; case FEB -> { if ( year % 400 == 0 ) yield 29 ; иначе, если ( год % 100 == 0 ) yield 28 ; иначе, если ( год % 4 == 0 ) yield 29 ; иначе выход 28 ; } };
Альтернативные варианты использования
Многие языки оценивают выражения внутри switch
блоков во время выполнения, что позволяет использовать ряд менее очевидных применений для построения. Это запрещает определенные оптимизации компилятора, поэтому чаще встречается в динамических языках и языках сценариев, где повышенная гибкость более важна, чем накладные расходы на производительность.
PHP
Например, в PHP константа может использоваться в качестве «переменной» для проверки, и будет выполнен первый оператор case, который оценивает эту константу:
переключатель ( истина ) { случай ( $ x == 'привет' ) : foo (); перерыв ; case ( $ z == 'Howdy' ) : перерыв ; } переключатель ( 5 ) { case $ x : break ; case $ y : break ; }
Эта функция также полезна для проверки нескольких переменных по одному значению, а не одной переменной по нескольким значениям. COBOL также поддерживает эту форму (и другие формы) в EVALUATE
заявлении. PL / I имеет альтернативную форму SELECT
оператора, в которой контрольное выражение полностью опускается, а выполняется первое, WHEN
которое оценивается как истинное .
Рубин
В Ruby , благодаря обработке ===
равенства, оператор может использоваться для проверки класса переменной:
case input, когда Array затем помещает 'input is an Array!' когда Hash затем помещает 'input is a Hash!' конец
Ruby также возвращает значение, которое может быть присвоено переменной, и на самом деле не требует case
наличия каких-либо параметров (действует как else if
оператор):
catfood = случай, когда кот . возраст <= 1 младший, когда кат . возраст > 10 старше остальное нормальный конец
Ассемблер
Оператор 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:
Обработка исключений
Ряд языков реализуют форму оператора switch при обработке исключений , где, если исключение возникает в блоке, в зависимости от исключения выбирается отдельная ветвь. В некоторых случаях также присутствует ветвь по умолчанию, если исключение не возникает. Ранним примером является Modula-3 , в котором используется синтаксис TRY
... EXCEPT
, каждый из которых EXCEPT
определяет регистр. Это также можно найти в Delphi , Scala и Visual Basic.NET .
Альтернативы
Некоторые альтернативы операторам переключения могут быть:
- Серия условных выражений if-else, которые проверяют целевое значение по одному за раз. Поведение провала может быть достигнуто с помощью последовательности условных выражений if, каждое без предложения else .
- Таблицы поиска , которая содержит, в качестве ключей, то
case
значений и, как значения, в части подcase
заявлением.
- (На некоторых языках в качестве значений в справочной таблице разрешены только фактические типы данных. На других языках также возможно назначать функции в качестве значений справочной таблицы, что обеспечивает такую же гибкость, что и реальный
switch
оператор. Подробнее см. В статье о контрольной таблице. на этом). - Lua не поддерживает операторы case / switch: http://lua-users.org/wiki/SwitchStatement . Этот метод поиска - один из способов реализации
switch
операторов на языке Lua, который не имеет встроенных функцийswitch
. [9] - В некоторых случаях таблицы поиска более эффективны, чем неоптимизированные
switch
операторы, поскольку многие языки могут оптимизировать поиск в таблицах, в то время как операторы switch не оптимизируются, если диапазон значений невелик с небольшими пробелами. Однако неоптимизированный, небинарный поиск почти наверняка будет медленнее, чем неоптимизированный переключатель или эквивалентные множественные операторы if-else . [ необходима цитата ]
- (На некоторых языках в качестве значений в справочной таблице разрешены только фактические типы данных. На других языках также возможно назначать функции в качестве значений справочной таблицы, что обеспечивает такую же гибкость, что и реальный
- Управления таблица (который может быть реализована в виде простой таблицы перекодировки) также может быть настроена для размещения нескольких условий на нескольких входах , если это необходимо , и , как правило , проявляет большую «» визуальную компактность , чем эквивалентный переключатель (который может занимать много заявлений).
- Сопоставление с образцом , которое используется для реализации функциональности переключателя во многих функциональных языках.
Смотрите также
- Алгоритмическая эффективность
- Таблица ответвлений
- Контрольный стол
- Устройство Даффа
- Отображение индекса
Рекомендации
- ^ "Определение по случаям", Kleene 1952: 229
- ^ Ван дер Линден, Питер (1994). Эксперт по программированию на C: секреты Deep C , стр. 38. Прентис-Холл, Иглвуд-Клифс. ISBN 0131774298 .
- ^ начиная с версии 4.0 , выпущенной в 2009 году.
- ↑ Влад Лазаренко. От оператора переключения к машинному коду
- ^ Гюнтерот, Курт (27 апреля 2016 г.). Оптимизированный C ++ . O'Reilly Media. п. 182. ISBN. 9781491922033.
- ^ «JEP 325: переключение выражений (предварительный просмотр)» . openjdk.java.net . Проверено 28 апреля 2021 .
- ^ «JEP 354: Выражения переключателя (второй просмотр)» . openjdk.java.net . Проверено 28 апреля 2021 .
- ^ «JEP 361: Switch Expressions» . openjdk.java.net . Проверено 28 апреля 2021 .
- ^ Оператор переключения в Lua
дальнейшее чтение
- Стивен Клини , 1952 г. (10-е издание, 1991 г.), Введение в метаматематику , издательство North-Holland Publishing Company, Амстердам, Нидерланды, ISBN 0-7204-2103-9
- Джордж Булос , Джон Берджесс и Ричард Джеффри , 2002 г., Computability and Logic: Fourth Edition , Cambridge University Press, Cambridge UK, ISBN 0-521-00758-5 в мягкой обложке. см. стр. 74-75.