Aus einer verschachtelten Schleife ausbrechen


216

Wenn ich eine for-Schleife habe, die in einer anderen verschachtelt ist, wie kann ich dann schnellstmöglich effizient aus beiden Schleifen (innen und außen) herauskommen?

Ich möchte keinen Booleschen Wert verwenden und dann "Gehe zu einer anderen Methode" sagen müssen, sondern nur die erste Codezeile nach der äußeren Schleife ausführen.

Was ist eine schnelle und nette Vorgehensweise?

Ich dachte, dass Ausnahmen nicht billig sind / nur in einem wirklich außergewöhnlichen Zustand usw. geworfen werden sollten. Daher denke ich nicht, dass diese Lösung aus Sicht der Leistung gut wäre.

Ich halte es nicht für richtig, die neueren Funktionen in .NET (anon-Methoden) zu nutzen, um etwas zu tun, das ziemlich grundlegend ist.


Ich wollte nur sichergehen: Warum willst du das tun?
Jon Limjap

3
Warum willst du keinen Booleschen Wert verwenden? Was ist daran falsch?
Anthony

In VB.net können Sie eine try / finally-Anweisung (kein catch) um eine beliebige Anzahl von Schleifen wickeln. Mit "exit try" werden alle zu jedem Zeitpunkt beendet.
Brain2000

Antworten:


206

Nun, gotoaber das ist hässlich und nicht immer möglich. Sie können die Schleifen auch in eine Methode (oder eine anon-Methode) returneinfügen und verwenden, um zum Hauptcode zurückzukehren.

    // goto
    for (int i = 0; i < 100; i++)
    {
        for (int j = 0; j < 100; j++)
        {
            goto Foo; // yeuck!
        }
    }
Foo:
    Console.WriteLine("Hi");

vs:

// anon-method
Action work = delegate
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits anon-method
        }
    }
};
work(); // execute anon-method
Console.WriteLine("Hi");

Beachten Sie, dass wir in C # 7 "lokale Funktionen" erhalten sollten, was (Syntax tbd usw.) bedeutet, dass es ungefähr so ​​funktionieren sollte:

// local function (declared **inside** another method)
void Work()
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits local function
        }
    }
};
Work(); // execute local function
Console.WriteLine("Hi");

49
In einer solchen Situation denke ich nicht, dass die Verwendung von goto schlechter ist als die normale Verwendung von so etwas wie break (schließlich sind beide nur bedingungslose Verzweigungen zu einem Label, es ist nur so, dass mit break das Label implizit ist).
Greg Beech

37
Manchmal ist goto weniger böse als die Alternativen
Seanb

7
@BeowulfOF - break bricht nur aus der inneren Schleife aus, nicht aus der inneren und der äußeren Schleife.
Greg Beech

36
Gehe selbst ist nicht hässlich. Was hässlich ist, ist der Missbrauch von goto, was zu Spaghetti-Code führt. Die Verwendung von goto zum Ausbrechen einer verschachtelten Schleife ist vollkommen in Ordnung. Beachten Sie außerdem, dass alle Unterbrechungen, Fortsetzungen und Rückgaben aus Sicht der strukturellen Programmierung kaum besser sind als goto - im Grunde sind sie dasselbe, nur in einer schöneren Verpackung. Deshalb fehlen reinen Struktursprachen (wie dem ursprünglichen Pascal) alle drei.
el.pescado

11
@ el.pescado ist völlig richtig: Viele Programmierer wurden irregeführt, um zu glauben, dass gotoes per se schädlich ist , während es einfach das richtige Instrument ist, um einige Dinge zu tun, und wie viele Instrumente möglicherweise missbraucht werden. Diese religiöse Abneigung gegen gotoist ehrlich gesagt ziemlich dumm und definitiv unwissenschaftlich.
o0 '.

96

C # -Anpassung des Ansatzes, der häufig in C verwendet wird - Wert der Variablen der äußeren Schleife außerhalb der Schleifenbedingungen einstellen (dh für Schleife mit int-Variable INT_MAX -1ist häufig eine gute Wahl):

for (int i = 0; i < 100; i++)
{
    for (int j = 0; j < 100; j++)
    {
        if (exit_condition)
        {
            // cause the outer loop to break:
            // use i = INT_MAX - 1; otherwise i++ == INT_MIN < 100 and loop will continue 
            i = int.MaxValue - 1;
            Console.WriteLine("Hi");
            // break the inner loop
            break;
        }
    }
    // if you have code in outer loop it will execute after break from inner loop    
}

