在C++中,如果达到结束条件,是否有可能提前退出for循环?

7

我想知道在C++中是否可以在满足其他条件时结束for循环,而不是到达规定的迭代次数。例如:

for (int i = 0; i < maxi; ++i)
    for (int j = 0; j < maxj; ++j)
        // But if i == 4 < maxi AND j == 3 < maxj, 
        // then jump out of the two nested loops.

我知道在Perl中可以通过使用next LABEL或last LABEL调用和带标签的块来实现此功能,那么在C++中是否可以实现呢?还是我应该使用while循环?谢谢。
15个回答

51
你可以使用return关键字:将嵌套循环移动到一个子程序中,调用该子程序以运行嵌套循环,并从子程序中“返回”以退出所有循环。

1
我不知道为什么这让我觉得更可取,尽管它与goto完全等效。 - Chris Conway
在我看来,这绝对是最好的答案。将代码流程封装在方法中通常要比创建一个过于复杂的代码块优越得多。 - Wedge
3
@Chris,每种控制流机制都等同于一个“goto”语句。在硬件层面上,除了“goto”语句,没有其他东西。但是,我们使用的控制流机制(如for循环,while循环,if/else语句,return语句)具有限制条件,使它们比“goto”语句更易用。 - Wedge
我认为如果你要回答这个问题,最好重构代码,尝试摆脱两个循环或者打破它们的需要。 - Dominik Grabiec

47

尽管有 "goto 被认为有害" 的争论,但在这种情况下,goto 似乎是最完美的选择。这基本上是你在 Perl 中正在做的事情。认真考虑一下其他选择:

额外状态变量


for (int i=0; i<maxi; ++i) {
    bool leaveLoop = false;
    for (int j=0; j<maxj; ++j) {
        if (i == 4 && j == 3) {
            leaveLoop = true;
            break; // leave the inner loop
        }
    }
    if (leaveLoop) {
        break; // leave the outside loop
    }
}

异常退出


try {
    for (int i=0; i<maxi; ++i) {
        for (int j=0; j<maxj; ++j) {
            if (i == 4 && j == 3) {
                throw leave_loop();
            }
        }
    }
} catch (leave_loop const&) {
}

复杂逻辑


int j = 0;
for (int i=0; i<maxi && !(i==4 && j==3); ++i) {
    for (j=0; j<maxj && !(i==4 && j==3); ++j) {
        // inner loop
    }
}

goto


for (int i=0; i<maxi; ++i) {
    for (int j=0; j<maxj; ++j) {
        if (i==4 && j==3) {
            goto leave_loop;
        }
    }
}
leave_loop:

最后一种更不清晰吗?我不认为是。它更脆弱吗?我认为,与其他版本相比,goto 版本非常容易出错和脆弱。很抱歉在这里发表自己的看法,但这是困扰我一段时间的问题 ;)

唯一需要注意的是,goto 和异常非常相似。它们都会导致资源泄漏等问题,因此必须小心处理。


1
为了完整起见,您可能还应该添加Chris的解决方案,创建一个单独的函数。 - tfinniga
你错过了其中一个更清晰的选项,即像@Gilad Naor建议的那样,在for循环的条件语句中放置break条件。 - Nathan Fellman

13

让我强调一下(但是礼貌地说;-)):在类C语言中,for循环结构不仅仅是用来计数的。

决定是否继续的测试表达式可以是与循环目的相关的任何内容;更新表达式不必是“将计数器加一”。

for (int i = 0, j = 0; i < maxi && j < maxj && i != 4 && j != 3;) {
    if (j < maxj) {
        ++j;
    } else {
        j = 0;
        ++i;
    }
}

这只是一种(相当随意的)重写方式。

关键在于,如果建立某些条件是迭代的重点,则通常可以以更明确地陈述继续/终止条件的方式编写循环(使用whilefor)。

(如果您可以发布实际发生的内容的描述,则可能编写的内容看起来不像上面那么随意。)


另一方面,您可以使用while循环,在大多数情况下它会更易读。虽然展示不必在正常严格的方式下使用for循环是好的。 - Dominik Grabiec
我完全同意使用while的观点。我的例子是人为的,因为不了解提问者的循环实际上在做什么。 - joel.neely
不需要测试哪个循环索引需要增加,这可能会显著减慢循环速度。 - MSalters
@MSalters:一点也不多余。这个示例用一个循环代替了两个循环,因此每次迭代都要么在覆盖的二维空间的同一“行”中继续,要么开始一个新的行。这是一个决策。 - joel.neely

8

一条break指令无法跳出两个循环,但是你可以使用goto从内部循环直接跳到外部。

如果goto是局部的,并且意味着比其他情况下有更少的逻辑,我认为这是完全可以接受的代码。在我的看法中,额外的标志变量或将迭代器变量提升到内部循环之外以便在外部循环中进行比较并不会使代码更易于理解。


8

从上面的所有建议中,我会避免使用try/catch机制,因为异常应该保留给非正常控制流的情况,而不是正常控制流。

