Почему оператор switch был разработан с учетом необходимости перерыва?

Учитывая простой оператор switch

switch (int)
{
    case 1 :
    {
        printf("1\n");
        break;
    }

    case 2 : 
    {
        printf("2\n");
    }

    case 3 : 
    {
        printf("3\n");
    }
}

Отсутствие оператора break в случае 2 означает, что выполнение будет продолжено внутри кода для случая 3. Это не случайность; так оно и было задумано. Почему было принято это решение? Какие преимущества это дает по сравнению с семантикой автоматического разбиения блоков? В чем было обоснование?


person EvilTeach    schedule 31.10.2008    source источник


Ответы (9)


Многие ответы, кажется, сосредоточены на способности провалиться как на причине для требования оператора break.

Я считаю, что это была просто ошибка, во многом из-за того, что при разработке C не было такого большого опыта в использовании этих конструкций.

Питер Ван дер Линден приводит доводы в своей книге «Экспертное программирование на языке Си»:

Мы проанализировали исходные коды компилятора Sun C, чтобы увидеть, как часто использовалось провалы по умолчанию. Передняя часть компилятора Sun ANSI C имеет 244 оператора переключения, каждый из которых имеет в среднем семь случаев. Падение происходит всего в 3% всех этих случаев.

Другими словами, нормальное поведение переключателя неправильное в 97% случаев. Дело не только в компиляторе - напротив, там, где в этом анализе использовался провал, это часто было для ситуаций, которые чаще возникают в компиляторе, чем в другом программном обеспечении, например, при компиляции операторов, которые могут иметь один или два операнда. :

switch (operator->num_of_operands) {
    case 2: process_operand( operator->operand_2);
              /* FALLTHRU */

    case 1: process_operand( operator->operand_1);
    break;
}

Провал кейса настолько широко признан как дефект, что существует даже специальное соглашение о комментариях, показанное выше, в котором говорится, что lint: «Это действительно один из тех 3% случаев, когда провал был желательным».

Я думаю, что для C # было хорошей идеей требовать явного оператора перехода в конце каждого блока case (при этом позволяя складывать несколько меток case - до тех пор, пока есть только один блок операторов). В C # у вас все еще может быть один случай, переходящий в другой - вам просто нужно сделать это явным, перейдя к следующему случаю с помощью goto.

Жаль, что Java не воспользовалась возможностью отказаться от семантики C.

person Michael Burr    schedule 31.10.2008
comment
Действительно, я думаю, они пошли на простоту реализации. Некоторые другие языки поддерживали более сложные случаи (диапазоны, множественные значения, строки ...) за счет, возможно, эффективности. - person PhiLho; 31.10.2008
comment
Наверное, Java не хотела ломать привычки и сеять путаницу. Для другого поведения им пришлось бы использовать другую семантику. Так или иначе, Java-дизайнеры потеряли ряд возможностей оторваться от C. - person PhiLho; 31.10.2008
comment
@PhiLho - Думаю, вы, наверное, ближе всего к истине по простоте реализации. - person Michael Burr; 31.10.2008
comment
Если вы используете явные переходы через GOTO, разве не так продуктивно просто использовать серию операторов IF? - person DevinB; 13.04.2009
comment
В этом случае goto действительно используются как указание на то, что вы хотите, чтобы случай провалился (подобно маркерам lint / * FALLTHRU * /). Это должна быть довольно исключительная ситуация, поскольку случаи, когда они не проходят, случаются только в 3% случаев (согласно анализу в книге Ван дер Линдена). - person Michael Burr; 13.04.2009
comment
даже Паскаль реализовал свой переключатель без перерыва. как мог компилятор-программист C не думать об этом @@ - person Chan Le; 28.05.2011

Во многих отношениях c - это просто чистый интерфейс для стандартных идиом сборки. При написании управления потоком, управляемым таблицей переходов, программист может выбрать падение или выпрыгивание из «структуры управления», а для перехода требуется явная инструкция.

Итак, c делает то же самое ...

