本文共 2805 字,大约阅读时间需要 9 分钟。
defer后面的函数在defer语句所在的函数执行结束的时候会被调用,用来做一些收尾工作,当然它比面向对象语言中的析构函数
强大,因为它还有其他的任务,比如,异常捕获,资源释放,修改函数返回值。
defer在实际应用中非常常见,因此需要对其有一个透彻的理解。
defer后面必须是函数调用语句,不能是其他语句,否则编译器会出错。
func a() { i := 0 defer fmt.Println(i) i++ return}
defer语句中的fmt.Println()
的参数i值在defer出现时就已经确定下来,实际上是拷贝了一份。
后面对变量i的修改不会影响fmt.Println()
函数的执行,仍然打印 0。
再比如:
func tt2() { a := []int{ 1, 2} defer func(x []int){ fmt.Println(x) // [1 2] 复制了切片 }(a) a = append(a, 3, 4, 5)}
对于指针类型参数,规则仍然适用,只不过延迟函数的参数是一个地址值,这种情况下,如果指针指向的变量值发生了变化,那么会影响到延迟函数的指向结果。
func tt3() { a := &Person{ "Rao", 12} defer func(p *Person){ fmt.Println(p.Name) // xiao }(a) a.Name = "xiao"}
由以上例子可知,defer语句在定义的时候会复制实参的值到临时变量,临时变量的值就此固定下来,要么是值要么是指针。
注意这里说的是延迟函数的参数,比如:
defer func(x int) { fmt.Println(x)}(i)
如果延迟函数不需要传参呢:
defer func() { fmt.Println(i)}()
此时的变量 i 是没有固定下来的。
如果延迟函数的参数是另一个函数 ff 的返回值,那么在定义 defer 语句的时候,ff 函数会被执行,以拿到返回值,确定延迟函数的额参数。
这个规则很好理解,定义defer类似于入栈操作,执行defer类似于出栈操作。
设计defer的初衷是简化函数返回时资源清理的动作,资源往往有依赖顺序,比如先申请A资源,再跟据A资源申请B资源,跟据B资源申请C资源,即申请顺序是:A–>B–>C,释放时往往又要反向进行。这就是把deffer设计成FIFO的原因。
每申请到一个用完需要释放的资源时,立即定义一个defer来释放资源是个很好的习惯。
定义defer的函数,即主函数可能有返回值,返回值有没有名字没有关系,defer所作用的函数,即延迟函数可能会影响到返回值。
若要理解延迟函数是如何影响主函数返回值的,只要明白函数是如何返回的就足够了。
有一个事实必须要了解,关键字return不是一个原子操作,实际上return只代理汇编指令ret,即将跳转程序执行。
比如语句return i,实际上分两步进行,即将i值存入栈中作为返回值,然后执行跳转,而defer的执行时机正是跳转前,所以说defer执行时还是有机会操作返回值的。
举个实际的例子进行说明这个过程:
func deferFuncReturn() (result int) { i := 1 defer func() { result++ }() return i}
返回值为 2
return语句执行过程:
将 i 赋值给 result; 然后执行 defer 函数; result++再比如:
func tt5() int { i := 1 defer func() { i++ }() return i}
返回值为 1
return语句执行过程:
将 i 赋值给 临时变量temp; 然后执行 defer 函数; i++关于主函数有不同的返回方式,但返回机制就如上面介绍所说,只要把return语句拆开都可以很好的理解。
同样的,对于指针变量,也可以这样分析。
func tt6() *Person { a := &Person{ "Rao", 12} defer func(p *Person){ p.Name = "xiao" }(a) return a}
返回值 &{xiao 12}
defer后面的函数在defer语句所在的 函数执行结束
并且 函数返回前
的时候会被调用。
设想有一个需要长期运行的函数,其中有无限循环语句,在循环体内不断的创建资源(或分配内存),并用defer语句确保释放。由于函数一直运行没有返回,所有defer语句都得不到执行,循环过程中创建的大量短暂性资源一直积累着,得不到回收。而且,系统为了存储defer列表还要额外占用资源,也是持续增加的。这样下去,过不了多久,整个系统就要因为资源耗尽而崩溃。
例如:
func tt7() { for { f, err := os.Open(...) defer f.Close() ... }}
由于 tt7 函数永远不会返回,所以 defer 语句永远不会被执行到。
针对这种情况,需要做一点优化,使系统资源得到释放,在defer外层套一个闭包函数就可以了,如下:
func tt7() { for { func(){ f, err := os.Open(...) defer f.Close() ... }() }}
一旦 defer 注册成功,那么后面即使有 panic 也不会影响其执行。
func main() { re := ttss() fmt.Println(re)}func ttss() (result bool) { defer func() { if err := recover(); err != nil { fmt.Println(err) } }() result = tts() return result}func tts() (result bool) { panic("xxx") return true}
打印
xxxfalse
转载地址:http://vjaui.baihongyu.com/