关于Go defer的详细使用
先抛砖引玉defer的延迟调用:
defer特性:
复制代码
1. 关键字 defer 用于注册延迟调用。
2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
3. 多个defer语句,按先进后出的方式执行。
4. defer语句中的变量,在defer声明时就决定了。
复制代码
defer用途:
复制代码
1. 关闭文件句柄
2. 锁资源释放
3. 数据库连接释放
复制代码
好,废话不多说,实例加深理解,我们先看看一段代码
复制代码
package main
import "fmt"
func main() {
var users [5]struct{}
for i := range users {
defer fmt.Println(i)
}
}
复制代码
输出:4 3 2 1 0 ,defer 是先进后出,这个输出没啥好说的。
我们把上面的代码改下:
defer 换上闭包:
复制代码
package main
import "fmt"
func main() {
var users [5]struct{}
for i := range users {
defer func() { fmt.Println(i) }()
}
}
复制代码
输出:4 4 4 4 4,很多人也包括我。预期的结果不是 4 3 2 1 0 吗?官网对defer 闭包的使用大致是这个意思:
函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4。那么 如何正常输出预期的 4 3 2 1 0 呢?
不用闭包,换成函数:
复制代码
package main
import "fmt"
func main() {
var users [5]struct{}
for i := range users {
defer Print(i)
}
}
func Print(i int) {
fmt.Println(i)
}
复制代码
函数正常延迟输出:4 3 2 1 0。
我们再举一个可能一不小心会犯错的例子:
defer调用引用结构体函数
复制代码
package main
import "fmt"
type Users struct {
name string
}
func (t *Users) GetName() { // 注意这里是 * 传地址 引用Users
fmt.Println(t.name)
}
func main() {
list := []Users{{"乔峰"}, {"慕容复"}, {"清风扬"}}
for _, t := range list {
defer t.GetName()
}
}
复制代码
输出:清风扬 清风扬 清风扬。
这个输出并不会像我们预计的输出:清风扬 慕容复 乔峰
可是按照前面的go defer函数中的使用说明,应该输出清风扬 慕容复 乔峰才对啊?
那我们换一种方式来调用一下
复制代码
package main
import "fmt"
type Users struct {
name string
}
func (t *Users) GetName() { // 注意这里是 * 传地址 引用Users
fmt.Println(t.name)
}
func GetName(t Users) { // 定义一个函数,名称自定义
t.GetName() // 调用结构体USers的方法GetName
}
func main() {
list := []Users{{"乔峰"}, {"慕容复"}, {"清风扬"}}
for _, t := range list {
defer GetName(t)
}
}
复制代码
输出:清风扬 慕容复 乔峰。
这个时候输出的就是所谓"预期"滴了
当然,如果你不想多写一个函数,也很简单,可以像下面这样(改2处),同样会输出清风扬 慕容复 乔峰
复制代码
package main
import "fmt"
type Users struct {
name string
}
func (t *Users) GetName() { // 注意这里是 * 传地址 引用Users
fmt.Println(t.name)
}
func GetName(t Users) { // 定义一个函数,名称自定义
t.GetName() // 调用结构体USers的方法GetName
}
func main() {
list := []Users{{"乔峰"}, {"慕容复"}, {"清风扬"}}
for _, t := range list {
t2 := t // 定义新变量t2 t赋值给t2
defer t2.GetName()
}
}
复制代码
输出:清风扬 慕容复 乔峰。
通过以上例子,结合
我们可以得出下面的结论:
defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份。但是并没有说struct这里的*指针如何处理,
通过这个例子可以看出go语言并没有把这个明确写出来的this指针(比如这里的* Users)当作参数来看待。到这里有滴朋友会说。看似多此一举的声明,
直接去掉指针调用 t *Users改成 t Users 不就行了?
复制代码
package main
import "fmt"
type Users struct {
name string
}
func (t Users) GetName() { // 注意这里是 * 传地址 引用Users
fmt.Println(t.name)
}
func main() {
list := []Users{{"乔峰"}, {"慕容复"}, {"清风扬"}}
for _, t := range list {
defer t.GetName()
}
}
复制代码
输出:清风扬 慕容复 乔峰。这就回归到上面的 defer 函数非引用调用的示例了。所以这里我们要注意defer后面的指针函数和普通函数的调用区别。很容易混淆出错。
多个 defer 注册,按 FILO 次序执行 ( 先进后出 )。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行,我们看看这一段
复制代码
package main
func users(i int) {
defer println("北丐")
defer println("南帝")
defer func() {
println("西毒")
println(10 / i) // 异常未被捕获,逐步往外传递,最终终止进程。
}()
defer println("东邪")
}
func main() {
users(0)
println("武林排行榜,这里不会被输出哦")
}
复制代码
输出:
复制代码
东邪
西毒
南帝
北丐
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.users.func1(0x0)
复制代码
我们发现函数中异常,最后才捕获输出,但是一旦捕获了异常,后面就不会再执行了,即终止了程序。
无论从事什么行业,只要做好两件事就够了,一个是你的专业、一个是你的人品,专业决定了你的存在,人品决定了你的人脉,剩下的就是坚持,用善良專業和真诚赢取更多的信任。https://www.cnblogs.com/phpper/p/11984161.html