Wie kann man aus einer Schleife in einem Schalter ausbrechen?

113

Ich schreibe einen Code, der so aussieht:

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

Gibt es einen direkten Weg, das zu tun?

Ich weiß, dass ich ein Flag verwenden und aus der Schleife ausbrechen kann, indem ich direkt nach dem Wechsel eine bedingte Pause einlege. Ich möchte nur wissen, ob C ++ bereits ein Konstrukt dafür hat.

jrharshath
quelle
20
Warum brauchen Sie nach dem Wechsel eine bedingte Pause? Ändern Sie einfach Ihre Weile von while (wahr) zu while (Flagge) ...
Tal Pressman
7
@ Dave_Jarvis Ich gehe davon aus, dass dies eine vereinfachte Version ist, die er hier eingefügt hat, um zu veranschaulichen, was er versucht hat.
Alterlife
6
Wenn Sie einer dieser Programmierer sind, die Funktionen erstellen, die mehrere Seiten lang sind, finden Sie gotoansprechende und manchmal den einzigen sauberen Ausweg. Wenn Sie dazu neigen, Ihren Code in kleine Funktionen zu organisieren, die nur wenige Zeilen lang sind und jeweils eine einzige Aufgabe ausführen, werden Sie nie auf dieses Problem stoßen. (Übrigens wird Ihr Code auch leichter zu lesen sein.)
sbi
13
Wenn Sie Lust haben, einen Rat zu bekommen, mit dem Rauchen aufzuhören, wenn Sie nur wissen möchten, wie Sie zur nächsten U-Bahnstation gelangen.
Michael Krelin - Hacker
7
@ Hacker: Nun, wenn Sie die U-Bahn-Station vor Ihnen wegen des Rauches nicht sehen können, ist dieser Rat vielleicht gar nicht so schlecht. :)
sbi

Antworten:

49

Prämisse

Der folgende Code sollte unabhängig von der Sprache oder der gewünschten Funktionalität als schlechte Form angesehen werden:

while( true ) {
}

Unterstützende Argumente

Die while( true )Schleife hat eine schlechte Form, weil sie:

  • Bricht den impliziten Vertrag einer while-Schleife.
    • Die while-Schleifendeklaration sollte explizit die einzige Exit-Bedingung angeben.
  • Impliziert, dass es für immer Schleifen.
    • Code innerhalb der Schleife muss gelesen werden, um die Abschlussklausel zu verstehen.
    • Schleifen, die sich für immer wiederholen, verhindern, dass der Benutzer das Programm innerhalb des Programms beendet.
  • Ist ineffizient.
    • Es gibt mehrere Bedingungen für die Beendigung von Schleifen, einschließlich der Überprüfung auf "true".
  • Ist anfällig für Fehler.
    • Es kann nicht einfach bestimmt werden, wo Code abgelegt werden soll, der immer für jede Iteration ausgeführt wird.
  • Führt zu unnötig komplexem Code.
  • Automatische Quellcode-Analyse.
    • Um Fehler zu finden, die Komplexität der Programme zu analysieren, Sicherheitsüberprüfungen durchzuführen oder automatisch ein anderes Verhalten des Quellcodes ohne Codeausführung abzuleiten, können Algorithmen durch Angabe der anfänglichen Unterbrechungsbedingungen nützliche Invarianten bestimmen und so die Metriken für die automatische Quellcode-Analyse verbessern.
  • Endlosschleifen.
    • Wenn jeder immer while(true)Schleifen verwendet, die nicht unendlich sind, verlieren wir die Fähigkeit zur präzisen Kommunikation, wenn Schleifen tatsächlich keine Abschlussbedingung haben. (Vermutlich ist dies bereits geschehen, daher ist der Punkt umstritten.)

Alternative zu "Gehe zu"

Der folgende Code ist eine bessere Form:

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

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

Vorteile

Keine Flagge. Nein goto. Keine Ausnahmen. Einfach zu wechseln. Leicht zu lesen. Einfach zu reparieren. Zusätzlich der Code:

  1. Isoliert das Wissen über die Arbeitslast der Schleife von der Schleife selbst.
  2. Ermöglicht es jemandem, der den Code verwaltet, die Funktionalität einfach zu erweitern.
  3. Ermöglicht die Zuweisung mehrerer Abschlussbedingungen an einem Ort.
  4. Trennt die Abschlussklausel vom auszuführenden Code.
  5. Ist sicherer für Kernkraftwerke. ;-);

Der zweite Punkt ist wichtig. Ohne zu wissen, wie der Code funktioniert, fallen mir zwei Lösungen ein, wenn mich jemand bittet, die Hauptschleife so zu gestalten, dass andere Threads (oder Prozesse) etwas CPU-Zeit haben:

Option 1

Fügen Sie die Pause bereitwillig ein:

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

Option 2

Überschreiben ausführen:

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

Dieser Code ist einfacher (daher leichter zu lesen) als eine Schleife mit einer eingebetteten switch. Die isValidStateMethode sollte nur bestimmen, ob die Schleife fortgesetzt werden soll. Das Arbeitspferd der Methode sollte in die executeMethode abstrahiert werden, damit Unterklassen das Standardverhalten überschreiben können (eine schwierige Aufgabe mit einem eingebetteten switchund goto).

Python-Beispiel

Vergleichen Sie die folgende Antwort (auf eine Python-Frage), die auf StackOverflow veröffentlicht wurde:

  1. Schleife für immer.
  2. Bitten Sie den Benutzer, seine Wahl einzugeben.
  3. Wenn die Benutzereingabe "Neustart" lautet, setzen Sie die Schleife für immer fort.
  4. Andernfalls hören Sie für immer auf, eine Schleife zu erstellen.
  5. Ende.
Code
while True: 
    choice = raw_input('What do you want? ')

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

print 'Break!' 

Gegen:

  1. Initialisieren Sie die Auswahl des Benutzers.
  2. Schleife, während der Benutzer das Wort "Neustart" auswählt.
  3. Bitten Sie den Benutzer, seine Wahl einzugeben.
  4. Ende.
Code
choice = 'restart';

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

print 'Break!'

Dies while Trueführt zu irreführendem und zu komplexem Code.

Dave Jarvis
quelle
45
Die Idee, while(true)die schädlich sein sollte, erscheint mir bizarr. Es gibt Schleifen, die prüfen, bevor sie ausgeführt werden, es gibt solche, die danach prüfen, und es gibt solche, die in der Mitte prüfen. Da letztere in C und C ++ kein syntaktisches Konstrukt haben, müssen Sie while(true)oder verwenden for(;;). Wenn Sie dies für falsch halten, haben Sie noch nicht genug über die verschiedenen Arten von Schleifen nachgedacht.
sbi
34
Gründe, warum ich nicht einverstanden bin: while (true) und for (;;;) sind nicht verwirrend (im Gegensatz zu do {} while (true)), weil sie angeben, was sie von vornherein tun. Es ist tatsächlich einfacher, nach "break" -Anweisungen zu suchen, als eine beliebige Schleifenbedingung zu analysieren und zu suchen, wo sie festgelegt ist, und es ist nicht schwieriger, die Richtigkeit zu beweisen. Das Argument, wo Code abgelegt werden soll, ist bei Vorhandensein einer continue-Anweisung ebenso unlösbar und bei if-Anweisungen nicht viel besser. Das Effizienzargument ist positiv albern.
David Thornley
23
Immer wenn Sie "while (true)" sehen, können Sie sich eine Schleife vorstellen, die interne Beendigungsbedingungen aufweist, die nicht schwieriger sind als beliebige Endbedingungen. Eine "while (foo)" - Schleife, bei der foo eine willkürlich festgelegte boolesche Variable ist, ist einfach nicht besser als "while (true)" mit Unterbrechungen. In beiden Fällen müssen Sie den Code durchsehen. Einen dummen Grund vorzubringen (und Mikroeffizienz ist ein dummes Argument) hilft Ihrem Fall übrigens nicht weiter.
David Thornley
5
@ Dave: Ich habe einen Grund angegeben: Es ist der einzige Weg, eine Schleife zu erhalten, die den Zustand in der Mitte überprüft. Klassisches Beispiel: while(true) {string line; std::getline(is,line); if(!is) break; lines.push_back(line);}Natürlich könnte ich dies in eine vorkonditionierte Schleife umwandeln (unter Verwendung std::getlineder Schleifenbedingung), aber dies hat seine eigenen Nachteile ( linemüsste eine Variable außerhalb des Bereichs der Schleife sein). Und wenn ich das tun würde, müsste ich fragen: Warum haben wir überhaupt drei Schleifen? Wir könnten immer alles in eine vorkonditionierte Schleife verwandeln. Verwenden Sie, was am besten passt. Wenn es while(true)passt, dann benutze es.
sbi
3
Mit freundlicher Genehmigung der Leute, die diese Antwort lesen, würde ich es vermeiden, die Qualität des Gutes zu kommentieren, das die Frage hervorgebracht hat, und mich an die Beantwortung der Frage selbst halten. Die Frage ist eine Variation des Folgenden, von der ich glaube, dass sie ein Merkmal vieler Sprachen ist: Sie haben eine Schleife, die in einer anderen Schleife verschachtelt ist. Sie möchten aus der äußeren Schleife aus der inneren Schleife ausbrechen. Wie wird das gemacht?
Alex Leibowitz
172

Sie können verwenden goto.

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

   }
}
exit_loop: ;
Mehrdad Afshari
quelle
12
Gehen Sie mit diesem Schlüsselwort einfach nicht wild um.
Fragsworth
45
Es ist lustig, dass ich abgelehnt werde, weil die Leute es nicht mögen goto. Das OP klar erwähnt ohne Verwendung von Flags . Können Sie angesichts der Einschränkungen des OP einen besseren Weg vorschlagen? :)
Mehrdad Afshari
18
nur t, um sinnlose Goto-Hasser zu entschädigen. Ich denke, Mehrdad wusste, dass er ein paar Punkte verlieren wird, wenn er hier eine vernünftige Lösung vorschlägt.
Michael Krelin - Hacker
6
+1, es gibt keinen besseren Weg imho, selbst wenn man die Einschränkungen des OP berücksichtigt. Flags sind hässlich, brechen die Logik über die gesamte Schleife auf und sind daher schwieriger zu erfassen.
Johannes Schaub - litb
11
Am Ende des Tages scheitern alle Sprachen ohne das goto-Konstrukt. Hochsprachen verschleiern es, aber wenn Sie unter der Haube genau genug hinschauen, werden Sie jede Schleife finden, oder die Bedingung läuft auf bedingte und bedingungslose Zweige hinaus. Wir täuschen uns, indem wir für, während, bis, umschalten, wenn Schlüsselwörter verwenden, aber schließlich verwenden alle GOTO (oder JMP) auf die eine oder andere Weise. Sie können sich etwas vormachen, alle möglichen cleveren Methoden erfinden, um es zu verbergen, oder Sie können einfach pragmatisch ehrlich sein und goto gegebenenfalls und sparsam verwenden.
synchronisiert
57

Eine alternative Lösung besteht darin, das Schlüsselwort continuein Kombination mit folgenden breakElementen zu verwenden:

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

Verwenden Sie die continueAnweisung, um jede Fallbezeichnung zu beenden, an der die Schleife fortgesetzt werden soll, und verwenden Sie die breakAnweisung, um Fallbezeichnungen zu beenden, die die Schleife beenden sollen.

Natürlich funktioniert diese Lösung nur, wenn nach der switch-Anweisung kein zusätzlicher Code ausgeführt werden muss.

