Переключатель
Переключатель можно считать разновидностью условного выражения,
которая в некоторых случаях удобнее обычного условного выражения.
Чем они отличаются?
-
В переключателе проверяется только один объект, который сопоставляется с чем-то другим.
-
Производится проверка этого объекта на равенство (только равенство!) ещё какому-то объекту.
-
После заголовка переключателя перечисляются возможные значения, которые может принимать объект.
Т.е. переключатель — это условный оператор, который «специализируется» на проверке одного единственного объекта
(и только его) на равенство (и только равенство) некоторому набору значений.
Исключать его из арсенала инструментов будущего языка программирования не стоит.
Оператор «switch» в C/C++ выглядит примерно так:
switch (проверяемый на равенство объект)
{ case вариант значения 1:
операторы
break;
case вариант значения 2:
операторы
break;
case вариант значения 3:
case вариант значения 4:
операторы
break;
default:
операторы
}
Преписываем в своём скобочном стиле:
(switch проверяемый на равенство объект
case вариант значения 1
операторы
case вариант значения 2
операторы
case {вариант значения 3, вариант значения 4} // не одно значение, а множество
операторы
default
операторы)
IDE нарисует тот синтаксический сахар, который мы старательно удалили.
Внимательные читатели могут заметить, что код на Си может быть таким:
switch (проверяемый на равенство объект)
{ case вариант значения 1:
операторы
case вариант значения 2:
операторы
break;
case вариант значения 3:
операторы
}
Мы видим, что оператор «break» может отсутствать между ветвями «case».
Т.е. после выполнения одной ветви «case» выполняется идущая ниже.
Ничего хорошего в этом нет.
Это провоцирует появление «спагетти-кода».
Такую возможность следует исключить так же безжалостно, как и в случае с «goto».
Поэтому одна ветвь «case» должна заканчиваться, когда начинается следующая.
А теперь подводим итог, изобразив наши идеи в «симметричном скобочном» стиле.
(
|
switch проверяемый на равенство объект
|
|
|
|
case вариант значения 1
|
|
|
операторы
|
|
|
case вариант значения 2
|
|
|
операторы
|
|
|
case { вариант значения 3, вариант значения 4 }
|
|
|
операторы
|
|
|
|
|
default // Может «default» заменить на «else»? Экономия...
|
|
|
операторы для всех остальных случаев
|
)
|
Опубликовано: 2013.05.04, последняя правка: 2014.12.20 10:48
Отзывы
✅ 2014/12/22 13:20, Сергей #0
А как бы выбиратель (переключатель) выглядел бы с русскими служебными словами?✅ 2014/12/23 03:32, Автор сайта #1
В языке «Эль-76» в этом случае писали «выбор» — «из».✅ 2015/04/10 02:01, misha_shar53 #2
Синтаксис переключателя взят от Си, а содержание от Паскаль. Это сбивает с толку. Надо тогда и синтаксис брать Паскаля. Он, по-моему, более лаконичный. Синтаксис Паскаля:case NUM of 1,2,3: writeln (‘Первый квартал’); 4,5,6: writeln (‘Второй квартал’); 7,8,9: writeln (‘Третий квартал’); 10,11,12: writeln (‘Четвертый квартал’) else writeln (‘Вы неправильно указали месяц’) end; Возможно end заменить на скобку).✅ 2021/03/27 13:32, Виталий Монастырский #3
Вот все-таки полезно иногда почитать чужие умные мысли. Насмотрелся на Вашу вакханалию с этими круглыми скобками аля-Лисп, и тут же мне пришла в голову идея как упростить синтаксис переключателей. А оно тут как раз и есть в подсказках на Паскале, хоть и немного в другом виде. Было: switch a case 1 оператор 1 case 2 оператор 2 case 3 оператор 3 case 4 оператор 4 else оператор 5
Стало: switch a 1 оператор 1 2 оператор 2 3 оператор 3 4 оператор 4 else оператор 5 (используется только если нужны операторы по умолчанию) :)
И проще, и быстрее, и меньше работы лексеру... ведь реально то этот "case" там вообще никаким образом не нужен. А потом ещё немного подумал и сделал так: Прерываемый переключатель: break sw a 1 оператор 1 2 оператор 2 3 оператор 3 4 оператор 4 else оператор 5
Непрерывный перекючатель: cont sw a 1 оператор 1 2 оператор 2 3 оператор 3 4 оператор 4 else оператор 5
А почему бы, собственно, и нет... вместо case будут два зарезервированных слова: break и cont, которые будут определять продолжаемость или прерываемость операторов проверки. Теперь у меня есть:break if cont if break unif break sw cont sw Похоже эта комбинация описывает вообще все возможные варианты проверки как сложных так и простых условий продолжаемым и прерываемым способом. И всего 6 служебных слов — break, cont, if, unif, sw, else. Учитывая, что это расширенная версия языка, а не ядровая — по-моему весьма не плохо. Решает все проблемы, не теряет удобочитаемости и понимаемости синтаксиса.
Так что — БЛАГОДАРЮ за хорошие идеи. :)✅ 2021/03/27 22:15, Виталий Монастырский #4
Итак, теперь у нас имеются новые переключатели. Точно таким же образом работают и множественные операторы переключения, с той лишь разницей, что они всегда сравнивают значения только с одним элементом и по сути выполняют только проверку — равно ли указанное в условии значение реальному значению проверяемого элемента. Общая схема таких операторов следующая:тип sw элемент значение команда 1 значение команда 2 значение команда 3 значение команда 4 else команда по умолчанию (если ни одно значение не подошло) Отдельно обратите внимание на то, что в операторе "sw" напротив каждого значения размещается только одна команда. Если Вам необходимо выполнить блок команд — размещайте его в одельном именованном блоке и выполняйте его через оператор безусловного перехода "doit".
Рассмотрим на примерах, как работают разные типы множественного переключения. Для начала рассмотрим работу непрерывного переключателя.a, b, c, d : 3, 2, 3, 5 z : 3 and sw z a команда 1 b команда 2 c команда 3 d команда 4 else команда 5 В данном случае будет выполнена команда 1 и команда 3, так как значение z=3 совпадает со значениями a и c.
Теперь рассмотрим работу прерываемого переключателя с выбором по Истине.z : 3 or sw z 1 команда 1 2 команда 2 3 команда 3 4 команда 4 В этом случае программа выполнит только команду 3 и сразу выйдет из данного блока проверки условия.
И, наконец, рассмотрим работу прерываемого переключателя с выбором по Ложному условию.a, b, c, d : 3, 3, 2, 5 z : 3 xor sw z a команда 1 b команда 2 c команда 3 d команда 4 В этом случае программа выполнит команды 1 и 2, так как значения a и b = z, а значит они соответствует истине. Но после этого программа сразу выйдет из блока проверки условия, так как третье проверяемое значение является ложным. Команды 3 и 4 выполнены не будут.
Преимущество переключателей в том, что их синтаксис позволяет указывать оператор выполнения сразу после значения условия, что дает очень компактную запись условного перехода.
✅ 2021/03/27 22:17, Виталий Монастырский #5
Ой, беда. Я похоже поломал Ваш сайт. Похоже, что у Вас в тегах не выполняется перенос предложений. Не знал. Извините. Учту.✅ 2021/04/01 14:45, Александр Коновалов aka Маздайщик #6
В языках Си и Паскаль метками оператора case могут быть только целочисленные константы (включая константы перечислимого типа). И это неслучайно, так сделано для повышения производительности. Если диапазон между наибольшей и наименьшей меткой сравнительно небольшой и метки в этом диапазоне уложены плотно (при этом веток с одинаковыми метками быть не должно), то вместо цепочки ветвлений переключатель компилируется в вычислимый goto:switch (n) { case 0: printf("zero"); break; case 1: printf("one"); break;
case 2: case 3: case 5: case 7: printf("prime %d", n); break;
case 4: case 6: case 8: case 9: printf("not prime %d", n); break;
default: printf("I don't know the number %d", n); } label jump[10] = { &zero, &one, &prime, &prime, ¬_prime, &prime, ¬_prime, &prime, ¬_prime, ¬_prime };
if (n < 10) goto jump[n]; else goto unknown;
zero: printf("zero"); goto exit; one: printf("one"); goto exit; prime: printf("prime %d", n); goto exit; not_prime: printf("not prime %d", n); goto exit; unknown: printf("I don't know the number %d", n); exit: В отличие от вложенных ветвлений, такая реализация переключателя обеспечивает более высокое быстродействие: переход на метку выполняется за небольшое константное время, не зависящее от числа меток.
Поэтому переключатель можно использовать в интерпретаторах байткода, где может быть около сотни различных машинных команд виртуальной машины:switch (program[pc]) { case ADD: … break; case SUB: … break; case JMP: … break; case CALL: … break; case RET: … break; … и ещё несколько десятков опкодов … default: fprintf(stderr, "BAD OPCODE %d AT %d!\n", program[pc], pc); abort(); } Быстродействие в случае цепочки if/else if/else if будет в дикие разы хуже.
Если язык допускает нецелочисленные метки для переключателя, либо переменные в метках, то переключатель в таком случае будет лишь синтаксическим сахаром для условного оператора.✅ 2021/04/03 00:00, alextretyak #7
Если диапазон между наибольшей и наименьшей меткой сравнительно небольшой и метки в этом диапазоне уложены плотно Данное условие для оптимизации конструкции switch соблюдать вовсе не обязательно, т.к. можно использовать HashMap {причём не простой HashMap, а на основе perfect hash function, т.к. все возможные ключи этого HashMap известны на этапе компиляции}, у него также сложность будет O(1).Если язык допускает нецелочисленные метки для переключателя Эта проблема аналогично решается с помощью HashMap, т.к. ключи у него вполне могут быть нецелочисленного типа, например строками.✅ 2021/04/04 13:02, Александр Коновалов aka Маздайщик #8
Спасибо, Алекс! Ценное дополнение.
Если брать целочисленные ключи из небольшого диапазона, то операция перехода выражается несколькими машинными инструкциями: проверкой на выход за границы, индексацией и переходом. Если веток около сотни (не редкость в интерпретаторах байткода), а тип проверяемой переменной однобайтовый (например, char в Си), то даже выход за границы проверять не обязательно — проще выделить массив 256 разных меток.
Хеширование потребует несколько больше команд, кроме того, компилятор должен уметь подбирать совершенную хеш-функцию для каждого набора значений). Посложнее, конечно, но я не удивлюсь, если это где-то уже реализовано.✅ 2021/04/04 13:13, Александр Коновалов aka Маздайщик #9
К чему я написал пред-предыдущий комментарий. К тому, что либо метки ветвей синтаксически должны быть константами, либо, когда метки могут быть произвольными выражениями (как в Go, например, или в языке Виталия), компилятор должен распознавать частный случай константных меток и компилировать его особым образом.
Практическая потребность в этом есть. Кроме того, пользователи языка будут ожидать, что выбор ветки переключателя выполняется за константное время.
Виталий в других темах утверждал, что программы в полном синтаксисе сначала упрощаются до подмножества, а затем подмножество компилируется в целевой код. Подмножество в языке Виталия (буду называть его так, поскольку, как показал Алекс, имя cup не однозначно) включает в себя только условные и безусловные переходы. Для эффективной компиляции переключателя в язык-ядро ему также следует добавить массивы меток (были в Алголе-60) и переход на метку из массива.✅ 2021/04/04 14:43, kt #10
Я бы ещё упомянул о таких GOTO, как переход по меткам с индексами в PL/1, типа dcl n fixed(31); n=1; goto m(n); … m(1): … m(2): ….
Или о такой экзотике, как метки-переменные и метки-массивы. // метка-массив dcl k (4) label static init(m1,m5,m10,m25); goto k(n); ... // метка-переменная dcl lab label; lab=m1;
m0:goto lab; m1:...lab=m2; goto m0; m2:...lab=m3; goto m0; ...
Эти механизмы удобны для программирования конечных автоматов, очень просты в реализации и сам переход всегда за константное время.✅ 2021/04/04 15:11, Автор сайта #11
Да, читая про массивы меток, мне первым делом подумалось про PL/1 :)✅ 2021/04/04 15:31, Александр Коновалов aka Маздайщик #12
Спасибо, Дмитрий Юрьевич, за ценное дополнение!
Метки-массивы видел в Алголе-60, о чём я упоминал. В Фортране, помню, параметром подпрограммы могла быть метка — подпрограмма могла возвращаться не в точку своего вызова, а прыгнуть куда-то. Например, на обработчик ошибки.
Сейчас перечитал сообщение об Алголе-60. Массивы меток были константными, описывались какswitch S := L1, L2, L3; … goto S[n]; Кроме того, тоже параметрами процедур могли быть метки. Переменных-меток не было.
В классическом Бейсике был операторON ‹выражение› GOTO 100, 150, 200, 300 Выражение вычисляло номер метки, на которую выполнялся переход. Если выражение вычислялось в 0, делался переход на метку 100, 1 — 150, 2 — 200, 3 — 300, в остальных случаях переход не выполнялся.
В общем, в старых языках возможности GOTO были богаче.
Впрочем, и в Си оператор switch довольно интересен: Устройство Даффа✅ 2021/04/04 15:58, kt #13
Метки-переменные и метки-массивы, это то, с чем мне пришлось столкнуться в PL/1 в первом же самостоятельном проекте (интерпретатор с ПРОЛ-2, 1987 год). Поскольку эти механизмы были просты и понятны — очень громоздкий конечный автомат заработал очень быстро (для новичка).
Лет через десять такими же механизмами мы вдвоем разрабатывали Систему Отладки м Моделирования СОМ. На спор уложились в 5 недель. Опять-таки из-за простоты и поэтому понятности. Ну и, конечно, X86 поддерживает — прямой командой перехода по адресу в переменной, т.е. прямо отображает переход по метке-переменной.✅ 2021/04/04 16:50, Автор сайта #14
Плавно и незаметно вышли на тему полезности goto, которая обсуждается в соседней ветке :)✅ 2021/04/04 18:29, kt #15
Это goto — какое надо goto )) Во всех примерах именно переключатели Добавить свой отзыв
Написать автору можно на электронную почту mail(аt)compiler.su
|