person dmckee --- ex-moderator kitten    schedule 31.10.2008
comment
Я знаю, что многие люди так говорят, но я думаю, что это не полная картина; C также часто является уродливым интерфейсом для антипаттернов сборки. 1. Уродливо: заявления вместо выражений. Декларация отражает использование. Прославленный копипаст как механизм модульности и переносимости. Тип system way слишком сложный; поощряет ошибки. NULL. (Продолжение в следующем комментарии.) - person Harrison; 18.05.2012
comment
2. Antipatterns: не предоставляет чистые помеченные объединения, одну из двух фундаментальных идиом сборки данных. (Knuth, том 1, глава 2) Вместо немаркированных союзов, неидиоматический взлом. Этот выбор мешал людям думать о данных на протяжении десятилетий. А строки с завершением NUL - едва ли не худшая идея на свете. - person Harrison; 18.05.2012
comment
@HarrisonKlaperman: Нет идеального метода хранения строк. Подавляющее большинство проблем, связанных со строками с завершающим нулем, не возникло бы, если бы подпрограммы, которые принимали строки, также принимали параметр максимальной длины, и возникли бы с подпрограммами, которые сохраняют строки с маркировкой длины в буферах фиксированного размера без принятия параметра размера буфера. . Дизайн оператора switch, в котором регистры представляют собой просто метки, может показаться современным глазам странным, но он не хуже, чем дизайн цикла Fortran DO. - person supercat; 16.07.2012
comment
Если я решу написать таблицу переходов в сборке, я возьму значение case и волшебным образом превращу его в индекс таблицы переходов, перейду в это место и выполню код. После этого я не перейду к следующему делу. Я перейду к единому адресу, который является выходом из всех дел. Мысль о том, что я прыгну или упаду в тело следующего дела, глупая. У этого шаблона нет варианта использования. - person EvilTeach; 16.03.2014
comment
В то время как в настоящее время люди больше используются для очистки и самозащиты идо и языковых функций, которые не позволяют выстрелить себе в ногу, это воспоминание из области, где байты были дорогими (C появился еще до 1970 года). если ваш код должен умещаться в пределах 1024 байтов, вы испытаете сильное желание повторно использовать фрагменты кода. Повторное использование кода, начиная с разных точек входа, имеющих один и тот же конец, является одним из механизмов достижения этого. - person rpy; 12.04.2016

Очевидно, что для реализации устройства Даффа:

dsend(to, from, count)
char *to, *from;
int count;
{
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}
person FlySwat    schedule 31.10.2008
comment
Я люблю устройство Даффа. Так элегантно и быстро. - person dicroce; 31.10.2008
comment
да, но должно ли оно появляться каждый раз, когда на SO есть оператор switch? - person billjamesdev; 31.10.2008
comment
Вы пропустили две закрывающие фигурные скобки ;-). - person Toon Krijthe; 31.10.2008

Если бы кейсы были спроектированы так, чтобы неявно ломаться, у вас не могло бы быть провала.

case 0:
case 1:
case 2:
    // all do the same thing.
    break;
case 3:
case 4:
    // do something different.
    break;
default:
    // something else entirely.

Если бы переключатель был спроектирован так, чтобы неявно срабатывать после каждого случая, у вас не было бы выбора. Конструкция корпуса выключателя должна быть более гибкой.

person Bill the Lizard    schedule 31.10.2008
comment
Вы можете представить себе переключатель, который неявно ломается, но имеет ключевое слово провала. Неудобно, но работоспособно. - person dmckee --- ex-moderator kitten; 31.10.2008
comment
Как бы это было лучше? Я бы предположил, что оператор case работает таким образом чаще, чем блок кода для каждого случая ... это блок if / then / else. - person billjamesdev; 31.10.2008
comment
Я бы добавил, что в вопросе вложения областей видимости {} в каждом блоке case добавляют путаницы, поскольку они выглядят как стиль оператора while. - person Matthew Smith; 31.10.2008
comment
@Bill: Было бы хуже, я думаю, но это касается жалобы, поданной Майком Б: это падение (за исключением нескольких случаев того же самого) является редким событием и не должно быть поведением по умолчанию. - person dmckee --- ex-moderator kitten; 31.10.2008
comment
Я оставил фигурные скобки для краткости, так как они должны присутствовать в нескольких утверждениях. Я согласен с тем, что провал следует использовать только тогда, когда случаи точно такие же. Это чертовски сбивает с толку, когда кейсы основываются на предыдущих кейсах с использованием провалов. - person Bill the Lizard; 31.10.2008
comment
В качестве альтернативы у вас может быть отдельный оператор, называемый чем-то вроде doswitch, который допускает падение, а переключатель по умолчанию - нет. По той же причине, что у нас есть циклы do-while, которые используются редко по сравнению с обычным циклом while. - person saolof; 04.03.2017

