Come uscire da un loop dall'interno di un interruttore?


113

Sto scrivendo del codice che assomiglia a questo:

while(true) {
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        break; // **HERE, I want to break out of the loop itself**
    }
}

C'è un modo diretto per farlo?

So di poter usare un flag e interrompere il ciclo inserendo un'interruzione condizionale subito dopo lo switch. Voglio solo sapere se C ++ ha già qualche costrutto per questo.


20
Perché è necessaria un'interruzione condizionale dopo il passaggio? Cambia il tuo tempo da while (vero) a while (bandiera) ...
Tal Pressman

7
@Dave_Jarvis Presumo che questa sia una versione semplificata che ha inserito qui per illustrare ciò che stava cercando di fare.
Alterlife

6
Se sei uno di questi programmatori che producono funzioni lunghe diverse pagine, troverai gotoattraente e, a volte, l'unica via d'uscita pulita. Se tendi a organizzare il tuo codice in piccole funzioni che sono lunghe solo poche righe e fai una singola cosa ciascuna, non incorrerai mai in questo problema. (Per inciso, anche il tuo codice sarà più facile da leggere.)
sbi

13
Se hai voglia di ricevere un consiglio per smettere di fumare quando tutto ciò che vuoi sapere è come raggiungere la stazione della metropolitana più vicina.
Michael Krelin - hacker

7
@hacker: Beh, se non riesci a vedere la stazione della metropolitana di fronte a te a causa di tutto il fumo, quel consiglio potrebbe non essere poi così male. :)
sbi

Risposte:


49

Premessa

Il codice seguente dovrebbe essere considerato una cattiva forma, indipendentemente dalla lingua o dalla funzionalità desiderata:

while( true ) {
}

Argomenti di supporto

Il while( true )loop è di scarsa qualità perché:

  • Rompe il contratto implicito di un ciclo while.
    • La dichiarazione del ciclo while dovrebbe indicare esplicitamente l' unica condizione di uscita.
  • Implica che funzioni per sempre.
    • Il codice all'interno del ciclo deve essere letto per comprendere la clausola di chiusura.
    • I cicli che si ripetono per sempre impediscono all'utente di terminare il programma dall'interno del programma.
  • È inefficiente.
    • Esistono più condizioni di terminazione del ciclo, incluso il controllo di "true".
  • È soggetto a insetti.
    • Non è possibile determinare facilmente dove inserire il codice che verrà sempre eseguito per ogni iterazione.
  • Porta a codice inutilmente complesso.
  • Analisi automatica del codice sorgente.
    • Per trovare bug, analisi della complessità del programma, controlli di sicurezza o derivare automaticamente qualsiasi altro comportamento del codice sorgente senza l'esecuzione del codice, la specifica delle condizioni di interruzione iniziali consente agli algoritmi di determinare invarianti utili, migliorando così le metriche di analisi automatica del codice sorgente.
  • Loop infiniti.
    • Se tutti usano sempre while(true)cicli for che non sono infiniti, perdiamo la capacità di comunicare in modo conciso quando i cicli non hanno effettivamente una condizione di terminazione. (Probabilmente, questo è già successo, quindi il punto è discutibile.)

Alternativa a "Vai a"

Il codice seguente è una forma migliore:

while( isValidState() ) {
  execute();
}

bool isValidState() {
  return msg->state != DONE;
}

vantaggi

Nessuna bandiera. No goto. Nessuna eccezione. Facile da cambiare. Facile da leggere. Facile da riparare. Inoltre il codice:

  1. Isola la conoscenza del carico di lavoro del loop dal loop stesso.
  2. Consente a qualcuno che mantiene il codice di estendere facilmente la funzionalità.
  3. Consente di assegnare più condizioni di terminazione in un'unica posizione.
  4. Separa la clausola di terminazione dal codice da eseguire.
  5. È più sicuro per le centrali nucleari. ;-)

