GO学习笔记——defer调用(21)

defer调用也是一种流程控制语句,经常用来调用一些资源处理函数。

defer语句确保被执行的语句具有下面的调用时机

defer调用必须出现在函数内,并且在该函数返回之前才回去执行defer调用的函数


 给一个示例来看一下

func testdefer(){
	defer fmt.Println("calling last")
	fmt.Println("calling first")
}

func main() {
	testdefer()
}

执行结果

calling first
calling last

可见,defer后面的函数调用在程序最后才去执行。

那如果有多个defer语句怎么办呢?defer执行顺序其实相当于一个defer栈,采用的是先进后出的原则

func testdefer(){
	defer fmt.Println("calling last")
	defer fmt.Println("calling next")
	fmt.Println("calling first")
}

func main() {
	testdefer()
}

执行结果

calling first
calling next
calling last

延迟方法

不仅是函数,方法也可以作为defer的调用

扫描二维码关注公众号,回复: 11388441 查看本文章
type student struct {
	name string
	age int
}

func (s student) print() {
	fmt.Println(s.name)
}

func testdefer(){
	s := student{"pigff",21}
	defer s.print()
	fmt.Println(s.age)	//会先执行这句,打印21
}

func main() {
	testdefer()
}

执行结果

21
pigff

defer的实参取值

defer调用的函数内的参数值,并不是在真正调用defer时确定的,而是在执行到defer时就确定了。

还是上面的例子

type student struct {
	name string
	age int
}

func (s student) print() {
	fmt.Println(s.name)
}

func testdefer(){
	s := student{"pigff",21}
	defer s.print()		//执行到defer语句时,name还是pigff,所以在最好defer调用时,也是这个
	s.name = "Kobe"	//这里改变名字,下一句打印的是Kobe
	s.print()
}

func main() {
	testdefer()
}

执行结果

Kobe
pigff

这里在执行到defer语句的时候,s.name还是pigff,所以在最后defer调用时s.name依旧还是pigff,并不是在程序后面改变的Kobe。

再换一个简单的例子

func  print(a int) {
	fmt.Println(a)
}

func testdefer(){
	a := 5
	defer print(a)    //在执行到这一句的时候a还是5
	a = 10
	print(10)
}

func main() {
	testdefer()
}

执行结果

10
5

因此,需要注意defer的实参取值,是在执行时确定的,而不是在调用时。

关于defer的实际使用场景

其实在学C++的智能指针的时候,学到过类似的概念。

当我们申请了资源的时候,比如锁,数据库连接,加了锁我们得解锁,建立了连接我们得关闭。假设我们确实写了释放资源的语句,但是如果程序突然在执行释放语句之前return了,比如说报出panic了导致程序中断等,这个时候释放资源的语句就没有被调用了,那么我们申请的资源就会没有释放,长此以往就会导致资源泄漏等很多问题。

虽说在程序运行结束时资源都会全部释放,但是一般服务器程序是不会经常关闭的。

所以defer调用保证调用的函数肯定会在函数结束之前被执行,即使程序报了panic中断,defer调用依旧会被执行。

这就是defer调用的好处,它经常与释放资源等操作配套执行,确保资源被释放。

常用的场景如下(都是配套的,我们在申请资源时就应该使用defer写释放资源的语句)

  • 打开文件,关闭文件
  • 加锁,解锁
  • 建立连接,释放连接

猜你喜欢

转载自blog.csdn.net/lvyibin890/article/details/83821481