Menu

C# break、continue 和 goto:跳出循环与嵌套循环

C# 中 break、continue 和 goto 的用法:提前结束循环、跳过一次迭代、switch 中的 break、跳出嵌套循环的三种方法(标志变量、goto、带 return 的方法),以及 while 循环里 continue 导致的死循环 bug。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

break 提前结束循环。continue 跳过当前迭代剩下的部分,进入下一次迭代。两者都作用于包含它的最内层循环,在 for、foreach、while 和 do while 中的用法完全相同。

输出:

process order of 45
skip invalid total 0
process order of 120
skip invalid total -5
process order of 80
test order found, stopping
after the loop

遇到 0 和 -5 时,continue 跳过了处理那一行。遇到 999 时,break 停止了循环,所以 60 根本没有被访问到,程序从循环后的第一条语句继续执行。

break:知道答案就停下

break 的典型用法是查找:一旦找到了要找的东西,就没有必要再看剩下的部分。

输出:

first invalid email at index 1

执行 break 之后,迭代语句(i++)不会再运行,所以如果 i 声明在循环外面,它仍然保存着匹配项的索引。对于简单的查找,也可以用库函数表达同样的意思:Array.FindIndex(emails, e => !e.Contains("@")) 返回索引(找不到时返回 -1),而 LINQ 的 emails.Any(e => !e.Contains("@")) 告诉你是否存在无效的邮箱。

continue:跳到下一次迭代

continue 就是循环里的卫语句。不必把真正的工作包在一个 if 里,而是在开头就把不需要的情况排除掉:

输出:

pen costs 1.20
mug costs 8.00

continue 之后去哪里,取决于循环的类型。在 for 循环中,它先执行迭代语句,再检查条件。在 while 循环中,它直接跳到条件判断,这会导致一个经典 bug:

int i = 0;
while (i < 10)
{
    if (i % 2 == 0)
        continue;        // i++ below is skipped: i stays 0 forever
    Console.WriteLine(i);
    i++;
}

请在任何 continue 之前更新计数器,或者改用 for 循环,它的更新语句写在循环头里,总会执行。

循环中 switch 里的 break

在 switch 中,break 结束的是 case 分支。当这个 switch 位于循环中时,break 属于 switch,而不属于循环:

输出:

quit received
count = 2

如果在 "quit" 分支里只写 break,它只会结束 switch,循环会继续,count 将是 3。goto 则跳到循环之后的标签。把循环移到一个方法中,并在这个分支里写 return,同样可行,而且不需要标签。

跳出嵌套循环

C# 没有 Java 中 break outer; 那样带标签的 break。当查找要遍历两层嵌套循环时,内层循环中的 break 只会结束内层循环,外层循环会继续处理下一行。有三种标准的解决办法。

1. 标志变量,由外层循环检查:

输出:

first free seat: row 1, seat 2

2. goto 到两层循环之后的标签。这是很多 C# 编码规范唯一认可使用 goto 的地方,因为它准确地表达了“离开两层循环”:

for (int row = 0; row < rows; row++)
{
    for (int col = 0; col < cols; col++)
    {
        if (seats[row, col] == 0)
        {
            Console.WriteLine($"first free seat: row {row}, seat {col}");
            goto done;
        }
    }
}
done:
Console.WriteLine("search finished");

3. 带 return 的方法,通常最清晰,因为这次查找有了名字,还能返回结果:

输出:

first free seat: row 1, seat 2

C# 7.0 还引入了局部函数,可以把这个辅助方法写在调用它的方法内部。这里用到的 out 参数写法在 ref 和 out 页面 中有讲解。

goto 和标签

标签是一个标识符加上冒号,goto label; 会跳到它那里。规则足够严格,可以防止最糟糕的滥用:

  • 标签必须在同一个方法中,并且位于 goto 所在的代码块或包含它的外层代码块里。你可以跳出代码块,但不能跳进代码块:从循环外跳到循环体内部的标签会报错 CS0159(在 goto 语句的作用域内没有这样的标签)。
  • 不能用 goto、break 或 continue 离开 finally 块。
  • 在 switch 中,goto case 3; 和 goto default; 会把控制权转到另一个分支。C# 的 switch 就是这样表达贯穿(fall-through)的,因为隐式地落入下一个 case 是编译错误。

goto 也能构造循环(向后跳到某个标签),但 while 循环能以每个读者都熟悉的形式表达同样的意思。goto 只留给跳出嵌套循环和 switch 分支跳转。

return、throw 与循环

return 离开整个方法,因此也会离开方法中的所有循环。throw 通过抛出异常达到同样的效果。在带有 finally 的 try 块中,break、continue、goto 和 return 都会先执行 finally 块,再转移控制权,所以从一个在 using 块中打开了文件的循环里 break 出来是安全的。

语句离开从哪里继续
continue当前迭代下一次迭代(先迭代语句,再条件)
break最内层的循环或 switch其后的语句
goto label任意外层代码块标签处
return方法调用方
throw直到匹配的 catch 为止的所有层级catch 块

常见问题

C# 中 break 和 continue 有什么区别?

break 立即结束最内层的循环(或 switch),从它后面的语句继续执行。continue 只结束当前这次迭代,跳到下一次:在 for 循环中接着执行迭代语句,在 while 和 do while 中接着检查条件,在 foreach 中接着取下一个元素。

C# 中如何跳出嵌套循环?

C# 没有带标签的 break。有三种做法:设置一个 bool 标志变量并在外层循环中检查它;用 goto 跳到两层循环之后的标签;或者把循环移到一个方法里,然后 return。方法的写法通常最清晰,因为它还给这次查找起了个名字,并能返回结果。

C# 中 switch 里的 break 会跳出循环吗?

不会。在循环中的 switch 里,break 只结束 switch 的这个分支,循环会继续。要在某个 case 里离开循环,可以用 return、在 switch 之后检查的标志变量,或者 goto 到循环之后的标签。而 case 里的 continue 确实会进入循环的下一次迭代。

C# 中可以用 goto 吗?

可以。goto label; 会跳到同一个方法中的 label:,但不能跳进一个代码块,也不能跳出 finally。在 switch 中,goto case value; 和 goto default; 可以在分支之间跳转。除了跳出嵌套循环和显式的 switch 贯穿,goto 很少是最清晰的选择。

Coddy 编程语言插图

和 Coddy 一起学编程

开始