我可以使用break来退出多个嵌套for循环吗?

Fak*_*ken 282 c++ for-loop break nested-loops

是否可以使用该break函数退出几个嵌套for循环?如果是这样,你会怎么做呢?你还可以控制休息退出的循环次数吗?

Hen*_*man 255

不,不要破坏它break.这是使用的最后一个据点goto.

  • MISRA C++编码标准允许使用goto来涵盖这种确切的情况. (15认同)
  • 真正的程序员不怕使用goto.做了25年 - 没有遗憾 - 节省了大量的开发时间. (10认同)
  • 我同意。如果您没有 8K 像素,并且需要调用一系列 30 个 Windows 操作系统调用,那么 goto 是必须的。 (4认同)
  • 或者通过将代码放入函数中来进行简单的“返回”。 (2认同)
  • 我来到这里寻找一种摆脱“while”循环内的“switch”语句的方法。从逻辑上讲,这只是一层循环,但“switch”吃掉了“break”。这是我自 1985 年左右用 BASIC 编写以来编写的第一个 `goto`。(这是一个非常“热门”的循环,即使每次检查一个额外的布尔值也要花费 15-20%,所以添加一个 `done` flag 不是一个好的选择。我只是不得不写这个评论来净化我的灵魂。) (2认同)

小智 222

AFAIK,C++不支持命名循环,如Java和其他语言.您可以使用goto,或创建您使用的标志值.在每个循环结束时检查标志值.如果设置为true,那么您可以突破该迭代.

  • 如果这是最好的选择,不要害怕使用`goto`. (303认同)
  • @Faken:使用goto没什么不对.它是_misusing_一个很麻烦的goto. (39认同)
  • @Hooked:没错,除了很少使用`goto`是最好的选择.为什么不把循环放到他们自己的函数中(`inline`,如果你关心速度)和`return`呢? (27认同)
  • @Faken:两种类型的程序员使用`goto`:糟糕的程序员和实用的程序员.前者是自我解释的.后者,如果你选择使用它们,你会适应,当它是(两个)邪恶中的较小者时,使用所谓的"邪恶"概念.阅读本文是为了更好地理解您可能需要不时使用的一些C++概念(宏,goto,预处理器,数组):http://www.parashift.com/c++-faq-lite/big-picture. HTML#FAQ-6.15 (25认同)
  • 我是一名新的C++程序员(并且没有任何正式的编程培训),因此在阅读了人们对goto的咆哮之后.我在使用它时犹豫不决,担心我的程序可能会突然爆炸并杀死我.除此之外,当我以前在我的ti-83上编写程序时(当然在无聊的数学课上),基本编辑器提供的功能需要使用goto. (18认同)
  • @sbi,有时我们的函数有很大的堆栈框架,传递大约9-10 + vars肯定会使我们的代码不可读.休息一下并使用goto,在这种情况下,它会使您的程序更性感,更具可读性.有些算法拥有太多的控制路径,这使得goto成为更受欢迎的方式,尤其是游戏循环中的状态mashines - 性能关键代码,即使是简单的堆栈帧也会损失最多或你的性能! (4认同)
  • inline是一个编译器提示关键字,不保证它会内联函数.在任何情况下,你编写代码就好像它们不是内联的,这意味着传递变量....我正在使用@ПетърПетров. (3认同)
  • 我似乎记得读过很多文章,当存在goto时,许多编译器都放弃了优化循环... (2认同)
  • @ПетърПетров内联函数没有堆栈框架 (2认同)
  • 创建 - 和展开 - 堆栈帧比检查标志然后执行跳转更昂贵。`if...goto` 经常被编译为 `je` 或 `jne` 本地跳转对,而 `call` / `ret` 函数语义经常执行远跳转和缓存未命中。所以,不要害怕使用 `goto`,它是两两的小恶。而且,如果你想要速度,_永远_不要使用“内联”。 (2认同)

Pre*_*nik 56

