go语言中defer使用指南
目录
1.使用场景
2.执行顺序
3.for循环中的defer及defer中的闭包陷阱
4.defer与返回值的关系
5.总结
1.使用场景
在编程的时候,经常需要打开一些资源,比如数据库连接、文件、锁等,这些资源需要在用完之后释放掉,否则会造成内存泄漏。
在 Go 中 defer
一般用于异常处理、资源释放、文件关闭、解锁互斥量等操作。有一个编码好习惯就是,在初始化资源后,可以在后面紧跟一个defer函数取释放资源。
2.执行顺序
defer执行顺序是后进先出(底层是由链栈实现的)。
在函数内,defer与return的执行顺序是:defer在函数内return执行之后、函数退出之前执行。
3.for循环中的defer及defer中的闭包陷阱
闭包:一个匿名函数捕获了外部变量,就形成了闭包
在 Go 语言 中,如果在循环内执行 defer
语句,每次循环中执行的 defer
语句都会将其延迟的函数调用压入栈中。所有的 defer
调用都会在循环所在的函数返回之前按照后进先出(LIFO) 顺序执行。
还有超级重要,即在 defer
声明时,函数的参数会立即求值(这针对的是有参数传入的函数),而不是等到延迟调用时才求值。
重要的事情说三遍,defer
声明时函数的参数会立即求值,deferzai
声明时函数的参数会立即求值,defer
声明时函数的参数会立即求值!!!
什么是函数的参数会立即求值?
栗子1:
package mainimport "fmt"func main() {for i := 0; i < 3; i++ {defer func(val int) { // 闭包,捕获了外部变量ifmt.Println(val) // 打印传递的值}(i) // 在这里立即捕获当前的 i 值,}fmt.Println("Loop ended")
}
输出结果:
Loop ended
2
1
0
栗子2:
package mainimport "fmt"func main() {for i := 0; i < 3; i++ {defer func() { // 闭包,捕获了外部变量ifmt.Println(i) // 直接引用 i,持有的是引用,而非具体的值}()}fmt.Println("Loop ended")
}
输出结果:
Loop ended
3
3
3
造成这种结果的原因:
闭包捕获的是引用,而非具体的值!!!这一点很重要
栗子1:通过参数传递固定
i
的值,每个匿名函数保存的是当前循环中i
的值。因此,输出结果是2, 1, 0
。栗子2:匿名函数捕获的是循环变量
i
的引用,所有匿名函数访问的都是同一个变量i
,而此时i
的值已经是3
。因此,输出结果是3, 3, 3
。
4.defer与返回值的关系
栗子3:
func foo() int { //返回值未命名i := 0defer func() { i++ }()return i
} // 返回0
栗子4:
func bar() (i int) { // 返回值命名了i = 0defer func() { i++ }()return i
} // 返回1
造成这种结果的原因是:
defer可以修改命名返回值,不能修改匿名返回值!!!
在
foo
函数中,由于使用的是匿名返回值,i
的值在return i
的时候就已经确定为0。即使随后的defer
函数将i
增加到1,这个改变不会反映在已经确定的返回值上,所以foo
函数返回0。在
bar
函数中,由于使用了命名返回值i
,defer
函数能够访问并修改这个命名返回值。因此,在return i
之后但函数真正返回之前,defer
函数将i
从0增加到1,导致bar
函数最终返回1。
5.总结
- defer 一般用于异常处理、资源释放、文件关闭、解锁互斥量
- defer 在函数内return执行之后、函数退出之前执行
- defer 声明时函数的参数会立即求值(针对有参数传入的函数)
- 闭包捕获的是引用,而非具体的值
- defer 可以修改命名返回值,不能修改匿名返回值