Sakra
quelle
9
Dies ist zwar sehr elegant, hat jedoch den Nachteil, dass die meisten Entwickler es sich zunächst eine Minute lang ansehen müssen, um zu verstehen, wie / ob dies funktioniert. :(
sbi
8
Der letzte Satz macht das nicht so toll. :(Sonst eine sehr saubere Implementierung.
Nawfal
Wenn Sie darüber nachdenken, ist kein Computercode gut. Wir sind nur darauf trainiert, es zu verstehen. Zum Beispiel sah XML wie Müll aus, bis ich es lernte. Jetzt sieht es weniger nach Müll aus. denn (int x = 0; x <10; ++ x, moveCursor (x, y)) hat tatsächlich einen logischen Fehler in meinem Programm verursacht. Ich habe vergessen, dass die Aktualisierungsbedingung am Ende aufgetreten ist.
22

Ein ordentlicher Weg, dies zu tun, wäre, dies in eine Funktion zu setzen:

int yourfunc() {

    while(true) {

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

    }
}

Optional (aber "schlechte Praktiken"): Wie bereits vorgeschlagen, können Sie ein goto verwenden oder eine Ausnahme in den Switch werfen.

Alterlife
quelle
4
Eine Ausnahme sollte verwendet werden, um eine Ausnahme auszulösen, nicht ein bekanntes Verhalten einer Funktion.
Clement Herreman
Ich stimme Afterlife zu: Setzen Sie es in eine Funktion.
Dalle
14
Eine Ausnahme zu machen ist in diesem Fall wirklich böse. Es bedeutet "Ich möchte ein goto verwenden, aber ich habe irgendwo gelesen, dass ich sie nicht verwenden sollte, also werde ich mit einer List gehen und so tun, als würde ich schlau aussehen".
Gorpik
Ich bin damit einverstanden, dass das Auslösen einer Ausnahme oder das Verwenden eines goto eine schreckliche Idee ist, aber es handelt sich um funktionierende Optionen, die in meiner Antwort als solche aufgeführt wurden.
Alterlife
2
Das Auslösen einer Ausnahme ersetzt ein GOTO durch ein COMEFROM. Tu es nicht. Das goto funktioniert viel besser.
David Thornley
14

AFAIK gibt es in C ++ kein "Double Break" oder ähnliches Konstrukt. Das nächste wäre ein goto- das zwar eine schlechte Konnotation zu seinem Namen hat, aber aus einem bestimmten Grund in der Sprache existiert - solange es sorgfältig und sparsam verwendet wird, ist es eine praktikable Option.

Bernstein
quelle
8

Sie können Ihren Schalter wie folgt in eine separate Funktion umwandeln:

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())
    ;
Adam Pierce
quelle
7

Auch wenn Sie goto nicht mögen, verwenden Sie keine Ausnahme, um eine Schleife zu verlassen. Das folgende Beispiel zeigt, wie hässlich es sein könnte:

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

Ich würde gotowie in dieser Antwort verwenden. In diesem Fall gotowird der Code klarer als bei jeder anderen Option. Ich hoffe, dass diese Frage hilfreich sein wird.

Aber ich denke, dass die Verwendung gotohier aufgrund der Zeichenfolge die einzige Option ist while(true). Sie sollten ein Refactoring Ihrer Schleife in Betracht ziehen. Ich würde die folgende Lösung annehmen:

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

Oder sogar das Folgende:

while ( msg->state != DONE ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
}
Kirill V. Lyadvinsky
quelle
1
Ja, aber ich mag Ausnahmen noch weniger ;-)
Quamrana
3
Die Verwendung einer Ausnahme zum Emulieren eines goto ist natürlich schlimmer als die tatsächliche Verwendung eines goto! Es wird wahrscheinlich auch deutlich langsamer sein.
John Carter
2
@Downvoter: Weil ich sage, dass Sie keine Ausnahme verwenden sollten, oder weil ich Refactoring vorschlage?
Kirill V. Lyadvinsky
1
Nicht der Abwärtswähler - aber ... Ihre Antwort ist nicht klar, ob Sie die Idee vorschlagen oder verurteilen, eine Ausnahme zum Verlassen der Schleife zu verwenden. Vielleicht sollte der erste Satz lauten: "Auch wenn Sie nicht mögen goto, verwenden Sie keine Ausnahme, um eine Schleife zu verlassen:"?
Jonathan Leffler
1
@ Jonathan Leffler, Danke, Sie haben das Beispiel eines wertvollen Kommentars gezeigt. Die Antwort wurde unter Berücksichtigung Ihrer Kommentare aktualisiert.
Kirill V. Lyadvinsky
5

