golang 的失败似乎出乎意料

golang's fallthrough seems unexpected

我有以下代码:

package main

import (
    "fmt"
)

func main() {
    switch {
    case 1 == 1:
        fmt.Println("1 == 1")
        fallthrough
    case 2 == 1:
        fmt.Println("2 == 1")
    }
}

在 go playground 上打印两行 - see example here。我本来希望 fallthrough 语句包括对下一个 case 语句的评估,但事实并非如此。

当然,我总是可以使用一堆if语句,所以这不是真正的障碍,但我很好奇这里的意图是什么,因为在我看来这不是-显而易见的结果。

有人愿意解释一下吗?例如:在 this 代码中,如何让第 1 和第 3 种情况执行?

来自language spec

A "fallthrough" statement transfers control to the first statement of the next case clause in an expression "switch" statement. It may be used only as the final non-empty statement in such a clause.

这似乎完美地描述了您观察到的行为。

据推测,Go 的 fallthrough 行为是模仿 C 的,它总是这样工作的。在 C 语言中,switch 语句只是条件 goto 链的简写,因此您的特定示例将被编译为 ,就好像 它写成这样:

    # Pseudocode
    if 1 == 1 goto alpha
    if 2 == 1 goto beta
alpha:
    fmt.Println("1 == 1")
beta:
    fmt.Println("2 == 1")

如您所见,一旦执行进入 alpha 案例,它将继续向下流动,忽略 beta 标签(因为标签本身并没有真正做任何事情)。条件检查已经发生,不会再发生。

因此,fallthrough switch 语句的非直觉性仅仅是因为 switch 语句是薄薄的 goto 语句。

Switch 不是一堆如果。它更类似于 if {} else if {} 构造,但有一些曲折 - 即 breakfallthrough。不可能让 switch 在第一种和第三种情况下执行 - switch 不会检查每个条件,它会找到第一个匹配项并执行它。就这些了。

它的主要目的是遍历可能值的列表并为每个值执行不同的代码。事实上,在C(switch语句的来源)中,switch表达式只能是整数类型,case值只能是常量,switch表达式也会进行比较。直到最近,语言才开始在 switch case 中添加对字符串、布尔表达式等的支持。

至于fallthrough逻辑,它也来自C。C中没有fallthrough运算符。在C中,除非遇到break运算符,否则执行将进入下一个case(不检查case值)。这种设计的原因是,有时您需要做一些特殊的事情,然后执行与另一种情况相同的步骤。因此,此设计仅允许这样做。不幸的是,它很少有用,所以默认情况下失败会在程序员忘记放入 break 语句时造成更多麻烦,然后在真正有意省略 break 时实际上会有所帮助。因此,许多现代语言将此逻辑更改为默认情况下永远不会失败,并且如果确实需要失败,则需要明确的失败声明。

不幸的是,很难想出一个非人为的 fallthrough 有用的例子,它足够短以适合答案。正如我所说,这种情况相对罕见。但有时你需要写类似这样的代码:

if x == a || x == b {
  if x == a {
    // do action a
  }
  // do action ab
} else if x == c {
   // do action c
} else if x == d {
  // do action d
}

事实上,我最近在我的一个项目中需要类似结构的代码。所以,我改用 switch 语句。它看起来像这样:

switch x {
  case a: // do action a
          fallthrough
  case b: // do action ab
  case c: // do action c
  case d: // do action d
}

你从这个问题的转换在功能上等同于:

if 1 == 1 || 2 == 1 {
    if 1 == 1 {
        fmt.Println("1 == 1")
    }
    fmt.Println("2 == 1")
}