Wie im Code angegeben, breakwird nicht auf magische Weise zur nächsten Iteration der äußeren Schleife gesprungen. Wenn Sie also Code außerhalb der inneren Schleife haben, erfordert dieser Ansatz mehr Überprüfungen. Betrachten Sie in diesem Fall andere Lösungen.

Dieser Ansatz funktioniert mit forund whileSchleifen, funktioniert aber nicht für foreach. Falls foreachSie keinen IEnumeratorCodezugriff auf den versteckten Enumerator haben, können Sie ihn nicht ändern (und selbst wenn Sie keine "MoveToEnd" -Methode haben könnten).

Danksagung an die Autoren
i = INT_MAX - 1von Inline-Kommentaren: Vorschlag von Meta
for / foreachKommentar von ygoe .
Richtig IntMaxvon jmbpiano
Bemerkung über Code nach innerer Schleife von blizpasta


@DrG: Funktioniert in c # nicht als "Die break-Anweisung beendet die nächste umschließende Schleifen- oder switch-Anweisung, in der sie angezeigt wird." (msdn)
Blizpasta

1
@blizpasta Also? Wenn er die Bedingung in der äußeren Schleife falsch macht (wie er es getan hat), werden beide beendet.
Patrick

51
Sie sollten i = INT_MAX - 1 verwenden; Andernfalls wird i ++ == INT_MIN <100 und die Schleife fortgesetzt
Meta

6
Irgendeine Idee zu foreach?
Ktutnik

4
@ktutnik Es funktioniert nicht mit foreach, da Sie keinen Codezugriff auf den versteckten Enumerator haben. Auch IEnumerator hat keine "MoveToEnd" -Methode.
Ygoe

39

Diese Lösung gilt nicht für C #

Für Personen, die diese Frage über andere Sprachen gefunden haben, ermöglichen Javascript, Java und D gekennzeichnete Pausen und fahren fort :

outer: while(fn1())
{
   while(fn2())
   {
     if(fn3()) continue outer;
     if(fn4()) break outer;
   }
}

40
Es ist traurig, dass dies mit c # nicht möglich ist. Es würde in so vielen Fällen saubereren Code erzeugen.
Rickard

17
Ich wurde tatsächlich für eine Sekunde aufgeregt, bis mir klar wurde, dass dies NICHT für c # war. :(
Arvo Bowen

1
Dieses Konzept gilt auch für PowerShell, falls dort jemand auf das Problem stößt. (Sie setzen einfach den Doppelpunkt vor den Labelnamen.) Jetzt weiß ich, dass dies nicht PowerShell-spezifisch ist ... Diese Syntax wäre nicht mit den in C # verfügbaren goto-Labels kompatibel. PHP verwendet etwas anderes: Pause 3; Geben Sie die Anzahl der Ebenen nach der break-Anweisung ein.
Ygoe

1
Dies ist Java nicht c #
Luca Ziegler

Für diejenigen, die diese Funktion in C # sehen möchten, platzieren Sie Ihr Lob hier 😉 github.com/dotnet/csharplang/issues/869#issuecomment-326606003
m93a

28

Verwenden Sie einen geeigneten Schutz in der äußeren Schlaufe. Setzen Sie den Schutz in die innere Schlaufe, bevor Sie brechen.

bool exitedInner = false;

for (int i = 0; i < N && !exitedInner; ++i) {

    .... some outer loop stuff

    for (int j = 0; j < M; ++j) {

        if (sometest) {
            exitedInner = true;
            break;
        }
    }
    if (!exitedInner) {
       ... more outer loop stuff
    }
}

Oder noch besser, abstrahieren Sie die innere Schleife in eine Methode und verlassen Sie die äußere Schleife, wenn sie false zurückgibt.

for (int i = 0; i < N; ++i) {

    .... some outer loop stuff

    if (!doInner(i, N, M)) {
       break;
    }

    ... more outer loop stuff
}

4
Außer das OP sagte "Ich möchte keinen Booleschen Wert verwenden müssen".
LeopardSkinPillBoxHat

Sehr pascalisch ... Ich würde wahrscheinlich lieber ein Goto verwenden, obwohl ich sie normalerweise wie die Pest meide.
Jonathan Leffler

21

Zitiere mich nicht dazu, aber du könntest goto gebrauchen wie im MSDN vorgeschlagen. Es gibt andere Lösungen, z. B. ein Flag, das in jeder Iteration beider Schleifen überprüft wird. Schließlich könnten Sie eine Ausnahme als wirklich schwere Lösung für Ihr Problem verwenden.

