基本概念
Go语言的延迟语句defer有哪些特点?通常在什么情况下使用?
Go语言的延迟语句(defer statement)具有以下特点:
通常情况下,延迟语句在以下情况下使用:
延迟语句的使用可以提高代码的可读性和可维护性,同时确保资源的释放和清理操作按照逆序进行。它是Go语言中一种常用的编程技巧,用于处理资源管理和错误处理等场景。
避坑之旅
实际开发中defer的使用并不像前面介绍的这么简单,defer用不好,会陷入泥潭。
下面我从两个角度带大家避坑:
拆解延迟语句
避免陷入泥潭的关键是必须深刻理解下面这条语句:
return xxx
上面这条语句经过编译之后,实际上生成了三条指令:
1)返回值 =xxx。
2)调用 defer 函数。
3)空的 return。
第1和第 3 步是return语句生成的指令,也就是说return并不是一条原子指令;
第2步是 defer 定义的语句,这里可能会操作返回值,从而影响最终结果。
下面来看两个例子,试着将return 语句和 defer语句拆解到正确的顺序。
第一个例子:
func f()(r int){
t:=5
defer func(){
t=t+5
}()
return t
}
拆解后:
func f()(r int){
t:=5
//1,赋值指令
r=t
// 2.defer 被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
func(){
t=t+5
}()
//3.空的 return 指令
return
}
这里第二步实际上并没有操作返回值r,因此,main函数中调用f()得到5。
图片
第二个例子:
func f()(r int){
defer func(r int){
r=r+5
}(r)
return 1
}
拆解后:
func f() (r int) {
//1.赋值
r=1
//2.这里改的r是之前传进去的r,不会改变要返回的那个r值
func(r int) {
r=r+5
}(r)
// 3. 空的 return
return
}
第二步,改变的是传值进去的r,是形参的一个复制值,不会影响实参r。因此,main函数中需要调用f()得到1。
图片
在使用匿名函数和非匿名函数作为defer的参数时,主要区别在于对函数参数的传递和作用域的影响:
产生这种区别的原因是,匿名函数和非匿名函数在定义和作用域上的差异。匿名函数可以直接在defer语句中定义,可以访问外部函数的变量,而非匿名函数需要先定义好,然后作为参数传递。这种设计灵活性使得开发者可以根据具体的需求选择合适的方式来使用defer语句。
举例来说
当使用匿名函数作为defer的参数时,可以在defer语句中直接定义匿名函数,并访问外部变量。
以下是一个示例代码:
package main
import "fmt"
func main() {
x := 10
defer func() {
fmt.Println("Deferred anonymous function:", x)
}()
x = 20
fmt.Println("Before return:", x)
}
在上述示例中,匿名函数作为defer的参数,可以访问外部变量x。在函数返回之前,defer语句中的匿名函数会执行,并打印出x的值。
输出结果如下:
图片
当使用非匿名函数作为defer的参数时,需要先定义好函数,然后将函数名作为defer的参数传递。
以下是一个示例代码:
package main
import "fmt"
func main() {
x := 10
defer printX(x)
x = 20
fmt.Println("Before return:", x)
}
func printX(x int) {
fmt.Println("Deferred function:", x)
}
在上述示例中,printX函数作为defer的参数传递,函数定义在main函数之后。
在函数返回之前,defer语句中的printX函数会执行,并打印出传递的参数x的值。输出结果如下:
图片
总结一下
通过以上示例,我们可以明确体现出使用匿名函数和非匿名函数作为defer的参数的区别。
匿名函数可以直接在defer语句中定义,并访问外部变量,而非匿名函数需要先定义好函数,然后将函数名作为参数传递。
通过前面带着大家拆解了defer的语句的执行,相信大家可以更好的理解了。
本文转载自微信公众号「 程序员升级打怪之旅」,作者「 王中阳Go」,可以通过以下二维码关注。
转载本文请联系「 程序员升级打怪之旅」公众号。