Kann ich break verwenden, um mehrere verschachtelte 'for'-Schleifen zu beenden?


305

Ist es möglich, mit dieser breakFunktion mehrere verschachtelte forSchleifen zu verlassen?

Wenn ja, wie würden Sie dies tun? Können Sie auch steuern, wie viele Schleifen die breakAusgänge haben?


1
Anstatt break oder goto zum Beenden mehrerer verschachtelter Schleifen zu verwenden, können Sie diese bestimmte Logik in eine Funktion einschließen und mit return mehrere verschachtelte Schleifen beenden. Dies behält die Ästhetik Ihres Codes bei und verhindert, dass Sie goto verwenden, was eine schlechte Programmierpraxis ist.
Rishab Shinghal

Antworten:


239

AFAIK, C ++ unterstützt keine Namensschleifen wie Java und andere Sprachen. Sie können ein goto verwenden oder einen von Ihnen verwendeten Flag-Wert erstellen. Überprüfen Sie am Ende jeder Schleife den Flag-Wert. Wenn es auf true gesetzt ist, können Sie aus dieser Iteration ausbrechen.


317
Haben Sie keine Angst, eine zu verwenden, gotowenn dies die beste Option ist.
Jkeys

18
Ich bin ein neuer C ++ - Programmierer (und einer ohne formale Programmierschulung), nachdem ich über die Beschimpfungen der Leute auf goto gelesen habe. Ich zögere, es zu verwenden, aus Angst, mein Programm könnte plötzlich explodieren und mich töten. Abgesehen davon, als ich Programme auf meinem ti-83 schrieb (natürlich im langweiligen Matheunterricht), erforderten die Funktionen, die der Basiseditor bereitstellte, die Verwendung von gotos.
Gefälscht

26
@Faken: Zwei Arten von Programmierern verwenden goto: schlechte Programmierer und pragmatische Programmierer. Ersteres ist selbsterklärend. Letztere, in die Sie passen würden, wenn Sie sie gut verwenden möchten, verwenden ein sogenanntes "böses" Konzept, wenn es das geringere von (zwei) Übeln ist. Lesen Sie dies, um einige C ++ - Konzepte besser zu verstehen, die Sie möglicherweise von Zeit zu Zeit verwenden müssen (Makros, Goto, Präprozessor, Arrays): parashift.com/c++-faq-lite/big-picture.html#faq-6.15
jkeys

41
@Faken: Es ist nichts falsch mit goto. Es ist ein Missbrauch eines goto, der problematisch ist.
Jeder

28
@Hooked: Das stimmt, außer dass die Verwendung gotoselten die beste Option ist. Warum nicht die Loops in ihre eigene Funktion versetzen ( inlinewenn Sie sich Gedanken über die Geschwindigkeit machen) und returndaraus?
sbi

265

Nein, verwöhne es nicht mit einem break. Dies ist die letzte verbleibende Festung für die Nutzung von goto.


19
Der MISRA C ++ - Codierungsstandard ermöglicht die Verwendung von goto, um genau diese Situation abzudecken.
Richard Corden

11
Echte Programmierer haben keine Angst davor, goto zu verwenden. 25 Jahre lang getan - kein Bedauern - hat eine Menge Entwicklungszeit gespart.
Doug Null

1
Genau. gotos sind ein Muss, wenn Sie nicht über 8 KB Pixel verfügen und eine Folge von 30 Windows-Betriebssystemaufrufen aufrufen müssen.
Michaël Roy

Oder eine einfache "Rückkehr", indem Sie den Code in eine Funktion einfügen.
Tara

68

Nur um eine explizite Antwort mit Lambdas hinzuzufügen:

  for (int i = 0; i < n1; ++i) {
    [&] {
      for (int j = 0; j < n2; ++j) {
        for (int k = 0; k < n3; ++k) {
          return; // yay we're breaking out of 2 loops here
        }
      }
    }();
  }

Natürlich hat dieses Muster einige Einschränkungen und natürlich nur C ++ 11, aber ich denke, es ist ziemlich nützlich.


7
Dies könnte den Leser verwirren, zu glauben, dass die Rückkehr die Funktion bewirkt, in der sich das Lambda befindet, und nicht das Lambda selbst.
Xunie

3
@Xunie: Wenn Ihre Schleife so kompliziert ist, dass Sie vergessen, dass Sie sich in einem Lambda befinden, ist es Zeit, sie in eine andere Funktion zu versetzen, aber in einfachen Fällen sollte dies recht gut funktionieren.
MikeMB

17
Ich denke, diese Lösung ist wunderschön
Tuket