GEHE ZU:

for ( int i = 0; i < 10; ++i ) {
   for ( int j = 0; j < 10; ++j ) {
      // code
      if ( break_condition ) goto End;
      // more code
   }
}
End: ;

Bedingung:

bool exit = false;
for ( int i = 0; i < 10 && !exit; ++i ) {
   for ( int j = 0; j < 10 && !exit; ++j ) {
      // code
      if ( break_condition ) {
         exit = true;
         break; // or continue
      }
      // more code
   }
}

Ausnahme:

try {
    for ( int i = 0; i < 10 && !exit; ++i ) {
       for ( int j = 0; j < 10 && !exit; ++j ) {
          // code
          if ( break_condition ) {
             throw new Exception()
          }
          // more code
       }
    }
catch ( Exception e ) {}

2
Dies sind alles hackige Workarounds, bei denen es super sauber wäre, nur eine Methode zu berücksichtigen und eine frühzeitige Rückkehr zu verwenden
Dustin Getz

3
:) Richtig, das ist eine einfache Lösung, aber Sie müssen alle erforderlichen lokalen Daten als Argumente an die Methode übergeben ... Dies ist einer der wenigen Orte, an denen goto die geeignete Lösung sein könnte
David Rodríguez - dribeas

Die Condition-Methode funktioniert nicht einmal, da "mehr Code" einmal nach dem Verlassen der inneren Schleife ausgeführt wird, bevor die äußere Schleife verlassen wird. Die GOTO-Methode funktioniert, macht aber genau das, was auf dem Poster angegeben ist. Die Ausnahmemethode funktioniert, ist jedoch hässlicher und langsamer als GOTO.
Windows-Programmierer

Ich würde dieses Semikolon nach dem Etikett hervorheben. Auf diese Weise kann sich diese Beschriftung sogar am Ende eines Blocks befinden. +1
Tamas Hegedus

@Windowsprogrammer Das OP fragte: "Was ist eine schnelle und nette Vorgehensweise?" Springen ist die bevorzugte Lösung: sauber und prägnant, ohne eine separate Methode.
Suncat2000

16

Ist es möglich, die verschachtelte for-Schleife in eine private Methode umzuwandeln? Auf diese Weise können Sie einfach aus der Methode "zurückkehren", um die Schleife zu verlassen.


Mit dem Nebeneffekt, Ihre ursprüngliche Methode kürzer zu machen :-)
Martin Capodici

C ++ 11 Lambdas machen dies in einigen Fällen einfach:[&] { ... return; ... }();
BCS

14

Es scheint mir, als ob die Leute eine gotoAussage nicht mögen , deshalb hatte ich das Bedürfnis, dies ein wenig zu korrigieren.

Ich glaube, die "Emotionen", die Menschen haben, laufen gotoletztendlich darauf hinaus, Code zu verstehen und (falsche Vorstellungen) über mögliche Auswirkungen auf die Leistung. Bevor ich die Frage beantworte, werde ich daher zunächst einige Details zur Kompilierung erläutern.

Wie wir alle wissen, wird C # zu IL kompiliert, das dann mit einem SSA-Compiler zu Assembler kompiliert wird. Ich werde ein paar Einblicke geben, wie das alles funktioniert, und dann versuchen, die Frage selbst zu beantworten.

Von C # nach IL

Zuerst brauchen wir ein Stück C # -Code. Fangen wir einfach an:

foreach (var item in array)
{
    // ... 
    break;
    // ...
}

Ich werde dies Schritt für Schritt tun, um Ihnen eine gute Vorstellung davon zu geben, was unter der Haube passiert.

Erste Übersetzung: von foreachin die äquivalente forSchleife (Hinweis: Ich verwende hier ein Array, da ich nicht auf Details von IDisposable eingehen möchte - in diesem Fall müsste ich auch eine IEnumerable verwenden):

for (int i=0; i<array.Length; ++i)
{
    var item = array[i];
    // ...
    break;
    // ...
}

Zweite Übersetzung: das forund breakwird in ein einfacheres Äquivalent übersetzt:

int i=0;
while (i < array.Length)
{
    var item = array[i];
    // ...
    break;
    // ...
    ++i;
}

Und dritte Übersetzung (dies entspricht dem IL-Code): Wir ändern uns breakund whilein einen Zweig:

    int i=0; // for initialization

startLoop:
    if (i >= array.Length) // for condition
    {
        goto exitLoop;
    }
    var item = array[i];
    // ...
    goto exitLoop; // break
    // ...
    ++i;           // for post-expression
    goto startLoop; 

