breakを使用して、ネストされた複数の「for」ループを終了できますか?


304

break関数を使用していくつかのネストされたforループを終了することは可能ですか?

もしそうなら、あなたはこれをどうやってやりますか?break出口のループの数も制御できますか?


1
複数のネストされたループを終了するためにbreakまたはgotoを使用する代わりに、その特定のロジックを関数で囲み、複数のネストされたループを終了するためにreturnを使用できます。これにより、コードの美観が維持され、不適切なプログラミング手法であるgotoを使用できなくなります。
Rishab Shinghal

回答:


239

私の知る限り、C ++はJavaや他の言語のように命名ループをサポートしていません。gotoを使用するか、使用するフラグ値を作成できます。各ループの最後にフラグの値を確認します。trueに設定されている場合、その反復から抜け出すことができます。


317
gotoそれが最善の選択肢である場合は、使用することを恐れないでください。
jkeys 2009

18
私は新しいC ++プログラマー(そして正式なプログラミングトレーニングを受けていないプログラマー)なので、gotoに関する人々の怒りについて読んだ後です。私のプログラムが突然爆発して私を殺してしまうのではないかと恐れて、私はそれを使うのをためらっています。それ以外は、ti-83(もちろん退屈な数学のクラス)でプログラムを作成していたとき、基本エディターが提供する機能にはgotoの使用が必要でした。
Faken、2009

26
@Faken:2つのタイプのプログラマーが使用しますgoto:悪いプログラマーと実用的なプログラマー。前者は自明です。後者は、それらをうまく使用することを選択した場合に当てはまるものであり、(2つの)悪のほうが小さい場合は、いわゆる「悪」の概念を使用します。時々使用する必要があるかもしれないいくつかのC ++の概念(マクロ、goto、プリプロセッサ、配列)をよりよく理解するためにこれを読んでください:parashift.com/c++
jkeys

41
@Faken:gotoの使用には何の問題もありません。面倒なのは後藤の誤用です。
全員、

28
@フック:そうgotoですが、ほとんど使用しないことが最善のオプションです。ループを独自の関数に入れて(inline速度が気になる場合は)、これからreturnどうですか?
sbi 2009

265

いいえ、それを台無しにしないでくださいbreak。これは、を使用するための最後の残りの拠点ですgoto


19
MISRA C ++コーディング標準では、gotoを使用して、このような状況を正確にカバーできます。
Richard Corden

11
本物のプログラマはgotoを使うことを恐れません。25年間それを行った-後悔はない-開発時間のトンを節約しました。
Doug Null

1
同意する。ゴトスは、横に8Kピクセルがなく、一連の30のWindows OS呼び出しを呼び出す必要がある場合は必須です。
ミカエル・ロイ

または、コードを関数に入れることによる単純な「戻り」。
タラ

68

ラムダを使用して明示的な回答を追加するだけです:

  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
        }
      }
    }();
  }

もちろん、このパターンには一定の制限があり、明らかにC ++ 11のみですが、それは非常に便利だと思います。


7
これは、戻りがラムダ自体ではなく、ラムダが含まれている関数を引き起こすと読者を混乱させるかもしれません。
Xunie

3
@Xunie:ループが非常に複雑で、ラムダにいることを忘れてしまった場合は、別の関数に配置するときですが、単純なケースでは、これは非常にうまく機能します。
MikeMB 2016

17
この解決策はすばらしいと思います
tuket

ラムダは、名前を付けてdtor(aka scope gaurd)で呼び出すRIIAテンプレートでキャプチャできます。これによってパフォーマンスが向上することはありませんが、読みやすさが向上し、関数呼び出しの括弧を見落とすリスクが軽減されます。
Red.Wave

56

ネストされたループから抜け出す別のアプローチは、両方のループを別々の関数に分解し、return終了するときにその関数から取り除くことです。

もちろん、これによりreturn、関数の最後以外の場所から明示的に関数を明示的に実行する必要があるかどうかという別の議論が生じます。


7
これはCの問題です。RIAAでは、早期復帰に関連するすべての問題が正しく処理されるため、早期復帰は問題になりません。
マーティンヨーク

4
RIAAを適切に適用することで、C ++のリソースクリーンアップの問題を解決できることを理解していますが、他の環境や言語でも、早期復帰に対する哲学的な議論が続いているのを見てきました。コーディング標準が早期復帰を禁止した私が取り組んだ1つのシステムにcontinue_processingは、関数のさらに下のコードブロックの実行を制御するブール変数(などの名前)が散らばった関数がありました。
グレッグヒューギル

21
RIAAとは それはRAIIのようなものですか?= D
jkeys 2009

1
彼が持っているforループの数とネストの深さによって異なります...青い錠剤と赤い錠剤のどちらが欲しいですか?
Matt

34

breakは、それを含む最も内側のループのみを終了します。

gotoを使用して、任意の数のループから抜け出すことができます。

もちろんgotoはしばしば有害と見なされます。

ブレーク機能を使用するのは適切ですか[...]?

breakとgotoを使用すると、プログラムの正確性を判断するのが難しくなる場合があります。これについての議論はここを見てください: ダイクストラは正気ではありませんでした


16
「goto is harmful」ミームは、より一般化された「制御フローの中断は有害」というステートメントに強く結びついているという点で、良い答えです。「goto is harmful」と言っても意味がなく、振り向いてbreakorの使用を勧めreturnます。
Pavel Minaev 2009

6
@Pavel:breakそしてreturngotoどこに行くかを見つけるためにラベルを探す必要がないという利点よりも優れています。はい、その下にはなんらかの種類がgotoありますが、非常に制限されています。それらは、制限のないものよりも、プログラマーのパターンマッチングの脳によって解読するのがはるかに簡単gotoです。したがって、IMOの方が望ましいです。
sbi 2009

@sbi:真ですが、ブレークはまだ構造化プログラミングの一部ではありません。それはgoto。よりも許容範囲が広いです。
jkeys 2009

2
@KarlVoigtlandダイクストラリンクは古くなっています。これは機能しているようです:blog.plover.com/2009/07
アーロンブラジャー2013

3
この状況でgotoを使用しても問題はありません。適切に配置されたgotoは、他の方法で提案されている多くの歪んだソリューションよりも飛躍的で読みやすくなっています。
ジェームズ

22

この回答はすでに提示されていますが、次のようにすることをお勧めします。

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
これは良いのですが、それほど読みにくいです。その場合はgotoを使用します
–етърПетровAug

2
これはgotoMainLoopサイクルごとにチェックされるため、コードが「かなり」遅くなります
Thomas

1
この場合、実数gotoを使用すると、コアが読みやすくなり、パフォーマンスが向上します。
ПетърПетров

20

これはどう?

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
興味深いアプローチですが、私はered @ inf.ig.shがそれを処理する方法を間違いなく気に入っています。for(unsigned int y = 0; y <y_max &&!gotoMainLoop; y ++)。
アンディ

15

gotoネストされたループから抜け出すためにおよびラベルを使用するコード例:

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

11

ネストされた複数のループから抜け出すための良い方法の1つは、コードを関数にリファクタリングすることです。

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
...この関数をスタックフレーミングするために10〜20個の変数を渡す必要がある場合、これはオプションではありません。
ПетърПетров

1
@ПетърПетров次にラムダを使用します。ラムダは、必要な場所に正確に定義できるため、より優れています。
DarioP 2013年

ラムダの場合は+1ですが、1つのスタックフレームでもボトルネックになるゲームエンジンコアのオーバーホール。申し訳ありませんが教えてくれますが、ラムダは、少なくともMSVC 2010年にとても軽量でないために
ПетърПетров

@ПетърПетров次に、関数のペアをクラスに変更し、スタック変数をプライベートメンバーに変更します。
Arthur Tacca 2017年

これはすでに複雑なコードを複雑にしすぎます:)場合によっては、gotoが唯一の解決策です。または、「goto state X」ドキュメントを使用して複雑なオートマトンを作成する場合、gotoは実際にコードをドキュメントに記述されたとおりに読み取らせます。また、C#とgoの両方に目的があるgotoがあります。gotoなしで言語を完全に完成させることはできません。また、gotoは多くの場合、中間トランスレーターやアセンブリのようなコードを記述するための最もよく使用されるツールです。
ПетърПетров

5

gotoはネストされたループを壊すのに非常に役立ちます

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

break文は最も内側の実行終了doforswitch、またはwhileそれが現れる文を。制御は、終了したステートメントに続くステートメントに渡されます。

msdnから。


3

私はgotoこの状況では有効だと思います:

break/ をシミュレートするにはcontinue、次のようにします。

ブレーク

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

継続する

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

最初のループの反復式が実行されないため、 "Continue"はそこで機能しません
Fabio A.

最初のループのイテレータはであると想定していますi。したがってi++
ジャンプの

0

PHPなどの他の言語では、break(つまり、break 2;)のパラメーターを受け入れて、ブレークアウトしたいネストされたループレベルの量を指定しますが、C ++では受け入れません。ループの前にfalseに設定したブール値を使用してそれを解決する必要があります。ブレークする場合はループでtrueに設定し、ネストされたループの後に条件付きブレークを実行して、ブール値がtrueに設定されているかどうかを確認します。はいの場合は中断します。


0

私はこれが古い記事であることを知っています。しかし、私は少し論理的で簡単な答えを提案します。

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
j = conditionj代わりに複雑な述語がある場合は機能しないため、これは非常にスケーラブルなソリューションではありませんj < conditionj
セルゲイ

0

bool以下の1つの変数だけでループをいくつでも分割できます。

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;
    }
}

このコードでは、break;すべてのループを処理しています。


0

価値があるかどうかはわかりませんが、Javaの名前付きループをいくつかの簡単なマクロでエミュレートできます。

#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

使用例:

#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);
        }
    }
}

もう一つの例:

#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

try ... catchを使用できます。

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

一度に複数のループから抜け出す必要がある場合は、とにかく例外です。


1
この回答がこれほど多くの反対票を投じる理由を知りたいのですが。
hkBattousai 2015年

6
@hkBattousai実行フローを制御するために例外を使用しているため、このソリューションは反対票を投じています。名前が示すように、例外は例外的な場合にのみ使用する必要があります。
Helio Santos

4
@HelioSantosこれは、言語が適切な解決策を提供していない例外的な状況ではありませんか?
hkBattousai 2015年

8
例外は遅いです。
Gordon

2
スローのパフォーマンスへの影響は、99%の確率で回復不可能なエラーではない場合に大きな影響を与えます。
ミハエルロイ2017

-4

forループのセマンティクスは通常、指定された回数実行されることを示しているため、forループから抜け出すことは少し奇妙です。ただし、すべての場合に悪いわけではありません。コレクションで何かを検索していて、それを見つけた後で中断したい場合は、便利です。ただし、ネストされたループから抜け出すことは、C ++では不可能です。ラベル付きの区切りを使用することにより、他の言語でも使用できます。ラベルと後藤を使うこともできますが、夜になると胸が痛くなるかもしれません。しかし、最良のオプションのようです。


11
それはまったく奇妙ではありません。コレクションを繰り返し処理して何かを探す場合(そして、より高速な検索方法がない場合)、ループを終了する意味はありません。(例として)
Joe
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.