Sie können das Lambda in einer RIIA-Vorlage erfassen, die ihm einen Namen gibt und es in dtor (auch bekannt als scope gaurd) aufruft. Dies würde keinen Leistungsgewinn hinzufügen, kann jedoch die Lesbarkeit verbessern und das Risiko verringern, dass die Klammer des Funktionsaufrufs fehlt.
Red.Wave

56

Ein anderer Ansatz zum Ausbrechen einer verschachtelten Schleife besteht darin, beide Schleifen in eine separate Funktion und returnaus dieser Funktion heraus zu zerlegen, wenn Sie sie beenden möchten.

Dies wirft natürlich das andere Argument auf, ob Sie jemals explizit returnvon einer anderen Funktion als am Ende ausgehen sollten.


7
Das ist ein C-Problem. Mit RIAA ist eine vorzeitige Rückgabe kein Problem, da alle mit der vorzeitigen Rückgabe verbundenen Probleme korrekt behandelt werden.
Martin York

4
Ich verstehe, dass eine ordnungsgemäße Anwendung von RIAA das Problem der Ressourcenbereinigung in C ++ lösen kann, aber ich habe gesehen, dass das philosophische Argument gegen eine frühzeitige Rückkehr in anderen Umgebungen und Sprachen fortgesetzt wird. Ein System, an dem ich gearbeitet habe, bei dem der Codierungsstandard die vorzeitige Rückgabe untersagte, hatte Funktionen, die mit booleschen Variablen (mit Namen wie continue_processing) übersät waren , die die Ausführung von Codeblöcken weiter unten in der Funktion kontrollierten.
Greg Hewgill

21
Was ist RIAA? Ist das so etwas wie RAII? = D
jkeys

1
Kommt darauf an, wie viele Schleifen er hat und wie tief das Nest geht ... willst du die blaue oder die rote Pille?
Matt

34

break beendet nur die innerste Schleife, die es enthält.

Sie können goto verwenden , um aus einer beliebigen Anzahl von Schleifen auszubrechen.

Natürlich wird goto oft als schädlich angesehen .

Ist es richtig, die Unterbrechungsfunktion [...] zu verwenden?

Die Verwendung von break und goto kann es schwieriger machen, über die Richtigkeit eines Programms nachzudenken. Hier finden Sie eine Diskussion dazu: Dijkstra war nicht verrückt .


16
Eine gute Antwort darin, dass es erklärt, dass "goto is schädlich" meme stark mit der allgemeineren Aussage "Unterbrechung des Kontrollflusses ist schädlich" verbunden ist. Es ist sinnlos zu sagen, "goto ist schädlich", und sich dann umzudrehen und die Verwendung von breakoder zu empfehlen return.
Pavel Minaev

6
@Pavel: breakund returnhaben den Vorteil, gotodass Sie nicht nach einem Label suchen müssen, um herauszufinden, wohin sie gehen. Ja, darunter sind sie eine Art goto, aber eine sehr eingeschränkte. Sie sind vom Mustervergleichsgehirn eines Programmierers viel einfacher zu entziffern als das uneingeschränkte goto. Also IMO sind sie vorzuziehen.
sbi

@sbi: Stimmt, aber Pause ist immer noch nicht Teil der strukturierten Programmierung. Es ist einfach besser verträglich als ein goto.
Jkeys

2
@KarlVoigtland der Dijkstra Link ist veraltet; Dies scheint zu funktionieren: blog.plover.com/2009/07
Aaron Brager

3
In dieser Situation ist absolut nichts falsch daran, goto zu verwenden. Ein gut platzierter Goto ist sprunghaft besser und lesbarer als viele der sonst vorgeschlagenen verzerrten Lösungen.
James

22

Obwohl diese Antwort bereits vorgestellt wurde, denke ich, dass ein guter Ansatz darin besteht, Folgendes zu tun:

for(unsigned int z = 0; z < z_max; z++)
{
    bool gotoMainLoop = false;
    for(unsigned int y = 0; y < y_max && !gotoMainLoop; y++)
    {
        for(unsigned int x = 0; x < x_max && !gotoMainLoop; x++)
        {
                          //do your stuff
                          if(condition)
                            gotoMainLoop = true;
        }
    }

}

5
Das ist gut, aber immer noch nicht so lesbar, ich würde es vorziehen, in diesem Fall zu gehen
28етър Петров

2
Dies macht Ihren Code "ziemlich" langsam, weil der gotoMainLoopin jedem Zyklus überprüft wird
Thomas

1
In diesem Fall gotomacht die Verwendung des Real den Kern lesbarer und leistungsfähiger.
25етър Петров

20

Wie wäre es damit?

for(unsigned int i=0; i < 50; i++)
{
    for(unsigned int j=0; j < 50; j++)
    {
        for(unsigned int k=0; k < 50; k++)
        {
            //Some statement
            if (condition)
            {
                j=50;
                k=50;
            }
        }
    }
}