只是使用lambdas添加一个明确的答案:

  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
        }
      }
    }();
  }
Run Code Online (Sandbox Code Playgroud)

当然这种模式有一定的局限性,显然只有C++ 11,但我认为它非常有用.

  • 我觉得这个解决方案很漂亮 (15认同)
  • 这可能会让读者误以为返回会导致lambda返回的函数,而不是lambda本身. (5认同)
  • @Xunie:如果你的循环如此复杂,以至于你忘记了你是一个lambda,是时候把它们放在一个不同的函数中了,但是对于简单的情况,这应该可以很好地工作. (3认同)
  • 我在 godbolt 上测试了这个,它似乎编译成与使用 goto 相同的代码!与 goto 相比,此解决方案的另一个优点是标签仅在标记语句时才起作用。例如,你不能写 `my_label: }` (2认同)

Gre*_*ill 55

打破嵌套循环的另一种方法是将两个循环分解为单独的函数,并return在要退出时从该函数中分解出来.

当然,这提出了另一个论点,即你是否应该return从最后的任何地方明确地使用函数.

  • 什么是RIAA?那是什么像RAII?= d (20认同)
  • 这是一个C问题.使用RIAA,早期返回不是问题,因为与早期返回相关的所有问题都得到了正确处理. (7认同)
  • 我理解RIAA的正确应用可以解决C++中的资源清理问题,但我已经看到反对早期返回的哲学论证在其他环境和语言中继续存在.我工作的一个系统,其中编码标准禁止早期返回的函数充满了布尔变量(名称为`continue_processing`),这些函数控制了函数中代码块的执行. (4认同)
  • 是的,s / RIAA / RAII / g;:) (2认同)
  • 取决于他有多少个 for 循环以及巢有多深......你想要蓝色药丸还是红色药丸? (2认同)

Kar*_*and 32

break将仅退出包含它的最内层循环.

你可以使用goto来打破任何数量的循环.

当然goto通常被认为是有害的.

是否适合使用break函数[...]?

使用break和goto会使得更难以推断程序的正确性.看到这里讨论: Dijkstra并不疯狂.

  • 一个很好的答案,它解释说"goto是有害的"模因与更普遍的"控制流中断是有害的"声明密切相关.说"goto是有害的"是没有意义的,然后转身并建议使用`break`或`return`. (15认同)
  • @Pavel:`break`和`return`比`goto`更有优势,你不需要寻找标签就能找到它们去的地方.是的,在它们下面是某种"goto",但是非常有限.程序员的模式匹配大脑比不受限制的"goto"更容易破译它们.所以IMO他们更可取. (5认同)
  • 在这种情况下使用goto绝对没有错.放置得很好的goto比其他提出的许多扭曲解决方案更好,更可读. (3认同)
  • @sbi:确实如此,但中断仍然不是结构化编程的一部分。它比“goto”更容易被容忍。 (2认同)
  • @KarlVoigtland Dijkstra链接已过时; 这看似有效:http://blog.plover.com/2009/07/ (2认同)

sci*_*gor 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;
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 这是好的,但仍然不那么可读,我更喜欢在这种情况下转到 (4认同)
  • 在这种情况下,使用真正的“goto”使核心更具可读性和更好的性能。 (3认同)
  • 这使得你的代码"非常"慢,因为每个周期都会检查`gotoMainLoop` (2认同)

jeb*_*det 19

这个怎么样?

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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 有趣的方法,但我绝对喜欢ered @ inf.ig.sh的处理方式。for(unsigned int y = 0; y &lt;y_max &amp;&amp;!gotoMainLoop; y ++)。 (2认同)

Hel*_*tos 15

一个代码示例使用goto和一个标签来打破嵌套循环:

for (;;)
  for (;;)
    goto theEnd;
theEnd:
Run Code Online (Sandbox Code Playgroud)


Deq*_*ing 10

打破几个嵌套循环的一个好方法是将代码重构为函数:

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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • ...如果我们必须为堆栈框架此函数传递10-20个变量,那么这不是一个选项. (4认同)

