defer
使用 defer 的最常见场景就是在函数调用结束后完成一些收尾工作,例如在 defer 中回滚数据库的事务:
func createPost(db *gorm.DB) error {
tx := db.Begin()
defer tx.Rollback()
if err := tx.Create(&Post{Author: "Draveness"}).Error; err != nil {
return err
}
return tx.Commit().Error
} 在使用数据库事务时,我们可以使用如上所示的代码在创建事务之后就立刻调用 Rollback 保证事务一定会回滚。哪怕事务真的执行成功了,那么调用 tx.Commit() 之后再执行 tx.Rollback() 也不会影响已经提交的事务。
作用域
func main() {
for i := 0; i < 5; i++ {
defer fmt.Println(i)
}
}
$ go run main.go
4
3
2
1
0 在函数返回之后,defer开始按照先入后出的顺序执行,由此看出defer的底层是由栈结构实现的。
func main() {
{
defer fmt.Println("defer runs")
fmt.Println("block ends")
}
fmt.Println("main ends")
}
$ go run main.go
block ends
main ends
defer runs 这个例子更详细说明了,defer不是在代码块结束之后执行的,而是在函数返回之后执行的。
预计算
Go 语言中所有的函数调用都是传值的,defer 虽然是关键字,但是也继承了这个特性。
func main() {
for i := 0; i < 5; i++ {
defer fmt.Println(i)
}
}
$ go run main.go
4
3
2
1
0 经过分析,我们会发现调用 defer 关键字会立刻对函数中引用的外部参数进行拷贝,所以 i 的结果不是在 main 函数退出之前计算的,而是在 defer 关键字调用时计算的,最终导致上述代码输出 4 3 2 1 0。
想要解决这个问题的方法非常简单,我们只需要向 defer 关键字传入匿名函数:
func main() {
for i := 0; i < 5; i++ {
defer func() { fmt.Println(i) }()
}
}
$ go run main.go
5
5
5
5
5 虽然调用 defer 关键字时也使用值传递,但是因为拷贝的是函数指针,所以会产生闭包,并打印出预期的结果。

京公网安备 11010502036488号