Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Новости и прочее

Переключатель

Переключатель можно считать разновидностью условного выражения, которая в некоторых случаях удобнее обычного условного выражения. Чем они отличаются?

  • В переключателе проверяется только один объект, который сопоставляется с чем-то другим.
  • Производится проверка этого объекта на равенство (только равенство!) ещё какому-то объекту.
  • После заголовка переключателя перечисляются возможные значения, которые может принимать объект.
        Т.е. переключатель — это условный оператор, который «специализируется» на проверке одного единственного объекта (и только его) на равенство (и только равенство) некоторому набору значений. Исключать его из арсенала инструментов будущего языка программирования не стоит.

        Оператор «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

ОценитеОценки посетителей
   ███████████████ 16 (35.5%)
   █████████████ 13 (28.8%)
   █████ 5 (11.1%)
   ███████████ 11 (24.4%)

Отзывы

✅  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 )) Во всех примерах именно переключатели

✅  2024/12/01 00:00, alextretyak          #16 

Предлагаю обсуждение break в switch продолжить уже здесь, чтобы не разводить оффтоп на странице «Продолжение цикла и выход из него».
(Начало обсуждения здесь: http://compiler.su/prodolzhenie-tsikla-i-vykhod-iz-nego.php#43)

Автор сайта
проваливание после case — 7 раз,
Похоже, вы посчитали пустые case за "проваливание". Я считаю, их не нужно учитывать, т.к. фактически никакое "проваливание" в таких случаях не требуется: синтаксис языка программирования должен позволять выражать намерение программиста более явно, т.е. вместо case 30: case 31: лучше использовать запись case 30, 31: или аналогичную.
И в данном файле реальное "проваливание" встречается только в одном месте: https://github.com/mortdeus/legacy-cc/blob/master/last1120c/c00.c#L129-L131
(Мой скрипт-анализатор также выдал только один этот случай проваливания для данного исходного файла.)
заканчивается goto — 23 раза
Похоже, вы посчитали все goto внутри всех case. Полагаю, что считать нужно только goto в конце case, т.к. только там они "конкурируют" с break. goto в конце case я насчитал в этом файле 16 штук (что, впрочем, тоже очень много).
заканчивается return — 26 раз
Аналогично. Вы посчитали все return внутри всех switch'ей. return в конце case в этом файле 18 штук.
заканчивается exit — 2 раза
exit внутри switch в данном файле всего один (в 356-й строке), причём расположен он не в конце case, поэтому я бы его вообще не стал учитывать.
А за второй exit внутри switch вы, похоже, приняли вот этот — https://github.com/mortdeus/legacy-cc/blob/master/last1120c/c00.c#L444
Он располагается внутри if сразу после switch, но не внутри него.
Не ахти какая статистика, но проваливание используется нечасто.
Однако. Ваша статистика заставила меня крепко задуматься. Вы посчитали случаев "проваливания" в 7 раз больше, чем я. И всё равно по вашим цифрам получается, что «проваливание используется нечасто».
Я почему-то решил считать не отдельные case'ы, в которых используется "проваливание", а switch'и, в которых есть хотя бы один case с "проваливанием". А ведь это очень большая разница! Допустим, у нас есть 10 switch'ей. И в каждом по 10 case'ов. Если "проваливание" используется только в одном-единственном case, то статистика по switch'ам покажет использование "проваливания" в 10% случаев. А статистика по case'ам — лишь в 1% случаев!
Если пересчитать статистику по-новому, то в результате получаются такие цифры:
Общее количество case'ов, не считая пустые: 326.
Case'ов, в которых используется "проваливание": 13. Т.е. всего 4% (против 19%, если считать по-старому)!
Если же говорить про исходный код ядра Linux 1.0, то "проваливание" используется только в 22 case'ах из 1143, т.е. лишь в 2% (против 7%) случаев!
В отличие от then в Паскале после if условие. Там оно так себе
А что не так с then в Паскале? На русский это слово переводится как «тогда» или «то» (и, например, в Глаголе используется именно такой перевод: https://atimopheyev.narod.ru/Glagol/otlichGO.htm), и получается что if a < 0 then return на русском можно записать как если а < 0 то возврат.
Думаю, для проваливания вниз было бы уместно использовать ключевое слово then вместо fallthrough.
Не уверен, что это хорошая идея. Хотя fallthrough мне тоже не нравится, несмотря на то, что именно такое ключевое слово используется во многих языках. Но давайте оставим выбор английского варианта названия тем, кто очень хорошо знает английский. Мне же гораздо интереснее выбрать русский вариант для этого ключевого слова. Может, у вас есть какие-то идеи? Я что-то так сходу удачного варианта подобрать не могу.

Клихальт
Под уменьшением количества кода Вы верно имели ввиду уменьшение количества исходного текста программы, а не количества кода программы?
Да. Ведь когда говорят, что «размер/объём программы: 100 тыс. строк кода», под «кодом» понимают исходный код (source code) программы [см. https://ru.wikipedia.org/wiki/Количество_строк_кода]. Объём сгенерированного компилятором машинного кода сейчас вообще мало кого интересует.
позволю отослать Вас к приведённому мною примеру исходных текстов компилятора языка Си, где break вообще практически не используется для выхода из ветки оператора выбора.
Хорошо. Только давайте быть более объективными. Да, конкретно в исходном файле last1120c/c00.c нет ни одного break в switch. Но вот в файле prestruct/c02.c break'ов в switch — 9, из них 6 располагается в конце case (причём не последнего в switch).
Во всём исходном тексте этого компилятора таких break в switch — 10 штук. Т.е. тех, которые в конце не последнего case и которые можно убрать из кода/текста программы при использовании поведения «break по умолчанию».
Псевдооператор "проваливания" потребуется (как я уже писал чуть выше) в 13 случаях.
Результат (10 − 13) получается, конечно, не в пользу «break по умолчанию», но если посмотреть на статистику для исходного кода ядра Linux 1.0, то картина получится совсем иная.
Псевдооператор "проваливания" потребуется (как я уже писал чуть выше) в 22 случаях.
А вот таких break в switch, которые можно убрать из кода/текста программы при использовании поведения «break по умолчанию»: 498.
Итого, количество строк кода (точнее, операторов в коде) сократится при таком поведении на 476 (498 − 22).

✅  2024/12/01 00:22, Gudleifr          #17 

Если уж перезапускаете тему, то начинайте, пожалуйста, сначала. Зачем нужен переключатель?

✅  2024/12/01 07:09, Вежливый Лис          #18 

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

Вот это слово и исользовать: "вперёд".

✅  2024/12/01 10:33, Клихальт          #19 

Gudleifr,
Зачем нужен переключатель?
Согласен для начала следовало бы разобраться что именно мы обсуждаем.

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

Ранее куда как чаще использовался первый вариант, сейчас этот стиль кодирования вытравливается из умов и навязывается совершенно другой.

Обсуждение вылилось в спор остроконечников и тупоконечников — в языке каким-то образом должны присутствовать оба варианта.

Мне ближе вариант чистого Си.

✅  2024/12/01 13:23, Gudleifr          #20 

Как мне видится
Мне кажется, вопрос немного шире. Ведь "рядом" — в bash — if и switch — это две совершенно разные конструкции: первая анализирует код возврата программ, вторая — строковые константы. В Си попал только максимально упрощенный вариант переключателя — по int (ведь Си — это простейший компилятор), с явным намеком на то, что программист должен сам его "расширить", придав этим int какой-то особый смысл. Но как — непонятно.

Программисты этим путем не пошли, предпочтя else-if (пример из книги "Идеальный код"):
/* matchhere: поиск соответствий регулярному выражению в начале текста */
int matchhere(char *regexp. char *text)
{
if (regexp[0] == '\0')
return 1;
if (regexp[l] == '*')
return matchstar(regexp[0], regexp+2, text);
if (regexp[0] == '$' && regexp[1] == '\0')
return *text == '\0’:
if (*text!='\0' && (regexp[0]=='.' || regexp[0]==*text))
return matchhere(regexp+1, text+1):
return 0;
}
Так нужен ли переключатель вообще? Выше правильно указано, что он является практически аналогом BASIC-конструкции ON-GOTO. Но и в таком виде она не прожила долго. Помните, как быстро switch в анализаторе Win-сообщений был заменен таблицей, а затем макросом?

Есть "окно" в выборе не по "вводу", а по "состоянию". Если реализовать ДКА "в лоб"... Например, программируя что-то вроде:

https://studfile.net/html/1549/349/html_yyEckH1eTr.fjB9/img-3fukaW.png ,

мы можем пройти от 1-го до 6-го состояния, "не выходя наружу", тупо "проваливаясь".

"Компилируя" автоматы, мы можем прийти к идее программы с вариантами, где "нормальный процесс" — "проваливание" от точки входа до конца.

ООП и здесь дает обходной путь (как в итераторах): определив в суперклассе метод "go" мы можем переопределить его во всех наследниках, заменив int ссылкой на объект, имеющий нужное поведение...

Как-то так.

✅  2024/12/01 18:57, Неслучайный читатель          #21 

Java, традиционный переключатель:
private static void printGreetingBasedOnInput(String input){
switch (input){
case "hello":
System.out.println("Hi There");
break;
case "goodbye":
System.out.println("See you Later!");
break;
case "thank you":
System.out.println("You are welcome");
break;
default:
System.out.println("I don't understand");
break;
}
}
Тоже Java, сопоставление с образцом:
private static void printGreetingBasedOnInput(String input){
switch (input){
case "hello" -> System.out.println("Hi There");
case "goodbye" -> System.out.println("See you Later!");
case "thank you" -> System.out.println("You are welcome");
default -> System.out.println("I don't understand");
}
}
Два варианта синтаксиса, в первом — проваливание по умолчанию, а во втором проваливания нет.

✅  2024/12/01 19:42, Вежливый Лис          #22 

Два варианта синтаксиса
И что теперь, нам всем по два варианта делать? Какие ваши аргументы за то, что именно наличие двух вариантов — это лучший подход из желательных?

✅  2024/12/01 20:37, Клихальт          #23 

Gudleifr,
Программисты этим путем не пошли, предпочтя else-if (пример из книги "Идеальный код"):
Извините, что не по теме обсуждения, но что-то слишком уж много очепяток в вашем примере.

✅  2024/12/01 20:41, Неслучайный читатель          #24 

уместно использовать ключевое слово then вместо fallthrough.
не уверен, что это хорошая идея. Хотя fallthrough мне тоже не нравится... Но давайте оставим выбор английского варианта названия тем, кто очень хорошо знает английский. Мне же гораздо интереснее выбрать русский вариант для этого ключевого слова.
Русское слово затем, которое, кстати, переводится на английский как then.
по два варианта делать? Какие ваши аргументы за то, что именно наличие двух вариантов — это лучший подход из желательных?
Никаких аргументов, просто констатация фактов строго по теме. Интересно, оптимизатор кода может сам увидеть, что код без проваливаний преобразовать в аналогичный с проваливаниями:
switch (i) { // по умолчанию нет проваливания
case "abc"-> print ("A"); print ("B"); print ("C");
case "bc"-> print ("B"); print ("C");
case "c"-> print ("C");
}
А это аналогичный с проваливаниями:
switch (i) { // проваливание есть
case "abc": print ("A");
case "bc": print ("B");
case "c": print ("C");
}

✅  2024/12/01 20:48, Gudleifr          #25 

но что-то слишком уж много очепяток
Да, это не по теме. Пример не мой, а Кернигана.

✅  2024/12/02 00:00, alextretyak          #26 

Клихальт
Вот тексты первого компилятора Си, написанного на Си: https://github.com/mortdeus/legacy-cc
Вообще, судя по описанию (http://cm.bell-labs.co/who/dmr/primevalC.html), там два Си компилятора или две версии одного компилятора, написанные в 1972-73 гг., когда сам язык Си ещё только-только появлялся. И всё указывает на то, что в окружении, в котором разрабатывался более ранний из них — last1120c — оператор break в switch вообще не поддерживался. Поэтому считать количество его присутствия было довольно глупо. :)(:

Так, в .c файлах в директории last1120c действительно нет ни одного break в switch. Его логика эмулируется через goto. Я решил это исправить {"осовременить" этот исходный код, заменив goto на break где это возможно, с моими изменениями можно ознакомиться по ссылке: https://github.com/alextretyak/legacy-cc/commit/01b639d} и посчитать статистику снова.

Теперь количество break в switch, которые можно убрать из кода/текста программы при использовании поведения «break по умолчанию» стало 27 (против 10 в оригинальной версии). Напомню, что псевдооператор "проваливания" потребуется лишь в 13 случаях. Данная статистика опровергает вот это ваше утверждение:
Ранее куда как чаще использовался первый вариант
Так как 27 > 13. Кому-то может не понравиться тот факт, что такая статистика является некой «средней температурой по репозиторию». Вот статистика раздельно по двум этим компиляторам:
last1120c: 12 > 5.
prestruct: 15 > 8.
сейчас этот стиль кодирования вытравливается из умов и навязывается совершенно другой.
Да, кодирование в стиле «лапши из goto» с тех пор активно вытравливается. :)(: Но что в этом плохого?

И как бы это неприятно не было для вас, но я считаю, что эти полученные результаты вполне убедительно развеивают миф, что есть какой-то там более совершенный ранний стиль кодирования, при котором поведение «проваливание по умолчанию» даёт некое преимущество.

✅  2024/12/02 13:19, Gudleifr          #27 

есть какой-то там более совершенный ранний стиль кодирования, при котором поведение «проваливание по умолчанию» даёт некое преимущество
Есть целых три стиля, окромя обсуждаемого злесь масштабирования:
1. В "плюем и копипастим", проваливание позволяет обособить наиболее общий участок кода. И сэкономить на прямом кодировании автоматов.
2. В структурном оно не играет особой роли, т.к. даже без него см. выше пример Кернигана — return всех помирит.
3. В проблемно-ориентированных языках switch если и реализуется, то "поперек". См. например Win-FOBOS https://gudleifr.forum2x2.ru/t35-topic#308 .

✅  2024/12/02 16:45, Клихальт          #28 

alextretyak,
оператор break в switch вообще не поддерживался. Поэтому считать количество его присутствия было довольно глупо. :)(:
Возможно и так, но по вашей ссылке я не увидел данного утверждения. Может поделитесь цитатой?
Да, кодирование в стиле «лапши из goto» с тех пор активно вытравливается. :)(: Но что в этом плохого?
Вытравливается не "кодирование в стиле «лапши из goto»", а его использование как таковое в любом контексте. Разве не так?
убедительно развеивают миф, что есть какой-то там более совершенный ранний стиль кодирования
Вот хлебом Вас не корми, дай какой нибудь миф развеять. А ведь в данном случае единственный миф это само существование развенчиваемого Вами мифа. Во всяком случае я к нему не имею никакого отношения — никогда не утверждал такового, а приводил эти исходные тексты лишь для примера стиля, отличного от современного нам и активно навязываемого, т.е. как пример просто другого и только.

Кстати, камешек в копилку предыдущей темы, из обсуждения коей и вышла эта — в приведённых мною текстах компилятора (версия last1120c) программист при помощи goto эмулирует отнюдь не только break в switch, но и continue из вложенного во внешний цикл и безконечные циклы с выходом из середины. Там вообще много интересных моментов.

✅  2024/12/04 00:00, alextretyak          #29 

Возможно и так, но по вашей ссылке я не увидел данного утверждения. Может поделитесь цитатой?
Такие мелкие детали в публичных документах, сохранившихся с того времени, обычно не описывают. И такое утверждение можно было бы поискать лишь в истории коммитов. Вот только систем управления версиями в те времена ещё не существовало. Поэтому цитаты, увы, не будет.

А вывод такой я сделал сам на основании такого факта, что в last1120c нет ни одного break в switch, а в более новом prestruct такие break есть. Значит дело не в том, что автору тех исходников «было удобнее обходиться без break в switch», а скорее всего при написании кода last1120c оператор break в switch не поддерживался [или даже не в switch, а просто break не поддерживался, т.к. в last1120c всего один break (в цикле while)], и его поддержка была добавлена позже.
программист при помощи goto эмулирует отнюдь не только break в switch, но и continue из вложенного во внешний цикл и безконечные циклы с выходом из середины.
Тоже мне, открыли Америку. Ведь в машинном коде все управляющие конструкции из языков высокого уровня реализуются через аналог goto — инструкции jmp/jcc (и даже вызов процедур и возврат при желании можно реализовать через них).
Вытравливается не "кодирование в стиле «лапши из goto»", а его использование как таковое в любом контексте. Разве не так?
Так. Вот только приведённый вами исходный текст первого компилятора Си ни разу не аргумент в защиту использования goto в современных программах. В те времена использовали goto скорее по привычке после программирования на языке ассемблера, в котором кроме аналога goto — безусловных переходов jmp и условных jcc — по сути управляющих инструкций больше и нету (максимум, что может быть ещё — это инструкции вызова процедуры call и возврата ret).

Ну и ещё, в те времена, по причине сильнейшего дефицита памяти (как оперативной, так и постоянной/энергонезависимой), использование goto позволяло несколько сократить объём сгенерированного машинного кода. Вот только ничего хорошего в такой оптимизации я не вижу, т.к. достигнута она не столько пониманием низкоуровневых вещей и аппаратной части, а больше "трюкачеством", которое крайне затрудняет понимание исходного кода другими программистами и его сопровождение. Но это уже оффтоп. Про goto на этом сайте есть отдельная страница.

✅  2024/12/04 00:12, Gudleifr          #30 

Ведь в машинном коде все управляющие конструкции из языков высокого уровня реализуются через аналог goto — инструкции jmp/jcc (и даже вызов процедур и возврат при желании можно реализовать через них)
Более того, разные "квази-структурные методики" тех лет предлагали реализовать "правильные структуры управления" на языках, их не имеющих, просто путем осторожного использования goto и тщательного его "структурного" комментирования.

Но, конечно, речь как всегда, идет просто о "неумении готовить кошек". И сложные примеры "без goto" читать сложно — как выше у Кернигана, и бездумный перевод алгоритмов в вид "правильных блоков" слишком дорого стоит — https://gudleifr.forum2x2.ru/t18-topic#390 .

✅  2024/12/05 23:48, Автор сайта          #31 

alextretyak, стало интересно, а был ли break изначально в switch? В те времена фактическим стандартом Си была книга K&R. Второе издание K&R от 1988 г. стало стандартом ANSI C. Искал-искал первое издание от 1978 г. (нас же интересует, как было изначально?), и даже оглавление находил. И там было написано, что switch описан на 54 странице. Вот только саму страницу так и не нашёл. Нужны цифровые археологи. 🤣

✅  2024/12/06 02:37, Gudleifr          #32 

Вот только саму страницу так и не нашёл
https://retrofun.pl/wp-content/uploads/sites/2/2023/12/1978-ritchie-the-c-programming-language.pdf

✅  2024/12/06 12:53, Вежливый Лис          #33 

The C Programming Language — Draft Version 1, 1977, Brian W. Kernighan; Dennis M. Ritchie

https://kmr.annas-archive.org/md5/2f2c1a8caeb08e1925f8bd82dda900db

43-я страница в .pdf-файле, там пример switch с break. Т.е. в 1977 всё уже было на месте.

✅  2024/12/07 00:00, alextretyak          #34 

Автор сайта
стало интересно, а был ли break изначально в switch?
Всё указывает на то, что да, был:
1. Здесь (http://cm.bell-labs.co/who/dmr/chist.html) указывается на различие между Си и BCPL (от которого и произошёл Си) — в последнем для break в switch использовалось специальное ключевое слово — endcase.
2. В обоих компиляторах last1120c и prestruct оператор break поддерживается в switch наряду с while и do (поддержки оператора for в этих компиляторах ещё не было).
3. В первом издании K&R (ссылку уже дал Gudleifr чуть выше) break в switch уже был.

✅  2024/12/07 20:54, Клихальт          #35 

alextretyak, как видите ваше предположение об отсутствии break в switch на основании того, что его не было в исходных текстах компилятора оказалось ложным — человек писал так, как привык и как ему было удобнее.

✅  2024/12/09 00:00, alextretyak          #36 

как видите ваше предположение об отсутствии break в switch ... оказалось ложным
Совсем не обязательно. То, что break в switch изначально был в языке, вовсе не означает, что он был в самой первой (и неполной!) реализации языка. Разработка компилятора — это итеративный процесс. Никто не станет пытаться реализовывать компилятор, который сразу же с самой первой своей версии поддерживает все возможности языка программирования. Для начала ограничиваются поддержкой базовых и наиболее насущных и простых в реализации операторов: if/else, goto, return. Затем можно добавить циклы (while и do-while). Реализация конструкции switch тут явно не в приоритете, как и оператора break.

И то, что оператор break реализован в самом старом из сохранившихся компиляторе last1120c (причём из написанных на Си, а не на ассемблере!), вовсе не означает, что он (оператор break) был реализован в «компиляторе, который компилировал этот компилятор (last1120c)». Скорее всего, тот компилятор вообще был написан на ассемблере. А реализовывать всякие приятные (но не обязательные) «плюшки» (вроде оператора break, который можно сэмулировать через goto) на ассемблере — это редко оправданная роскошь. И я по-прежнему считаю, что тот «компилятор, который компилировал компилятор last1120c» break в switch (а точнее, просто break) не поддерживал. А вот более новый prestruct вполне вероятно компилировался уже посредством last1120c, поэтому break в prestruct используется достаточно активно: всего 17 break'ов, из них 15 — внутри switch.
человек писал так, как привык и как ему было удобнее...
Достоверно подтвердить или опровергнуть это может только тот, кто писал этот исходный код. Если это был действительно Деннис Ритчи (как написано в README репозитория https://github.com/mortdeus/legacy-cc), то, увы, спросить его об этом уже не получится.

✅  2024/12/09 00:16, Неслучайный читатель          #37 

Язык C произошёл от B, который произошёл от BCPL. В "Users' Reference to B" (https://web.archive.org/web/ 20070807110336/ http://cm.bell-labs.com/ cm/cs/who/dmr/kbman.html) нет ни одного случая, чтобы после case был break. А вот goto — пожалуйста.

В "A TUTORIAL INTRODUCTION TO THE LANGUAGE B" by B. W. Kernighan (https://web.archive.org/ web/20070807110157 /http://cm.bell-labs.com/ cm/cs/who/dmr/btut.html) нашёл пример, в котором внутри цикла есть switch, а внутри switch — break. Но этот break является не концом ветви case, а выходом из цикла.¯\_(ツ)_/¯

❔  2024/12/15 08:12, Клихальт          #38 

Неслучайный читатель, Ваши доводы подтверждают лишь то, что в языках B и BCPL такая конструкция не была предусмотрена и реализована, но разговор то шел не о них, а о C, который от этих языков хоть и произошел, но имел от них немало отличий и использование break в операторе switch одно из них.

❔  2024/12/15 14:14, Неслучайный читатель          #39 

Язык Би появился в 1969 году, а Си — в 1972. Би делал Томсон, ему помогал Ритчи, а Си делал Ритчи, ему помогал Томсон. Си создавался для того, чтобы написать Unix. Чтобы понять, был ли break изначально или он результат эволюции и появился ближе к версии K&R, было бы хорошо посмотреть первую версию Unix.

❔  2024/12/15 16:50, Клихальт          #40 

Неслучайный читатель,
Чтобы понять, был ли break изначально или он результат эволюции и появился ближе к версии K&R, было бы хорошо посмотреть первую версию Unix.
Да, было бы интересно взглянуть на первую версию Unix, написанную на Си. Вот только мне в сети она не попадалась. Быть может Вы знаете где её взять?

❔  2024/12/15 17:05, Деньги на WWWетер          #41 

Первая версия Unix написана на ассемблере:
https://archive.computerhistory.org/resources/access/text/2019/09/102785108-05-001-acc.pdf

❔  2024/12/15 17:32, Клихальт          #42 

Деньги на WWWетер, Именно поэтому я и уточнил: "написанную на Си".

❔  2024/12/22 13:09, Gudleifr          #43 

Чем заканчивать SWICH — вопрос второй. А для чего его применять? Какой из вариантов предпочитаете?
1. Анализ входных сигналов. Как, например, пытались разбирать Win-сообщения.
2. Анализ выходных сигналов. Например, изначальный арифметический IF в Фортране — на три позиции.
3. Анализ состояния. Как в моделях ДКА. Сюда же метод Даффа.
4. Выбор нужного объекта-исполнителя. Как в Си++ обработка исключений.
(Примеры, пардон, достаточно условные).

Добавить свой отзыв

Написать автору можно на электронную почту
mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Содержание

Каким должен быть язык программирования?

Анализ и критика

●  Устарел ли текст как форма представления программы

●  Русский язык и программирование

●  Многоязыковое программирование

Синтаксис языков программирования

Синтаксический сахар

●  Некоторые «вкусности» Алгол-68

●  «Двухмерный» синтаксис Python

●  Почему языки с синтаксисом Си популярнее языков с синтаксисом Паскаля?

●  Должна ли программа быть удобочитаемой?

●  Стиль языка программирования

●  Тексто-графическое представление программы

●●  Разделители

●●  Строки программы

●●  Слева направо или справа налево?

●  Комментарии

●●  Длинные комментарии

●●  Короткие комментарии

●●  Комментарии автоматической генерации документации

●●  Нерабочий код

●●  Помеченные комментарии

●  Нужны ли беззнаковые целые?

●  Шестнадцатиричные и двоичные константы

●  Условные операторы

●  Переключатель

●  Циклы

●●  Продолжение цикла и выход из него

●  Некошерный «goto»

●  Изменение приоритетов операций

●  Операции присвоения и проверки на равенство. Возможно ли одинаковое обозначение?

●  Так ли нужны операции «&&», «||» и «^^»?

●  Постфиксные инкремент и декремент

●  Почему в PHP для конкатенации строк используется «.»?

●  Указатели и ссылки в C++

●●  О неправомерном доступе к памяти через указатели

●  Обработка ошибок

●  Функциональное программирование

●●  Нечистые действия в чистых функциях

●●  О чистоте и нечистоте функций и языков

●●  Макросы — это чистые функции, исполняемые во время компиляции

●●  Хаскелл, детище британских учёных

●●  Измеряем замедление при вызове функций высших порядков

●●  C vs Haskell: сравнение скорости на простом примере

●●  Уникальность имён функций: за и против

●●  Каррирование: для чего и как

●●  О тестах, доказывающих отсутствие ошибок

●  Надёжные программы из ненадёжных компонентов

●●  О многократном резервировании функций

●  Оценка надёжности функции с несколькими реализациями

●  Использование памяти

●  Почему динамическое распределение памяти — это плохо

●  Как обеспечить возврат функциями объектов переменной длины?

●●  Типы переменного размера (dynamically sized types, DST) в языке Rust

●●  Массивы переменной длины в C/C++

●●  Размещение объектов в стеке, традиционный подход

●●  Размещение объектов переменной длины с использованием множества стеков

●●  Размещение объектов переменной длины с использованием двух стеков

●●  Реализация двухстековой модели размещения данных

●●  Двухстековая модель: тесты на скорость

●●  Изменение длины объекта в стеке во время исполнения

●●  Размещение объектов переменной длины с использованием одного стека

●  Можно ли забыть о «куче», если объекты переменной длины хранить в стеке

●  Безопасность и размещение объектов переменной длины в стеке

●  Массивы, структуры, типы, классы переменной длины

●  О хранении данных в стеке, вместо заключения

●  Реализация параметрического полиморфизма

Описание языка

Компилятор

Отечественные разработки

Cтатьи на компьютерные темы

Компьютерный юмор

Новости и прочее




Последние отзывы

2025/01/15 23:53 ••• Автор сайта
Каким должен быть язык программирования?

2025/01/15 13:28 ••• Gudleifr
В защиту PL/1

2025/01/15 13:00 ••• Иван
Энтузиасты-разработчики компиляторов и их проекты

2025/01/15 00:34 ••• Автор сайта
Предложения и замечания

2025/01/14 07:23 ••• Вежливый Лис
Компилятор

2025/01/14 00:00 ••• alextretyak
Про лебедей, раков и щук

2025/01/13 15:46 ••• Вежливый Лис
Экскурс в историю разработок языков программирования и компиляторов в СССР

2025/01/12 11:44 ••• Gudleifr
Программирование исчезнет. Будет дрессировка нейронных сетей

2025/01/11 17:57 ••• Денис Будяк
Арифметика синтаксиса-3

2025/01/06 22:56 ••• Борис К.
Циклы

2025/01/04 01:18 ••• Автор сайта
Новости и прочее

2024/12/22 13:09 ••• Gudleifr
Переключатель

2024/12/20 23:08 ••• Gudleifr
Русский язык и программирование