Hol*_*Cat 9

我不确定这是否值得,但是您可以使用一些简单的宏来模拟 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
Run Code Online (Sandbox Code Playgroud)

用法示例:

#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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

另一个例子:

#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);
}
Run Code Online (Sandbox Code Playgroud)

  • 这是我在这个线程中看到的最精彩的东西 (2认同)
  • @Dumbled0re 需要该变量来阻止您在此循环之外使用中断/继续。如果需要创建新变量,编译器会拒绝“转到”作用域,从而允许您仅在此 if-else 块内中断/继续。 (2认同)

小智 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
Run Code Online (Sandbox Code Playgroud)


The*_*zer 5

我知道这是一个旧线程,但我觉得这真的需要说并且没有其他地方可以说。对于这里的每个人,请使用goto。我刚用过。

就像几乎所有事情一样,goto 不是 100% 的“坏”或“好”。至少有两种用途,我要说的是,如果您为它们使用 goto - 并且不要将它用于其他任何用途 - 您不仅应该 100% 没问题,而且您的程序将比没有它更具可读性,因为它使您的意图更加清晰(有很多方法可以避免它,但我发现它们都更加笨拙):

  1. 打破嵌套循环,以及
  2. 错误处理(即跳转到函数末尾的清理例程以返回失败代码并释放内存。)。

不要只教条地接受“一般般是'邪恶'”这样的规则,而是要理解为什么要声称这种情绪,并遵循“为什么”,而不是情绪的字面意思。不知道这一点也给我带来了很多麻烦,以至于我会说教条地称事物为“邪恶”可能比事物本身更有害。最坏的情况是,你只会得到糟糕的代码——然后你知道你没有正确使用它,只要你听说过要小心,但如果你正在努力满足教条主义,我会说那更糟。

为什么“goto”被称为“evil”是因为你永远不应该用它来代替普通的 ifs、fors 和 whiles。为什么?试试看,一直尝试使用“goto”代替普通的控制逻辑语句,然后尝试用控制逻辑再次编写相同的代码,并告诉我哪个看起来更好更容易理解,哪个看起来更像一团糟. 你去吧。(奖励:现在尝试将新功能添加到 goto-only 代码中。)这就是为什么它是“邪恶的”,在“邪恶”周围有合适的范围限定。用它来短路C的" break"命令的缺点不是一个有问题的用法,只要你从代码中明确你的 goto 应该完成什么(例如使用像“nestedBreak”之类的标签)。打破嵌套循环是自然的。

(或者更简单地说:使用 goto 跳出循环。我会说这更可取。不要使用 goto创建循环。那是“邪恶的”。)

你怎么知道你是否在教条?如果遵循“xyz 是邪恶的”规则导致您的代码不太容易理解,因为您正在扭曲自己试图绕过它(例如通过在每个循环中添加额外的条件,或一些标志变量,或其他类似的技巧) ,那么你很可能是教条的。

学习良好的思维习惯是无可替代的,比良好的编码习惯更重要。前者在后者之前,而后者通常会在前者被采用后随之而来。然而,问题是,我经常发现,后者没有得到足够的解释。太多简单地说“这是不好的”和“这需要更多的心思”不用说什么想,想什么,以及为什么。这是一个很大的耻辱。

(FWIW,在 C++ 中,仍然需要打破嵌套循环,但不需要错误代码:在这种情况下,始终使用异常来处理错误代码,永远不要返回它们,除非它非常频繁以至于异常抛出和捕获将导致性能问题,例如在高需求服务器代码中的紧密循环中,也许[有些人可能会说“异常”应该“很少使用”,但这是深思熟虑的教条主义的另一部分:不,至少在我推翻那个教条之后的经验中,我发现它们使事情变得更加清晰——只是不要滥用它们做错误处理以外的事情,比如将它们用作控制流;实际上与“goto”相同。如果您将它们全部用于错误处理,这就是它们的用途。]。)