Variable Shadowing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

var a = 11

func foo(n int) {
// 局部变量遮蔽了同名的包级变量
a := 1
a += n
}

func main() {
fmt.Println(a) // 11
foo(5)
fmt.Println(a) // 11
}

Block

Explicit Block

大括号包裹

1
2
3
4
5
6
7
8
func foo() { // Block 1
{ // Block 2
{ // Block 3
{ // Block 4
}
}
}
}
  1. Explicit Block 是包裹在大括号内部的声明和语句序列
  2. 如果一对大括号内没有任何声明和其它语句,为空 Block
  3. Block 支持嵌套

Implicit Block

无法通过大括号识别

image-20231011220321917

Implicit Block Desc
Universe Block 所有的 Go 源码都在这个隐式代码块中
Package Block 每个 Go 包都对应一个隐式包代码块
这个包代码块包含了该包中所有的 Go 源码(分散在多个源文件中)
File Block 每个 Go 源文件都对应着一个文件代码块
如果 Go 包有多个源文件,就会对应多个文件代码块
Control statement(if / for / switch) 每个控制语句都在自己的隐式代码块中
控制语句的隐式代码块在其显式代码块的外面
switch 或者 select 的 case/default 子s句 每个子句都是一个隐式代码块

Scope

  1. 作用域是针对标识符的,不局限于变量
  2. 每个标识符都有作用域
    • 即一个标识符在被声明后可以被有效使用源码区域
  3. 作用域是一个编译期的概念
    • 编译器在编译过程中会对每个标识符的作用域进行检查
    • 如果在标识符作用域外使用该标识符,编译报错
  4. 可以使用代码块的概念来划定标识符作用域
    • 原则:声明在外层代码块中的标识符,其作用域包括所有内层代码块
    • 同时适用于Explicit BlockImplicit Block

Universe Block

开发者并不能声明 Universe Block 的标识符,因为这一区域是 Go 用于预定义标识符

image-20231011222446605

预定义标识符并不是关键字,可以在内层代码块中声明同名的标识符

1
2
3
4
5
6
7
package main

var make = 1

func main() {
_ = make(map[string]string)
}
1
2
3
4
$ go build
# github.com/zhongmingmao/go101
./main.go:6:10: cannot call non-function make (type int), declared at ./main.go:3:5
./main.go:6:11: type map[string]string is not an expression

Package Block

  1. 包顶层声明的常量变量类型函数(不包括方法)对应的标识符的作用域是包代码块
  2. 当 A 包导入 B 包后,A 包仅可以使用 B 包的导出标识符(Exported Identifier)
  3. Exported Identifier
    • 标识符声明在包代码块中,或者是一个字段名或者函数名
    • 第一个字符是大写的 Unicode 字符

File Block

导入的包名 属于 File Block - 如果 A 包有两个源码文件,都依赖了 B 包的标识符,那么两个源码文件都需要导入 B 包

Control statement

if

1
2
3
4
5
6
7
8
func main() {
if a := 1; false {
} else if b := 2; false {
} else if c := 3; false {
} else {
println(a, b, c) // 1 2 3
}
}

将 Implicit Block 等价转换为 Explicit Block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func main() {
{ // 等价于第1个 if 的隐式代码块
a := 1 // 变量 a 的作用域开始于此
if false {

} else {
{ // 等价于第2个 if 的隐式代码块
b := 2 // 变量 b 的作用域开始于此
if false {

} else {
{ // 等价于第3个 if 的隐式代码块
c := 3 // 变量 c 的作用域开始于此
if false {

} else {
println(a, b, c)
}
// 变量 c 的作用域结束于此
}
}
// 变量 b 的作用域结束于此
}
}
// 变量 a 的作用域结束于此
}
}

变量是标识符的一种,因此标识符的作用域规则同样适用于变量

规避遮蔽

内层变量遮蔽外层同名变量

main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"errors"
"fmt"
)

var year = 2020

func checkYear() error {
err := errors.New("wrong year")

switch year, err := getYear(); year { // 遮蔽包级变量 year + 遮蔽局部变量 err
case 2020:
fmt.Println("it is", year, err)
case 2021:
fmt.Println("it is", year)
err = nil
}

fmt.Println("after check, it is", year)
return err
}

type new int // 遮蔽预定义标识符 new

func getYear() (new, error) {
year := int16(2021)
return new(year), nil
}

func main() {
err := checkYear()
if err != nil {
fmt.Println("call checkYear error:", err)
return
}

fmt.Println("call checkYear ok")
}
1
2
3
4
$ go run main.go
it is 2021
after check, it is 2020
call checkYear error: wrong year

静态检查(辅助检测)

  1. go vet 可以对 Go 源码进行一系列的静态检查
  2. Go 1.14 之前,默认支持变量遮蔽检查,在 Go 1.14 之后,需要单独安装插件来进行