Während der Compiler diese Aufgaben in einem einzigen Schritt ausführt, erhalten Sie einen Einblick in den Prozess. Der IL-Code, der sich aus dem C # -Programm entwickelt, ist die wörtliche Übersetzung des letzten C # -Codes. Sie können sich hier selbst davon überzeugen: https://dotnetfiddle.net/QaiLRz (klicken Sie auf 'IL anzeigen')

Eine Sache, die Sie hier beobachtet haben, ist, dass der Code während des Prozesses komplexer wird. Der einfachste Weg, dies zu beobachten, ist die Tatsache, dass wir immer mehr Code benötigten, um dasselbe zu erreichen. Man könnte auch argumentieren , dass foreach, for, whileund breaksind eigentlich Kurz Hände goto, die zum Teil wahr ist.

Von IL zu Assembler

Der .NET JIT-Compiler ist ein SSA-Compiler. Ich werde hier nicht auf alle Details des SSA-Formulars eingehen und darauf, wie ein optimierender Compiler erstellt wird. Es ist einfach zu viel, kann aber ein grundlegendes Verständnis darüber vermitteln, was passieren wird. Für ein tieferes Verständnis ist es am besten, sich mit der Optimierung von Compilern zu befassen (ich mag dieses Buch für eine kurze Einführung: http://ssabook.gforge.inria.fr/latest/book.pdf befassen ) und LLVM (llvm.org) .

Jeder optimierende Compiler verlässt sich auf die Tatsache, dass Code einfach ist und vorhersehbaren Mustern folgt . Im Fall von FOR-Schleifen verwenden wir die Graphentheorie, um Zweige zu analysieren und dann Dinge wie Zyklen in unseren Zweigen zu optimieren (z. B. Zweige rückwärts).

Wir haben jetzt jedoch Forward-Zweige, um unsere Schleifen zu implementieren. Wie Sie vielleicht vermutet haben, ist dies tatsächlich einer der ersten Schritte, die die JIT wie folgt beheben wird:

    int i=0; // for initialization

    if (i >= array.Length) // for condition
    {
        goto endOfLoop;
    }

startLoop:
    var item = array[i];
    // ...
    goto endOfLoop; // break
    // ...
    ++i;           // for post-expression

    if (i >= array.Length) // for condition
    {
        goto startLoop;
    }

endOfLoop:
    // ...

Wie Sie sehen können, haben wir jetzt einen Rückwärtszweig, der unsere kleine Schleife ist. Das einzige, was hier noch böse ist, ist der Zweig, mit dem wir aufgrund unserer breakAussage gelandet sind. In einigen Fällen können wir dies auf die gleiche Weise verschieben, in anderen bleibt es jedoch bestehen.

Warum macht der Compiler das? Wenn wir die Schleife abrollen können, können wir sie möglicherweise vektorisieren. Wir könnten sogar beweisen können, dass nur Konstanten hinzugefügt werden, was bedeutet, dass unsere gesamte Schleife in Luft aufgehen könnte. Zusammenfassend lässt sich sagen: Indem wir die Muster vorhersehbar machen (indem wir die Zweige vorhersehbar machen), können wir beweisen, dass bestimmte Bedingungen in unserer Schleife gelten, was bedeutet, dass wir während der JIT-Optimierung zaubern können.

Zweige neigen jedoch dazu, diese schönen vorhersehbaren Muster zu brechen, was Optimierer daher nicht mögen. Brechen Sie, fahren Sie fort, gehen Sie - sie alle beabsichtigen, diese vorhersehbaren Muster zu brechen - und sind daher nicht wirklich "nett".

Sie sollten an dieser Stelle auch erkennen, dass eine einfache Aussage foreachvorhersehbarer ist als eine Reihe von gotoAussagen, die überall vorkommen. In Bezug auf (1) Lesbarkeit und (2) aus Sicht des Optimierers ist dies die bessere Lösung.

Eine weitere erwähnenswerte Sache ist, dass es für die Optimierung von Compilern sehr relevant ist, Register Variablen zuzuweisen (ein Prozess, der als Registerzuweisung bezeichnet wird ). Wie Sie vielleicht wissen, gibt es in Ihrer CPU nur eine begrenzte Anzahl von Registern, und diese sind bei weitem die schnellsten Speicher in Ihrer Hardware. Variablen, die in Code verwendet werden, der sich in der innersten Schleife befindet, erhalten mit größerer Wahrscheinlichkeit ein Register, während Variablen außerhalb Ihrer Schleife weniger wichtig sind (da dieser Code wahrscheinlich weniger getroffen wird).

Hilfe, zu viel Komplexität ... was soll ich tun?

Das Fazit ist, dass Sie immer die Sprachkonstrukte verwenden sollten, die Ihnen zur Verfügung stehen, wodurch normalerweise (implizit) vorhersehbare Muster für Ihren Compiler erstellt werden. Versuchen Sie , seltsame Zweige wenn möglich zu vermeiden (insbesondere: break, continue, gotooder ein returnin der Mitte nichts).

Die gute Nachricht hier ist, dass diese vorhersehbaren Muster sowohl leicht zu lesen (für Menschen) als auch leicht zu erkennen (für Compiler) sind.

Eines dieser Muster heißt SESE und steht für Single Entry Single Exit.

Und jetzt kommen wir zur eigentlichen Frage.

Stellen Sie sich vor, Sie haben so etwas:

// a is a variable.

for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j)
  {
     // ...

     if (i*j > a) 
     {
        // break everything
     }
  }
}

