如何理解 Go 语言中作用域延伸?

103 天前
 assassing

在 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# 中,规定更是严格,ifelse 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 语言书管这叫「惰性求值」行为,但我也仅仅是知道有这么个现象,不明白为什么会设计成这样?

817 次点击
所在节点    程序员
6 条回复
MoYi123
103 天前
c++的例子有文档吗? 我测试了 clang++ 18.1.6 和 g++ 14.0.1 在 else 里都能访问 b, 和 go 是一样的.
assassing
103 天前
@MoYi123 你是对的,我使用的在线 IDE ,C++ 版本比较老。换了新的 C++20 可以运行,和 Go 行为一致。
DOLLOR
103 天前
确实也能算“惰性求值”吧。

如果下一条 if 的判断需要依赖上一条 if 的值,这样设计可能会比较方便。
比如第一个 if 计算值后判断,如果为 false ,则到下一个 if 再利用之前求的值计算再求值,一直下去,直到为 true 的 else if 语句或者 else 。

如果不这样设计,那就要在进入第一个 if 之前,提前把每个 if 的值都求出来,这样就不够“惰性”了。
assassing
103 天前
@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)
}
}
}
看起来没有破坏块级作用域规则。
vx7298
103 天前
这个不叫惰性,惰性是表达式不会立即被执行赋值,子表达式声明的,只能在 if else 中用,你把 b:=2 换成 b:=func(),这个 func 会在 else 的时候,立即执行
assassing
103 天前
@vx7298 确实不算惰性,defer 语句才叫惰性求值。这里是涉及隐藏语句块的边界问题。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1065673

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX