defer的使用场景
defer关键字主要是用于延迟调用
- 可以放在函数的任何位置
- defer函数的传入参数在定义的时候就确定
- 函数返回之前执行
- 可以同时设置多个defer函数,多个defer函数执行类似于栈的方式,先进后出
- 常用网络连接、数据库连接的释放
- 和recover一起处理panic
资源的释放
通过defer延迟调用机制,可以简洁优雅处理资源回收问题,避免在复杂代码逻辑下,遗漏资源回收的相关问题
- 关闭db连接池
// db connection pool closer
defer func(db *gorm.DB) {
pool, _ := db.DB()
_ = pool.Close()
}(db)
和revocer一起处理panic
defer另外一个常用的地方就是处理程序panic的时候,当Go语言用panic来抛出异常,用recover来捕获异常,所以当我们程序出现异常的时候,需要知道发生了什么异常,可以用defer revover来捕获异常
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
a := 1
b := 0
fmt.Println(a / b)
}
打印结果
runtime error: integer divide by zero
return 和 defer 的爱恨情仇
函数的return并非一个原子操作
- 分别三步:
- 设置返回值
- 执行defer语句
- 将结果返回
第一段感情
func Run() {
var num = 1
defer fmt.Println("num:",num)
num++
}
func main() {
Run()
}
num: 1
- defer函数的传入参数在定义的时候就确定:defer fmt.Println("num:",num)在定义的时候参数num就已经确定了,num就确定为1,后面的改变不会影响此时的打印
第二段感情
func getArr(arr *[2]int) {
for i := range arr {
fmt.Println(arr[i])
}
}
func Run() {
var arr = [2]int{1, 2}
defer getArr(&arr)
arr[0] = 3
}
func main() {
Run()
}
- 有同学就疑惑了,这里不是defer在定义的时候参数不会变化吗?
- 确实没有变化,这里传的是地址,地址没有变化,但是地址里面的内容改变了
第三段感情
func Run() (res int){
num := 1
defer func() {
res++
}()
return num
}
func main() {
Run()
fmt.Println(res)
}
- 第一步设置返回值为1,然后执行defer语句将res+1,最终将res返回,所以打印出来的值为2
第三段感情
func Run() int {
var num int
defer func() {
num++
}()
return 1
}
func main() {
res := Run()
fmt.Println(res)
}
- 与上面的区别是匿名的,但是同样是return的逻辑,首先设置返回值res = 1,然后执行defer语句 num++,第三步将res返回,所以打印出来的值为1
第四段感情
func Run() int {
var num = 1
defer func() {
num++
}()
return num
}
func main() {
res := Run()
fmt.Println(res)
}
- 首先设置res等于num,所以res的值为1,第二步将执行defer将num+1,此时num为2,但是res为1,第三步将res返回,所以最终结果是1
第五段感情
func Run() (res int) {
var num = 1
defer func() {
num++
}()
return num
}
func main() {
res := Run()
fmt.Println(res)
}
- 分析结果:首先设置res = num,然后执行num++,最终返回res = 1
总结
- defer 定义的延迟函数的参数在 defer 语句的时候就已经确定下来
- return 不是原子操作
- 1.设置返回值
- 2.执行defer语句
- 3.将结果返回