Der einfachste Weg, dies zu einem vorhersehbaren Muster zu machen, besteht darin, einfach Folgendes ifvollständig zu eliminieren :

int i, j;
for (i=0; i<100 && i*j <= a; ++i) 
{
  for (j=0; j<100 && i*j <= a; ++j)
  {
     // ...
  }
}

In anderen Fällen können Sie die Methode auch in zwei Methoden aufteilen:

// Outer loop in method 1:

for (i=0; i<100 && processInner(i); ++i) 
{
}

private bool processInner(int i)
{
  int j;
  for (j=0; j<100 && i*j <= a; ++j)
  {
     // ...
  }
  return i*j<=a;
}

Temporäre Variablen? Gut, schlecht oder hässlich?

Sie könnten sogar beschließen, einen Booleschen Wert aus der Schleife zurückzugeben (aber ich persönlich bevorzuge das SESE-Formular, da der Compiler es so sieht und ich denke, es ist sauberer zu lesen).

Einige Leute halten es für sauberer, eine temporäre Variable zu verwenden, und schlagen eine Lösung wie diese vor:

bool more = true;
for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j) 
  {
     // ...
     if (i*j > a) { more = false; break; } // yuck.
     // ...
  }
  if (!more) { break; } // yuck.
  // ...
}
// ...

Ich persönlich bin gegen diesen Ansatz. Sehen Sie sich noch einmal an, wie der Code kompiliert wird. Überlegen Sie nun, was dies mit diesen schönen, vorhersehbaren Mustern bewirken wird. Verstehe?

Richtig, lassen Sie es mich formulieren. Was passieren wird ist das:

  • Der Compiler schreibt alles als Zweige aus.
  • Als Optimierungsschritt führt der Compiler eine Datenflussanalyse durch, um die seltsame moreVariable zu entfernen, die nur im Kontrollfluss verwendet wird.
  • Bei Erfolg wird die Variable moreaus dem Programm entfernt und es bleiben nur Verzweigungen übrig. Diese Zweige werden optimiert, sodass Sie nur einen einzigen Zweig aus der inneren Schleife erhalten.
  • Wenn dies nicht erfolgreich ist, wird die Variable moredefinitiv in der innersten Schleife verwendet. Wenn der Compiler sie also nicht optimiert, besteht eine hohe Wahrscheinlichkeit, dass sie einem Register zugewiesen wird (das wertvollen Registerspeicher verbraucht).

Zusammenfassend lässt sich sagen, dass der Optimierer in Ihrem Compiler eine Menge Probleme haben wird, um herauszufinden, dass er morenur für den Kontrollfluss verwendet wird, und ihn im besten Fall in einen einzelnen Zweig außerhalb des äußeren für übersetzt Schleife.

Mit anderen Worten, das beste Szenario ist, dass es das Äquivalent dazu ergibt:

for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j)
  {
     // ...
     if (i*j > a) { goto exitLoop; } // perhaps add a comment
     // ...
  }
  // ...
}
exitLoop:

// ...

Meine persönliche Meinung dazu ist ganz einfach: Wenn wir dies die ganze Zeit beabsichtigt haben, lassen Sie uns die Welt sowohl für den Compiler als auch für die Lesbarkeit einfacher machen und das sofort schreiben.

tl; dr:

Endeffekt:

  • Verwenden Sie nach Möglichkeit eine einfache Bedingung in Ihrer for-Schleife. Halten Sie sich so weit wie möglich an die Hochsprachenkonstrukte, die Ihnen zur Verfügung stehen.
  • Wenn alles fehlschlägt und Sie entweder gotooder bleiben bool more, bevorzugen Sie das erstere.

