defer原理
defer语句会进入一个栈,在函数return前按先进后出的顺序执行(原因是后面定义的函数可能会依赖前面的资源)
在defer函数定义时,对外部变量的引用是有两种方式的:
- 作为函数参数,则在defer定义时就把值传递给defer,并被cache起来;
- 作为闭包引用,则会在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 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 }
|
执行结果是:
第四个defer语句是闭包,引用外部函数的n, 最终结果是3
第三个defer语句是闭包同第三个
第二个defer语句,n是引用,最终求值是3
第一个defer语句,对n直接求职,开始的时候n=0,所以最后是0
defer返回
返回语句 return xxx
编译后编程三条命令
- 返回值 = xxx
- 调用defer函数
- 空的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
参考链接
- https://qcrao.com/2019/02/12/how-to-keep-off-trap-of-defer/