In diesem Fall gibt es kein C ++ - Konstrukt, um aus der Schleife auszubrechen.

Verwenden Sie entweder ein Flag, um die Schleife zu unterbrechen, oder extrahieren Sie (falls zutreffend) Ihren Code in eine Funktion und verwenden Sie return.

scharfer Zahn
quelle
2

Sie könnten möglicherweise goto verwenden, aber ich würde es vorziehen, ein Flag zu setzen, das die Schleife stoppt. Dann aus dem Schalter ausbrechen.

Martin York
quelle
2

Warum nicht einfach den Zustand in Ihrer while-Schleife beheben und das Problem verschwinden lassen?

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**
    }
}
Mark B.
quelle
2

Nein, C ++ hat hierfür kein Konstrukt, da das Schlüsselwort "break" bereits für das Verlassen des Switch-Blocks reserviert ist. Alternativ könnte ein do..while () mit einem Exit-Flag ausreichen.

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

Meiner Ansicht nach;

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

Der einfachste Weg, dies zu tun, besteht darin, vor dem SWITCH eine einfache IF zu setzen und diese IF Ihre Bedingung für das Verlassen der Schleife zu testen .......... so einfach wie möglich

SpiriAd
quelle
2
Ähm ... Sie könnten noch einfacher sein, wenn Sie diese Bedingung in das while-Argument einfügen. Ich meine, dafür ist es da! :)
Mark A. Donohoe
0

Das breakSchlüsselwort in C ++ beendet nur die am meisten verschachtelte einschließende Iteration oder switchAnweisung. Sie konnten also nicht while (true)direkt innerhalb der switchAnweisung aus der Schleife ausbrechen . Sie können jedoch den folgenden Code verwenden, der meiner Meinung nach ein hervorragendes Muster für diese Art von Problem darstellt:

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

    //...
    }
}

Wenn Sie etwas tun müssen, wenn es msg->stategleich ist DONE(z. B. eine Bereinigungsroutine ausführen), platzieren Sie diesen Code unmittelbar nach der forSchleife. dh wenn Sie derzeit haben:

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

    //...

    case DONE:
        do_cleanup();
        break;
    }

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

    msg = next_message();
}

Verwenden Sie stattdessen:

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

    //...
    }
}

assert(msg->state == DONE);
do_cleanup();
Daniel Trebbien
quelle
0

Es wundert mich, wie einfach dies angesichts der Tiefe der Erklärungen ist ... Hier ist alles, was Sie brauchen ...

bool imLoopin = true;

while(imLoopin) {

    switch(msg->state) {

        case MSGTYPE: // ... 
            break;

        // ... more stuff ...

        case DONE:
            imLoopin = false;
            break;

    }

}

LOL !! Ja wirklich! Das ist alles was du brauchst! Eine zusätzliche Variable!

Mark A. Donohoe
quelle
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**
}
}
Wajeed-MSFT
quelle
0

Ich habe das gleiche Problem und mit einer Flagge gelöst.

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;
}
Ram Suthar
quelle
-2

Wenn ich mich gut an die C ++ - Syntax erinnere, können Sie breakAnweisungen wie für eine Bezeichnung hinzufügen goto. Was Sie wollen, lässt sich also leicht schreiben:

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
Andrei Vajna II
quelle
1
Leider ist Ihr Gedächtnis fehlerhaft. Es wäre eine nette Funktion, in den seltenen Fällen, in denen es nützlich wäre. (Ich habe Vorschläge für die Anzahl der Levels gesehen, von denen ich abbrechen kann, aber diese sehen für mich wie Fehler aus, die darauf warten, passiert zu werden.)
David Thornley
Das muss also Java gewesen sein. Danke für die Antwort. Und natürlich haben Sie auch mit dem Rest Recht.
Andrei Vajna II
-3
  while(true)
  {
    switch(x)
    {
     case 1:
     {
      break;
     }
    break;
   case 2:
    //some code here
   break;
  default:
  //some code here
  }
}
Chevaughn
quelle
1
Alle Unterbrechungen in diesem Codebeispiel brechen aus der switch-Anweisung aus.
Dan Hulme