Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

breakcontinue

breakcontinue 允许你在循环内部进行“跳转”。

早期的程序员直接编写处理器,使用数字 操作码 作为指令,或者使用 汇编语言,它可以翻译成操作码。这种类型的编程是最底层的。例如,许多编码决策通过在代码中直接“跳转”到其他地方来实现。早期的高级语言(包括 FORTRAN、ALGOL、Pascal、C 和 C++)通过实现 goto 关键字来复制这种做法。

goto 使得从汇编语言转向高级语言的程序员更加舒适。然而,随着我们积累了更多经验,编程社区发现无条件跳转会产生复杂和难以维护的代码。这对 goto 产生了很大的反感,大多数后来的语言都避免了任何形式的无条件跳转。

Kotlin 提供了一种受限制的跳转,即 breakcontinue。这些与循环结构 forwhiledo-while 相关联,你只能在这些循环内部使用 breakcontinue。此外,continue 只能跳转到循环的开始处,break 只能跳转到循环的结束处。

实际上,在编写新的 Kotlin 代码时,很少使用 breakcontinue。这些特性是早期语言的产物。尽管它们偶尔会有用,但在本书中,您将了解到 Kotlin 提供了更优越的机制。

这里有一个包含 continuebreakfor 循环的示例:

// 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]"
}

breakcontinue 的行为保持不变,就像 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 测试位于循环的末尾。

标签

普通的 breakcontinue 只能跳转到其本地循环的边界。标签 允许 breakcontinue 跳转到包围循环的边界,因此您不仅局限于当前循环的范围。

您可以使用 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@ 的块的结尾,并从那里继续执行。

标签适用于 whiledo-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 的行为相同。

  • -

breakcontinue 往往会创建复杂且难以维护的代码。虽然这些跳转比“goto”更文明,但它们仍然会中断程序流程。没有跳转的代码几乎总是更容易理解的。

在某些情况下,您可以显式地编写迭代的条件,而不是使用 breakcontinue,就像我们在上面的示例中所做的那样。在其他情况下,您可以重构代码并引入新的函数。如果您将整个循环或循环体提取到新的函数中,则可以用 return 替代 breakcontinue。在接下来的部分 函数式编程 中,您将学会编写清晰的代码,而无需使用 breakcontinue

考虑替代方法,并选择更简单和更易读的解决方案。这通常不会包括 breakcontinue

练习和解答可在 www.AtomicKotlin.com 找到。