Perché dobbiamo usare l'interruttore break in?


74

Chi ha deciso (e in base a quali concetti) che la switchcostruzione (in molte lingue) deve usare breakin ogni affermazione?

Perché dobbiamo scrivere qualcosa del genere:

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

(notato questo in PHP e JS; probabilmente ci sono molte altre lingue che usano questo)

Se switchè un'alternativa di if, perché non possiamo usare la stessa costruzione di if? Vale a dire:

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

Si dice che breakimpedisce l'esecuzione del blocco successivo a quello attuale. Ma qualcuno si imbatte davvero nella situazione, dove c'era bisogno di eseguire il blocco corrente e quelli seguenti? Non l'ho fatto Per me, breakc'è sempre. In ogni blocco. In ogni codice.


1
Come la maggior parte delle risposte ha notato, ciò è correlato a "fall-through", in cui più condizioni vengono instradate attraverso gli stessi blocchi "allora". Tuttavia, questo dipende dalla lingua - RPG, ad esempio, tratta CASEun'istruzione in modo equivalente a un blocco if / elseif gigante.
Clockwork-Muse

34
È stata una cattiva decisione presa dai progettisti C (come molte decisioni, è stata presa per rendere la transizione dall'assemblaggio -> C, e la traduzione indietro, più facile, piuttosto che per facilità di programmazione) , che è stata purtroppo ereditata in altre lingue basate su C. Utilizzando la semplice regola "Rendi sempre il caso comune predefinito !!" , possiamo vedere che il comportamento predefinito avrebbe dovuto essere la rottura, con una parola chiave separata per fallire, poiché questo è di gran lunga il caso più comune.
BlueRaja - Danny Pflughoeft,

4
Ho usato il fall-through in diverse occasioni, anche se probabilmente l'istruzione switch nella mia lingua preferita al momento (C) avrebbe potuto essere progettata per evitarlo; ad es. l'ho fatto, case 'a': case 'A': case 'b': case 'B'ma soprattutto perché non posso farlo case in [ 'a', 'A', 'b', 'B' ]. Una domanda leggermente migliore è, nella mia attuale lingua preferita (C #), l'interruzione è obbligatoria e non vi è alcuna caduta implicita; dimenticare breakè un errore di sintassi ...: \
KutuluMike il

1
Il fall-through con il codice attuale si trova spesso nelle implementazioni del parser. case TOKEN_A: /*set flag*/; case TOKEN_B: /*consume token*/; break; case TOKEN_C: /*...*/
Smetti di fare del male a Monica il

1
@ BlueRaja-DannyPflughoeft: C è stato progettato anche per essere facile da compilare. "Emetti un salto se breakè presente ovunque" è una regola significativamente più semplice da implementare rispetto a "Non emettere un salto se fallthroughè presente in un switch".
Jon Purdy,

Risposte:


95

C è stata una delle prime lingue ad avere l' switchaffermazione in questo modulo, e tutte le altre lingue principali l'hanno ereditata da lì, scegliendo principalmente di mantenere la semantica C per impostazione predefinita - o non hanno pensato ai vantaggi di cambiarla, o hanno giudicato meno importanti del mantenere il comportamento a cui erano abituati tutti.

Per quanto riguarda il motivo per cui C è stato progettato in questo modo, probabilmente deriva dal concetto di C come "assemblaggio portatile". L' switchistruzione è fondamentalmente un'astrazione di una tabella di diramazione e una tabella di diramazione ha anche un implicito fall-through e richiede un'istruzione di salto aggiuntiva per evitarlo.

Quindi, fondamentalmente, i progettisti di C hanno anche scelto di mantenere la semantica dell'assemblatore per impostazione predefinita.


3
VB.NET è un esempio di una lingua che l'ha modificata. Non vi è alcun pericolo di avere il flusso nella clausola del caso.
colpisci il

1
Tcl è un'altra lingua che non passa mai alla clausola successiva. Praticamente non ho mai voluto farlo (a differenza di C dove è utile quando si scrivono determinati tipi di programmi).
Donal Fellows,

4
I linguaggi antenati di C, B e il precedente BCPL , hanno entrambi (avuto?) Istruzioni switch; BCPL l'ha scritto switchon.
Keith Thompson,

Le dichiarazioni del caso di Ruby non passano attraverso; puoi ottenere un comportamento simile avendo due valori di test in uno solo when.
Nathan Long,

86

Perché switchnon è un'alternativa di if ... elsedichiarazioni in quelle lingue.

Usando switch, possiamo abbinare più di una condizione alla volta, il che è molto apprezzato in alcuni casi.

Esempio:

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 casesNon sono d'accordo con te
Satish Pandey,

2
@DanielB Tuttavia dipende dalla lingua; Le istruzioni switch C # non consentono tale possibilità.
Andy,

5
@Andy Stiamo ancora parlando di fall-through? Le istruzioni switch C # lo consentono sicuramente (controlla il terzo esempio), quindi la necessità di break. Ma sì, per quanto ne so, il dispositivo di Duff non funziona in C #.
Daniel B,

8
@DanielB Sì, ma il compilatore non consentirà una condizione, un codice e quindi un'altra condizione senza interruzione. Puoi avere condizioni multiple impilate senza codice tra, o hai bisogno di una pausa. Dal tuo link C# does not support an implicit fall through from one case label to another. Puoi fallire, ma non c'è alcuna possibilità di dimenticare accidentalmente una pausa.
Andy,

3
@Matsemann .. Ora posso fare tutte le cose usando if..elsema in alcune situazioni switch..casesarebbe preferibile. L'esempio sopra sarebbe un po 'complesso se usiamo if-else.
Satish Pandey,

15

Questo è stato chiesto su Stack Overflow nel contesto di C: Perché l'istruzione switch è stata progettata per richiedere un'interruzione?

Per riassumere la risposta accettata, è stato probabilmente un errore. La maggior parte delle altre lingue probabilmente ha appena seguito C. Tuttavia, alcune lingue come C # sembrano aver risolto questo problema consentendo fall-through - ma solo quando il programmatore lo dice esplicitamente (fonte: il link sopra, non parlo C # me stesso) .


Google Go fa la stessa cosa IIRC (consentendo fallthrough se esplicitamente specificato).
Leone,

Anche PHP lo fa. E più guardi il codice risultante, più a disagio ti fa sentire. Mi piace il /* FALLTHRU */commento nella risposta collegata da @Tapio sopra, per dimostrare che lo intendevi.
DaveP,

1
Dire che C # ha goto case, come fa il collegamento, non illustra perché funzioni così bene. Puoi ancora impilare più casi come sopra, ma non appena inserisci il codice, devi avere un esplicito goto case whateverper passare al caso successivo o ottieni un errore del compilatore. Se mi chiedete, dei due, la possibilità di impilare i casi senza preoccuparsi di una negligenza involontaria è di gran lunga una caratteristica migliore rispetto all'abilitazione di fallimenti espliciti goto case.
Jesper,

9

Risponderò con un esempio. Se si desidera elencare il numero di giorni per ogni mese di un anno, è ovvio che alcuni mesi hanno 31, circa 30 e 1 28/29. Sembrerebbe così,

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

Questo è un esempio in cui più casi hanno lo stesso effetto e sono tutti raggruppati insieme. C'era ovviamente una ragione per la scelta della parola chiave break e non del if ... else if construct.

La cosa principale da prendere nota qui è che un'istruzione switch con molti casi simili non è un if ... else if ... else if ... else per ciascuno dei casi, ma piuttosto if (1, 2, 3) ... altrimenti se (4,5,6) altro ...


2
Il tuo esempio sembra più dettagliato di un ifsenza alcun vantaggio. Forse se il tuo esempio assegnasse un nome o facesse un lavoro specifico per mese oltre al fall-through, l'utilità del fall-through sarebbe più evidente? (senza commentare se uno DOVREBBE in primo luogo)
horatio

1
Questo è vero. Stavo comunque solo tentando di mostrare casi in cui poteva essere utilizzato fall fall. Sarebbe ovviamente molto meglio se ciascuno dei mesi avesse bisogno di qualcosa fatto individualmente.
Awemo,

8

Esistono due situazioni in cui può succedere che “passi da un caso all'altro”: il caso vuoto:

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

e il caso non vuoto

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

Nonostante il riferimento al dispositivo di Duff , le istanze legittime per il secondo caso sono poche e distanti tra loro e generalmente vietate dagli standard di codifica e contrassegnate durante l'analisi statica. E dove si trova, il più delle volte è dovuto all'omissione di a break.

Il primo è perfettamente sensato e comune.

Ad essere sincero, non vedo alcuna ragione per aver bisogno del breakparser linguistico e del sapere che un caso-corpo vuoto è una caduta, mentre un caso non vuoto è autonomo.

È un peccato che il pannello ISO C sembri più preoccupato di aggiungere nuove funzionalità (indesiderate) e mal definite alla lingua, piuttosto che fissare le caratteristiche indefinite, non specificate o definite dall'implementazione, per non parlare del illogico.


2
Per fortuna l'ISO C non sta introducendo cambiamenti radicali nella lingua!
Jack Aidley il

4

Non forzare ciò breakconsente una serie di cose che altrimenti potrebbero essere difficili da fare. Altri hanno notato casi di raggruppamento, per i quali esistono numerosi casi non banali.

Un caso in cui è indispensabile che il breaknon utilizzato sia il dispositivo di Duff . Questo è usato per " srotolare " i loop in cui può velocizzare le operazioni limitando il numero di confronti richiesti. Credo che l'uso iniziale consentisse una funzionalità che in precedenza era stata troppo lenta con un loop completamente arrotolato. Scambia le dimensioni del codice per la velocità in alcuni casi.

È buona norma sostituirlo breakcon un commento appropriato, se il caso ha un codice. Altrimenti, qualcuno risolverà i dispersi breake introdurrà un bug.


Grazie per l' esempio del dispositivo Duff (+1). Non ero conscio di ciò.
Trejder,

3

In C, dove l'origine sembra essere, il blocco di codice dell'istruzione switchnon è un costrutto speciale. È un normale blocco di codice, proprio come un blocco sotto ifun'istruzione.

switch ()
{
}

if ()
{
}

casee defaultsono etichette di salto all'interno di questo blocco, specificamente correlate a switch. Sono gestiti come normali etichette di salto per goto. C'è una regola specifica che è importante qui: le etichette Jump possono essere quasi ovunque nel codice, senza interrompere il flusso del codice.

Come un normale blocco di codice, non è necessario che sia un'istruzione composta. Anche le etichette sono opzionali. Queste sono switchdichiarazioni valide in C:

switch (a)
    case 1: Foo();

switch (a)
    Foo();

switch (a)
{
    Foo();
}

Lo stesso standard C fornisce questo come esempio (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); 
} 

Nel frammento di programma artificiale, l'oggetto il cui identificatore è i esiste con durata di memorizzazione automatica (all'interno del blocco) ma non viene mai inizializzato, e quindi se l'espressione di controllo ha un valore diverso da zero, la chiamata alla funzione printf accederà a un valore indeterminato. Allo stesso modo, non è possibile raggiungere la chiamata alla funzione f.

Inoltre, defaultè anche un'etichetta di salto e quindi può essere ovunque, senza la necessità di essere l'ultimo caso.

Questo spiega anche il dispositivo Duff:

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

Perché il fall-through? Perché nel normale flusso di codice in un normale blocco di codice, si prevede di passare all'istruzione successiva , proprio come ci si aspetterebbe in un ifblocco di codice.

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

La mia ipotesi è che la ragione di ciò sia stata la facilità di implementazione. Non hai bisogno di un codice speciale per analizzare e compilare un switchblocco, occupandoti di regole speciali. Lo analizzi come qualsiasi altro codice e devi solo preoccuparti delle etichette e della selezione del salto.

Un'interessante domanda di follow-up da tutto questo è se le seguenti affermazioni nidificate stampano "Fatto". o no.

int a = 10;

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

Lo standard C si prende cura di questo (6.8.4.2.4):

Un'etichetta case o default è accessibile solo all'interno dell'istruzione switch più vicina.


1

Diverse persone hanno già menzionato l'idea di abbinare più condizioni, che è molto preziosa di volta in volta. Tuttavia, la capacità di abbinare più condizioni non richiede necessariamente che tu faccia esattamente la stessa cosa con ogni condizione corrispondente. Considera quanto segue:

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

    case 3:
        doSomething3();

    case 4:
        doSomething3And4();
        break;

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

