Go 中有几个比较特殊的关键字,如 defer,尤其 defer+panic+recover
的组合可以发挥出 java 中 try...catch...fanilly
的作用,功能非常强大,值的去深入学习。同时他们每个又有自身的特性,这一节我们先来理解一下 defer
。
defer
关键字在 go 中的使用率算是非常高的,类似于 finally
与 析构函数的作用,用来做方法的善后工作。
1. 怎么使用?
defer
后面会接受一个函数,但该函数不会立刻被执行,而是等到包含它的程序返回时(包含它的函数执行了return语句、运行到函数结尾自动返回、对应的goroutine panic),defer函数才会被执行。通常用于资源释放、打印日志、异常捕获等
下面看一段打开文件的🌰:
func OpenFile() {
file, err := os.Open("/home/demo/info.txt")
if err != nil {
fmt.Println(err)
}
// 这里将 defer 放在 err 判断的后面,而不是 os.Open() 的后面
// 因为若 err != nil ,文件打开是失败的, 没必要释放
// 若 err != nil, file 有可能为 nil ,这时候释放资源可能会导致程序崩溃
defer file.Close()
}
2. 理解 defer 的执行时机
下面有一段相对复杂的 defer 定义,这个方法里面有定义 4 个 defer , 分别以不同的方式来定义 defer 后的函数
func deferTest() {
fish := 0
defer func() {
fmt.Println("d1: ", fish)
}()
defer fmt.Println("d2: ", fish)
defer func(fish int) {
fish += 2
fmt.Println("d3: ", fish)
}(fish)
defer func() {
fmt.Println("d4: ", fish)
fish +=2
}()
fish++
}
这里可以先不要往下看答案,或者直接本地运行看结果,可以先猜猜每个结果的值,然后去跟真实结果做对比
如果是新手的话,可能会这么认为:d1: 1, d2: 1, d3: 3, d4: 3
, 这肯定是错的啦,要不然我还讲什么呢?哈哈
首先要明白一点:一个方法中声明了多个defer, 那么 defer 是按顺序从后往前执行的
根据这个规则,我们再猜想一次结果:d4: 1, d3:5, d2: 5, d1: 5
, 真的对么? 我们来看看真正的结果
d4: 1
d3: 2
d2: 0
d1: 3
这个结果是不是很奇怪,无论你怎么想都想不出来,因为它牵涉到 defer 声明时的一些特殊规则,defer 后面的函数对外部参数有两种引用方式:
- 参数传递:在defer声明时,即将值传递给defer,并缓存起来,调用defer的时候使用缓存值进行计算
- 直接引用:根据上下文确定当前值,作用域与外部变量相同
d1,d4
是在闭包里面直接引用,而 d2,d3
是经过参数传递来饮用的,因此 d2,d3
传递进去的初始值都是 0
func deferTest() {
// 1. 声明 fish=0
fish := 0
// 直接引用(闭包)
//
defer func() {
// 8. 打印 fish=3
fmt.Println("d1: ", fish) // 3
}()
// 参数传递
// 2. 传递 fish=0
// 7. 打印内部 fish 0
defer fmt.Println("d2: ", fish) // 0
// 参数传递(闭包)
// 3. 传递 fish=0
defer func(fish int) {
// 6. 内部 fish 0 + 2 =2
fish += 2 // fish 只作用于内部
// 7. 打印内部 fish
fmt.Println("d3: ", fish) // 2
}(fish) // 声明时传递, fish = 0
// 直接引用(闭包)
defer func() {
// 4. 打印 fish = 1
fmt.Println("d4: ", fish) // 1
// 5. fish = 3
fish +=2 // fish = 3, 作用域与外部的相同
}()
// 3. fish=1
fish++
}
3. defer 与 return 返回值的关系
再看一段代码,猜猜下面3个方法的返回值各是多少
func defer1() (res int) {
defer func() {
res ++
}()
return 10
}
func defer2() (res int) {
sb := 10
defer func() {
sb += 5
}()
return sb
}
func defer3() (res int) {
res = 2
defer func(res int) {
res += 2
fmt.Println("内部 res ", res)
}(res)
return 10
}
接下来我们一个个看,也许你会认为defer1
的结果是10,其实是11,在这里我们先明白一个概念:
return 语句并不是一条原子指令,有没有被震慑到!!!
有返回值的且带有 defer 函数的方法中, return 语句执行顺序:
1. 返回值赋值
2. 调用 defer 函数 (在这里是可以修改返回值的)
3. return 返回值
那 defer1
方法可以这样解析:
// 不是 10 , 是 11
func defer1() (res int) {
defer func() {
// 2. res = 10 + 1 = 11
res ++
}()
// 1. res = 10
// 3. return res
return 10
}
也就是说最后的值为 11
defer2
返回值不是15,而是10,这样解析
// 不是15, 是10
func defer2() (res int) {
// 1. sb = 10
sb := 10
defer func() {
// 3. sb = 15, 但是 res = 10
sb += 5
}()
// 2. res = sb = 10
// 4. return res(10)
return sb
}
defer3
的返回值不是12, 而是10, 解析如下:
// 不是12,是10
func defer3() (res int) {
// 1. res=2
res = 2
defer func(res int) {
// 3. 内部res为形参,不影响外边的值 res=2+2=4
res += 2
fmt.Println("内部 res ", res) // 4
}(res) // defer 参数的值是在声明的时候确定的,也就是只有 defer 之前的语句会影响这个值
// 2. res = 10
// 4. return res(10)
return 10
}