在 Go 语言中,代码块内声明的标识符,只能在代码块内使用。例如函数体是个代码块,里面定义的变量,只要没发生逃逸,在外部无法访问。 但有一种特殊情况,就是在支持初始化子语句的流程语句中,初始化子语句中定义的变量,作用域会延伸到条件对应的代码块之外。请看代码:
package main
import "fmt"
func main() {
if a := 1; false {
fmt.Println(a)
//fmt.Println(b) // 不能引用下面语句定义的块级变量
} else if b := 2; a > b {
fmt.Println(a - b) // 可以引用上面语句定义的块级变量
} else {
fmt.Println(a + b) // 在最后 a 和 b 都可以引用
}
}
左看右看也想不明白,为什么 else if
中定义的变量 b
也能在 else
块中使用?如果有更多条件判断,那么前一个 else if
中定义的变量,在之后所有 else if
块中都能使用。
在 C++ 中,if
中定义的变量 a
,可以跨整个流程语句,else if
中定义的变量明确不能在其他 else
代码块中引用:
#include <iostream>
int main() {
if (int a = 1; false) { // a 在整个 if-else 结构中可见
std::cout << a << std::endl;
} else if (int b = 2; a > b) { // a 可访问
std::cout << a - b << std::endl;
} else {
std::cout << a << std::endl; // a 可访问
// std::cout << b << std::endl; // 错误:b 不可访问
}
return 0;
}
在 C# 中,规定更是严格,if
和 else if
中定义的变量,只能在随后的代码块中使用,不能跨到代码块:
public class Program {
public static void Main() {
if (int a = 1; false) {
Console.WriteLine(a);
} else if (int b = 2; a > b) { // 错误:a 不可访问
Console.WriteLine(a - b);
} else {
Console.WriteLine(a + b); // 错误:a 和 b 都不可访问
}
}
}
有本 Go 语言书管这叫「惰性求值」行为,但我也仅仅是知道有这么个现象,不明白为什么会设计成这样?
1
MoYi123 83 天前
c++的例子有文档吗? 我测试了 clang++ 18.1.6 和 g++ 14.0.1 在 else 里都能访问 b, 和 go 是一样的.
|
3
DOLLOR 83 天前 1
确实也能算“惰性求值”吧。
如果下一条 if 的判断需要依赖上一条 if 的值,这样设计可能会比较方便。 比如第一个 if 计算值后判断,如果为 false ,则到下一个 if 再利用之前求的值计算再求值,一直下去,直到为 true 的 else if 语句或者 else 。 如果不这样设计,那就要在进入第一个 if 之前,提前把每个 if 的值都求出来,这样就不够“惰性”了。 |
4
assassing OP @DOLLOR 突然懒得深究了,我把流程转成这样去理解:
func main() { { a := 1; if false { fmt.Println(a) } b := 2; else if a > b { fmt.Println(a - b) } else { fmt.Println(a + b) } } } 看起来没有破坏块级作用域规则。 |
5
vx7298 83 天前
这个不叫惰性,惰性是表达式不会立即被执行赋值,子表达式声明的,只能在 if else 中用,你把 b:=2 换成 b:=func(),这个 func 会在 else 的时候,立即执行
|