Операторы case в операторах switch - это просто метки.

Когда вы включаете значение, оператор switch по существу выполняет goto для метки с совпадающим значением.

Это означает, что разрыв необходим, чтобы избежать перехода к коду под следующей меткой.

Что касается причины, почему это было реализовано таким образом - сквозной характер оператора switch может быть полезен в некоторых сценариях. Например:

case optionA:
    // optionA needs to do its own thing, and also B's thing.
    // Fall-through to optionB afterwards.
    // Its behaviour is a superset of B's.
case optionB:
    // optionB needs to do its own thing
    // Its behaviour is a subset of A's.
    break;
case optionC:
    // optionC is quite independent so it does its own thing.
    break;
person LeopardSkinPillBoxHat    schedule 31.10.2008
comment
Есть две большие проблемы: 1) Забыть break там, где он нужен. 2) Если порядок операторов case изменен, откат может привести к запуску неправильного case. Таким образом, я считаю, что C # работает намного лучше (явное goto case для провала, за исключением пустых меток case). - person Daniel Rose; 28.08.2012
comment
@DanielRose: 1) есть способы забыть break и в C # - самый простой - когда вы не хотите, чтобы case что-либо делал, но забыли добавить break (возможно, вы так запутались в пояснительном комментарии или вам позвонили прочь на другую задачу): выполнение просто перестанет быть case ниже. 2) поощрение goto case поощряет неструктурированное спагетти-кодирование - вы можете получить случайные циклы (case A: ... goto case B; case B: ... ; goto case A;), особенно когда случаи в файле разделены и их сложно найти в сочетании. В C ++ локализован провал. - person Tony Delroy; 27.06.2016

Чтобы разрешить такие вещи, как:

switch(foo) {
case 1:
    /* stuff for case 1 only */
    if (0) {
case 2:
    /* stuff for case 2 only */
    }
    /* stuff for cases 1 and 2 */
case 3:
    /* stuff for cases 1, 2, and 3 */
}

Подумайте о case ключевом слове как о goto ярлыке, и это будет намного естественнее.

person R.. GitHub STOP HELPING ICE    schedule 12.11.2010
comment
Знак if(0) в конце первого случая является злом, и его следует использовать только в том случае, если целью является обфускация кода. - person Daniel Rose; 28.08.2012
comment
Думаю, весь этот ответ был упражнением в злодействе. :-) - person R.. GitHub STOP HELPING ICE; 28.08.2012

Это исключает дублирование кода, когда в нескольких случаях необходимо выполнить один и тот же код (или один и тот же код последовательно).

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

person Greg Rogers    schedule 31.10.2008

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

switch (nShorts)
{
case 4: frame.leadV1    = shortArray[3];
case 3: frame.leadIII   = shortArray[2];
case 2: frame.leadII    = shortArray[1];
case 1: frame.leadI     = shortArray[0]; break;
default: TS_ASSERT(false);
}
person Martti K    schedule 14.08.2018

Как многие здесь указали, это позволяет одному блоку кода работать в нескольких случаях. Это должно быть более частым явлением для ваших операторов switch, чем «блок кода на случай», который вы указываете в своем примере.

Если у вас есть блок кода для каждого случая без провалов, возможно, вам следует рассмотреть возможность использования блока if-elseif-else, поскольку это может показаться более подходящим.

person billjamesdev    schedule 31.10.2008