Il secondo punto è importante. Senza sapere come funziona il codice, se qualcuno mi chiedesse di fare in modo che il ciclo principale lasci che altri thread (o processi) abbiano un po 'di tempo CPU, vengono in mente due soluzioni:

Opzione 1

Inserisci subito la pausa:

while( isValidState() ) {
  execute();
  sleep();
}

Opzione 2

Esegui override:

void execute() {
  super->execute();
  sleep();
}

Questo codice è più semplice (quindi più facile da leggere) di un ciclo con un file switch. Il isValidStatemetodo dovrebbe determinare solo se il ciclo deve continuare. Il cavallo di battaglia del metodo dovrebbe essere astratto nel executemetodo, che consente alle sottoclassi di sovrascrivere il comportamento predefinito (un compito difficile utilizzando un switche incorporato goto).

Esempio di Python

Confronta la seguente risposta (a una domanda di Python) che è stata pubblicata su StackOverflow:

  1. Loop per sempre.
  2. Chiedere all'utente di inserire la propria scelta.
  3. Se l'input dell'utente è "riavvia", continua il ciclo per sempre.
  4. Altrimenti, interrompi il ciclo per sempre.
  5. Fine.
Codice
while True: 
    choice = raw_input('What do you want? ')

    if choice == 'restart':
        continue
    else:
        break

print 'Break!' 

Contro:

  1. Inizializza la scelta dell'utente.
  2. Esegui il ciclo mentre la scelta dell'utente è la parola "riavvia".
  3. Chiedere all'utente di inserire la propria scelta.
  4. Fine.
Codice
choice = 'restart';

while choice == 'restart': 
    choice = raw_input('What do you want? ')

print 'Break!'

Qui, si while Truetraduce in codice fuorviante ed eccessivamente complesso.


45
L'idea che while(true)dovrebbe essere dannosa mi sembra bizzarra. Ci sono loop che controllano prima di essere eseguiti, ci sono quelli che controllano dopo e ci sono quelli che controllano nel mezzo. Poiché quest'ultimo non ha un costrutto sintattico in C e C ++, dovrai usare while(true)o for(;;). Se lo consideri sbagliato, non hai ancora pensato abbastanza ai diversi tipi di loop.
sbi

34
Motivi per cui non sono d'accordo: while (true) e for (;;;) non sono fonte di confusione (a differenza di do {} while (true)) perché affermano cosa stanno facendo proprio in primo piano. In realtà è più facile cercare istruzioni "break" che analizzare una condizione di ciclo arbitrario e cercare dove è impostata, e non è più difficile dimostrare la correttezza. L'argomento su dove mettere il codice è altrettanto intrattabile in presenza di un'istruzione continue e non molto meglio con le istruzioni if. L'argomento dell'efficienza è decisamente sciocco.
David Thornley,

23
Ogni volta che vedi "while (true)", puoi pensare a un ciclo che ha condizioni di terminazione interne, che non sono più difficili di condizioni di fine arbitrarie. Semplicemente, un ciclo "while (foo)", dove foo è una variabile booleana impostata in modo arbitrario, non è migliore di "while (true)" con interruzioni. Devi esaminare il codice in entrambi i casi. Proporre una ragione stupida (e la microefficienza è un argomento sciocco) non aiuta il tuo caso, a proposito.
David Thornley,

5
@ Dave: ho dato una ragione: è l'unico modo per ottenere un ciclo che controlla le condizioni nel mezzo. Esempio classico: while(true) {string line; std::getline(is,line); if(!is) break; lines.push_back(line);}ovviamente potrei trasformarlo in un ciclo precondizionato (usando std::getlinecome condizione del ciclo), ma questo ha degli svantaggi da solo ( linedovrebbe essere una variabile al di fuori dell'ambito del ciclo). E se lo facessi, dovrei chiedere: perché abbiamo comunque tre loop? Potremmo sempre trasformare tutto in un ciclo precondizionato. Usa ciò che si adatta meglio. Se while(true)va bene, allora usalo.
sbi

3
A titolo di cortesia per le persone che leggono questa risposta, eviterei di commentare la qualità del bene che ha prodotto la domanda e mi limiterò a rispondere alla domanda stessa. La domanda è una variazione di quanto segue, che credo sia una caratteristica di molti linguaggi: hai un ciclo annidato all'interno di un altro ciclo. Vuoi uscire dal ciclo esterno dal ciclo interno. Come si fa?
Alex Leibowitz

172

Puoi usare goto.

while ( ... ) {
   switch( ... ) {
     case ...:
         goto exit_loop;

   }
}
exit_loop: ;

12
Basta non impazzire con quella parola chiave.
Fragsworth

45
È divertente che vada sottovalutato perché alla gente non piace goto. L'OP chiaramente menzionato senza usare flag . Puoi suggerire un modo migliore, considerando la limitazione del PO? :)
Mehrdad Afshari

18
votato solo per compensare gli haters goto insensati. Immagino che Mehrdad sapesse che avrebbe perso un paio di punti per aver suggerito una soluzione sensata qui.
Michael Krelin - hacker

6
+1, non c'è modo migliore per imho, anche se si considerano i limiti dei PO. Le bandiere sono brutte, interrompono la logica attraverso l'intero ciclo e quindi sono più difficili da afferrare.
Johannes Schaub - litb

11
Alla fine della giornata, senza il costrutto goto, tutte le lingue falliscono. le lingue di alto livello lo offuscano, ma se guardi abbastanza attentamente sotto il cofano, troverai ogni ciclo, o la condizione si riduce a rami condizionali e incondizionati. ci illudiamo usando for, while, until, switch, if le parole chiave, ma alla fine tutte utilizzano GOTO (o JMP) in un modo o nell'altro. puoi illuderti, inventando ogni sorta di modo intelligente per nasconderlo, oppure puoi semplicemente essere pragmaticamente onesto e usare goto, dove appropriato, e con parsimonia.
non sincronizzato

58

Una soluzione alternativa consiste nell'usare la parola chiave continuein combinazione con break, ad esempio:

for (;;) {
    switch(msg->state) {
    case MSGTYPE
        // code
        continue; // continue with loop
    case DONE:
        break;
    }
    break;
}

Utilizzare l' continueistruzione per terminare ciascuna etichetta di caso in cui si desidera che il ciclo continui e utilizzare l' breakistruzione per terminare le etichette di caso che dovrebbero terminare il ciclo.

Ovviamente questa soluzione funziona solo se non c'è codice aggiuntivo da eseguire dopo l'istruzione switch.


9
Sebbene sia davvero molto elegante, ha lo svantaggio che la maggior parte degli sviluppatori deve prima guardarlo per un minuto per capire come / se funziona. :(
sbi

8
L'ultima frase è ciò che rende questo non così eccezionale :(Altrimenti un'implementazione molto pulita.
nawfal

Quando ci pensi, nessun codice del computer è buono. Siamo addestrati solo per capirlo. Ad esempio xml sembrava spazzatura finché non l'ho imparato. Ora sembra meno spazzatura. for (int x = 0; x <10; ++ x, moveCursor (x, y)) ha effettivamente causato un errore logico nel mio programma. Ho dimenticato che la condizione di aggiornamento si è verificata alla fine.

22

Un modo accurato per farlo sarebbe metterlo in una funzione:

int yourfunc() {

    while(true) {

        switch(msg->state) {
        case MSGTYPE: // ... 
            break;
        // ... more stuff ...
        case DONE:
            return; 
        }

    }
}

Facoltativamente (ma "cattive pratiche"): come già suggerito potresti usare un goto o lanciare un'eccezione all'interno dello switch.


4
L'eccezione dovrebbe essere usata per, beh, lanciare un'eccezione, non un comportamento ben noto di una funzione.
Clement Herreman

Sono d'accordo con Afterlife: mettilo in una funzione.
Dalle

14
Lanciare un'eccezione è davvero un male in questo caso. Significa "Voglio usare un goto, ma ho letto da qualche parte che non dovrei usarli, quindi farò un sotterfugio e fingo di sembrare intelligente".
Gorpik

Sono d'accordo che lanciare un'eccezione o usare un goto sia un'idea terribile, ma sono opzioni funzionanti e sono state elencate come tali nella mia risposta.
Alterlife

2
Lanciare un'eccezione significa sostituire un COMEFROM con un GOTO. Non farlo. Il goto funziona molto meglio.
David Thornley,

14

AFAIK non esiste una "doppia interruzione" o un costrutto simile in C ++. Il più vicino sarebbe un goto- che, sebbene abbia una cattiva connotazione al suo nome, esiste nella lingua per un motivo - purché sia ​​usato con attenzione e con parsimonia, è un'opzione praticabile.


8

Potresti inserire il tuo interruttore in una funzione separata come questa:

bool myswitchfunction()
{
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        return false; // **HERE, I want to break out of the loop itself**
    }
    return true;
}

while(myswitchfunction())
    ;

7

Anche se non ti piace goto, non utilizzare un'eccezione per uscire da un ciclo. Il seguente esempio mostra quanto potrebbe essere brutto:

try {
  while ( ... ) {
    switch( ... ) {
      case ...:
        throw 777; // I'm afraid of goto
     }
  }
}
catch ( int )
{
}

Userei gotocome in questa risposta. In questo caso il gotocodice sarà più chiaro rispetto a qualsiasi altra opzione. Spero che questa domanda sia utile.

Ma penso che l'uso gotosia l'unica opzione qui a causa della stringa while(true). Dovresti considerare il refactoring del tuo loop. Suppongo la seguente soluzione:

bool end_loop = false;
while ( !end_loop ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        end_loop = true; break;
    }
}

O anche il seguente:

while ( msg->state != DONE ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
}

1
Già, ma le eccezioni mi piacciono ancora meno ;-)
quamrana

3
Usare un'eccezione per emulare un goto è ovviamente peggio che usare effettivamente un goto! Probabilmente sarà anche molto più lento.
John Carter

2
@Downvoter: è perché affermo che non dovresti usare l'eccezione o perché suggerisco il refactoring?
Kirill V. Lyadvinsky

1
Non il votante - ma ... La tua risposta non è chiara se stai proponendo o condannando l'idea di usare un'eccezione per uscire dal giro. Forse la prima frase dovrebbe essere: "Anche se non ti piace goto, non usare un'eccezione per uscire da un ciclo:"?
Jonathan Leffler,

1
@Jonathan Leffler, grazie, hai mostrato il campione di prezioso commento. ha aggiornato la risposta tenendo presente i tuoi commenti.
Kirill V. Lyadvinsky

5

Non esiste un costrutto C ++ per uscire dal ciclo in questo caso.

Utilizzare un flag per interrompere il ciclo o (se appropriato) estrarre il codice in una funzione e utilizzare return.


2

Potresti potenzialmente usare goto, ma preferirei impostare un flag che interrompa il ciclo. Quindi rompi l'interruttore.


2

Perché non correggere semplicemente la condizione nel ciclo while, facendo scomparire il problema?

while(msg->state != DONE)
{
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        // We can't get here, but for completeness we list it.
        break; // **HERE, I want to break out of the loop itself**
    }
}

2

No, C ++ non ha un costrutto per questo, dato che la parola chiave "break" è già riservata per uscire dal blocco switch. In alternativa, un do..while () con un flag di uscita potrebbe essere sufficiente.

do { 
    switch(option){
        case 1: ..; break;
        ...
        case n: .. ;break;
        default: flag = false; break;
    }
} while(flag);

