stringtranslate.com

Оператор переключения

В языках компьютерного программирования оператор переключения — это тип механизма управления выбором, который позволяет значению переменной или выражения изменять поток управления выполнением программы посредством поиска и отображения.

Операторы 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следующим образом:

«#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]

Клини предоставляет доказательство этого в терминах булевых рекурсивных функций «знак» sg( ) и «не знак» ~sg( ) (Kleene 1952:222-223); первый возвращает 1, если его входной сигнал положителен, и -1, если его входной сигнал отрицательный.

Булос-Берджесс-Джеффри делают дополнительное наблюдение, что «определение по случаям» должно быть как взаимоисключающим , так и коллективно исчерпывающим . Они также предлагают доказательство примитивной рекурсивности этой функции (Boolos-Burgess-Jeffrey 2002:74-75).

IF-THEN-ELSE является основой формализма Маккарти : его использование заменяет как примитивную рекурсию, так и mu-оператор .

Типичный синтаксис

В большинстве языков программисты пишут оператор переключения во многих отдельных строках, используя одно или два ключевых слова. Типичный синтаксис включает в себя:

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

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

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

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

Например, в 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 .

Альтернативы

Некоторыми альтернативами операторам переключения могут быть:

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

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

Рекомендации

  1. Скит, Джон (23 марта 2019 г.). C# в глубине . Мэннинг. ISBN 978-1617294532.
  2. ^ Блох, Джошуа (2018). «Эффективная Java: Руководство по языку программирования» (третье изд.). Аддисон-Уэсли. ISBN 978-0134685991.
  3. ^ «Определение по случаям», Клини 1952: 229.
  4. ^ ван дер Линден, Питер (1994). Экспертное программирование на языке C: глубокие секреты C , с. 38. Прентис Холл, Иглвуд Клиффс. ISBN 0131774298
  5. ^ начиная с версии 4.0, выпущенной в 2009 году.
  6. ^ Влад Лазаренко. От оператора Switch до машинного кода
  7. Гюнтерот, Курт (27 апреля 2016 г.). Оптимизированный С++ . О'Рейли Медиа. п. 182. ИСБН 9781491922033.
  8. ^ «JEP 325: Переключение выражений (предварительная версия)» . openjdk.java.net . Проверено 28 апреля 2021 г.
  9. ^ «JEP 354: Переключение выражений (второй предварительный просмотр)» . openjdk.java.net . Проверено 28 апреля 2021 г.
  10. ^ «JEP 361: Переключение выражений» . openjdk.java.net . Проверено 28 апреля 2021 г.
  11. ^ Галиндо Сальгадо, Пабло. «Что нового в Python 3.10». Документация Python 3.10.6 . Проверено 19 августа 2022 г.
  12. ^ Бухер, Брандт; ван Россум, Гвидо (12 сентября 2020 г.). «PEP 634 – Соответствие структурному шаблону: Спецификация». Предложения по улучшению Python . Проверено 19 августа 2022 г.
  13. ^ Кон, Тобиас ; ван Россум, Гвидо (12 сентября 2020 г.). «PEP 635 – Сопоставление структурных шаблонов: мотивация и обоснование». Предложения по улучшению Python . Проверено 19 августа 2022 г.
  14. ^ Муассе, Дэниел Ф. «PEP 636 - Сопоставление структурных шаблонов: Учебное пособие». Предложения по улучшению Python . Проверено 19 августа 2022 г.
  15. ^ ab Оператор Switch в Lua

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