OTOH: Ich habe nur selten den Wunsch, äußere Schleifen zu durchbrechen oder nach goto-equiv (und viel Code, der wohl sauberer geschrieben werden könnte), obwohl dies in anderen Bereichen möglicherweise häufiger vorkommt. Heute war ein solcher Fall. aber das war größtenteils darauf zurückzuführen yield return. Das heißt, obwohl es leicht zu zeigen ist, dass es einige nützliche Fälle gibt, ist es für die meisten Codes auf "Anwendungsebene" wahrscheinlich ein sehr geringes Auftreten von Frustration mit C #, mit Ausnahme derjenigen, die "nur von" C / C ++ kommen ;-) Ich vermisse es auch gelegentlich Rubys "Wurf" (Nicht-Ausnahme-Abwickeln) gelegentlich, da er auch in diese Domäne passt.
user2864740

1
@ Suncat2000 Nur mehr Code wie Variablen und bedingte Verzweigungen einzuführen, um die Verwendung von a zu vermeiden goto, macht Ihren Code nicht lesbarer - ich würde argumentieren, was genau das Gegenteil passiert: Tatsächlich wird Ihr Kontrollfluss mehr Knoten enthalten - was ziemlich genau ist die Definition von Komplexität. Nur zu vermeiden, um die Selbstdisziplin zu unterstützen, klingt in Bezug auf die Lesbarkeit kontraproduktiv.
Atlaste

@atlaste: Entschuldigung, lassen Sie mich meinen Kommentar klarer formulieren. Was ich hätte sagen sollen war: Schöne Erklärung. Aber Menschen, die goto meiden, haben nichts mit Leistung zu tun. Es geht darum, Missbrauch zu vermeiden, der zu mangelnder Lesbarkeit führt. Wir können davon ausgehen, dass die Leute, die es vermeiden, gotoeinfach nicht die Disziplin haben, es nicht zu missbrauchen.
Suncat2000

@atlaste, welche Sprache ist "ackomplish"?
Pacerier

11

Berücksichtigen Sie eine Funktion / Methode und verwenden Sie die frühe Rückgabe oder ordnen Sie Ihre Schleifen in eine while-Klausel um. goto / Ausnahmen / was auch immer sind hier sicherlich nicht angebracht.

def do_until_equal():
  foreach a:
    foreach b:
      if a==b: return

10

Sie haben nach einer Kombination aus schnell, nett, ohne Verwendung eines Booleschen Werts, ohne Verwendung von goto und C # gefragt. Sie haben alle Möglichkeiten ausgeschlossen, das zu tun, was Sie wollen.

Der schnellste und am wenigsten hässliche Weg ist, ein goto zu verwenden.


Stimme voll und ganz zu. Es ist dumm, eine neue Methode einzuführen, um ein einziges Goto loszuwerden. Wenn der Compiler diesen Methodenaufruf aus irgendeinem Grund nicht einbinden kann, entsteht ein völlig unnötiger Aufwand für einen zusätzlichen Methodenaufruf. Ausnahmen zu werfen und zu fangen, nur um die Schleife zu durchbrechen, ist sowohl ausführlicher als auch lächerlich teurer.
Zar Shardan

@Windowsprogramm: OP hat nicht nach "no use of goto" gefragt. Er wollte nicht "zu einer anderen Methode gehen". Die Frage war weit davon entfernt, alle möglichen Wege auszuschließen , aber Sie haben Recht zu implizieren, dass goto hier am besten ist.
Suncat2000

5

Manchmal ist es schön, den Code in seine eigene Funktion zu abstrahieren und dann eine frühe Rückkehr zu verwenden - frühe Rückkehr ist jedoch böse :)

public void GetIndexOf(Transform transform, out int outX, out int outY)
{
    outX = -1;
    outY = -1;

    for (int x = 0; x < Columns.Length; x++)
    {
        var column = Columns[x];

        for (int y = 0; y < column.Transforms.Length; y++)
        {
            if(column.Transforms[y] == transform)
            {
                outX = x;
                outY = y;

                return;
            }
        }
    }
}

1
aber dann erlaubt dies die traditionelle Lösung, die von OP
Surya Pratap am

2

Ich habe viele Beispiele gesehen, die "break" verwenden, aber keine, die "continue" verwenden.

Es würde immer noch eine Art Flag in der inneren Schleife erfordern:

