stringtranslate.com

Переключить заявление

В языках программирования оператор 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 на многих отдельных строках, используя одно или два ключевых слова. Типичный синтаксис включает:

Каждая альтернатива начинается с определенного значения или списка значений (см. ниже), с которыми может сопоставляться управляющая переменная, и которые заставят элемент управления перейти к соответствующей последовательности операторов. Значение (или список/диапазон значений) обычно отделяется от соответствующей последовательности операторов двоеточием или стрелкой импликации. Во многих языках каждому случаю также должно предшествовать ключевое слово, например 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# , в котором все блоки должны завершаться символом или , если только блок не является пустым (т. е. проход используется как способ указания нескольких значений).breakreturn

В некоторых случаях языки предоставляют опциональный проход. Например, Perl не проваливается по умолчанию, но случай может явно сделать это с помощью continueключевого слова. Это предотвращает непреднамеренный проход, но допускает его при желании. Аналогично, Bash по умолчанию не проваливается при завершении с помощью ;;, но допускает проход [5] с помощью ;&или ;;&вместо этого.

Примером оператора switch, который полагается на проход, является устройство Даффа .

Компиляция

Оптимизирующие компиляторы, такие как GCC или Clang, могут компилировать оператор switch либо в таблицу ветвлений , либо в двоичный поиск по значениям в вариантах. [6] Таблица ветвлений позволяет оператору switch с помощью небольшого постоянного числа инструкций определять, какую ветвь выполнять, не проходя по списку сравнений, в то время как двоичный поиск требует только логарифмического числа сравнений, измеряемого числом случаев в операторе switch.

Обычно единственный способ выяснить, произошла ли эта оптимизация, — это фактически просмотреть полученный ассемблерный или машинный код , сгенерированный компилятором.

Преимущества и недостатки

В некоторых языках и средах программирования использование оператора caseor 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

Например, в 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 могут быть:

(В некоторых языках в качестве значений в таблице поиска допускаются только фактические типы данных. В других языках также можно назначать функции в качестве значений таблицы поиска, получая ту же гибкость, что и реальный switchоператор. Более подробную информацию об этом см. в статье Таблица управления ).
Lua не поддерживает операторы case/switch. [15] Этот метод поиска является одним из способов реализации switchоператоров в языке Lua, в котором нет встроенных switch. [15]
В некоторых случаях таблицы поиска более эффективны, чем неоптимизированные операторы switch , поскольку многие языки могут оптимизировать поиск в таблицах, тогда как операторы switch не оптимизируются, если только диапазон значений не мал с небольшим количеством пробелов. Однако неоптимизированный, небинарный поиск почти наверняка будет медленнее, чем неоптимизированный switch или эквивалентные множественные операторы if-else . [ необходима цитата ]

Смотрите также

Ссылки

  1. ^ Скит, Джон (23 марта 2019 г.). C# in Depth . Мэннинг. ISBN 978-1617294532.
  2. ^ Блох, Джошуа (2018). «Effective Java: Programming Language Guide» (третье изд.). Addison-Wesley. ISBN 978-0134685991.
  3. ^ «Определение по случаям», Клини 1952:229
  4. ^ Ван дер Линден, Питер (1994). Экспертное программирование на языке C: глубокие секреты C , стр. 38. Prentice Hall, Eaglewood Cliffs. ISBN 0131774298
  5. ^ начиная с версии 4.0, выпущенной в 2009 году.
  6. ^ Влад Лазаренко. От оператора switch до машинного кода
  7. ^ Гюнтерот, Курт (27 апреля 2016 г.). Оптимизированный C++ . O'Reilly Media. стр. 182. ISBN 9781491922033.
  8. ^ "JEP 325: Switch Expressions (Preview)". openjdk.java.net . Получено 28.04.2021 .
  9. ^ "JEP 354: Switch Expressions (второй предварительный просмотр)". openjdk.java.net . Получено 28.04.2021 .
  10. ^ "JEP 361: Switch Expressions". openjdk.java.net . Получено 28.04.2021 .
  11. ^ Галиндо Сальгадо, Пабло. "Что нового в Python 3.10". Документация Python 3.10.6 . Получено 19 августа 2022 г.
  12. ^ Bucher, Brandt; van Rossum, Guido (2020-09-12). "PEP 634 – Структурное сопоставление шаблонов: спецификация". Предложения по улучшению Python . Получено 2022-08-19 .
  13. ^ Kohn, Tobias ; van Rossum, Guido (2020-09-12). "PEP 635 – Структурное сопоставление шаблонов: мотивация и обоснование". Предложения по улучшению Python . Получено 2022-08-19 .
  14. ^ Moisset, Daniel F. "PEP 636 – Structural Pattern Matching: Tutorial". Предложения по улучшению Python . Получено 19 августа 2022 г.
  15. ^ ab Оператор Switch в Lua

Дальнейшее чтение