Go语言核心36讲(Go语言基础知识五)--学习笔记

05 | 程序实体的那些事儿(中)

在前文中,我解释过代码块的含义。Go 语言的代码块是一层套一层的,就像大圆套小圆。

一个代码块可以有若干个子代码块;但对于每个代码块,最多只会有一个直接包含它的代码块(后者可以简称为前者的外层代码块)。

这种代码块的划分,也间接地决定了程序实体的作用域。我们今天就来看看它们之间的关系。

我先说说作用域是什么?大家都知道,一个程序实体被创造出来,是为了让别的代码引用的。那么,哪里的代码可以引用它呢,这就涉及了它的作用域。

我在前面说过,程序实体的访问权限有三种:包级私有的、模块级私有的和公开的。这其实就是 Go 语言在语言层面,依据代码块对程序实体作用域进行的定义。

包级私有和模块级私有访问权限对应的都是代码包代码块,公开的访问权限对应的是全域代码块。然而,这个颗粒度是比较粗的,我们往往需要利用代码块再细化程序实体的作用域。

比如,我在一个函数中声明了一个变量,那么在通常情况下,这个变量是无法被这个函数以外的代码引用的。这里的函数就是一个代码块,而变量的作用域被限制在了该代码块中。当然了,还有例外的情况,这部分内容,我留到讲函数的时候再说。

总之,请记住,一个程序实体的作用域总是会被限制在某个代码块中,而这个作用域最大的用处,就是对程序实体的访问权限的控制。对“高内聚,低耦合”这种程序设计思想的实践,恰恰可以从这里开始。

今天的问题是:如果一个变量与其外层代码块中的变量重名会出现什么状况?

我把此题的代码存到了 demo10.go 文件中了。你可以在“Golang_Puzzlers”项目的puzzlers/article5/q1包中找到它。

package main import "fmt" var block = "package" func main() { block := "function" { block := "inner" fmt.Printf("The block is %s.\n", block) } fmt.Printf("The block is %s.\n", block) }

这个命令源码文件中有四个代码块,它们是:全域代码块、main包代表的代码块、main函数代表的代码块,以及在main函数中的一个用花括号包起来的代码块。

我在后三个代码块中分别声明了一个名为block的变量,并分别把字符串值"package"、"function"和"inner"赋给了它们。此外,我在后两个代码块的最后分别尝试用fmt.Printf函数打印出“The block is %s.”。这里的“%s”只是为了占位,程序会用block变量的实际值替换掉。

具体的问题是:该源码文件中的代码能通过编译吗?如果不能,原因是什么?如果能,运行它后会打印出什么内容?

典型回答

能通过编译。运行后打印出的内容是:

The block is inner. The block is function. 问题解析

初看这道题,你可能会认为它无法通过编译,因为三处代码都声明了相同名称的变量。的确,声明重名的变量是无法通过编译的,用短变量声明对已有变量进行重声明除外,但这只是对于同一个代码块而言的。

对于不同的代码块来说,其中的变量重名没什么大不了,照样可以通过编译。即使这些代码块有直接的嵌套关系也是如此,就像 demo10.go 中的main包代码块、main函数代码块和那个最内层的代码块那样。

这样规定显然很方便也很合理,否则我们会每天为了选择变量名而烦恼。但是这会导致另外一个问题,我引用变量时到底用的是哪一个?这也是这道题的第二个考点。

这其实有一个很有画面感的查找过程。这个查找过程不只针对于变量,还适用于任何程序实体。如下面所示。

首先,代码引用变量的时候总会最优先查找当前代码块中的那个变量。注意,这里的“当前代码块”仅仅是引用变量的代码所在的那个代码块,并不包含任何子代码块。

其次,如果当前代码块中没有声明以此为名的变量,那么程序会沿着代码块的嵌套关系,从直接包含当前代码块的那个代码块开始,一层一层地查找。

一般情况下,程序会一直查到当前代码包代表的代码块。如果仍然找不到,那么 Go 语言的编译器就会报错了。

好了,当你明白了上述过程之后,再去看 demo10.go 中的代码。是不是感觉清晰了很多?

从作用域的角度也可以说,虽然通过var block = "package"声明的变量作用域是整个main代码包,但是在main函数中,它却被那两个同名的变量“屏蔽”了。

相似的,虽然main函数首先声明的block的作用域,是整个main函数,但是在最内层的那个代码块中,它却是不可能被引用到的。反过来讲,最内层代码块中的block也不可能被该块之外的代码引用到,这也是打印内容的第二行是“The block is function.”的另一半原因。

你现在应该知道了,这道题看似简单,但是它考察以及可延展的范围并不窄。

知识扩展

不同代码块中的重名变量与变量重声明中的变量区别到底在哪儿?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwzgjp.html