Чому ми повинні використовувати перерву в комутаторі?


74

Хто вирішив (і базуючись на яких поняттях), що switchконструкція (багатьма мовами) повинна використовуватись breakу кожному висловлюванні?

Чому ми повинні написати щось подібне:

switch(a)
{
    case 1:
        result = 'one';
        break;
    case 2:
        result = 'two';
        break;
    default:
        result = 'not determined';
        break;
}

(помітили це в PHP та JS; можливо, це багато інших мов, що використовують це)

Якщо switchце альтернатива if, чому ми не можемо використовувати ту саму конструкцію, що і для if? Тобто:

switch(a)
{
    case 1:
    {
        result = 'one';
    }
    case 2:
    {
        result = 'two';
    }
    default:
    {
        result = 'not determined';
    }
}

Кажуть, що breakперешкоджає виконанню блоку, що слідує за поточним. Але чи дійсно хтось стикається з ситуацією, коли була необхідність у виконанні поточного блоку та наступних? Я цього не зробив. Для мене breakзавжди є. У кожному блоці. У кожному коді.


1
Як зазначалося в більшості відповідей, це пов'язано з "пропусканням", де декілька умов переносяться через одні і ті ж блоки "тоді". Однак це залежить від мови - наприклад, RPG розглядає CASEвисловлювання еквівалентно гігантському блоку if / elseif.
Завод-Муза

34
Це було поганим рішенням, прийнятим C-дизайнерами (як і багато рішень, було прийнято зробити перехід від складання -> C, а переклад назад - простішим, а не для простоти програмування) , яке тоді, на жаль, успадковано іншими мовами на базі С Використовуючи просте правило "Завжди робити загальний випадок за замовчуванням !!" ми можемо бачити, що поведінка за замовчуванням повинна була бути зламаною, з окремим ключовим словом для проникнення, оскільки це, безумовно, найбільш поширений випадок.
BlueRaja - Danny Pflughoeft

4
Я декілька разів використовував пропуск, хоча, мабуть, оператор переключення на моїй бажаній мові в той час (C) міг бути розроблений, щоб уникнути цього; наприклад, я зробив, case 'a': case 'A': case 'b': case 'B'але в основному тому, що не можу case in [ 'a', 'A', 'b', 'B' ]. Трохи кращим питанням є те, що в моїй поточній бажаній мові (C #) перерва є обов'язковою , і немає неявного падіння, хоча; забуття break- синтаксична помилка ...: \
KutuluMike

1
Проходження з фактичним кодом часто зустрічається в реалізаціях парсера. case TOKEN_A: /*set flag*/; case TOKEN_B: /*consume token*/; break; case TOKEN_C: /*...*/
OrangeDog

1
@ BlueRaja-DannyPflughoeft: C також був розроблений таким чином, щоб його легко було скласти. "Випустити стрибок, якщо breakвін присутній де-небудь" є значно простішим для реалізації правилом, ніж "Не випускати стрибок, якщо fallthroughвін присутній в а switch".
Джон Перді

Відповіді:


95

C була однією з перших мов, яка отримала switchвисловлювання в цій формі, і всі інші основні мови успадкували її звідти, здебільшого вирішили зберегти C семантику за замовчуванням - вони або не думали про переваги її зміни, або судили вони менш важливі, ніж збереження поведінки, до якої всі звикли.

Що стосується того, чому C був сконструйований таким чином, він, ймовірно, випливає з концепції C як "портативної збірки". switchЗатвердження в основному це абстракція таблиці галузевої і таблиця філії також має неявне падіння-через та вимагає додаткової інструкції переходу , щоб уникнути цього.

Таким чином, дизайнери C також вирішили зберегти семантику асемблера за замовчуванням.


3
VB.NET - приклад мови, яка його змінила. Немає небезпеки того, що воно буде втікатися у пункті справи.
ткнути

1
Tcl - інша мова, яка ніколи не переходить до наступного пункту. Практично ніколи не хотів це робити (на відміну від C, де це корисно при написанні певних типів програм).
Дональні стипендіати

4
Мови предків C, B і старіші BCPL , обидві мають (мали?) Заяви переключення; BCPL написав це switchon.
Кіт Томпсон

Виклади справи Рубі не пропадають; ви можете отримати подібну поведінку, маючи два тестових значення в одному when.
Натан Лонг

86

Тому що switchце не альтернатива if ... elseтверджень на цих мовах.

Використовуючи switch, ми можемо зіставити одночасно декілька умов, що в деяких випадках високо цінується.

Приклад:

public Season SeasonFromMonth(Month month)
{
    Season season;
    switch (month)
    {
        case Month.December:
        case Month.January:
        case Month.February:
            season = Season.Winter;
            break;

        case Month.March:
        case Month.April:
        case Month.May:
            season = Season.Spring;
            break;

        case Month.June:
        case Month.July:
        case Month.August:
            season = Season.Summer;
            break;

        default:
            season = Season.Autumn;
            break;
    }

    return season;
}

15
more than one block ... unwanted in most casesЯ з вами не згоден.
Satish Pandey

2
@DanielB Хоча це залежить від мови; Оператори перемикання C # не допускають такої можливості.
Енді

5
@Andy Ми говоримо про провал все ще? Оператори перемикання C # безумовно дозволяють це (перевірити 3-й приклад), звідси і необхідність break. Але так, наскільки я знаю, пристрій Даффа не працює в C #.
Даніель Б

8
@DanielB Так, але компілятор не дозволить умову, код та іншу умову без перерви. У вас можуть бути суміщені умови, без коду між ними, або вам потрібна перерва. З вашого посилання C# does not support an implicit fall through from one case label to another. Ви можете провалитися, але немає шансів випадково забути перерву.
Енді

3
@Matsemann .. Я зараз ми можемо робити все, використовуючи, if..elseале в деяких ситуаціях switch..caseбуло б краще. Наведений вище приклад був би трохи складним, якщо ми використовуємо if-else.
Satish Pandey

15

Про це було задано питання про переповнення стека в контексті C: Чому оператор перемикання був розроблений для необхідності перерви?

Підсумовуючи прийняту відповідь, ймовірно, була помилка. Більшість інших мов, ймовірно, щойно слідували за C. Однак, схоже, деякі мови, такі як C #, виправили це, дозволяючи пропустити, але лише тоді, коли програміст прямо говорить про це (джерело: посилання вище, я не розмовляю з C # сам) .


Google Go робить те ж саме, що і IIRC (допускаючи прорив, якщо прямо вказано).
Лев

PHP також. І чим більше ви дивитеся на отриманий код, тим неприємніше це відчуває. Мені подобається /* FALLTHRU */коментар у відповіді, пов’язаній вище @Tapio, щоб довести, що ти це мав на увазі.
DaveP

1
Скажімо, C # goto case, як і посилання, не ілюструє, чому це працює так добре. Ви все ще можете складати декілька випадків, як вище, але як тільки ви вставите код, у вас повинен бути явний goto case whateverперехід до наступного випадку або ви отримаєте помилку компілятора. Якщо ви запитаєте мене, з двох, можливість складати справи, не турбуючись про ненавмисне проникнення через недбалість, є набагато кращою особливістю, ніж включення явного проходу goto case.
Джеспер

9

Відповім на прикладі. Якщо ви хотіли перерахувати кількість днів за кожен місяць року, очевидно, що деякі місяці мають 31, десь 30 та 1 28/29. Це виглядатиме так,

switch(month) {
    case 4:
    case 6:
    case 9:
    case 11;
        days = 30;
        break;
    case 2:
        //Find out if is leap year( divisible by 4 and all that other stuff)
        days = 28 or 29;
        break;
    default:
        days = 31;
}

Це приклад, коли декілька випадків мають однаковий ефект і всі згруповані разом. Очевидно, була причина вибору ключового слова break, а не if ... else if construct.

Головне, що слід зазначити тут, - це те, що оператор перемикання з багатьма подібними випадками не є if ... else if ... else if ... else для кожного з випадків, а, скоріше, якщо (1, 2, 3) ... інше, якщо (4,5,6) ще ...


2
Ваш приклад виглядає більш багатослівним, ніж ifбез будь-якої вигоди. Можливо, якби ваш приклад призначив ім’я або зробив роботу, пов’язану з місяцем, на додаток до прохідного, корисність пропуску була б більш очевидною? (не коментуючи, чи потрібно
ВЗАЄМО

1
Це правда. Однак я просто намагався показати випадки, коли можна застосувати падіння. Очевидно, було б набагато краще, якби кожен з місяців потребував чогось, що робиться індивідуально.
Awemo

8

Є дві ситуації, коли "проникнути" з одного випадку в інший може статися - порожній випадок:

switch ( ... )
{
  case 1:
  case 2:
    do_something_for_1_and_2();
    break;
  ...
}

і не порожній випадок

switch ( ... )
{
  case 1:
    do_something_for_1();
    /* Deliberately fall through */
  case 2:
    do_something_for_1_and_2();
    break;
...
}

Незважаючи на посилання на пристрій Даффа , законних примірників для другого випадку мало і далеко, і взагалі заборонено стандартами кодування та позначено під час статичного аналізу. І там, де він виявлений, це найчастіше через відсутність а break.

Перший ідеально розважливий і загальний.

Якщо чесно, я не бачу причин для того, щоб потребувати breakмовного і мовного аналізатора, знаючи, що порожній корпус - це провал, а не порожній випадок - автономний.

Шкода, що панель ISO C здається більш зайнятою додаванням до мови нових (небажаних) та погано визначених функцій, а не виправленням невизначених, не визначених чи визначених реалізацією функцій, не кажучи вже про нелогічних.


2
Слава богу, ISO C не впускає в мову різких змін!
Джек Едлі

4

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

Один з випадків, коли необхідно breakне використовувати це Пристрій Даффа . Це використовується для " розгортання " циклів, де це може прискорити операції, обмеживши кількість необхідних порівнянь. Я вважаю, що початкове використання дозволило функціонал, який раніше був занадто повільним, повністю закрученим циклом. Він торгує розміром коду для швидкості в деяких випадках.

breakЯкщо у справі є який-небудь код, корисно замінити відповідний коментар. Інакше хтось виправить відсутніх breakта введе помилку.


Дякуємо за приклад пристрою Даффа (+1). Я цього не знав.
трейдер

3

У C, де здається джерело, блок коду switchтвердження не є особливою конструкцією. Це звичайний блок коду, подібно до блоку під ifоператором.

switch ()
{
}

if ()
{
}

caseі defaultє мітками стрибків всередині цього блоку, спеціально пов'язаними з switch. Вони обробляються так само, як і звичайні мітки стрибків goto. Тут важливе одне конкретне правило: мітки стрибків можуть бути майже скрізь у коді, не перериваючи потік коду.

Як звичайний блок коду, він не повинен бути складеним твердженням. Етикетки також необов’язкові. Це дійсні switchзаяви на мові C:

switch (a)
    case 1: Foo();

switch (a)
    Foo();

switch (a)
{
    Foo();
}

Сам стандарт С наводить це як приклад (6.8.4.2):

switch (expr) 
{ 
    int i = 4; 
    f(i); 
  case 0: 
    i=17; 
    /*falls through into default code */ 
  default: 
    printf("%d\n", i); 
} 

У фрагменті штучної програми об'єкт, ідентифікатором якого i є, з автоматичною тривалістю зберігання (всередині блоку), але ніколи не ініціалізується, і, якщо вираз керування має ненульове значення, виклик функції printf отримає доступ до невизначеного значення. Так само не можна отримати виклик до функції f.

Крім того, також defaultє ярлик стрибка, і тому він може бути де завгодно, не потребуючи останнього випадку.

Це також пояснює пристрій Даффа:

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);
}