如果您可以适当地创建第二个条件,则使用两个break是可以的。为此目的使用布尔值也是不错的选择,您甚至可以将其连接到每个for循环的条件中。例如:

bool exit_loops = false;
for (int a = 0; a < A && !exit_loops; ++a)
{
    for (int b = 0; b < B && !exit_loops; ++b)
    {
        if (some_condition) exit_loops = true;
    }
}

尽管如此,如果您正在使用超过两个循环,则将它们包装在函数中并只使用返回退出函数(以及所有循环)可能更为合适。另一方面,您可以通过调用执行内部循环代码的函数等方式重构代码,以消除所有循环,除了一个循环。
最后,在这种情况下不要害怕使用goto,通常goto是不良结构化编程,但在某些情况下(比如这种情况)它们非常有用。

我也总是这样做,简单易读,为什么要引入goto?唯一的改变是我会这样写:for ( int a=0, bool done=false; a<A && !done; ++a ) // 这样更加一致 - Bill Forster
说实话,我个人会重构算法,使其根本不需要打破嵌套循环。 - Dominik Grabiec
我会这样做 - 这并不更简单,但我不想花时间向一群开发人员证明使用goto的合理性。 - Martin Beckett

6
您不能在C/C++中像这样跳出:

for (...)
{
  for (...)
  {
    // from here...
  }
}
// ...to here

不使用goto语句,需要使用像这样的结构:

for (...)
{
  bool
    exit = false;

  for (...)
  {
    if (do_exit)
    {
      exit = true; // or set outer loop counter to end value
      break;
    }
  }
  if (exit)
  {
    break;
  }
}

另一种方法是使用throw和catch - 但这并不好,因为throw应该用于异常而不是流程控制。

一种简洁的方法是将内部循环作为函数:

bool F ()
{
  if inner loop terminates, return false else return true
}

void G ()
{
  for (...)
  {
    if (!F ())
    {
      break;
    }
  }
}

一个更加优雅的方式是将两个循环放在一个函数中,并在内部循环达到停止条件时返回(而不是跳出)。 - RobH
RobH - 如果您对多个返回结果感到满意,那么这也可以起作用。我个人尽量避免多个返回结果。 - Skizz

6
bool done = false;

for (int i = 0; i < maxi && !done; ++i)
    for (int j = 0; j < maxj && !done; ++j)
        if (i == 4 && i < maxi && j == 3 && j < maxj )
             done = true;
        else {
        }

或者你可以直接转到。或者不转 :-)


@丹 - 正确的。我会更新答案。花太多时间在C上了。还是现在花太多时间在C++上了? :-) - Gilad Naor
我的内心挑剔者现在感觉好多了 :) 昨晚看到你的答案后,我检查了一下问题是否标记为C或C++,然后才发表评论...但是,现在C也有<stdbool.h>,所以这可能并不那么重要。 - Dan

4
for (int i = 0; i < maxi; ++i)
{
    int j = 0;
    for (j = 0; j < maxj; ++j)
    {
         if (i == 4 && j == 3) // i < maxi and j < maxj otherwise we would not be here
             break; // exit inner loop
    }
    if (i == 4 && j == 3) // i < maxi and j < maxj otherwise we would not be here
        break; // exit outer loop
}

4

你可以使用goto语句,但一般认为这是不好的做法。

你的另一个选择是像这样做:

int i;
int j = 0;
for (i = 0; i < maxi && !(i==4 && j==3); ++i)
    for (j = 0; j < maxj && !(i==4 && j==3); ++j)

2

阅读代码不应该像读侦探小说(总是需要推理)...

例如:

Java:

iterate_rows:
for (int i = 0; i < maxi; ++i)
{       
    for (int j = 0; j < maxj; ++j)
    {
        if (i == 4 < maxi && j == 3 < maxj) 
            break iterate_rows;
        else
            continue iterate_rows;
    }   
}

你不需要弄清楚 break iterate_rows 的作用,只需要阅读它。

C++:

//iterate_rows:
for (int i = 0; i < maxi; ++i)
{
    for (int j = 0; j < maxj; ++j)
    {
        if (i == 4 < maxi && j == 3 < maxj) 
            goto break_iterate_rows;
        else
            goto continue_iterate_rows;
    }

continue_iterate_rows:;
}
break_iterate_rows:;

goto break_iterate_rows只是 break iterate_rows 的一个 可见版本.

如果你只把goto和labels用在这种代码上,你将无法找出意图。 限制在这种代码中使用goto和labels将使你只读代码,而不是分析或理解它。你不会被指责成为一个邪恶的程序员。

如果你真的限制了这种代码中的goto,你就能养成一个习惯,不需要弄清楚那些可恶的goto在你的代码中到底做了什么。附加的好处是你不必引入布尔值并跟踪它们(在我看来,这会导致你“检测代码”,使它有点难以阅读,这违背了避免使用goto的初衷)。

附言:

在循环之前使用注释配对这些标签,当你读过带有goto语句的那些行时,你已经知道了这些goto的意图。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接