1

Penso;

while(msg->state != mExit) 
{
    switch(msg->state) 
    {
      case MSGTYPE: // ...
         break;
      case DONE:
      //  .. 
      //  ..
      msg->state =mExit;
      break;
    }
}
if (msg->state ==mExit)
     msg->state =DONE;

0

Il modo più semplice per farlo è mettere un semplice IF prima di fare lo SWITCH, e IF testare la tua condizione per uscire dal ciclo .......... il più semplice possibile


2
Um ... potresti essere ancora più semplice inserendo quella condizione nell'argomento while. Voglio dire, è lì per questo! :)
Mark A. Donohoe

0

La breakparola chiave in C ++ termina solo l'iterazione o l' switchistruzione di inclusione più annidata . Pertanto, non è possibile uscire dal while (true)ciclo direttamente all'interno switchdell'istruzione; tuttavia potresti usare il seguente codice, che penso sia un modello eccellente per questo tipo di problema:

for (; msg->state != DONE; msg = next_message()) {
    switch (msg->state) {
    case MSGTYPE:
        //...
        break;

    //...
    }
}

Se hai bisogno di fare qualcosa quando è msg->stateuguale DONE(come eseguire una routine di pulizia), inserisci quel codice immediatamente dopo il forciclo; cioè se attualmente hai:

while (true) {
    switch (msg->state) {
    case MSGTYPE:
        //... 
        break;

    //...

    case DONE:
        do_cleanup();
        break;
    }

    if (msg->state == DONE)
        break;

    msg = next_message();
}

Quindi usa invece:

for (; msg->state != DONE; msg = next_message()) {
    switch (msg->state) {
    case MSGTYPE:
        //...
        break;

    //...
    }
}

assert(msg->state == DONE);
do_cleanup();

0

Mi stupisce quanto sia semplice considerando la profondità delle spiegazioni ... Ecco tutto ciò di cui hai bisogno ...

bool imLoopin = true;

while(imLoopin) {

    switch(msg->state) {

        case MSGTYPE: // ... 
            break;

        // ... more stuff ...

        case DONE:
            imLoopin = false;
            break;

    }

}

LOL !! Veramente! È tutto ciò di cui hai bisogno! Una variabile in più!


0
while(MyCondition) {
switch(msg->state) {
case MSGTYPE: // ... 
    break;
// ... more stuff ...
case DONE:
   MyCondition=false; // just add this code and you will be out of loop.
    break; // **HERE, you want to break out of the loop itself**
}
}

0

Ho avuto lo stesso problema e l'ho risolto usando una bandiera.

bool flag = false;
while(true) {
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        flag = true; // **HERE, I want to break out of the loop itself**
    }
    if(flag) break;
}

-2

Se ricordo bene la sintassi C ++, puoi aggiungere un'etichetta alle breakistruzioni, proprio come per goto. Quindi quello che vuoi sarebbe facilmente scritto:

while(true) {
    switch(msg->state) {
    case MSGTYPE: // ...
        break;
    // ... more stuff ...
    case DONE:
        break outofloop; // **HERE, I want to break out of the loop itself**
    }
}

outofloop:
// rest of your code here

1
Sfortunatamente, la tua memoria è difettosa. Sarebbe una bella caratteristica, in quelle rare occasioni in cui sarebbe utile. (Ho visto proposte per il numero di livelli da cui interrompere, ma mi sembrano bug in attesa di accadere.)
David Thornley

Allora doveva essere Java. Grazie per aver risposto. E, naturalmente, hai ragione anche con il resto.
Andrei Vajna II

-3
  while(true)
  {
    switch(x)
    {
     case 1:
     {
      break;
     }
    break;
   case 2:
    //some code here
   break;
  default:
  //some code here
  }
}

1
Tutte le interruzioni in quell'esempio di codice vengono interrotte dall'istruzione switch.
Dan Hulme
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.