while( some_condition )
{
    // outer loop stuff
    ...

    bool get_out = false;
    for(...)
    {
        // inner loop stuff
        ...

        get_out = true;
        break;
    }

    if( get_out )
    {
        some_condition=false;
        continue;
    }

    // more out loop stuff
    ...

}

1

Seit ich vor breakein paar Jahrzehnten zum ersten Mal in C gesehen habe, hat mich dieses Problem geärgert. Ich hatte gehofft, dass eine Sprachverbesserung eine Erweiterung zum Brechen haben würde, die so funktionieren würde:

break; // our trusty friend, breaks out of current looping construct.
break 2; // breaks out of the current and it's parent looping construct.
break 3; // breaks out of 3 looping constructs.
break all; // totally decimates any looping constructs in force.

3
Dann fügt ein Wartungsprogrammierer eine andere Verschachtelungsebene ein, korrigiert einige der break-Anweisungen und bricht einige der anderen break-Anweisungen. Die Lösung dafür besteht darin, stattdessen zu einem Etikett zu wechseln. Das wurde wirklich vorgeschlagen, aber Pragmatiker verwenden stattdessen ein Etikett.
Windows-Programmierer

Warten Sie, wer programmiert die Wartung noch? :)
Jesse C. Slicer

2
JavaScript hat sogar Blocks / Break-Anweisungen beschriftet. devguru.com/Technologies/ecmascript/quickref/break.html
David Grant

@ Chris Bartow: cool! machte mein Weihnachten :) @David Grant: so scheint es JS break == C's goto?
Jesse C. Slicer

D hat break / continue markiert
BCS

0

Ich erinnere mich aus meiner Studienzeit, dass gesagt wurde, es sei mathematisch beweisbar, dass man alles im Code ohne goto machen kann (dh es gibt keine Situation, in der goto die einzige Antwort ist). Also benutze ich niemals goto's (nur meine persönliche Präferenz, was nicht bedeutet, dass ich richtig oder falsch bin)

Um aus verschachtelten Schleifen auszubrechen, mache ich ungefähr so:

var isDone = false;
for (var x in collectionX) {
    for (var y in collectionY) {
        for (var z in collectionZ) {
            if (conditionMet) {
                // some code
                isDone = true;
            }
            if (isDone)
                break;
        }
        if (isDone) 
            break;
    }
    if (isDone)
        break;
}

... ich hoffe das hilft für diejenigen, die mich mögen, sind Anti-Goto "Fanboys" :)


Es tut mir leid, es Ihnen zu sagen, aber Ihr Dozent ist derjenige, der für Ihren Zustand verantwortlich ist. Wenn er sich die Mühe gemacht hat, Sie zum Erlernen des Zusammenbaus zu zwingen, dann wissen Sie, dass 'goto' nur ein Sprung ist (natürlich ignoriere ich die Tatsache, dass dies eine Frage ist).
cmroanirgo

0

So habe ich es gemacht. Immer noch eine Problemumgehung.

foreach (var substring in substrings) {
  //To be used to break from 1st loop.
  int breaker=1;
  foreach (char c in substring) {
    if (char.IsLetter(c)) {
      Console.WriteLine(line.IndexOf(c));
      \\setting condition to break from 1st loop.
      breaker=9;
      break;
    }
  }
  if (breaker==9) {
    break;
  }
}


0

Der einfachste Weg, eine Doppelschleife zu beenden, wäre das direkte Beenden der ersten Schleife

string TestStr = "The frog jumped over the hill";
char[] KillChar = {'w', 'l'};

for(int i = 0; i < TestStr.Length; i++)
{
    for(int E = 0; E < KillChar.Length; E++)
    {
        if(KillChar[E] == TestStr[i])
        {
            i = TestStr.Length; //Ends First Loop
            break; //Ends Second Loop
        }
    }
}

Dies funktioniert nicht, da Sie mit break die innere Schleife beenden. Die äußere Schleife wird weiter iteriert.
Alex Leo

0

Schleifen können unter Verwendung benutzerdefinierter Bedingungen in der Schleife unterbrochen werden, um sauberen Code zu erhalten.

    static void Main(string[] args)
    {
        bool isBreak = false;
        for (int i = 0; ConditionLoop(isBreak, i, 500); i++)
        {
            Console.WriteLine($"External loop iteration {i}");
            for (int j = 0; ConditionLoop(isBreak, j, 500); j++)
            {
                Console.WriteLine($"Inner loop iteration {j}");

                // This code is only to produce the break.
                if (j > 3)
                {
                    isBreak = true;
                }                  
            }

            Console.WriteLine("The code after the inner loop will be executed when breaks");
        }

        Console.ReadKey();
    }

    private static bool ConditionLoop(bool isBreak, int i, int maxIterations) => i < maxIterations && !isBreak;   

Mit diesem Code erhalten wir die folgende Ausgabe:

  • Externe Schleifeniteration 0
  • Iteration der inneren Schleife 0
  • Iteration der inneren Schleife 1
  • Iteration der inneren Schleife 2
  • Iteration der inneren Schleife 3
  • Iteration der inneren Schleife 4
  • Der Code nach der inneren Schleife wird ausgeführt, wenn Unterbrechungen auftreten

0

Eine andere Option ist eine selbst aufgerufene anonyme Funktion . Kein goto, keine Bezeichnung, keine neue Variable, kein neuer Funktionsname und eine Zeile kürzer als das Beispiel der anonymen Methode oben.

// self-invoked-anonymous-function
new Action(() =>
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits self invoked lambda expression
        }
    }
})();
Console.WriteLine("Hi");

-3

Wirf eine benutzerdefinierte Ausnahme aus, die eine Oterter-Schleife auslöst.

Es funktioniert für for, foreachoder whileoder irgendeine Art von Schleife und jede Sprache , dass Anwendungen try catch exceptionblockieren

try 
{
   foreach (object o in list)
   {
      foreach (object another in otherList)
      {
         // ... some stuff here
         if (condition)
         {
            throw new CustomExcpetion();
         }
      }
   }
}
catch (CustomException)
{
   // log 
}

-4
         bool breakInnerLoop=false
        for(int i=0;i<=10;i++)
        {
          for(int J=0;i<=10;i++)
          {
              if(i<=j)
                {
                    breakInnerLoop=true;
                    break;
                }
          }
            if(breakInnerLoop)
            {
            continue
            }
        }

Was ist der wesentliche Unterschied zu Dviljoens Antwort?
Gert Arnold

Das wird nicht funktionieren, da Sie die Bedingung "breakInnerLoop" in der äußeren for-Schleife nicht überprüfen. Sie iterieren also einfach zur nächsten Schleife, haben auch j und J geschrieben und ein Semikolon verpasst, das nicht kompiliert werden kann.
Sebastian

-4

Wie ich sehe, haben Sie die Antwort akzeptiert, in der die Person auf Ihre Aussage verweist, wo in der modernen Programmierung und in der Expertenmeinung goto ein Killer ist, haben wir es einen Killer in der Programmierung genannt, der einige bestimmte Gründe hat, über die ich hier nicht sprechen werde An diesem Punkt, aber die Lösung Ihrer Frage ist sehr einfach, können Sie in einem solchen Szenario ein Boolesches Flag verwenden, wie ich es in meinem Beispiel demonstrieren werde:

            for (; j < 10; j++)
            {
                //solution
                bool breakme = false;
                for (int k = 1; k < 10; k++)
                {
                   //place the condition where you want to stop it
                    if ()
                    {
                        breakme = true;
                        break;
                    }
                }

                if(breakme)
                    break;
               }

einfach und schlicht. :) :)


Lesen Sie die Frage, bevor Sie eine Pause vorschlagen. Daher die Abstimmungen.
cmroanirgo

1
Lies es jetzt, aber als ich diese Antwort gepostet habe, wurde die bearbeitete Version, in der kein Boolescher Wert verwendet wurde, nicht hinzugefügt. Deshalb habe ich diese Antwort gepostet. Aber danke für die Ablehnung!
Steve

1
Es ist keine persönliche Sache. Leider ist die Abstimmung jetzt gesperrt;) Es ist ein notwendiger Teil von SO, um die besten Antworten nach oben zu bekommen (was nicht immer passiert)
cmroanirgo

-6

Hast du dir das breakSchlüsselwort überhaupt angesehen? Oo

Dies ist nur Pseudocode, aber Sie sollten sehen können, was ich meine:

<?php
for(...) {
    while(...) {
        foreach(...) {
            break 3;
        }
    }
}

Wenn Sie darüber nachdenken break, eine Funktion wie zu seinbreak() , ist der Parameter die Anzahl der Schleifen, aus denen Sie ausbrechen müssen. Da wir uns hier in der dritten Schleife des Codes befinden, können wir aus allen drei ausbrechen.

Handbuch: http://php.net/break


13
Keine PHP-Frage.
Zerga

-10

Ich denke, wenn Sie nicht das "Boolesche Ding" machen wollen, ist die einzige Lösung tatsächlich zu werfen. Was du natürlich nicht tun solltest ..!

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.