Чому провал? Тому що в нормальному потоці коду в нормальному блоці коду очікується перехід до наступного оператора так само, як і в ifкодовому блоці.

if (a == b)
{
    Foo();
    /* "Fall-through" to Bar expected here. */
    Bar();
}

switch (a)
{
    case 1: 
        Foo();
        /* Automatic break would violate expected code execution semantics. */
    case 2:
        Bar();
}

Я здогадуюсь, що причиною цього стала простота реалізації. Вам не потрібен спеціальний код для розбору та складання switchблоку, догляду за спеціальними правилами. Ви просто розбираєте його, як і будь-який інший код, і лише потрібно дбати про мітки та вибір стрибків.

Цікаве подальше запитання з усього цього, якщо наступні вкладені заяви надруковують "Готово". чи ні.

int a = 10;

switch (a)
{
    switch (a)
    {
        case 10: printf("Done.\n");
    }
}

Стандарт C піклується про це (6.8.4.2.4):

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


1

Кілька людей вже згадували поняття відповідності декількох умов, що час від часу є дуже цінним. Однак здатність відповідати декільком умовам не обов'язково вимагає, щоб ви робили абсолютно те саме, що відповідає кожній умові. Розглянемо наступне:

switch (someCase)
{
    case 1:
    case 2:
        doSomething1And2();
        break;

    case 3:
        doSomething3();

    case 4:
        doSomething3And4();
        break;

    default:
        throw new Error("Invalid Case");
}

