![](http://image.iswbm.com/20200607145423.png)
在线博客:http://golang.iswbm.com/
Github:https://github.com/iswbm/GolangCodingTime
---
由于 Go 使用的是词法作用域,而词法作用域依赖于语句块。所以在讲作用域时,需要先了解一下 Go 中的语句块是怎么一回事?
## 1. 显示语句块与隐式语句块
通俗地说,语句块是由花括弧(`{}`)所包含的一系列语句。
语句块内部声明的名字是无法被外部块访问的。这个块决定了内部声明的名字的作用域范围,也就是作用域。
用花括弧包含的语句块,属于显示语句块。
在 Go 中还有很多的隐式语句块:
- 主语句块:包括所有源码,对应内置作用域
- 包语句块:包括该包中所有的源码(一个包可能会包括一个目录下的多个文件),对应包级作用域
- 文件语句块:包括该文件中的所有源码,对应文件级作用域
- for 、if、switch等语句本身也在它自身的隐式语句块中,对应局部作用域
前面三点好理解,第四点举几个例子
for 循环完后,不能再使用变量 i
```go
for i := 0; i < 5; i++ {
fmt.Println(i)
}
```
if 语句判断完后,同样不能再使用变量 i
```go
if i := 0; i >= 0 {
fmt.Println(i)
}
```
switch 语句完了后,也是不是再使用变量 i
```go
switch i := 2; i * 4 {
case 8:
fmt.Println(i)
default:
fmt.Println(“default”)
}
```
且每个 switch 语句的子句都是一个隐式的语句块
```go
switch i := 2; i * 4 {
case 8:
j := 0
fmt.Println(i, j)
default:
// "j" is undefined here
fmt.Println(“default”)
}
// "j" is undefined here
```
## 2. 四种作用域的理解
变量的声明,除了声明其类型,其声明的位置也有讲究,不同的位置决定了其拥有不同的作用范围,说白了就是我这个变量,在哪里可用,在哪里不可用。
根据声明位置的不同,作用域可以分为以下四个类型:
- 内置作用域:不需要自己声明,所有的关键字和内置类型、函数都拥有全局作用域
- 包级作用域:必須函数外声明,在该包内的所有文件都可以访问
- 文件级作用域:不需要声明,导入即可。一个文件中通过import导入的包名,只在该文件内可用
- 局部作用域:在自己的语句块内声明,包括函数,for、if 等语句块,或自定义的 {} 语句块形成的作用域,只在自己的局部作用域内可用
以上的四种作用域,从上往下,范围从大到小,为了表述方便,我这里自己将范围大的作用域称为高层作用域,而范围小的称为低层作用域。
对于作用域,有以下几点总结:
- 低层作用域,可以访问高层作用域
- 同一层级的作用域,是相互隔离的
- 低层作用域里声明的变量,会覆盖高层作用域里声明的变量
在这里要注意一下,不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。
而一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。
## 3. 静态作用域与动态作用域
根据局部作用域内变量的可见性,是否是静态不变,可以将编程语言分为如下两种:
- 静态作用域,如 Go 语言
- 动态作用域,如 Shell 语言
具体什么是动态作用域,这里用 Shell 的代码演示一下,你就知道了
```python
#!/bin/bash
func01() {
local value=1
func02
}
func02() {
echo "func02 sees value as ${value}"
}
# 执行函数
func01
func02
```
从代码中,可以看到在 func01 函数中定义了个局部变量 value,按理说,这个 value 变量只在该函数内可用,但由于在 shell 中的作用域是动态的,所以在 func01中也可以调用 func02 时,func02 可以访问到 value 变量,此时的 func02 作用域可以当成是 局部作用域中(func01)的局部作用域。
但若脱离了 func01的执行环境,将其放在全局环境下或者其他函数中, func02 是访问不了 value 变量的。
所以此时的输出结果是
```shell
func02 sees value as 1
func02 sees value as
```
但在 Go 中并不存在这种动态作用域,比如这段代码,在func01函数中,要想取得 name 这个变量,只能从func01的作用域或者更高层作用域里查找(文件级作用域,包级作用域和内置作用域),而不能从调用它的另一个局部作用域中(因为他们在层级上属于同一级)查找。
```go
import "fmt"
func func01() {
fmt.Println("在 func01 函数中,name:", name)
}
func main() {
var name string = "Python编程时光"
fmt.Println("在 main 函数中,name:", name)
func01()
}
```
因此你在执行这段代码时,会报错,提示在func01中的name还未定义。
参考文章:https://studygolang.com/articles/12632
## 系列导读
---
[从零学习 Go 语言(01):一文搞定开发环境的搭建](https://studygolang.com/articles/27365)
[从零学习 Go 语言(02):学习五种变量创建的方法](https://studygolang.com/articles/27432)
[从零学习 Go 语言(03):数据类型之整型与浮点型](https://studygolang.com/articles/27440)
[从零学习 Go 语言(04):byte、rune与字符串](https://studygolang.com/articles/27463)
[从零学习 Go 语言(05):数据类型之数组与切片](https://studygolang.com/articles/27508)
[从零学习 Go 语言(06):数据类型之字典与布尔类型](https://studygolang.com/articles/27563)
[从零学习 Go 语言(07):数据类型之指针](https://studygolang.com/articles/27585)
[从零学习 Go 语言(08):流程控制之if-else](https://studygolang.com/articles/27613)
[从零学习 Go 语言(09):流程控制之switch-case](https://studygolang.com/articles/27660)
[从零学习 Go 语言(10):流程控制之for 循环](https://studygolang.com/articles/28120)
[从零学习 Go 语言(11):goto 无条件跳转](https://studygolang.com/articles/28472)
[从零学习 Go 语言(12):流程控制之defer 延迟语句](https://studygolang.com/articles/28515)
[从零学习 Go 语言(13):异常机制 panic 和 recover](https://studygolang.com/articles/28519)
[从零学习 Go 语言(14):Go 语言中的类型断言是什么?](https://studygolang.com/articles/29305)
[从零学习 Go 语言(15):学习 Go 语言的结构体与继承](https://studygolang.com/articles/29306)
[从零学习 Go 语言(17):Go 语言中的 make 和 new 有什么区别?](https://studygolang.com/articles/29315)
---
![](http://image.python-online.cn/20200321153457.png)
有疑问加站长微信联系(非本文作者))