Go-10-defer

defer原理

defer语句会进入一个栈,在函数return前按先进后出的顺序执行(原因是后面定义的函数可能会依赖前面的资源)

在defer函数定义时,对外部变量的引用是有两种方式的:

  1. 作为函数参数,则在defer定义时就把值传递给defer,并被cache起来
  2. 作为闭包引用,则会在defer函数真正调用时根据整个上下文确定当前的值

defer后的语句在执行的时候,函数调用的参数会被复制。如果此变量是一个“值”,那么就和定义的时候是一致的。如果此变量是一个“引用”,那么就可能和定义的时候不一致。

1
2
3
4
5
6
7
8
9
func main() {
var whatever [3]struct{}

for i := range whatever {
defer func() {
fmt.Println(i)
}()
}
}

执行结果为:

1
2
3
2
2
2

因为闭包是根据上下文确定当前的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type number int

func (n number) print() { fmt.Println(n) }
func (n *number) pprint() { fmt.Println(*n) }

func main() {
var n number

defer n.print()
defer n.pprint()
defer func() { n.print() }()
defer func() { n.pprint() }()

n = 3
}

执行结果是:

1
2
3
4
3
3
3
0

第四个defer语句是闭包,引用外部函数的n, 最终结果是3

第三个defer语句是闭包同第三个

第二个defer语句,n是引用,最终求值是3

第一个defer语句,对n直接求职,开始的时候n=0,所以最后是0

defer返回

返回语句 return xxx编译后编程三条命令

  1. 返回值 = xxx
  2. 调用defer函数
  3. 空的return
1
2
3
4
5
6
7
func f() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}

可以看成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func f() (r int) {
t := 5

// 1. 赋值指令
r = t

// 2. defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
func() {
t = t + 5
}

// 3. 空的return指令
return
}

结果为5

第二例子:

1
2
3
4
5
6
func f() (r int) {
defer func(r int) {
r = r + 5
}(r)
return 1
}

可以看成:

1
2
3
4
5
6
7
8
9
10
11
12
func f() (r int) {
// 1. 赋值
r = 1

// 2. 这里改的r是之前传值传进去的r,不会改变要返回的那个r值
func(r int) {
r = r + 5
}(r)

// 3. 空的return
return
}

结果为 1

参考链接

  1. https://qcrao.com/2019/02/12/how-to-keep-off-trap-of-defer/