Оператор переключения
Эта статья нуждается в дополнительных цитатах для проверки . ( апрель 2013 г. ) |
В языках компьютерного программирования оператор переключения — это тип механизма управления выбором, который позволяет значению переменной или выражения изменять поток управления выполнением программы посредством поиска и отображения.
Операторы 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
.
Операторы переключения существуют в двух основных вариантах: структурированный переключатель, как в Паскале, который принимает ровно одну ветвь, и неструктурированный переключатель, как в C, который функционирует как тип goto . Основные причины использования переключателя включают повышение ясности за счет сокращения повторяющегося кодирования, а также (если эвристика позволяет ) также предоставление возможности более быстрого выполнения во многих случаях за счет более простой оптимизации компилятора .
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 является ее простейшей формой) является примитивно-рекурсивной функцией , где он определяет понятие «определение по случаям» следующим образом:
«#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-оператор .
Самые ранние компиляторы Фортрана поддерживали вычисляемый оператор GOTO для многопутевого ветвления. Ранние компиляторы АЛГОЛА поддерживали тип данных SWITCH, который содержит список «выражений обозначения». Оператор GOTO может ссылаться на переменную переключателя и, предоставляя индекс, переходить к желаемому месту назначения. С опытом стало понятно, что необходима более формальная многоходовая конструкция с единой точкой входа и выхода. Такие языки, как BCPL , ALGOL-W и ALGOL-68 , представили формы этой конструкции, которые сохранились в современных языках.
Типичный синтаксис [ править ]
В большинстве языков программисты пишут оператор переключения во многих отдельных строках, используя одно или два ключевых слова. Типичный синтаксис включает в себя:
- первый
select
, за которым следует выражение, которое часто называют управляющим выражением или управляющей переменной оператора переключения. - последующие строки, определяющие фактические случаи (значения), с соответствующими последовательностями операторов для выполнения при возникновении совпадения.
- В языках с провальным поведением
break
заявление обычно следует заcase
заявление о завершении указанного заявления. [Уэллс] - В некоторых языках, например, PL/I , управляющее выражение является необязательным; если управляющего выражения нет, то каждая альтернатива начинается с
WHEN
предложение, содержащее логическое выражение, и совпадение происходит для первого случая, для которого это выражение имеет значение true. Такое использование похоже на структуры if/then/elseif/else в некоторых других языках, например Perl . - В некоторых языках, например, Rexx , управляющие выражения не допускаются, и каждая альтернатива начинается с символа.
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] как языковая бородавка, и некоторые инструменты для ворса предупреждают об этом. Синтаксически случаи интерпретируются как метки, а не блоки, а операторы переключения и прерывания явно изменяют поток управления. Некоторые языки, находящиеся под влиянием C, такие как JavaScript , сохраняют провал по умолчанию, в то время как другие удаляют провал или допускают его только в особых обстоятельствах. Заметные варианты этого в семействе C включают C# , в котором все блоки должны заканчиваться символом break
или return
если только блок не пуст (т. е. проходной режим используется как способ указания нескольких значений).
В некоторых случаях языки предусматривают необязательный отказ. Например, Perl по умолчанию не проваливается, но в некоторых случаях это можно сделать явно, используя continue
ключевое слово. Это предотвращает непреднамеренный провал, но допускает его при желании. Аналогично, Bash по умолчанию не проваливается при завершении с помощью ;;
, но допускает провал [5] с ;&
или ;;&
вместо.
Примером оператора переключения, основанного на провале, является устройство Даффа .
Сборник [ править ]
Оптимизирующие компиляторы, такие как GCC или Clang, могут скомпилировать оператор переключения либо в таблицу ветвей , либо выполнить двоичный поиск по значениям в вариантах. [6] Таблица ветвей позволяет оператору переключения с помощью небольшого постоянного количества инструкций определить, какую ветвь выполнять, без необходимости проходить через список сравнений, в то время как двоичный поиск требует только логарифмического количества сравнений, измеряемого количеством случаев в оператор переключения.
Обычно единственный способ узнать, произошла ли эта оптимизация, — это фактически просмотреть результирующий вывод сборки или машинного кода , сгенерированный компилятором.
Преимущества и недостатки [ править ]
В некоторых языках и средах программирования использование case
или switch
Оператор считается превосходящим эквивалентную серию операторов if else if, поскольку он:
- Легче отлаживать (например, устанавливать точки останова в коде, а не в таблице вызовов, если отладчик не поддерживает условные точки останова).
- Человеку легче читать
- Легче понять и, следовательно, легче поддерживать.
- Фиксированная глубина: последовательность операторов «if else if» может привести к глубокой вложенности, что затрудняет компиляцию (особенно в автоматически сгенерированном коде).
- Легче проверить, что все значения обработаны. Компиляторы могут выдать предупреждение, если некоторые значения перечисления не обрабатываются.
Кроме того, оптимизированная реализация может выполняться намного быстрее, чем альтернативная, поскольку она часто реализуется с использованием индексированной таблицы ветвей . [7] Например, решение о ходе выполнения программы на основе значения одного символа, если оно правильно реализовано, значительно более эффективно, чем альтернативный вариант, что значительно сокращает длину пути инструкции . При такой реализации оператор переключения по сути становится идеальным хешем .
С точки зрения графа потока управления , оператор переключения состоит из двух узлов (вход и выход) плюс одно ребро между ними для каждого варианта. Напротив, последовательность операторов «if...else if...else if» имеет дополнительный узел для каждого случая, кроме первого и последнего, вместе с соответствующим ребром. Таким образом, результирующий граф потока управления для последовательностей «если» имеет гораздо больше узлов и почти вдвое больше ребер, причем они не добавляют никакой полезной информации. Однако простые ветви оператора if по отдельности концептуально проще, чем сложная ветвь оператора переключателя. С точки зрения цикломатической сложности оба этих варианта увеличивают ее на k −1, если дано k случаев.
Переключение выражений [ править ]
Выражения переключения представлены в Java SE 12 от 19 марта 2019 г. в качестве предварительной функции. Здесь для возврата значения можно использовать целое выражение переключателя. Также имеется новая форма этикетки корпуса, case L->
где правая часть представляет собой одно выражение. Однако это также предотвращает падение и требует, чтобы случаи были исчерпывающими. В Java SE 13 yield
введен оператор, и в Java SE 14 выражения переключения становятся стандартной функцией языка. [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 == 'hello'):
foo();
break;
case ($z == 'howdy'): break;
}
switch (5) {
case $x: break;
case $y: break;
}
Эта функция также полезна для проверки нескольких переменных по одному значению, а не одной переменной по множеству значений. COBOL также поддерживает эту форму (и другие формы) в EVALUATE
заявление. PL/I имеет альтернативную форму SELECT
оператор, в котором управляющее выражение вообще опущено, а первое WHEN
который имеет значение true, выполняется.
Руби [ править ]
В 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 <= 1
junior
when cat.age > 10
senior
else
normal
end
Собрать [ править ]
Оператор переключения на языке ассемблера :
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:
Питон [ править ]
Для Python 3.10.6 были приняты PEP 634-636, что добавило match
и case
ключевые слова. [11] [12] [13] [14] В отличие от других языков, Python не демонстрирует провалов.
letter = input("Put in a single letter: ").strip()[0].casefold() # first non-whitespace character of the input, lowercase
match letter:
case 'a' | 'e' | 'i' | 'o' | 'u': # Unlike conditions in if statements, the `or` keyword cannot be used here to differentiate between cases
print(f"Letter {letter} is a vowel!")
case 'y':
print(f"Letter {letter} may be a vowel.")
case _: # `case _` is equivalent to `default` from C and others
print(f"Letter {letter} is not a vowel!")
Обработка исключений [ править ]
Ряд языков реализуют форму оператора переключения при обработке исключений , где, если исключение возникает в блоке, в зависимости от исключения выбирается отдельная ветвь. В некоторых случаях также присутствует ветвь по умолчанию, если не возникает исключение. Ранним примером является Modula-3 , в которой используется TRY
... EXCEPT
синтаксис, где каждый EXCEPT
определяет случай. Это также встречается в Delphi , Scala и Visual Basic .NET .
Альтернативы [ править ]
Некоторыми альтернативами операторам переключения могут быть:
- Ряд if-else условных операторов , которые проверяют целевое значение по одному. Поведение Fallthrough может быть достигнуто с помощью последовательности условных операторов if без предложения else .
- Таблица поиска , которая в качестве ключей содержит
case
значения и, как значения, часть подcase
заявление.
- (В некоторых языках в качестве значений в таблице поиска разрешены только фактические типы данных. В других языках также можно назначать функции в качестве значений таблицы поиска, получая ту же гибкость, что и реальные типы данных.
switch
заявление. см. в статье «Таблица управления» ). Более подробную информацию об этом - Lua не поддерживает операторы case/switch. [15] Этот метод поиска является одним из способов реализации
switch
операторы на языке Lua, который не имеет встроенныхswitch
. [15] - В некоторых случаях таблицы поиска более эффективны, чем неоптимизированные .
switch
операторы, поскольку многие языки могут оптимизировать поиск в таблицах, тогда как операторы переключения не оптимизируются, если диапазон значений не мал с небольшим количеством пробелов. Однако неоптимизированный, недвоичный поиск почти наверняка будет медленнее, чем неоптимизированный переключатель или эквивалентные несколько операторов if-else . [ нужна ссылка ]
- (В некоторых языках в качестве значений в таблице поиска разрешены только фактические типы данных. В других языках также можно назначать функции в качестве значений таблицы поиска, получая ту же гибкость, что и реальные типы данных.
- Таблица управления (которая может быть реализована как простая таблица поиска) также может быть настроена для учета нескольких условий на нескольких входах, если это необходимо, и обычно демонстрирует большую «визуальную компактность», чем эквивалентный переключатель (который может занимать много операторов).
- Сопоставление с образцом , которое используется для реализации функциональности, подобной переключателю, во многих функциональных языках.
См. также [ править ]
- Алгоритмическая эффективность
- Таблица филиалов
- Таблица управления
- Устройство Даффа
- Сопоставление индексов
Ссылки [ править ]
- ^ Скит, Джон (23 марта 2019 г.). C# в глубине . Мэннинг. ISBN 978-1617294532 .
- ^ Блох, Джошуа (2018). «Эффективная Java: Руководство по языку программирования» (третье изд.). Аддисон-Уэсли. ISBN 978-0134685991 .
- ^ «Определение по случаям», Клини 1952: 229.
- ^ ван дер Линден, Питер (1994). Экспертное программирование на языке C: глубокие секреты C , с. 38. Прентис Холл, Иглвуд Клиффс. ISBN 0131774298 .
- ^ начиная с версии 4.0 , выпущенной в 2009 году.
- ^ Влад Лазаренко. От оператора Switch до машинного кода
- ^ Гюнтерот, Курт (27 апреля 2016 г.). Оптимизированный С++ . О'Рейли Медиа. п. 182. ИСБН 9781491922033 .
- ^ «JEP 325: Переключение выражений (предварительная версия)» . openjdk.java.net . Проверено 28 апреля 2021 г.
- ^ «JEP 354: Переключение выражений (вторая предварительная версия)» . openjdk.java.net . Проверено 28 апреля 2021 г.
- ^ «JEP 361: Переключение выражений» . openjdk.java.net . Проверено 28 апреля 2021 г.
- ^ Галиндо Сальгадо, Пабло. «Что нового в Python 3.10» . Документация Python 3.10.6 . Проверено 19 августа 2022 г.
- ^ Бухер, Брандт; ван Россум, Гвидо (12 сентября 2020 г.). «PEP 634 – Соответствие структурному шаблону: Спецификация» . Предложения по улучшению Python . Проверено 19 августа 2022 г.
- ^ Кон, Тобиас ; ван Россум, Гвидо (12 сентября 2020 г.). «PEP 635 – Сопоставление структурных шаблонов: мотивация и обоснование» . Предложения по улучшению Python . Проверено 19 августа 2022 г.
- ^ Муассе, Дэниел Ф. «PEP 636 – Сопоставление структурных шаблонов: Учебное пособие» . Предложения по улучшению Python . Проверено 19 августа 2022 г.
- ^ Jump up to: Перейти обратно: а б Оператор Switch в Lua
Дальнейшее чтение [ править ]
- Стивен Клини , 1952 (10-е переиздание 1991 г.), «Введение в метаматематику» , издательство North-Holland Publishing Company, Амстердам, Нидерланды, ISBN 0-7204-2103-9
- Джордж Булос , Джон Берджесс и Ричард Джеффри , 2002, Вычислимость и логика: четвертое издание , издательство Кембриджского университета, Кембридж, Великобритания, ISBN 0-521-00758-5 в мягкой обложке. см. стр. 74–75.