defer语句用于延迟函数调用,每次会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行。延迟函数可以有参数:
延迟函数的参数在defer语句出现时就已确定下来(传值的就是当前值)
return先赋值(对于命名返回值),然后执行defer,最后函数返回
延迟函数执行按后进先出顺序执行
延迟函数可操作主函数的变量名返回值(修改返回值)
defer后面的表达式可以是func或者是method的调用,如果defer的函数为nil,则会panic
日常开发中,使用不当很容易造成意外的“坑”。下面我整理了下常规使用场景下,defer的问题可能的踩坑汇总。
defer 语句正好是在函数退出时执行的语句,所以使用 defer 能非常方便地处理资源释放、句柄关闭等问题。
defer中的变量会被提前捕获,后续的修改不会影响到已捕获的值,举个例子:
Go
package mainimport (
"fmt")func main() {
i := 0
defer fmt.Println("Defer运行值:", i)
i = 10 // 这里虽然修改了值,但是不会影响上面的i值
fmt.Println("最后输出值:", i)}
结果defer语句中打印的值是修改前的值。:
最后输出值: 10
Defer运行值: 0
在defer中修改具体变量名返回值时,会影响到函数的实际返回值,继续举个例子:
Go
package mainimport (
"fmt")func ShowDefer() {
fmt.Println("最后输出值:", deferValue())}func deferValue() (ret int) { // 注意这里返回res变量值
ret = 0
defer func() { // 会直接修改栈中对应的返回值
ret += 10
fmt.Println("Defer 运行值:", ret)
}()
ret = 2
fmt.Println("Ret重置值:", ret)
return //返回的ret最后是 其实是本次2+上面的ret+=10的结果}func main() {
ShowDefer()}//Ret重置值: 2//Defer 运行值: 12//最后输出值: 12
当函数为非具体名返回值时,defer无法影响返回值(因在return时,对应返回值已存入栈中),继续举个例子:
Go
package mainimport (
"fmt")func ShowDefer() {
fmt.Println("最后输出值:", deferValue())}func deferValue() int { // 非命名变量返回
ret := 0
defer func() {
ret += 10
fmt.Println("Defer 运行值:", ret)}()
ret = 2
return ret // 这里直接返回ret2}func main() {
ShowDefer()}//Defer 运行值: 12//最后输出值: 2
经过上面的实践理解,我们来看下下面的笔试题:
Go
package mainimport "fmt"func f() (result int) {
defer func() {
result *= 7}()
return 3}func main() {
fmt.Println(f())}
问题解析:这里return先给result赋值为3,之后执行defer,result变为21,最后返回21。
笔试题二Go
package mainimport "fmt"func f() int {
result := 3
defer func() {
result *= 7
}()
return result}func main() {
fmt.Println(f())}
问题解析:这里return确定返回值3,之后defer才修改result,最后函数返回return确定的返回值3。
笔试题三Go
package mainimport "fmt"// 多个deferfunc multiDefer() {
for i := 3; i > 0; i-- {
defer func(n int) {
fmt.Print(n, " ")
}(i)
}
for i := 33; i > 30; i-- {
defer fmt.Print(i, " ")
}}func main() {
multiDefer()}
问题解析:多个defer函数,按顺序逆序执行,这里输出31 32 33 1 2 3 。
笔试题四Go
package mainimport "fmt"var fun func() stringfunc main() {
fmt.Println("hello monkey")
defer fun()}
问题解析:由于这里的defer指定的func为nil,所以会panic 。
笔试题五Go
package mainimport "fmt"func main() {
for i := 3; i > 0; i-- {
defer func() {
fmt.Print(i, " ")
}()
}}
问题解析:这里是极度容易踩坑的地方,由于defer这里调用的func没有参数,等执行的时候,i已经为0(按3 2 1逆序,最后一个i=1时,i--的结果最后是0),所以这里输出3个0 。
如果还不太好理解?
Go
package mainimport "fmt"func main() {
for i := 3; i > 1; i-- { // 循环满足条件的是 3 2,
defer func() { // 因为func 没有参数,defer运行最后i--即 2-- 结果为 1
fmt.Print(i, " ") // 循环2次 结果均为 1
}()
}}//输出 1 1
按照常规的思维理解应该是这样:
Go
package mainimport "fmt"func main() {
for i := 3; i > 0; i-- {
defer func(i int) {
fmt.Print(i, " ")
}(i)
}}
感兴趣的朋友可以细细品下。