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 中是可能的,下一个 LABEL 或最后一个 LABEL 调用和标记的块,是否可以在 C++ 中做到这一点,或者我应该使用 while 循环?

谢谢你。

4

15 回答 15

51

您可以使用return关键字:将嵌套循环移动到子例程中,调用子例程以运行嵌套循环,并从子例程中“返回”以退出 [所有] 循环。

于 2009-01-06T13:33:03.527 回答
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例外情况非常相似。他们都打开了泄漏资源的机会,并且小心对待他们。

于 2009-01-06T13:45:19.213 回答
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;
    }
}

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

关键是,如果建立某些条件是交互的重点,通常可以编写一个循环(使用whileor for)以更明确地说明继续/终止条件的方式。

(如果您可以发布实际发生的事情的描述,它可能会写出看起来不像上面那样随意的东西。)

于 2009-01-06T13:31:56.340 回答
8

你不能用一条 break 指令跳出两个循环,但你可以使用 goto 从外面的内循环跳出。

如果 goto 是本地化的,并且意味着逻辑比其他情况少,我认为这是完全可以接受的代码。拥有额外的标志变量,或者将迭代器变量提升到内部循环之外,以便您可以在外部循环中比较它并不能更容易理解代码恕我直言。

于 2009-01-06T13:22:23.580 回答
8

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

如果您可以适当地创建第二个条件,则可以使用两个中断。为此目的使用布尔值也很好,您甚至可以将其连接到每个 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;
    }
}

虽然如果您使用两个以上的循环,将它们包装在一个函数中并使用 return 退出函数(以及所有循环)可能更合适。然后,您可以再次以一种可以消除除一个循环之外的所有循环的方式重构代码,例如通过调用函数来执行内部循环代码等。

最后不要害怕在这种情况下使用 goto,通常 goto 是糟糕的非结构化编程,但在某些情况下(像这样)它们非常有用。

于 2009-01-06T13:48:07.367 回答
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;
    }
  }
}
于 2009-01-06T13:25:17.287 回答
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 {
        }

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

于 2009-01-06T13:47:34.450 回答
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
}
于 2009-01-06T13:22:36.543 回答
4

您可以使用goto statement,但这通常被认为是一种不好的做法。

你的另一个选择是做这样的事情

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)
于 2009-01-06T13:22:58.837 回答
2

阅读代码不应该像阅读侦探书(总是需要弄清楚)......

一个例子:

爪哇:

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 和标签的使用仅限于此类代码,您将无法弄清楚其意图。限制在这种代码上使用 goto 和标签将使您只阅读代码,而不是分析或弄清楚它。你不会被指责为邪恶的程序员。

如果你真的在这种代码中限制你的 goto,你将能够养成不需要弄清楚那些该死的 goto 在你的代码中做了什么的习惯。额外的好处是您不必引入布尔值并跟踪它们(恕我直言,这会导致您检测代码,使其有点不可读,这违背了避免 goto 的目的)

附言

将这些标签与注释配对(在循环之前),当您阅读这些带有 goto 语句的行时,您已经知道这些 goto 的意图

于 2009-01-06T15:11:43.470 回答
1

您可以使用标签,类似于:

Outer:
for(...)
{
    Inner:
    for(...)
    {
    if(condition)
        {
        goto End;
        }
    }
}
End:

在Java中你可以传递标签来打破我认为?

编辑 - 将 goto 更改为 End 而不是 Outer,但我不认为负面代表是合理的。这个答案提供了最简单的方法。

于 2009-01-06T13:20:53.663 回答
1

我有几个建议:

  1. throw .... 将两个循环放在“try {}”中,然后根据条件“捕获”“throw”。

  2. 将两个循环放在一个方法中并返回条件。

  3. Goto 并不邪恶,人们也使用它......您可以使用“goto”它可以是最清晰的代码,尤其是在处理错误时。我已经 20 年没用过一台了。

托尼

于 2009-01-06T13:31:44.310 回答
1

我一直试图远离goto陈述(出于某种原因,它在学校和我的工作中总是被看不起)。我会使用类似 Daemin 建议的东西。

于 2009-01-06T14:02:59.507 回答
1

完全重新考虑该构造的另一个原因for是它的作用域阻止了循环终止后对受控变量的访问。由于各种原因(例如,区分搜索中的成功与失败),循环中被变异的变量的值可能很有用,否则将需要额外的变量来在范围退出后保留该信息。a这是一个小例子,它搜索一个以值命名的方形数组target(假设它SIZE是非零的,否则不需要搜索!):

int i = 0;
int j = 0;
while (i < SIZE && a[i][j] != target) { // still in array but not at target
    if (SIZE <= ++j) {                  // fallen off the end of a row
        j = 0;
        ++i;
    }
}

随后的代码可以i < SIZE用来确定是否找到了所需的值。

上述的另一个优点是灵活性。假设我们现在被告知行中的值a是升序的,因此如果遇到大于 的值,则行的其余部分无关紧要target。很容易准确地知道要进行哪些更改以及在哪里进行更改。因为新信息允许我们放弃当前行,所以只有内部决策受到影响,变为:

    if (target < a[i][j] || SIZE <= ++j) { // can't be in row or fallen off end
    ...

我看到更多的新语言(尤其是面向功能的语言)放弃了旧的“计数”循环结构;这可能是一件好事,因为它鼓励我们思考循环的含义,而不是简单地计数。

于 2009-01-06T18:36:58.877 回答
1

我见过的最好的方法涉及宏和 goto,但它实际上非常好(链接到帖子的开头是谈论 Perl,但最后一段左右介绍了宏)。

它允许您编写如下代码:

named (LOOPS) for (i=1; i<10; i++) {
    for (j=1; j<10; j++) {
        for (j=1; j<10; j++) {
            /* Process data[i][j][k] here */
            if (data[i][j][k] < threshold) break(LOOPS);
        }
    }
}
于 2009-01-06T19:26:56.887 回答