原文链接:
defer 语句用于延迟函数调用。每次将一个函数压入堆栈时,都会在函数返回之前将延迟的函数取出并执行。延迟函数可以有参数:
在日常开发中,使用不当很容易造成意想不到的“坑”。下面我对常规使用场景中defer问题可能存在的陷阱进行了总结。
释放资源
defer 语句用于延迟函数调用。每次将一个函数压入堆栈时,都会在函数返回之前将延迟的函数取出并执行。延迟函数可以有参数:
defer语句正是函数退出时执行的语句,所以使用defer可以轻松处理资源释放、句柄关闭等问题。
package main import ( "fmt" "os" ) func fileSize(filename string) int64 { f, err := os.Open(filename) if err != nil { return 0 } // 延迟调用Close, 此时Close不会被调用 defer f.Close() info, err := f.Stat() if err != nil { // defer机制触发, 调用Close关闭文件 return 0 } size := info.Size() // defer机制触发, 调用Close关闭文件 return size } func main() { fmt.Println(fileSize("demo.txt")) }
变量捕获
defer中的变量会提前被捕获,后续修改不会影响捕获的值,例如:
package main import ( "fmt" ) func main() { i := 0 defer fmt.Println("Defer运行值:", i) i = 10 // 这里虽然修改了值,但是不会影响上面的i值 fmt.Println("最后输出值:", i) }
生成的 defer 语句中打印的值是修改前的值。 :
最后输出值:10
延迟运行值:0
变量名返回值
在defer中修改具体变量名的返回值时,会影响函数的实际返回值。继续举例:
package main import ( "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不能影响返回值(因为返回时对应的返回值已经入栈),继续举例:
package main import ( "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
经过以上实战了解后,我们再来看看以下笔试题:
笔试题 1
package main import "fmt" func f() (result int) { defer func() { result *= 7 }() return 3 } func main() { fmt.Println(f()) }
问题分析:这里return先给result赋值3,然后执行defer,result变成21,最后返回21。
笔试题2
package main import "fmt" func f() int { result := 3 defer func() { result *= 7 }() return result } func main() { fmt.Println(f()) }
问题分析:这里return确定返回值3,然后defer修改结果,最后函数返回return确定的返回值3。
书面问题 3
package main import "fmt" // 多个defer func 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。
笔试题 4
package main import "fmt" var fun func() string func main() { fmt.Println("hello monkey") defer fun() }
问题分析:由于这里defer指定的func为nil,所以会panic。
问题 5
package main import "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 个零。
如果你不明白呢?
package main import "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
按照常规思路,应该是这样的:
package main import "fmt" func main() { for i := 3; i > 0; i-- { defer func(i int) { fmt.Print(i, " ") }(i) } }
感兴趣的朋友可以细细品下。
暂无评论内容