2
interessanter Ansatz, aber ich mag definitiv die Art und Weise, wie ered @ inf.ig.sh damit umgeht. for (vorzeichenloses int y = 0; y <y_max &&! gotoMainLoop; y ++).
Andy

15

Ein Codebeispiel mit gotound eine Bezeichnung zum Ausbrechen einer verschachtelten Schleife:

for (;;)
  for (;;)
    goto theEnd;
theEnd:

11

Eine gute Möglichkeit, aus mehreren verschachtelten Schleifen auszubrechen, besteht darin, Ihren Code in eine Funktion umzuwandeln:

void foo()
{
    for(unsigned int i=0; i < 50; i++)
    {
        for(unsigned int j=0; j < 50; j++)
        {
            for(unsigned int k=0; k < 50; k++)
            {
                // If condition is true
                return;
            }
        }
    }
}

4
... was keine Option ist, wenn wir 10-20 Variablen für das Stack-Framing dieser Funktion übergeben müssen.
28етър Петров

1
@ ПетърПетров dann gehen Sie für ein Lambda, das auch besser ist, da Sie es genau dort definieren können, wo Sie es brauchen.
DarioP

+1 für Lambdas, aber Überarbeitung des Game-Engine-Kerns, bei dem selbst ein Stack-Frame immer noch ein Engpass ist. Tut mir leid zu sagen, aber Lambdas sind zumindest in MSVC 2010 nicht so leicht.
Петров

@ ПетърПетров Ändern Sie dann das Funktionspaar in eine Klasse und die Stapelvariablen in private Mitglieder.
Arthur Tacca

Dies macht den bereits komplexen Code nur zu kompliziert :) Manchmal ist goto die einzige Lösung. Oder wenn Sie komplexe Automaten mit der Dokumentation "goto state X" schreiben, lässt goto den Code tatsächlich so lesen, wie er im Dokument geschrieben ist. Außerdem haben C # und go beide einen Zweck: Keine Sprache ist ohne goto vollständig, und gotos sind häufig die am besten verwendeten Werkzeuge zum Schreiben von Zwischenübersetzern oder Assembler-ähnlichem Code.
1етър Петров

5

goto kann sehr hilfreich sein, um verschachtelte Schleifen zu brechen

for (i = 0; i < 1000; i++) {
    for (j = 0; j < 1000; j++) {
        for (k = 0; k < 1000; k++) {
             for (l = 0; l < 1000; l++){
                ....
                if (condition)
                    goto break_me_here;
                ....
            }
        }
    }
}

break_me_here:
// Statements to be executed after code breaks at if condition

3

Die breakAnweisung beendet die Ausführung der nächsten einschließenden do, for, switch, oder whileErklärung , in der sie erscheint. Die Steuerung wird an die Anweisung übergeben, die auf die terminierte Anweisung folgt.

von msdn .


3

Ich denke, a gotoist unter diesen Umständen gültig:

Um ein break/ zu simulieren continue, möchten Sie:

Brechen

for ( ;  ;  ) {
    for ( ;  ;  ) {
        /*Code here*/
        if (condition) {
            goto theEnd;
        }
    }
}
theEnd:

Fortsetzen

for ( ;  ; ) {
    for ( ;  ;  ) {
        /*Code here*/
        if (condition) {
            i++;
            goto multiCont;
        }
    }
    multiCont:
}

"Weiter" funktioniert dort nicht, da der Iterationsausdruck der ersten Schleife nicht ausgeführt wird
Fabio A.

Ich gehe davon aus, dass der Iterator für die erste Schleife ist i. Daher i++vor dem goto
JuliusAlphonso

0

Andere Sprachen wie PHP akzeptieren einen Parameter für break (dh break 2;), um die Anzahl der verschachtelten Schleifenebenen anzugeben, aus denen Sie ausbrechen möchten, C ++ jedoch nicht. Sie müssen es herausfinden, indem Sie einen Booleschen Wert verwenden, den Sie vor der Schleife auf false gesetzt haben, der in der Schleife auf true gesetzt ist, wenn Sie brechen möchten, sowie eine bedingte Unterbrechung nach der verschachtelten Schleife, um zu überprüfen, ob der Boolesche Wert auf true gesetzt wurde und brechen wenn ja.


0

Ich weiß, das ist ein alter Beitrag. Aber ich würde eine etwas logische und einfachere Antwort vorschlagen.

for(unsigned int i=0; i < 50; i++)
    {
        for(unsigned int j=0; j < conditionj; j++)
        {
            for(unsigned int k=0; k< conditionk ; k++)
            {
                // If condition is true

                j= conditionj;
               break;
            }
        }
    }

