本篇文章对Go语言的一个重要特性——defer进行了简单的介绍,并表明了使用defer过程中需要注意的事项
1. defer的含义与作用
对于从其他语言切换过来的朋友们,在第一次接触defer时或许会和我一样感到奇怪又惊喜。首先必须明确一点:defer用于推迟函数的执行——直到调用defer的函数结束时(前)。这里有几个关键点我们一个一个来看:
- 推迟:推迟意味着在出现defer关键词时,其后紧跟的函数并不会立马执行,而是会被推迟——这与常规的顺序执行流程存在差异需要关注
- 调用defer的函数:在defer出现时一定存在两个主体,一个是调用defer的函数(也就是defer出现的scope所属的函数),另一个则是defer所指的函数
- “前”:被推迟的函数会在该scope结束前被执行
- 作用:
- 由于defer的特性,defer常被用于对关键资源的管理,例如关闭操作——对文件、套接字等需要执行闭合操作的对象可以在创建之后立刻通过defer做出关闭指示,避免后期忘记导致资源泄露等问题
- 承接上一个作用,由于对象的创建与关闭被组织在一小段程序中,增加了程序的可读性
2. defer的使用示例
- 下面展示一个最简单的defer使用示例:
package main
import "fmt"
func main() {
defer func() {
fmt.Println("This is the first!")
}() // 这里表示调用
fmt.Println("This is the second!")
}
输出为:
This is the second!
This is the first!
- 通过上述的简单小例子,大家应该能够体会defer的作用
3. defer函数的执行顺序为FILO
- 如果出现了多个defer functions,那么各个function执行的顺序是FILO(first in last out)——也就是说第一个defer的函数在最后一个被执行,下面我们通过一个小例子来进行说明:
package main
import "fmt"
func main() {
for i:=0; i<5; i++ {
defer func(index int) { fmt.Println(index) }(i)
}
}
输出为:
4
3
2
1
0
- 上述的小示例中以供出现了5个defered function,它们会输出对应的i,从顺序执行的角度来看,应该是以01234的顺序激活的各个函数,但是最终的输出却是43210,由此可以说明FILO的含义
4. 被defer作用的函数其参数是在defer时确定而不是在执行时确定
- 关于对这一个小点的说明,细心的小伙伴应该在第3小节就已经感到异常了,注意:虽然我们顺序激活了5个defer function,但是它们的参数都是 i ,而 i 在最终函数结束时应该为5,但是各个defered functions却仍然正确地输出了 i 的值,这在一定程度上说明了defer function的参数是在defer时确定的而不是在函数最终执行时确定的
- 下面我们再看一个更直观的小例子:
package main
import "fmt"
func main() {
s := "Hello World!!"
defer func(sentence string) { fmt.Println(sentence) } (s)
s = "Hello Golang!!"
}
输出为:
Hello World!!
上面的小例子非常的直观,初始时s的值为“Hello World!!”,紧接着我们defer了一个函数,并将s当做该函数的参数传递给它;之后我们在该函数执行前更改了s的值“Hello Golang!!”,最终函数执行,输出的却仍是之前的s值。其实看到这里大家应该也会和我一样想到一个更常见的特性——“闭包”,虽然有些差异但大家也可以联想记忆一下,总之都是记住其执行环境而不会丢失。
- 下面我们来看一个复杂一些的例子(这个例子来源于官方文档),咱们一起来分析一下:
package main
import "fmt"
func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a"))
fmt.Println("in a")
}
func b() {
defer un(trace("b"))
fmt.Println("in b")
a()
}
func main() {
b()
}
输出为:
entering: b
in b
entering: a
in a
leaving: a
leaving: b
大家可以尝试自己先分析一下再来看我的解析:
- 共定义了四个功能函数,
trace
、un
、a
以及b
- 主函数为
main
,其调用了b
b
首先defer了un
——defer时要确定该函数的参数值,该参数值为trace("b")
——trace
执行,输出① “entering: b” 并返回“b”b
接着输出②“in b”- 执行
a
——a
中defer了un
——defer时要确定该函数的参数值,该参数值为trace("a")
——trace
执行,输出③“entering: a”并返回“a” a
接着输出④“in a”a
即将结束,执行defer函数un("a")
——输出⑤“leaving: a”——a
退出返回b
b
即将结束,执行defer函数un("b")
——输出⑥“leaving: b”——b
退出返回main
main
结束执行——程序结束