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 找到。