3
Dies ist keine sehr skalierbare Lösung, da j = conditionjsie nicht funktioniert, wenn Sie stattdessen ein komplexes Prädikat haben j < conditionj.
Sergey

0

Brechen Sie eine beliebige Anzahl von Schleifen mit nur einer boolVariablen (siehe unten):

bool check = true;

for (unsigned int i = 0; i < 50; i++)
{
    for (unsigned int j = 0; j < 50; j++)
    {
        for (unsigned int k = 0; k < 50; k++)
        {
            //Some statement
            if (condition)
            {
                check = false;
                break;
            }
        }
        if (!check)
        {
            break;
        }
    }
    if (!check)
    {
        break;
    }
}

In diesem Code haben wir break;alle Schleifen.


0

Ich bin mir nicht sicher, ob es sich lohnt, aber Sie können Javas benannte Schleifen mit ein paar einfachen Makros emulieren:

#define LOOP_NAME(name) \
    if ([[maybe_unused]] constexpr bool _namedloop_InvalidBreakOrContinue = false) \
    { \
        [[maybe_unused]] CAT(_namedloop_break_,name): break; \
        [[maybe_unused]] CAT(_namedloop_continue_,name): continue; \
    } \
    else

#define BREAK(name) goto CAT(_namedloop_break_,name)
#define CONTINUE(name) goto CAT(_namedloop_continue_,name)

#define CAT(x,y) CAT_(x,y)
#define CAT_(x,y) x##y

Anwendungsbeispiel:

#include <iostream>

int main()
{
    // Prints:
    // 0 0
    // 0 1
    // 0 2
    // 1 0
    // 1 1

    for (int i = 0; i < 3; i++) LOOP_NAME(foo)
    {
        for (int j = 0; j < 3; j++)
        {
            std::cout << i << ' ' << j << '\n';
            if (i == 1 && j == 1)
                BREAK(foo);
        }
    }
}

Ein anderes Beispiel:

#include <iostream>

int main()
{
    // Prints: 
    // 0
    // 1
    // 0
    // 1
    // 0
    // 1

    int count = 3;
    do LOOP_NAME(foo)
    {
        for (int j = 0; j < 3; j++)
        {
            std::cout << ' ' << j << '\n';
            if (j == 1)
                CONTINUE(foo);
        }
    }
    while(count-- > 1);
}

-1
  while (i<n) {
    bool shouldBreakOuter = false;
    for (int j=i + 1; j<n; ++j) {
      if (someCondition) {
          shouldBreakOuter = true;
      }
    }

    if (shouldBreakOuter == true)
      break;

  }

-3

Sie können try ... catch verwenden.

try {
    for(int i=0; i<10; ++i) {
        for(int j=0; j<10; ++j) {
            if(i*j == 42)
                throw 0; // this is something like "break 2"
        }
    }
}
catch(int e) {} // just do nothing
// just continue with other code

Wenn Sie aus mehreren Schleifen gleichzeitig ausbrechen müssen, ist dies oft eine Ausnahme.


1
Ich würde gerne wissen, warum diese Antwort so viele Abstimmungen erhält.
hkBattousai

6
@hkBattousai Die Lösung hat Abstimmungen, da eine Ausnahme zur Steuerung des Ausführungsflusses verwendet wird. Wie der Name schon sagt, sollten Ausnahmen nur in Ausnahmefällen verwendet werden.
Helio Santos

4
@HelioSantos Ist dies nicht eine Ausnahmesituation, für die die Sprache keine angemessene Lösung liefert?
hkBattousai

8
Ausnahmen sind langsam.
Gordon

2
Die Auswirkung des Wurfs auf die Leistung ist enorm für etwas, das in 99% der Fälle kein nicht behebbarer Fehler ist.
Michaël Roy

-4

Das Ausbrechen einer for-Schleife ist für mich etwas seltsam, da die Semantik einer for-Schleife normalerweise anzeigt, dass sie eine bestimmte Anzahl von Malen ausgeführt wird. Es ist jedoch nicht in allen Fällen schlecht; Wenn Sie nach etwas in einer Sammlung suchen und es brechen möchten, nachdem Sie es gefunden haben, ist es nützlich. Das Aufbrechen verschachtelter Schleifen ist in C ++ jedoch nicht möglich. Es ist in anderen Sprachen durch die Verwendung einer beschrifteten Pause. Sie können ein Etikett und ein Goto verwenden, aber das kann nachts zu Sodbrennen führen. Scheint jedoch die beste Option zu sein.


11
Es ist überhaupt nicht seltsam. Wenn Sie eine Sammlung durchlaufen, um nach etwas zu suchen (und keine schnellere Suchmethode haben), macht es keinen Sinn, die Schleife zu beenden. (als ein Beispiel)
Joe
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.