目录
- defer是什么
- defer定义
- 注意事项
- 多个defer的执行顺序
- 延迟参数传入时机
- 基本语法
- 小结
- 声明时机和执行时机
- 声明时机
- 执行时机
- defer与return的区别
- 函数返回值
- 不具名返回
- 具名返回
defer是什么
defer是go中一种延迟调用机制。
执行时机
defer后面的函数只有在当前函数执行完毕后才能执行。
执行顺序
将延迟的语句按defer的逆序进行执行,也就是说先被defer的语句最后被执行,最后被defer的语句,最先被执行,通常用于释放资源。
多个defer本质就是用栈存储,先进后出。
defer定义
//最后不要忘记函数调用 //使用匿名函数 defer func (形参列表){ }(实参) //最后不要忘记调用函数 //使用事先声明的函数 defer 函数名(实参) //最后不要忘记调用方法 defer 方法名(形参列表){ }(实参)
defer的功能一般是用于释放资源。
defer后面的函数是可以有返回值的,但是一般没有作用。
注意事项
方法或函数必需调用
//报错:未调用函数 defer func(){ fmt.Println("a") }
注意声明顺序
虽然defer的执行时机在函数结束后,但是声明的时候使用的变量或者参数得是函数内在defer声明之前就定义好的。
//报错:student未定义 defer student.GetName(2) var student Student //报错:age未定义 defer func(a int){ fmt.Printf("年龄为%d\n",age) }(age) age := 15
多个defer的执行顺序
多个defer出现的时候,它会把defer之后的函数压入一个栈中延迟执行,也就是先进后出(LIFO).
写在前面的defer会比写在后面的defer调用的晚。下面通过一个示例看一下:
package defer_knowledge import "fmt" type Student struct{ Name string } func (s Student) GetName(n int){ fmt.Printf("这是第%d个defer\n",n) } func sayHello(n int){ fmt.Printf("这是第%d个defer\n",n) } //验证defer的执行顺序 func DeferFirst(){ fmt.Println("hello world") var age int = 25 defer func (){ fmt.Println("我是第1个defer") }() age++ /* 虽然defer的执行时机在return之后 但是声明defer时,结构体实例要先声明,否则无法访问结构体实例方法 */ var student Student defer student.GetName(2) defer func (){ fmt.Println("我是第3个defer") }() defer sayHello(4) }
结果
这是第4个defer
我是第3个defer这是第2个defer我是第1个defer图示
延迟参数传入时机
基本语法
注意事项
defer函数的入参参数是在defer函数声明时决定的。例如
package defer_knowledge import "fmt" //defer的参数是声明时传入的 func DeferParams(){ var age = 10 defer func(a int){ fmt.Printf("defer内的参数为%d\n",a) }(age) 编程客栈age = 25 fmt.Printf("age已经变成了%d\n",age) }
调用结果
age已经变成了25 defer内的参数为10
小结
值类型
所以我们要注意传入的参数,
【值类型参数】
值类型参数原始变量改变不影响传入参数,例如int、数组、结构体如果我们想要defer执行时能读取到变化后的"值类型"参数,可以传入指针例如
package defer_knowledge import "fmt" //defer的参数是声明时传入的 func DeferParams(){ var age = 10 //如果想要追踪值类型的变化可以传入值类型指针 defer func(a *int){ fmt.Printf("最初如果传入指针,defer内参数为%d\n",*a) }(&age) defer func(a int){ fmt.Printf("defer内的参数为%d\n",a) }(age) age = 25 fmt.Printf("age已经变成了%d\n",age) }
调用结果
age已经变成了25
defer内的参数为10最初如果传入指针,defer内参数为25
引用类型
具体看引用的底层是否发生变换,例如切片,如果没发生扩容将使用相同的。
package defer_knowledge import "fmt" func DeferParams2(){ var arr = make([]int,5,5) //引用类型直接传递即可,将追踪到引用改变为止 defer func(a []int){ fmt.Printf("defer内的参数为%#v\n",a) }(arr) arr[2] = 10 fmt.Printf("arr已经变成了%#v\n",arr) }
调用结果
arr已经变成了[]int{0, 0, 10, 0, 0}
defer内的参数为[]int{0, 0, 10, 0, 0}
声明时机和执行时机
声明时机
defer的声明时机时按照他出现在代码中的顺序,这时会执行两个操作。
1.传入参数
2.检查内部要访问的变量是否已经定义举例
func DeferTime(){ var age int /* 声明时会传入参数,以及检查内部逻辑是否正确 */ defer func(){ //注意,这个不是defer函数的参数 //和常规变量作用域一样,本层找不到就去外面找 age++ }() }
错误示范
func DeferTime(){ defer func(){ //报错:age未定义 age++ }() var age int }
执行时机
defer的执行时机是在函数逻辑结束后,或者说return
后,按照defer栈调用。
举例
func DeferTime2(){ var age int defer func(){ //按照defer栈,此时访问到的age为 11 age = age+5 fmt.Printf("age的值为%d\n",age) }() defer func(){ //defer执行时机为函数结束后,所以此时访问到的 age = 10 age++ fmt.Printf("age的值为%d\n",age) }() age = 10 }
结果
age的值为11
age的值为16
defer与return的区别
图示
可以看到 return
执行的时候,并不是原子性操作,一般是分为两步:将结果x
赋值给了返回值,然后执行了RET
指令;而defer
语句执行的时候,是在赋值变量之后,在RET
指令之前。所以这里注意一下。返回值和x的关系。如果x
是一个值类型,这里是进行了拷贝的。
执行图示意
函数返回值
不具名返回
形式
func 函数名(参数列表) 返回值类型{ return 返回值 } //例如 /* return sum 操作拆解: 实际对外暴露返回值为 sum_copy sum_copy = sum 所以return实际执行拷贝操作,他不是将函数内的变量抛出,而是将拷贝后的值抛出 */ func Add(a,b int) int{ sum := a+b return sum }
案例1
func DeferAndReturn1() int{ var num int defer func(){ num++ //num的值为16 fmt.Printf("num的值为%d\n",num) }() num = 15 return num } //调用 target := DeferAndReturn1() //target的值为15 fmt.Printf("target的值为%d\n",target)
解析
func DeferAndReturn1() int{ var num int defer func(){ num++ //num的值为16 fmt.Printf("num的值为%d\n",num) }() num = 15 /* 实际操作 copy_num = num 对外暴露copy_num, 由于num是值类型,所以后续defer中对num的操作不影响copy_num */ return num }
误区1
想到了指针操作,但是理解出错。
func DeferAndReturn1() int{ var num int var ptr = new(int) ptr = &num defer func(){ *ptr++ //num的值为16 fmt.Printf("num的值为%d\n",num) }() num = 15 return num } //调用 target := DeferAndReturn1() //target的值为15 fmt.Printf("target的值为%djavascript\n",target)
原因:
func DeferAndReturn1() int{ var num int var ptr = new(int) ptr = &num defer func(){ *ptr++ //num的值为16 fmt.Printf("num的值为%d\n",num) } num = 15 /* 实际操作 copy_num = num 对外暴露copy_num, 我们修改通过指针ptr修改nupythonm的值,还是没影响到copy_num */ return num }
正确思维
func DeferAndReturn1() *int{ var num int var ptr = new(int) ptr = &num defer func(){ num++ //num的值为16 fmt.Printf("num的值为%d\n",num) }() num = 15 return ptr } //调用 target := DeferAndReturn1() //target的值为16 fmt.Printf("target的值为%d\n",*target)
结果
num的值为16
target的值为16
原因
func DeferAndReturn1() *int{ var num int var ptr = new(int) ptr = &num defer func(){ num++ //num的值为16 fmt.Printf("num的值为%d\n",num) }() num = 15 /* 实际操作 copy_ptr = ptr 由于ptr是引用类型,所以defer对ptr的影响会影响到cojavascriptpy_ptr num的本质就是*ptr,操作num就是在操作ptr */ return ptr }
具名返回
相当于实际要暴露的返回值早就确定好了,return
只是起到一个结束函数的作用。
func 函数名(参数列表)(返回值 返回值类型){ return } //示例 /* sum就是实际暴露的返回值,且已经声明了 */ func Add(a,b int) (sum int){ return }
案例1
func DeferAndReturn2() (num int){ defer func(){ num++ }() num = 10 return } num2 := DeferAndReturn2() fmt.Printf("外部num的值为%d\n",num2)
结果
外部num的值为11
原因
func DeferAndReturn2() (num int){ defer func(){ num++ }() num = 10 /* 这里写return 和 return num一样 最终暴露的值为 num 所以defer中对num的操作会影响到最终返回值 */ return }
到此这篇关于golang中编程客栈defer延迟机制的实现示例的文章就介绍到这了,更多相关golang defer延迟机制内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论