Тут є два різних способи узгодження множинних умов. З умовами 1 і 2 вони просто потрапляють в той самий сюжет коду і роблять те саме. Однак за умовами 3 та 4, хоча вони обидва закінчуються дзвінками doSomething3And4(), лише 3 виклики doSomething3().


Пізно до партії, але це абсолютно не "1 і 2", як підказує назва функції: є три різні стани, які можуть призвести до case 2запуску коду , тож якщо ми хочемо бути правильними (а ми це робимо) реальними ім'я функції повинно бути doSomethingBecause_1_OR_2_OR_1AND2()чи щось таке (хоча законний код, у якому незмінна відповідність двом різним випадкам, хоча все-таки добре написаний код практично не існує, тому принаймні це має бути doSomethingBecause1or2())
Майк "Pomax" Камерманс

Іншими словами, doSomething[ThatShouldBeDoneInTheCaseOf]1And[InTheCaseOf]2(). Це не стосується логічного та / або.
Panzercrisis

Я знаю, що це не так, але враховуючи, чому люди плутаються в такому коді, слід абсолютно пояснити, що таке "і", тому що покладаючись на те, що хтось здогадається "природне" та "логічне завершення" у відповіді, яка працює лише коли точно пояснено, потрібно більше пояснень у самій відповіді, або перепишіть ім’я функції, щоб усунути це двозначність =)
Майк 'Pomax' Камерманс

1

Щоб відповісти на два ваші запитання.

Чому C потрібні перерви?

Це зводиться до коренів Cs як "портативний асемблер". Там, де подібний псудо-код був таким:

    targets=(addr1,addr2,addr3);
    opt = 1  ## or 0 or 2
switch:
    br targets[opt]  ## go to addr2 
addr1:
    do 1stuff
    br switchend
addr2:
    do 2stuff
    br switchend
addr3
    do 3stuff
switchend:
    ......

оператор перемикання був розроблений, щоб забезпечити подібну функціональність на більш високому рівні.

У нас коли-небудь є вимикачі без перерв?

Так, це досить часто і є кілька випадків використання;

По-перше, ви можете захотіти вжити однакових дій у кількох випадках. Ми робимо це, складаючи корпуси один на одного:

case 6:
case 9:
    // six or nine code

Інший випадок використання, який часто зустрічається в державних машинах, полягає в тому, що після обробки одного стану ми негайно хочемо ввести та обробити інший стан:

case 9:
    crashed()
    newstate=10;
case 10:
    claim_damage();
    break;

-2

Заява про перерву - це стрибковий вислів, який дозволяє користувачеві вийти з найближчого вкладеного комутатора (для вашого випадку), в той час як, робити, для або передбачити. це так само просто, як це.


-3

Я думаю, що з усього написаного вище - головний результат цього потоку полягає в тому, що якщо ви плануєте створити нову мову - за замовчуванням має бути те, що вам не потрібно додавати breakзаяву, і компілятор буде ставитись до цього так, як ніби ви.

Якщо вам потрібен той рідкісний випадок, коли ви хочете продовжувати роботу до наступної справи - просто озвучте це continueзаявою.

Це можна вдосконалити так, що лише якщо ви використовуєте фігурні дужки всередині корпусу, тоді це не продовжується так, що приклад із місяцями, наведеними вище, декількох випадків, які виконують один і той же точний код - завжди працюватиме так, як очікувалося, не потребуючи continue.


2
Я не впевнений, що я розумію вашу пропозицію щодо твердження, що продовжується. Продовжити має зовсім інше значення в C і C ++, і якщо нова мова була схожа на C, але використовується ключове слово інколи, як і з C, а іноді таким альтернативним способом, я думаю, плутанина запанує. Незважаючи на те, що можуть виникнути проблеми з переходом з одного міченого блоку в інший, мені подобається використання декількох випадків з однаковою обробкою зі спільним кодом коду.
DeveloperDon

C має давню традицію використання одних і тих же ключових слів у кількох цілях. Подумайте *, &і staticт. Д.
idrougge
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.