break
和 continue
break
和continue
允许你在循环内部进行“跳转”。
早期的程序员直接编写处理器,使用数字 操作码 作为指令,或者使用 汇编语言,它可以翻译成操作码。这种类型的编程是最底层的。例如,许多编码决策通过在代码中直接“跳转”到其他地方来实现。早期的高级语言(包括 FORTRAN、ALGOL、Pascal、C 和 C++)通过实现 goto
关键字来复制这种做法。
goto
使得从汇编语言转向高级语言的程序员更加舒适。然而,随着我们积累了更多经验,编程社区发现无条件跳转会产生复杂和难以维护的代码。这对 goto
产生了很大的反感,大多数后来的语言都避免了任何形式的无条件跳转。
Kotlin 提供了一种受限制的跳转,即 break
和 continue
。这些与循环结构 for
、while
和 do-while
相关联,你只能在这些循环内部使用 break
和 continue
。此外,continue
只能跳转到循环的开始处,break
只能跳转到循环的结束处。
实际上,在编写新的 Kotlin 代码时,很少使用 break
和 continue
。这些特性是早期语言的产物。尽管它们偶尔会有用,但在本书中,您将了解到 Kotlin 提供了更优越的机制。
这里有一个包含 continue
和 break
的 for
循环的示例:
// BreakAndContinue/ForControl.kt
import atomictest.eq
fun main() {
val nums = mutableListOf(0)
for (i in 4 until 100 step 4) { // [1]
if (i == 8) continue // [2]
if (i == 40) break // [3]
nums.add(i)
} // [4]
nums eq "[0, 4, 12, 16, 20, 24, 28, 32, 36]"
}
示例将 Int
聚合到可变的 List
中。在 [2] 处的 continue
跳回循环的开始,即 [1] 处的大括号。它在下一次循环迭代开始时“继续”执行。请注意,在 for
循环体中 continue
后面的代码不会被执行:当 i == 8
时不会调用 nums.add(i)
,因此在结果 nums
中看不到它。
当 i == 40
时,[3] 处的 break
被执行,通过跳转到其作用域的结尾处([4] 处)“跳出”了 for
循环。以 40
开头的数字不会被添加到最终的 List
中,因为 for
循环停止执行。
第 [2] 行和 [3] 行是可以互换的,因为它们的逻辑没有重叠。尝试交换这两行并验证输出是否不会更改。
我们可以使用 while
循环重写 ForControl.kt
:
// BreakAndContinue/WhileControl.kt
import atomictest.eq
fun main() {
val nums = mutableListOf(0)
var i = 0
while (i < 100) {
i += 4
if (i == 8) continue
if (i == 40) break
nums.add(i)
}
nums eq "[0, 4, 12, 16, 20, 24, 28, 32, 36]"
}
break
和 continue
的行为保持不变,就像 do
-while
循环一样:
// BreakAndContinue/DoWhileControl.kt
import atomictest.eq
fun main() {
val nums = mutableListOf(0)
var i = 0
do {
i += 4
if (i == 8) continue
if (i == 40) break
nums.add(i)
} while (i < 100)
nums eq "[0, 4, 12, 16, 20, 24, 28, 32, 36]"
}
do
-while
循环总是至少执行一次,因为 while
测试位于循环的末尾。
标签
普通的 break
和 continue
只能跳转到其本地循环的边界。标签 允许 break
和 continue
跳转到包围循环的边界,因此您不仅局限于当前循环的范围。
您可以使用 label@
创建标签,其中 label
可以是任何名称。在这里,标签是 outer
:
// BreakAndContinue/ForLabeled.kt
import atomictest.eq
fun main() {
val strings = mutableListOf<String>()
outer@ for (c in 'a'..'e') {
for (i in 1..9) {
if (i == 5) continue@outer
if ("$c$i" == "c3") break@outer
strings.add("$c$i")
}
}
strings eq listOf("a1", "a2", "a3", "a4",
"b1", "b2", "b3", "b4", "c1", "c2")
}
带有标签的 continue
表达式 continue@outer
返回到标签 outer@
。带有标签的 break
表达式 break@outer
找到了名为 outer@
的块的结尾,并从那里继续执行。
标签适用于 while
和 do
-while
:
// BreakAndContinue/WhileLabeled.kt
import atomictest.eq
fun main() {
val strings = mutableListOf<String>()
var c = 'a' - 1
outer@ while (c < 'f') {
c += 1
var i = 0
do {
i++
if (i == 5) continue@outer
if ("$c$i" == "c3") break@outer
strings.add("$c$i")
} while (i < 10)
}
strings eq listOf("a1", "a2", "a3", "a4",
"b1", "b2", "b3", "b4", "c1", "c2")
}
WhileLabeled.kt
可以重写为:
// BreakAndContinue/Improved.kt
import atomictest.eq
fun main() {
val strings = mutableListOf<String>()
for (c in 'a'..'c') {
for (i in 1..4) {
val value = "$c$i"
if (value < "c3") { // [1]
strings.add(value)
}
}
}
strings eq listOf("a1", "a2", "a3", "a4",
"b1", "b2", "b3", "b4", "c1", "c2")
}
这种写法更加易于理解。在第 [1] 行,我们只添加(按字母顺序)位于 "c3"
之前的 String
。这与在先前版本的示例中到达 "c3"
时使用 break
的行为相同。
- -
break
和 continue
往往会创建复杂且难以维护的代码。虽然这些跳转比“goto”更文明,但它们仍然会中断程序流程。没有跳转的代码几乎总是更容易理解的。
在某些情况下,您可以显式地编写迭代的条件,而不是使用 break
和 continue
,就像我们在上面的示例中所做的那样。在其他情况下,您可以重构代码并引入新的函数。如果您将整个循环或循环体提取到新的函数中,则可以用 return
替代 break
和 continue
。在接下来的部分 函数式编程 中,您将学会编写清晰的代码,而无需使用 break
和 continue
。
考虑替代方法,并选择更简单和更易读的解决方案。这通常不会包括 break
和 continue
。
练习和解答可在 www.AtomicKotlin.com 找到。