defer是Go语言中的一个关键字(延迟调用),一般用于释放资源、数据库连接、关闭文件句柄、释放锁等
1. defer的执行顺序
当函数中含有多个defer语句时,按照先进后出的方式执行
func main() {
var whatever [5]struct{}
for i := range whatever {
defer fmt.Println(i)
}
}
/*
4
3
2
1
0
*/
所有的defer语句都会被放入栈中,在入栈的时候会进行相关的值拷贝,也就是所谓的对应参数实时解析
2. 声明defer时,对应的参数会实时解析
func main() {
i := 1
fmt.Println("1 i =", i)
defer fmt.Print(i)
i++
fmt.Println("2 i =", i)
}
/* output
1 i = 1
2 i = 2
1
*/
简单一句话就是,defer语句中的变量,在defer声明时就决定了
defer后面跟无参函数,有参函数,方法时:
func test(a int) { //无返回值函数
defer fmt.Println("1、a =", a) //方法
defer func(v int) { fmt.Println("2、a =", v) }(a) //有参函数
defer func() { fmt.Println("3、a =", a) }() //无参函数
a++
}
func main() {
test(1)
}
/* output
3、a = 2
2、a = 1
1、a = 1
*/
方法中的参数a,有参函数中的v,会请求参数,将参数解析带入,所以输出都是1,执行到a++后,a变成2,3个defer语句以后声明先执行的顺序执行,无参函数中使用的a现在已经是2
3. 可读取函数返回值(return返回机制)
defer、return、返回值三者的执行逻辑是:
- return最先执行,return负责将结果写入返回值中
- 接着defer进行收尾工作
- 最后函数携带当前返回值(可能和最初返回值不相同)退出
ps:当defer放在return后面时,不会被执行
无名返回值
func a() int {
var i int
defer func() {
i++
fmt.Println("defer2:", i)
}()
defer func() {
i++
fmt.Println("defer1:", i)
}()
return i
}
func main() {
fmt.Println("return:", a())
}
/*
defer1: 1
defer2: 2
return: 0
*/
返回值由变量i赋值,相当于返回值=i=0。第二个defer中i++后i=2,所以最终i的值是2,但是返回值已经在被记录过了,即使后续操作i也不会影响返回值,最终返回值返回0
有名返回值
func a() (i int) {
defer func() {
i++
fmt.Println("defer2:", i)
}()
defer func() {
i++
fmt.Println("defer1:", i)
}()
return i
}
func main() {
fmt.Println("return:", a())
}
/*
defer1: 1
defer2: 2
return: 2
*/
函数已经指明返回值就是i,所以后续对i的操作都相当于在修改返回值,所以最终函数的返回值是2
函数返回值为地址
func c() *int {
var i int
defer func() {
i++
fmt.Println("defer2:", i)
}()
defer func() {
i++
fmt.Println("defer1:", i)
}()
return &i
}
func main() {
fmt.Println("return:", *(c()))
}
/*
defer1: 1
defer2: 2
return: 2
*/
此时的返回值是一个指针地址,这个指针等于&i,相当于指向变量i所在的地址,两个defer语句都对i进行了修改,那么返回值指向的地址内容也发生了变化,所以最终的返回值是2
综合来看一个例子:
func f() (r int) {
defer func() {
r += 5
}()
return 1
}
func main() {
fmt.Println(f())
}
// 6
解释:由于return最先执行且返回值是有名的,此时r=1,接着执行defer后的函数此时r=5+1=6,最后返回函数返回值r=6
4. defer和panic
go语言中没有try catch这样对于运行时异常进行捕获和处理的语句,但是可以通过panic/recover模式来处理错误,panic可以在任何地方引发,但是recover只有在defer调用的函数中才有效。另外,defer一定要在可能引发panic的语句之前定义
func f() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Panic...")
}
}()
panic("this is a panic")
}