如何在Objective-C中跳出两个嵌套的for循环?

76

我有两个嵌套在一起的for循环:

for(...) {
    for(...) {

    }
}
我知道有一个`break`语句,但是我对于它是否会同时打破嵌套的循环还是只打破它所在的那个循环很困惑。当我发现没有必要再迭代更多次时,我需要同时打破两个循环。
13个回答

109
如果使用goto可以简化代码,那么使用它是合适的。
for (;;) 
{
    for (;;) 
    {
        break; /* breaks inner loop */
    } 
    for (;;) 
    {
        goto outer; /* breaks outer loop */
    }
} 
outer:;

4
进一步解释这段代码,for (;;) {for (;;) {break; /* breaks inner loop */} for (;;) {goto outer; /* breaks outer loop */}} outer:;不断重复一个空语句的无限循环嵌套结构。内层循环包含一个 break; 语句用于跳出内层循环,外层循环包含一个带标签的 goto 语句用于跳出外层循环。最终程序进入到带有标签 outer 的行并跳出了外层循环。 - ephemient
7
(;;) 看起来像一个小脸。 - zakdances

95

break可以跳出当前循环,但你可以在外层循环中添加一个检查,当内层循环跳出时也会跳出外层循环。

bool dobreak = false;
for ( ..; !dobreak && ..; .. ) {
   for ( ... ) {
      if (...) {
         dobreak = true;
         break;
      }
   }
}

56
在我看来,使用goto语句更加简洁。 - sigjuice
4
!dobreak 放错了位置,应该在条件语句中而不是增量步骤中;我还会使用 !dobreak && ..,这样在跳出循环时就不需要评估其他条件了。我同意 sigjuice 的观点:适当使用 goto 并不是不好的编程习惯,在这种情况下,goto 确实 可以使代码更好。 - ephemient
2
这仍然会执行可能在内部循环之后出现的外部循环中的代码。 - Martijn
@Martijn - 这可以通过用 continue 语句替换 break 来解决。不过,这只是更多需要担心的不明显的东西。 - Ori Pessach

14

break语句只能使你退出最内层的循环。如果你不想在代码、内存和性能上增加额外的负担,我建议将代码重构为自己的函数或方法,并使用return来跳出所有循环:

void do_lots_of_work(void)
{
  int i, j;

  for(i=0; i<10 ; i++)
  {
    for(j=0;j< 10; j++)
    {
     ..
     ..
     if(disaster_struck())
      return; /* Gets us out of the loops, and the function too. */
    }
  }
}

如果在循环中进行函数调用,要想达到专门状态变量的性能水平,我会感到非常惊讶,特别是如果您需要传递参数才能访问外部函数中的变量。也许如果编译器内联复制函数... - ephemient
6
在我看来更糟糕的是,失去了通过在一个地方阅读算法来理解它的能力。所有这一切都是为了避免使用goto语句? - Ori Pessach
我也遇到了这个break/continue问题,有时想要在2-3级别上继续循环。我认为最好的方法是将嵌套循环重构为一个方法,以便该方法可以返回是否继续父循环。 - Jason
如果用户只想退出循环而不是函数怎么办? - Akshay J
@OriPessach 如果你给这个方法起一个好名字,那么很容易理解正在发生的事情。你的论点认为将原始汇编放在Objective-C之上是一种更好的语言(因为它将所有内容都放在一个地方),这与问题的要点相矛盾。 - Ky -
显示剩余2条评论

9

除了已经提到的标志变量或goto之外,您可以抛出Objective-C异常:

@try {
  for() {
    for() {
       @throw ...
    }
  }
}
@catch{
  ...
}

5
哇,就在我要修改我的答案以指出人们会提出所有错综复杂的解决方案,而不使用异常来控制流程只是为了避免使用goto时... - Ori Pessach
4
@Ori Pessach,异常是现代面向对象版本的goto;-) - lothar
1
他们至少很好地清理了堆栈。 - Ori Pessach
2
根据我的经验,异常非常慢,不应该用于处理执行流程。 - Natalie Adams
3
在 ObjC 中研究异常处理。在苹果文档中,他们明确指出性能损失很大,并表示:“Cocoa 框架通常不支持异常安全。一般的模式是将异常保留仅用于程序员错误,捕获此类异常的程序应尽快退出。” - jpswain

7

其他人已经提到了如何设置标志或使用goto,但我建议您重构代码,将内部循环转换为单独的方法。该方法可以返回一些标志,以指示外部循环应该break。如果您适当地命名方法,这样更易读。

for (int i = 0; i < 10; i++) {
   if (timeToStop(i)) break;
}

-(bool) timeToStop: (int) i {
    for (int j = 0; j < 10; j++) {
        if (somethingBadHappens) return true;
    }

    return false;
}

伪代码,未经过测试,但你能明白意思。


4

break语句只能跳出当前循环的范围,也就是父级循环。如果你想要同时跳出第二个循环,你可以使用一个布尔变量,该变量在两个循环的范围内。

bool isTerminated = false;

for (...)
{
    if (!isTerminated)
    {
        for(...)
        {
            ...

            isTerminated = true;
            break;
        }
    }
    else
    {
        break;
    }
}

2

另一个解决方案是将第二个循环提取到一个函数中:

int i;

for(i=0; i<10 ; i++){
    if !innerLoop(i) {
        break;
    }
}

bool innerLoop(int i)
    int j;
    for(j=0;j< 10; j++){
        doSomthing(i,j);
        if(endcondtion){
            return false;
        }
    }
}

2
也许最简单的方法是使用一个“标志”变量。
for(i=0; i<10 && (done==false); i++)
  for(j=0;j< 10; j++){
     ..
     ..
     if(...){done=true; break;}
  }

@Jonny,这些在Obj-C中被称为“快速枚举”循环。 - Albert Renshaw

2

在退出循环之前更改顶部循环计数器

for(i=0; i<10 ; i++)
  for(j=0;j< 10; j++){
     ..
     ..
     i = 10; 
     break;
  }

13
我不太喜欢这个。风险在于有人会在一个地方更改退出条件,然后忘记另一个地方。 - Johan Kotlinski
NSUInteger limit = 10; 然后: for(i=0; i<limit; i++) { for(j=0; j<10; j++) { .. .. i = limit; break; } } - Matt Mc

1

break语句会跳出最内层的循环。如果要跳出外层循环,需要添加额外的测试和break语句。


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