Esistono due modi diversi per far corrispondere insiemi di più condizioni qui. Con le condizioni 1 e 2, cadono semplicemente nella stessa trama del codice e fanno esattamente la stessa cosa. Con le condizioni 3 e 4, tuttavia, sebbene entrambe terminino chiamando doSomething3And4(), solo 3 chiamate doSomething3().


In ritardo alla festa, ma questo non è assolutamente "1 e 2" come suggerisce il nome della funzione: ci sono tre stati diversi che possono portare al codice nel case 2trigger, quindi se vogliamo essere corretti (e lo facciamo) il reale il nome della funzione dovrebbe essere doSomethingBecause_1_OR_2_OR_1AND2()o qualcosa del genere (anche se il codice legittimo in cui un immutabile corrisponde a due casi diversi pur essendo un codice ben scritto è praticamente inesistente, quindi almeno questo dovrebbe essere doSomethingBecause1or2())
Mike 'Pomax' Kamermans

In altre parole, doSomething[ThatShouldBeDoneInTheCaseOf]1And[InTheCaseOf]2(). Non si riferisce a logico e / o.
Panzercrisis,

So che non lo è, ma dato il motivo per cui le persone si confondono su questo tipo di codice, dovrebbe assolutamente spiegare cosa sia quel "e", perché fare affidamento su qualcuno per indovinare "fine naturale" e "vs." fine logica in una risposta che funziona solo quando spiegato con precisione, necessita di maggiori spiegazioni nella risposta stessa o di una riscrittura del nome della funzione per rimuovere
quell'ambiguità

