Perché dobbiamo usare l'interruttore break-in?

65

Chi ha deciso, e basandosi su quali concetti, quella costruzione switch (in molte lingue) deve essere, come è? Perché dobbiamo usare break in ogni istruzione? Perché dobbiamo scrivere qualcosa del genere:

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

(Ho notato questa costruzione in PHP e JS, ma probabilmente ci sono molti altri linguaggi che lo usano)

Se switch è un'alternativa di if , perché non possiamo usare la stessa costruzione per switch , come per if ? Cioè:.

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

Si dice che break impedisce l'esecuzione di un blocco che segue quello corrente. Ma qualcuno si imbatte davvero in una situazione, dove c'era bisogno di eseguire il blocco corrente e quelli successivi? Non l'ho fatto Per me, break è sempre lì. In ogni blocco In ogni codice.

    
posta trejder 28.08.2012 - 11:16
fonte

11 risposte

89

C è stata una delle prime lingue ad avere l'istruzione switch in questo modulo, e tutti gli altri linguaggi principali l'hanno ereditata da lì, per lo più scegliendo di mantenere la semantica C per impostazione predefinita - o non avevano pensato ai vantaggi di cambiarlo o giudicarlo meno importante del mantenere il comportamento a cui tutti erano abituati.

Per quanto riguarda il motivo per cui C è stato progettato in questo modo, probabilmente deriva dal concetto di C come "assemblaggio portatile". L'istruzione switch è fondamentalmente un'astrazione di una tabella delle diramazioni , e una tabella delle diramazioni ha anche implicito una ricaduta e richiede un'ulteriore istruzione di salto per evitarlo.

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

    
risposta data 28.08.2012 - 12:40
fonte
82

Perché switch non è un'alternativa delle dichiarazioni if ... else 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;
}
    
risposta data 28.08.2012 - 11:21
fonte
13

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, probabilmente è stato un errore. La maggior parte degli altri linguaggi hanno probabilmente seguito C. Tuttavia, alcune lingue come C # sembrano aver risolto questo problema consentendo il fall-through - ma solo quando il programmatore lo dice esplicitamente (fonte: il link sopra, io non parlo C # me stesso) .

    
risposta data 28.08.2012 - 11:36
fonte
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, 30 e 1 28/29. Sembrerebbe questo,

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 di interruzione e non il if ... else if construct.

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

    
risposta data 28.08.2012 - 11:50
fonte
8

Ci sono due situazioni in cui "passare attraverso" da un caso all'altro può accadere - 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 a Dispositivo di Duff , le istanze legittime per il secondo caso sono poche e lontane tra loro, e generalmente vietato dagli standard di codifica e contrassegnato durante l'analisi statica. E dove viene trovato, è più spesso che no a causa dell'omissione di un break .

Il primo è perfettamente sensato e comune.

Per essere onesti, non vedo alcun motivo per cui abbia bisogno del break e del parser della lingua sapendo che un caso-corpo vuoto è un fallimento, mentre un caso non vuoto è autonomo.

È un peccato che il pannello ISO C sembri più preoccupato di aggiungere nuove funzionalità (indesiderate) e mal definite al linguaggio, piuttosto che correggere le caratteristiche indefinite, non specificate o implementate, per non parlare dell'illogicità.

    
risposta data 28.08.2012 - 12:09
fonte
4

Non forzare break consente un numero 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 è imperativo che il break non venga utilizzato è Dispositivo di Duff . Questo è usato per " srotolare " loop dove può accelerare le operazioni limitando il numero di confronti richiesti. Credo che l'utilizzo iniziale abbia consentito funzionalità che in precedenza erano state troppo lente con un ciclo completo. In alcuni casi, scambia le dimensioni del codice per la velocità.

È buona norma sostituire break con un commento appropriato, se il caso ha un codice. Altrimenti, qualcuno correggerà il break mancante e introdurrà un bug.

    
risposta data 29.08.2012 - 05:20
fonte
2

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

switch ()
{
}

if ()
{
}

case e default sono etichette di salto all'interno di questo blocco, in particolare relative a switch . Vengono 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 una dichiarazione composta. Anche le etichette sono opzionali. Queste sono dichiarazioni switch valide in C:

switch (a)
    case 1: Foo();

switch (a)
    Foo();

switch (a)
{
    Foo();
}

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

In the artificial program fragment, the object whose identifier is i exists with automatic storage duration (within the block) but is never initialized, and thus if the controlling expression has a nonzero value, the call to the printf function will access an indeterminate value. Similarly, the call to the function f cannot be reached.

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 di 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? Poiché nel normale flusso di codice in un normale blocco di codice, l'istruzione successiva alla successiva è prevista , proprio come ci si aspetterebbe in un blocco di codice 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();
}

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

Una domanda di follow-up interessante da tutto ciò è se le seguenti istruzioni nidificate stampano "Fatto". oppure 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):

A case or default label is accessible only within the closest enclosing switch statement.

    
risposta data 29.08.2012 - 07:49
fonte
1

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

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

    case 3:
        doSomething3();

    case 4:
        doSomething3And4();
        break;

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

Ci sono due diversi modi in cui vengono abbinati insiemi di più condizioni. Con le condizioni 1 e 2, semplicemente cadono nello stesso identico diagramma di codice e fanno esattamente la stessa cosa. Con le condizioni 3 e 4, tuttavia, anche se entrambe terminano chiamando doSomething3And4() , solo 3 chiamate doSomething3() .

    
risposta data 18.12.2014 - 16:53
fonte
1

Per rispondere a due delle tue domande.

Perché C ha bisogno di pause?

Si tratta delle radici di Cs come "assemblatore portatile". Il codice di 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;

In primo luogo potresti voler prendere la stessa azione per diversi casi. Facciamo questo sovrapponendo 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 entrare ed elaborare un altro stato:

case 9:
    crashed()
    newstate=10;
case 10:
    claim_damage();
    break;
    
risposta data 18.12.2014 - 09:51
fonte
-2

L'istruzione break è un'istruzione saltata che consente all'utente di uscire dallo switch di chiusura più vicino (per il tuo caso), mentre, do, for o foreach. è altrettanto semplice.

    
risposta data 28.08.2012 - 19:39
fonte
-3

Penso che con tutto quanto scritto sopra - il risultato principale di questa discussione è che se si deve progettare una nuova lingua - il default dovrebbe essere che non è necessario aggiungere un'istruzione break e il compilatore la tratterà come se lo avessi fatto.

Se desideri il caso raro in cui desideri passare al caso successivo, devi semplicemente indicarlo con un'istruzione continue .

Questo può essere migliorato in modo che solo se si usano parentesi graffe all'interno della custodia non vada avanti in modo che l'esempio con i mesi precedenti di diversi casi che eseguono lo stesso codice esatto - funzionerebbe sempre come previsto senza la necessità continue .

    
risposta data 05.09.2012 - 07:39
fonte

Leggi altre domande sui tag