Go语言defer分析
什么是defer?
defer语句是专门在函数结束以后做一些清理工作的。我们先举一个例子来更好的理解,现在有一个函数,它的作用是把一个文件内容拷贝到另一个文件。
func CopyFile(dstName string, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
dst, err := os.Create(dstName)
if err != nil {
return
}
written, err = io.Copy(dst, src)
src.Close()
dst.Close()
return
}
以上代码是可以正常执行的,但是存在一个问题,如果os.Create执行失败,那么就无法执行到文件资源的Close函数。进程每打开一个文件就会占用一个文件描述符,而在系统当中,文件描述符是有上限的,可以通过ulimit -n
查看,如果资源没有被及时释放,会出现资源浪费的情况。如果打开文件过多,也会出现Too many open files
的提示。这个时候就需要通过defer来解决问题了,代码如下。
func CopyFile(dstName string, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close()
written, err = io.Copy(dst, src)
return
}
defer语句会在return参数设置之后、函数返回给调用者之前执行,这样就不再担心文件资源无法被Close了。
defer的三个规则
规则一:被deferred的函数参数在defer时确定
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
我们通过以上代码来理解这个拗口的规则,如果根据官方的定义来理解这段代码,变量i的值铁定为1,但实际执行的结果不是1,却是0。
Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred.
那我们再重新来理解这个规则,变量i是在逐行执行到defer语句的时候就已经确定了值,这个时候变量i还没有进行自增,所以输出的结果应该是0而不是1。
规则二:被deferred函数执行顺序遵循LIFO原则
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
LIFO全称为Last In First Out
,意为后进先出,栈是一种典型的LIFO数据结构。defer也是如此,拿以上代码为例,先后遍历了四次,也就是做了四次压栈操作。同理,在函数return之前,就会逐一出栈,倒序执行defer语句,所以上述代码输出内容为3210
。
规则三:deferred函数可以读取和修改函数的返回值
func c() (i int) {
defer func() { i++ }()
return 1
}
我们定义一个defer函数,将变量i自增,那么最终变量i的值为2。我们从官方文档中可以看出,defer语句是在函数设置返回值后,且在返回给主调函数前执行的,根据这个思路,c函数已经return了1,这个时候执行了defer函数,将返回的结果1进行了自增,然后返回给主调函数,这个时候主调函数拿到的值就是2了。
deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller