【问题标题】:Can I use break to exit multiple nested 'for' loops?我可以使用 break 退出多个嵌套的“for”循环吗?
【发布时间】:2009-08-10 23:15:11
【问题描述】:

是否可以使用break 函数退出多个嵌套的for 循环?

如果是这样,你会怎么做?你还能控制break退出多少个循环吗?

【问题讨论】:

  • 不使用 break 或 goto 退出多个嵌套循环,您可以将特定逻辑包含在一个函数中并使用 return 退出多个嵌套循环。这将保持代码的美观,并防止您使用 goto,这是一种不好的编程习惯。
  • 为什么 goto 是不好的编程习惯?它在内核中被广泛使用。

标签: c++ for-loop break nested-loops


【解决方案1】:

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

【讨论】:

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

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

【讨论】:

  • 不要害怕使用goto,如果这是最好的选择。
  • 我是一名新的 C++ 程序员(并且没有任何正式的编程培训),因此在阅读了人们对 goto 的咆哮之后。我对使用它犹豫不决,因为担心我的程序可能会突然爆炸并杀死我。除此之外,当我以前在我的 ti-83 上编写程序时(当然是在无聊的数学课上),基本编辑器提供的功能需要使用 goto。
  • @Faken:两种程序员使用goto:糟糕的程序员和务实的程序员。前者是不言自明的。如果您选择很好地使用它们,您将适合后者,当它是(两个)邪恶中较小的一个时,使用所谓的“邪恶”概念。阅读本文以更好地理解您可能需要不时使用的一些 C++ 概念(宏、goto、预处理器、数组):parashift.com/c++-faq-lite/big-picture.html#faq-6.15
  • @Faken:使用 goto 没有错。 误用 goto 很麻烦。
  • @Hooked:没错,除了很少使用goto 是最好的选择。为什么不把循环放到它们自己的函数中(inline,如果你关心速度的话)和 return 呢?
【解决方案3】:

只是为了使用 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
        }
      }
    }();
  }

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

【讨论】:

  • 这可能会让读者误以为 return 会导致 lambda 所在的函数返回,而不是 lambda 本身。
  • @Xunie:如果您的循环非常复杂,以至于您忘记了自己在 lambda 中,那么是时候将它们放在不同的函数中了,但是对于简单的情况,这应该可以很好地工作。
  • 我觉得这个解决方案很漂亮
  • 您可以在 RIIA 模板中捕获 lambda,该模板为其命名并在 dtor(又名 scope gaurd)中调用它。这不会增加任何性能提升,但可以提高可读性并降低丢失函数调用括号的风险。
  • 我在 Godbolt 上测试了它,它似乎编译成与使用 goto 相同的代码!与 goto 相比,此解决方案的另一个优点是标签仅在标记语句时才有效。比如你不能写my_label: }
【解决方案4】:

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

当然,这带来了另一个论点,即您是否应该在函数末尾以外的任何地方显式地 return

【讨论】:

  • 这是一个 C 问题。使用 RIAA 提前退货不是问题,因为与提前退货相关的所有问题都得到了正确处理。
  • 我知道正确应用 RIAA 可以解决 C++ 中的资源清理问题,但我看到反对提前返回的哲学论点在其他环境和语言中仍在继续。在我工作的一个系统中,编码标准禁止提前返回,其函数中散布着布尔变量(名称类似于 continue_processing),这些变量控制着函数中更下方代码块的执行。
  • 什么是美国唱片工业协会?像RAII这样的吗? =D
  • 取决于他有多少个for循环以及巢有多深......你想要蓝色药丸还是红色药丸?
【解决方案5】:

break 将只退出包含它的最内层循环。

您可以使用 goto 来跳出任意数量的循环。

当然 goto 通常是Considered Harmful

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

使用 break 和 goto 会使推理程序的正确性变得更加困难。有关此问题的讨论,请参见此处:Dijkstra was not insane

【讨论】:

  • 一个很好的答案,因为它解释了“goto 是有害的”meme 与更普遍的“控制流中断是有害的”声明密切相关。说“goto有害”是没有意义的,然后转身推荐使用breakreturn
  • @Pavel: breakreturngoto 具有优势,您无需寻找标签即可找到它们的去向。是的,在它们下面是某种goto,但非常受限制。它们比不受限制的goto 更容易被程序员的模式匹配大脑破译。所以 IMO 他们更可取。
  • @sbi:是的,但是 break 仍然不是结构化编程的一部分。它比goto 更容易被容忍。
  • @KarlVoigtland Dijkstra 链接已过时;这似乎有效:blog.plover.com/2009/07
  • 在这种情况下使用 goto 绝对没有错。与其他提出的许多扭曲的解决方案相比,放置良好的 goto 具有更好的飞跃性和可读性。
【解决方案6】:

这个怎么样?

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

【讨论】:

  • 有趣的方法,但我绝对喜欢 ered @inf.ig.sh 处理它的方式。 for (unsigned int y = 0; y
【解决方案7】:

虽然已经提出了这个答案,但我认为一个好的方法是执行以下操作:

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

}

【讨论】:

  • 这很好但仍然不那么可读,在这种情况下我更喜欢 goto
  • 这会使您的代码“相当”缓慢,因为每个周期都会检查 gotoMainLoop
  • 在这种情况下,使用真正的goto 会使核心更具可读性和性能。
【解决方案8】:

使用goto 和标签来跳出嵌套循环的代码示例:

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

