Golang中这个switch语句能编译通过,应该允许吗?
Golang中这个switch语句能编译通过,应该允许吗? 今天遇到了一个关于Go语言switch语句的有趣陷阱。
考虑以下代码:
package main
import "fmt"
func f() bool {
return false
}
func main() {
switch f()
{
case false:
fmt.Println(1)
case f():
fmt.Println(2)
case true:
fmt.Println(3)
default:
fmt.Println(4)
}
}
这段代码输出3。
这个陷阱(大多数编辑器不会捕捉到)在于switch语句后面的大括号被放在了下一行。我想知道为什么这会被允许?以及这些case语句是否仍然与它们前面的switch语句相关联。
谢谢
更多关于Golang中这个switch语句能编译通过,应该允许吗?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
太棒了!!!我纯粹出于好奇,刚刚把这个工具加入了我的工具箱……!!!谢谢!!!
更多关于Golang中这个switch语句能编译通过,应该允许吗?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我不是“反编译”专家,但我猜想 switch 语句只是编写一堆嵌套 if…else…if 的“简写”方式。
这段代码:
switch f(); {
case false:
fmt.Println(1)
case f():
fmt.Println(2)
case true:
fmt.Println(3)
default:
fmt.Println(4)
}
其运行方式相当于:
f()
if false {
fmt.Println(1)
} else if f() {
fmt.Println(2)
} else if true {
fmt.Println(3)
} else {
fmt.Println(4)
}
只需在 f 函数中添加一个 Println 来检查它被执行了多少次(2次)。
func f() bool {
fmt.Println("f() Called")
return false
}
你可以在这个网站上(它展示生成的汇编代码)进一步研究并反馈回来 😉 (我已经在里面编辑了你的示例并使用了ARM64 Go编译器…)
![]()
Compiler Explorer - Go (ARM64 gccgo 13.2.0)
package main
import (
"fmt"
)
func f() bool {
return false
}
func doswitch() {
switch f(); {
case false:
fmt.Println(1)
case f():
fmt.Println(2)
case true:
fmt.Println(3)
default:
fmt.Println(4)
}
}
func doifelse() {
f()
if...
在实际工作中,我从未见过由类似情况引发的错误。而且在实际开发中,工具会对你的代码执行 go fmt。例如,尝试将以下代码粘贴到 Go Playground 中,一旦你点击“运行”(它也会格式化你的代码),问题就会变得显而易见。来自 Go 语言之旅:
Go 的
switch语句类似于 C、C++、Java、JavaScript 和 PHP 中的switch,不同之处在于 Go 只运行选中的 case,而不是之后所有的 case。实际上,这些语言中每个 case 末尾所需的break语句在 Go 中是自动提供的。另一个重要区别是,Go 的switchcase 不必是常量,所涉及的值也不必是整数。
正如我所说——我从未见过由你上面所写的那种情况导致的错误。然而,我见过很多在 Go 中被避免的错误,正是因为 Go 隐式地添加了 break 语句。无论如何,如果你在 Go Playground 上运行你的代码,你就会明白发生了什么。本质上,你的代码等同于 没有条件的 switch:
没有条件的 switch 等同于
switch true。
另请参阅:
这是一个关于Go语言语法解析的经典案例。你观察到的现象是正确的,这段代码能够编译通过,并且输出3。
原因分析
Go语言的分号自动插入规则导致了这个问题。根据Go语言规范,当一行以特定标记结束时(如标识符、数字、字符串字面量或break、continue等关键字),解析器会自动插入分号。
在你的代码中:
switch f()
{
// ...
}
解析器将switch f()解析为一个完整的语句,并在右括号后自动插入分号,相当于:
switch f();
{
// ...
}
这实际上创建了两个独立的语句:
- 一个没有case的
switch f()语句 - 一个独立的代码块(复合语句)
实际解析结果
你的代码被解析为:
switch f(); // 空的switch语句,没有case,不执行任何操作
{
case false: // 这是一个标签语句,不是switch的case
fmt.Println(1)
case f(): // 另一个标签语句
fmt.Println(2)
case true: // 标签语句,程序会执行到这里
fmt.Println(3)
default: // 默认标签
fmt.Println(4)
}
大括号内的case和default被解析为标签语句,而不是switch语句的一部分。在Go中,标签可以用于break、continue和goto语句。
验证示例
这里有一个更清晰的示例来展示这个现象:
package main
import "fmt"
func main() {
// 示例1:你遇到的情况
fmt.Println("示例1:")
switch f()
{
case false:
fmt.Println("不会执行")
case true:
fmt.Println("会执行 - 作为标签")
}
// 示例2:正确的写法
fmt.Println("\n示例2:")
switch f() {
case false:
fmt.Println("会执行 - 作为switch的case")
case true:
fmt.Println("不会执行")
}
// 示例3:展示标签的实际用途
fmt.Println("\n示例3:")
i := 0
Loop:
for i < 3 {
switch i {
case 1:
break Loop // 使用标签跳出外层循环
}
fmt.Println(i)
i++
}
}
func f() bool {
return false
}
输出:
示例1:
会执行 - 作为标签
示例2:
会执行 - 作为switch的case
示例3:
0
结论
- 允许的原因:这是Go语言分号自动插入规则的副作用,语法上完全合法。
- 关联性:大括号内的
case语句与前面的switch语句没有关联,它们是独立的标签语句。 - 最佳实践:始终将switch语句的左大括号放在同一行,这是Go语言的官方代码风格。
这种语法虽然合法,但容易引起误解,因此Go的gofmt工具会强制将左大括号放在同一行,避免这种混淆。

