转自个人博客chinazt.cc

在golang当中,defer代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用。因此,defer通常用来释放函数内部变量。

为了更好的学习defer的行为,我们首先来看下面一段代码:

func CopyFile(dstName, 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)
dst.Close()
src.Close()return}

这段代码可以运行,但存在'安全隐患'。如果调用dst, err := os.Create(dstName)失败,则函数会执行return退出运行。但之前创建的src(文件句柄)没有被释放。 上面这段代码很简单,所以我们可以一眼看出存在文件未被释放的问题。 如果我们的逻辑复杂或者代码调用过多时,这样的错误未必会被及时发现。 而使用defer则可以避免这种情况的发生,下面是使用defer的代码:

func CopyFile(dstName, 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()return io.Copy(dst, src)
}

通过defer,我们可以在代码中优雅的关闭/清理代码中所使用的变量。defer作为golang清理变量的特性,有其独有且明确的行为。以下是defer三条使用规则。

规则一 当defer被声明时,其参数就会被实时解析

我们通过以下代码来解释这条规则:

func a() {i := 0defer fmt.Println(i)
i++
return
}

上面我们说过,defer函数会在return之后被调用。那么这段函数执行完之后,是不用应该输出1呢?

读者自行编译看一下,结果输出的是0. why?

这是因为虽然我们在defer后面定义的是一个带变量的函数: fmt.Println(i). 但这个变量(i)在defer被声明的时候,就已经确定其确定的值了。 换言之,上面的代码等同于下面的代码:

func a() {
i := 0defer fmt.Println(0) //因为i=