【问题标题】:What is a neat way of breaking out of many for loops at once?一次打破许多 for 循环的巧妙方法是什么?
【发布时间】:2009-10-19 04:20:14
【问题描述】:

假设我需要在最内层循环中发生某些事件时一次跳出三个或四个嵌套的 for 循环。 有什么巧妙的方法?

我所做的是像这样使用 标志

int i, j, k;
int flag1 = 0;
int flag2 = 0;

for (i = 0; i < 100; i++) {
    for (j = 0; j < 100; j++) {
        for (k = 0; k < 100; k++) {
            if (k == 50) {
                flag1 = 1;
                flag2 = 1;
                break;
            }
        }
        if (flag1 == 1)break;
    }
    if (flag2 == 1)break;
}

我认为这不是特别整洁。

你将如何完成同样的事情?(不使用跳转)

【问题讨论】:

标签: c for-loop break


【解决方案1】:

使用转到。它干净简单。

【讨论】:

  • 很多人认为一堆标志比 goto 更好,只是因为他们曾经听到过一些言论
  • @Asaph - 听说过狂热吗? Dijkstra 说“有害”,而不是“撒旦之子”。你完全没有理解他的意思。
  • @hanifr - 简单、整洁、清晰和可读性有什么不健康的地方?通常goto没有这些东西,但每隔一段时间它就会成为解决您问题的理想解决方案。几乎所有其他语言都对内置关键字(如 continuenext 987654326@ 和 for 语句可以。 goto 这里无论如何都不错
  • +1 这是唯一让 java 比 c++ 或 c# 更重要的地方。 Java 有一个break &lt;label&gt; 语句,它打破以特定标签命名的循环。 C 和 C# 都没有类似的东西,所以唯一干净的方法是使用 goto。还有@asaph,时不时自己想一想,不会有伤害的!
  • 是的,goto 是。我经常惊讶于有些人愿意避免编写“被诅咒”的关键字,即使它显然是最好的——例如,最明显、最易读、最可维护的——解决手头问题的方法。跨度>
【解决方案2】:

将所有循环放在一个函数中,然后直接返回而不是中断。

【讨论】:

  • 请注意,虽然这巧妙地避免了明确的goto,但它本质上是在做同样的事情,所以如果你认为这是一个很好的解决方案,你真的不能反对goto 解决方案.请注意,如果出现复杂的循环行为可能会使函数调用变得不那么清晰或简单,goto 可能更可取。
  • 不同的是不能像滥用goto那样滥用return。顺便说一句,Java 有一个带标签的 break 语句,可以让您跳出嵌套循环,但没有 goto。
  • 在 C++ 中 return 更好,因为它会尊重堆栈展开 - 因此会破坏在任何循环中声明的任何局部范围的变量。
  • 我肯定更喜欢返回方法而不是 goto,因为根据我的经验,它倾向于创建更清晰的整体代码。不是因为 goto-s 不能干净,而是如果你使用 return,你也将函数隔离到一个责任,执行循环。如果您使用 goto,那么您可能会将更多单独的关注点放在同一个函数中。当然,这不是一个普遍的真理,在这种情况下可能不是这样,但这将是我的一般立场。
  • 它们可能本质上是一样的,但在代码方面,我认为 return 要干净得多。
【解决方案3】:

如果您使用 Java,您可以将标签与每个 for 块相关联,然后在 continue 语句后引用该标签。例如:

outerfor:
for (int i=0; i<5; i++) {
    innerfor:
    for (int j=0; j<5; j++) {
        if (i == 1 && j == 2) {
             continue outerfor;
        }
    }
}

【讨论】:

  • 许多语言(如 Perl)允许这种标签行为,这是一件好事,应该在适用的地方使用。因此,如果这适用于 C,我会 +1。(我知道 OP 对其他语言感兴趣,但他可能对 C 更感兴趣。)
  • @Chris Lutz:很公平。 FWIW:当 OP 最初发布时,没有提到他/她使用的是什么语言。因此所有的 cmets 都在问什么语言。 c标签是我发布后添加的。
【解决方案4】:

你将如何完成同样的事情?(不使用跳转)

为什么?没有什么是万能的,每一种工具都有它的用途(gets() 除外)。在此处使用goto 会使您的代码看起来更干净,并且是我们仅有的选择之一(假设为 C)。看:

int i, j, k;

for (i = 0; i < 100; i++) {
    for (j = 0; j < 100; j++) {
        for (k = 0; k < 100; k++) {
            if (k == 50) {
                goto END;
            }
        }
    }
}
END:

比所有这些标志变量都清晰得多,它甚至更清楚地显示了您的代码在做什么

【讨论】:

  • 你将如何重构它?您真的不希望代码中有 3 个嵌套循环吗?
  • 我不知道如何重构它,因为 OP 并没有非常具体地说明他的三个嵌套循环在他的代码中 在做什么。他只是想从其中一个内部突破所有这些,所以我提供了答案。一些操作需要 O(n^3) 时间来完成,但在实践中最终足够快,因为它们通常会在小型数据集上操作和/或执行的操作简单且易于优化。不要试图重构清晰、干净且工作正常的东西。
【解决方案5】:

稍微好一点。

int i, j, k;
int flag1 = 0;
int flag2 = 0;

for (i = 0; i < 100 && !flag2; i++) {
    for (j = 0; j < 100 && !flag1; j++) {
        for (k = 0; k < 100; k++) {
            if (k == 50) {
                flag1 = 1;
                flag2 = 1;
                break;
            }
        }
    }
}

但是如果你真的需要这些循环,那么为了可读性,在每个循环中明确声明它必须满足哪些条件才能继续是有意义的。

【讨论】:

  • 在我看来,这并没有更好。放入for 语句的大量内容开始看起来像线路噪音。
  • 拥有多个标志有什么意义?只要有一个。叫它done。更清晰。
  • 刚刚复制了样本。我假设标志不是那么简单,只是实际代码的代表。
  • 再说一次,如果它像他所说的那样简单(立即爆发)。你是对的
【解决方案6】:

goto。这是goto 是适当工具的极少数地方之一,通常也是为什么goto 不是完全邪恶的论据。

不过,有时我会这样做:

void foo() {
    bar_t *b = make_bar();
    foo_helper(bar);
    free_bar(b);
}

void foo_helper(bar_t *b) {
    int i,j;
    for (i=0; i < imax; i++) {
        for (j=0; j < jmax; j++) {
            if (uhoh(i, j) {
                return;
            }
        }
    }
}

我的想法是,我可以保证没有 bar,而且我可以通过 return 获得干净的两级突破。

【讨论】:

    【解决方案7】:

    如果您绝对不想使用 goto,请将所有循环条件设置为 false:

    int i, j, k;
    
    for (i = 0; i < 100; i++) {
        for (j = 0; j < 100; j++) {
            for (k = 0; k < 100; k++) {
                if (k == 50) {
                    i = j = k = INT_MAX;
                    break;
                }
            }
        }
    }
    

    注意:智能优化编译器会将 if 中的内容跳转到最外层循环的末尾

    【讨论】:

      【解决方案8】:

      有时你可以使用这样的技巧:

      for (i = 0; i < 100 && !flag2; i++) {
      for (j = 0; j < 100 && !flag1; j++) {
          for (k = 0; k < 100; k++) {
              if (k == 50) {
                  k = 100;
                  i = 100;
                  j = 100;
              }
          }
      }
      

      }

      或在循环中声明添加标志:

      bool end = false;
      for(int i =0; i < 1000 && !end; i++) {
         //do thing
         end = true;
      }
      

      我认为它只花费一条线但很干净。

      贾斯汀

      【讨论】:

        【解决方案9】:

        如果任何循环的过早完成总是意味着您也必须打破封闭循环,那么您不需要任何额外的标志。整个事情可能如下所示

        int i, j, k;
        for (i = 0; i < 100; i++) {
            for (j = 0; j < 100; j++) {
                for (k = 0; k < 100; k++) {
                    if (k == 50)
                        break;
                }
                if (k < 100) break;
            }
            if (j < 100) break;
        }
        

        根据我的经验,这是大多数情况下需要的。

        【讨论】:

          【解决方案10】:

          有点愚蠢的自我记录:

          int i, j, k;
          int done = 0;
          
          for (i = 0; i < 100 && ! done; i++) {
              for (j = 0; j < 100 && ! done; j++) {
                  for (k = 0; k < 100 && ! done; k++) {
                      if (k == 50) we_are(done);
                  }
              }
          }
          
          //...
          
          void we_are(int *done) {
              *done = 1;
          }
          

          但实际上,您不应该有三个嵌套的 for 循环。您应该考虑重构为不同的函数并改进程序的逻辑,而不是这样做。

          虽然我同意有时goto 确实是最好的解决方案,但我认为goto 是解决方案的任何问题都是糟糕代码的结果。

          【讨论】:

            【解决方案11】:

            除以 0 是我所知道的最可靠的方法,它可以让你摆脱 任何 个循环。这是可行的,因为 DIV 汇编指令不喜欢这种愚蠢。

            所以,你可以试试这个:

            int i, j, k;
            int flag1 = 0;
            int flag2 = 0;
            
            for (i = 0; i < 100; i++) {
                for (j = 0; j < 100; j++) {
                    for (k = 0; k < 100; k++) {
                        if (k == 50) {
                            flag1 = 1;
                            flag2 = 1;
                            int z = 1 / 0;  // we're outta here!!!
                        }
                    }
                    if (flag1 == 1)break;
                }
                if (flag2 == 1)break;
            }
            

            从发生在此类事件中的trap 中恢复过来,留给读者作为练习(这很简单)。

            【讨论】:

            • 一个足够好的编译器不应该编译它(因为它是一个无效的常量表达式),除此之外,永远不能用异常来代替控制逻辑。建议人们这样做对我来说似乎是 DIVilish :-)
            • 我投票赞成,因为它太有趣了。
            【解决方案12】:

            我会这样做:

              int i, j, k;
            
              for (i = 0; i < 100; i++) {
                  for (j = 0; j < 100; j++) {
                      for (k = 0; k < 100; k++) {
                          if (k == 50) {
                              return;
                          }
                      }
                  }
              }
            

            【讨论】:

              【解决方案13】:

              如果您使用 GCC 和 this librarybreak 可以接受您要退出的嵌套循环数:

              int i, j, k;
              
              for (i = 0; i < 100; i++) {
                  for (j = 0; j < 100; j++) {
                      for (k = 0; k < 100; k++) {
                          if (k == 50) {
                              break(3);
                          }
                      }
                  }
              }
              

              【讨论】:

                【解决方案14】:

                一种方法是状态机。但我仍然会使用 goto。这要简单得多。 :)

                state = 0;
                while( state >= 0){
                    switch(state){
                        case 0: i = 0; state = 1; // for i = 0
                        case 1:
                            i++; 
                            if (i < 100)   // if for i < 100 not finished
                                state = 2; // do the inner j loop
                            else
                                state = -1; // finish loop
                        case 2: j = 0; state = 3; // for j = 0
                        case 3: 
                            j++;
                            if (j < 100)  // if j < 100 not finished
                                state = 4 // do the inner k loop
                            else
                                state = 1; // go backt to loop i
                            break;
                        case 4: k = 0; state = 5;
                        case 5:
                            k++;
                            if (k == 50){
                                state = -1;
                                break;
                            }
                            if (k < 100) // if k loop not finished
                                state = 5; // do this loop
                            else
                                state = 3; // go back to upper loop
                            break;
                        default : state = -1;
                    }
                }
                

                【讨论】:

                • 状态机相对于 goto 的优势是什么?
                • 其实没有。这只是执行这些循环并在没有 goto 的情况下中断的另一种方式。我会用 goto 来做这个。在某些情况下,最好使用它。例如,如果你有很多从一个循环跳到另一个循环,比如 regExp。 RegExp 解析器会编译这些机器,因为它们运行速度快并且可以进行优化。
                猜你喜欢
                • 2021-07-28
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-03-11
                • 1970-01-01
                • 2010-09-16
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多