1

Per rispondere a due delle tue domande.

Perché C ha bisogno di pause?

Dipende dalle radici Cs come "assemblatore portatile". Dove il codice psudo come questo era comune: -

    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:
    ......

l'istruzione switch è stata progettata per fornire funzionalità simili a un livello superiore.

Abbiamo mai interruttori senza interruzioni?

Sì, questo è abbastanza comune e ci sono alcuni casi d'uso;

Innanzitutto potresti voler eseguire la stessa azione per diversi casi. Facciamo questo accatastando i casi uno sopra l'altro:

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

Un altro caso d'uso comune nelle macchine a stati è che dopo aver elaborato uno stato vogliamo immediatamente inserire ed elaborare un altro stato:

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

-2

L'istruzione break è un'istruzione saltata che consente all'utente di uscire dall'interruttore di chiusura più vicino (per il proprio caso), mentre, do, for o foreach. è altrettanto facile.


-3

Penso che con tutto quanto scritto sopra - il risultato principale di questo thread è che se si deve progettare una nuova lingua - il valore predefinito dovrebbe essere che non è necessario aggiungere breakun'istruzione e il compilatore lo tratterà come se fosse stato.

Se si desidera quel raro caso in cui si desidera continuare con il caso successivo, è sufficiente dichiararlo con continueun'istruzione.

Questo può essere migliorato in modo che solo se si utilizzano parentesi graffe all'interno del case, non vada avanti in modo che l'esempio con i mesi precedenti di diversi casi esegua lo stesso codice esatto - funzionerà sempre come previsto senza la necessità di continue.


2
Non sono sicuro di aver compreso il tuo suggerimento relativo alla dichiarazione di continuazione. Continuare ha un significato molto diverso in C e C ++ e se un nuovo linguaggio fosse come C, ma a volte usava la parola chiave come con C e talvolta in questo modo alternativo, penso che la confusione regnerebbe. Mentre ci possono essere alcuni problemi con la caduta da un blocco etichettato a un altro, mi piace l'uso di più casi con la stessa gestione con un blocco di codice condiviso.
Sviluppatore:

C ha una lunga tradizione di utilizzo delle stesse parole chiave per molteplici scopi. Pensare *, &, staticecc
idrougge
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.