【讨论】:

    【解决方案9】:

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

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

    【讨论】:

    • ...如果我们必须传递 10-20 个变量来构建此函数的堆栈,这不是一个选项。
    • @ПетърПетров 然后选择一个更好的 lambda,因为您可以在需要的地方准确定义它。
    • +1 用于 lambda,但对游戏引擎核心进行了大修,即使一个堆栈帧仍然是瓶颈。很抱歉,但至少在 MSVC 2010 中,lambda 并不是那么轻量级。
    • @ПетърПетров 然后把这对函数改成一个类,把栈变量改成私有成员。
    • 这只会使已经很复杂的代码过于复杂:) 有时,goto 是唯一的解决方案。或者,如果您使用“goto state X”文档编写复杂的自动机,那么 goto 实际上就是让代码按照文档中的内容进行读取。此外,C# 和 go 都有一个目的:没有 goto,没有语言是图灵完备的,而 goto 通常是编写中间翻译器或类汇编代码的最佳使用工具。
    【解决方案10】:

    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
    

    【讨论】:

      【解决方案11】:

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

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

      1. 跳出嵌套循环,并且
      2. 错误处理(即在函数结束时跳转到清理例程以返回失败代码并释放内存。)。

      不要只是教条地接受诸如“马马虎虎就是'邪恶'”这样的规则,了解为什么这种情绪被主张,并遵循“为什么”,不是情书。不知道这也给我带来了很多麻烦,以至于我会说教条地称事物为“邪恶”可能比事物本身更有害。在最坏的情况下,你只是得到了糟糕的代码——然后你知道你没有正确使用它,只要你听到要小心,但如果你为了满足教条主义而苦恼自己,我会说那更糟。

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

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

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

      学习良好的思考习惯是无可替代的,比良好的编码习惯更重要。前者先于后者,一旦采用前者,后者往往会紧随其后。然而,问题是,我经常发现后者不够解释。太多人只是说“这很糟糕”和“这需要更多思考”,而没有说明要考虑什么、要考虑什么以及为什么 .这是一个很大的耻辱。

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

      【讨论】:

        【解决方案12】:

        break 语句终止执行它出现的最近的封闭doforswitchwhile 语句。控制权传递给终止语句之后的语句。

        来自msdn

        【讨论】:

          【解决方案13】:

          我认为goto 在这种情况下是有效的:

          要模拟break/continue,您需要:

          休息

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

          继续

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

          【讨论】:

          • "Continue" 在那里不起作用,因为第一个循环的迭代表达式不会被执行
          • 我假设第一个循环的迭代器是i。因此在 goto 之前i++
          【解决方案14】:

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

          【讨论】:

            【解决方案15】:

            只需一个bool 变量即可打破任意数量的循环,请参见下文:

            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; 所有的循环。

            【讨论】:

              【解决方案16】:

              PHP 等其他语言接受 break 参数(即 break 2;)来指定要中断的嵌套循环级别的数量,但 C++ 不接受。您必须使用在循环之前设置为 false 的布尔值来解决它,如果要中断,则在循环中设置为 true,在嵌套循环之后加上条件中断,检查布尔值是否设置为 true如果是,则中断。

              【讨论】:

                【解决方案17】:

                我知道这是旧帖子。但我会建议一个更合乎逻辑且更简单的答案。

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

                【讨论】:

                • 这不是非常可扩展的解决方案,因为如果您有一个复杂的谓词而不是 j &lt; conditionjj = conditionj 将不起作用。
                【解决方案18】:
                 bool found = false;
                    
                    for(int i=0; i < m; ++i){
                        for(int j=0; j < n; ++j)
                            if(grid[i][j] == '*'){
                                q.push(make_pair(i,j));
                                found = true;
                                break;
                            }
                        if(found)
                            break;
                    }
                

                【讨论】:

                  【解决方案19】:
                    while (i<n) {
                      bool shouldBreakOuter = false;
                      for (int j=i + 1; j<n; ++j) {
                        if (someCondition) {
                            shouldBreakOuter = true;
                        }
                      }
                  
                      if (shouldBreakOuter == true)
                        break;
                  
                    }
                  

                  【讨论】:

                    【解决方案20】:

                    你可以使用 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
                    

                    如果你必须一次跳出几个循环,无论如何它通常是一个例外。

                    【讨论】:

                    • 我想知道为什么这个答案会得到如此多的反对。
                    • @hkBattousai 该解决方案投了反对票,因为它使用异常来控制执行流程。顾名思义,异常只能用于例外情况。
                    • @HelioSantos 这不是语言无法提供适当解决方案的特殊情况吗?
                    • 对于 99% 的情况下并非不可恢复的错误,抛出的性能影响是巨大的。
                    • @hkbattousai 该语言确实提供了解决方案,例如使用测试条件,或使用goto
                    【解决方案21】:

                    跳出 for 循环对我来说有点奇怪,因为 for 循环的语义通常表明它将执行指定次数。但是,在所有情况下都不错。如果您正在搜索集合中的某些内容并想在找到它后打破它,那么它很有用。然而,在 C++ 中不可能跳出嵌套循环。它在其他语言中是通过使用带标签的中断来实现的。您可以使用标签和 goto,但这可能会让您在晚上胃灼热..?不过似乎是最好的选择。

                    【讨论】:

                    • 一点也不奇怪。如果您正在遍历集合以查找某些内容(并且没有更快的搜索方式),那么完成循环是没有意义的。 (作为一个例子)
                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2019-12-27
                    • 1970-01-01
                    • 2011-05-05
                    • 2019-11-11
                    • 1970-01-01
                    • 2016-04-17
                    • 2015-10-15
                    相关资源
                    最近更新 更多