Go defer用法

文章详细阐述了Go语言中defer的关键字用法,包括其在函数执行收尾、异常处理中的角色。defer遵循后进先出(LIFO)的执行顺序,参数值在编译时即被确定。当panic发生时,defer仍会执行,且panic会被最后执行的defer中的recover捕获。文章还探讨了defer对函数返回值的影响以及包含panic的defer行为,并提供了示例代码进行说明。
摘要由CSDN通过智能技术生成

defer概览

  • defer是go语言里的一个关键字,在 函数内部使用;
  • defer关键字后面跟一个 函数或匿名函数;

defer用法

  • 执行一些资源的收尾工作,如 关闭数据库连接,关闭文件描述符,释放资源等等;
  • 结合recover()函数使用,防止函数内部的异常导致整个程序停止;
  • defer在遇到panic后,仍然会执行(defer语句要在panic之前编译),从而保证即使出错也能进行对应的错误处理;

函数中多个defer间存储方式及运行顺序

defer数据结构

type _defer struct {
	siz       int32
	started   bool
	openDefer bool
	sp        uintptr
	pc        uintptr
	fn        *funcval
	_panic    *_panic
	link      *_defer
}
  • _defer结构体中的link字段 指向下一个defer结构体地址的指针;
  • _defer结构体是 延迟调用链表中的一个元素,所有的结构体都是通过 link字段串联成链表;
  • 使用链表方式存储,
    在这里插入图片描述
  • 代码从上到下编译,遇到的 defer都会放到链表头部,后面执行的执行按照链表顺序从头到尾执行
  • defer编译及执行顺序 就是 栈的入栈出栈的顺序

defer必须要了解的特性

多defer执行顺序

结论

多defer执行顺序是 最后编译的先执行;

defer后面的函数的参数值的预计算

结论

defer在编译时,会对后面跟着的函数的参数值进行预计算;
也就是 编译器编译到此行时,会立刻确定 defer后面跟着函数的参数值,并且是 值传递方式,不是引用传递方式; 这样后续无论 这个参数对应的变量 值怎么改变,都和defer后面的函数无关;
所以 即使 参数 传了一个 函数,也会先计算函数的值 作为参数 进行值引用;

示例代码

defer后面跟的是系统自带的 fmt.Println函数

package main

import "fmt"

func A(v int) int {
	fmt.Println("A函数,入参为", v)
	v += 1
	return v
}

func UseA(v int) int {
	defer fmt.Println("defer执行结果", A(v)) // A(v)的值:2    对应解析里的   "即使 参数 传了一个 函数,也会先计算函数的值 作为参数 进行值引用;  "
	defer fmt.Println("defer执行结果", v)    //v的值:1   对应解析里的   "后续无论 这个参数对应的变量 值怎么改变,都和defer后面的函数无关;"
	fmt.Println("UseA执行")
	v += 5
	fmt.Println("UseA执行return代码前的最后一行")
	return v
}

func main() {
	fmt.Println("UseA执行结果", UseA(1)) //6
	return
}

代码执行结果

在这里插入图片描述

defer与return关键字执行顺序

结论

  • defer会在return关键字之后, 在返回给调用方前执行;

示例代码

package main

import "fmt"

func A(v int) int {
	fmt.Println("A函数,入参为", v)
	v += 1
	return v
}

func UseOtherA(v int) int {
	defer fmt.Println("defer执行")
	return A(v)
}

func main() {
	fmt.Println("UseOtherA执行结果",UseOtherA(1))
	return
}

代码执行结果

在这里插入图片描述

defer对 函数的返回值 是否定义变量名的影响

示例代码


package main

import "fmt"

func A(v int) int {
	fmt.Println("A函数,入参为", v)
	v += 1
	return v
}
func B(v int) (result int) {
	result = v
	defer func() {
		result = A(v)
	}()
	return
}

func C(v int) int {
	defer func() {
		v = A(v)
	}()
	return v
}
func main() {
	fmt.Println("B执行结果", B(1))
	fmt.Println("C执行结果", C(1))
	return
}

代码执行结果

在这里插入图片描述

结论

  • 定义返回值变量名 的函数,在返回给函数调用方前,这个变量的值都是可以修改的;
  • 未定义返回值变量名 的函数, 如上示例的C函数在 return语句执行时,其实是 将v变量 赋值给一个未定义名字的隐藏变量,来完成值传递, 所以后续对v变量的操作对 返回给函数调用结果无任何影响; 示意如下
    在这里插入图片描述

defer遇到panic执行情况

结论

  • panic触发后,函数内的后续代码不再执行,在panic之前编译的defer仍然会执行;

示例代码

package main

import "fmt"

func PanicT() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("执行recover()方法,尝试恢复程序,错误内容为:", err)
		}
	}()
	defer fmt.Println("defer1")
	defer fmt.Println("defer2")
	panic("手动触发panic")
	defer fmt.Println("defer3,执行不到")
}

func main() {
	PanicT()
	return
}

运行结果

在这里插入图片描述

defer中包含panic

结论

  • panic会被覆盖,只保留最新的panic;
  • 如: 函数中的panic会被defer的panic覆盖;
  • 如: 多个defer都有panic,只有最后执行的defer的panic会保留;
  • 其实 defer 后面也是普通函数,那么普通函数遇到panic就会停止运行,执行后续defer的函数;

代码示例


package main

import "fmt"

func PanicMany() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("执行recover()方法,尝试恢复程序,错误内容为:", err)
		}
	}()
	defer func() {
		fmt.Println("defer1执行")
		panic("defer1手动触发panic")
		fmt.Println("defer1执行不到此处")
	}()
	defer func() {
		fmt.Println("defer2执行")
		panic("defer2手动触发panic")
		fmt.Println("defer2执行不到此处")
	}()
	panic("手动触发panic")
	defer fmt.Println("defer3,执行不到")
}

func main() {
	PanicMany()
	return
}

代码执行结果

在这里插入图片描述

知识点训练

func main() {
	fmt.Println(test1())
	fmt.Println(test2())
	fmt.Println(test3())
	fmt.Println(test4())
}
func test1() (v int) {
	defer fmt.Println(v) //0
	return v             //0
}

func test2() (v int) {
	defer func() {
		fmt.Println(v) //3
	}()
	return 3 //3
}

func test3() (v int) {
	defer func() {fmt.Println(v)}() //4
	defer fmt.Println(v)                    //0
	defer func(v int) { fmt.Println(v) }(v) //0
	v = 3
	return 4 //4
}

func test4() (v int) {
	defer func(n int) {
		fmt.Println(n) //0
	}(v)
	return 5 //5
}

总结

  • 对于defer执行结果的准确预测, 要了解函数的参数传递方式,函数的返回值是否定义变量名时 编译器的执行过程 等额外的知识点;
  • defer代码编写时经常遇到,常用于 异常捕捉,资源释放等收尾工作;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值