网络编程
位置:首页>> 网络编程>> Go语言>> Golang 的defer执行规则说明

Golang 的defer执行规则说明

作者:dengming0922  发布时间:2023-07-14 08:31:10 

标签:Golang,defer,执行

defer介绍

defer是golang的一个特色功能,被称为“延迟调用函数”。当外部函数返回后执行defer。类似于其他语言的 try… catch … finally… 中的finally,当然差别还是明显的。

在使用defer之前我们应该多了解defer的特性,这样才能避免使用上的误区。

1. 最简单的defer


func test(){
   defer func(){ fmt.Println("defer") }()
   //todo
   //...
   return
   //defer执行时机
}

我们可以稍微改动一下上述代码再次确认defer的执行时机。


func main() {
   fmt.Println(test())
}
func test() (i int) {
   defer func() { i++ }()
   defer func() { fmt.Println(i) }()
   //todo
   //...
   fmt.Println(0)
   return 1
   //defer执行时机
}

output:

0

1

2

从上面示例可以发现defer执行是在return之后,且按照defer声明的先进后出顺序执行。以下是真实场景中比较常见的用法。

释放占用的资源


func test() error {
   file, err := os.Open("path")
   if err != nil {
       return err
   }
   //放在判断err状态之后
   defer file.Close()
   //todo
   //...
   return nil
   //defer执行时机
}

捕捉处理异常


func test2() {
   defer func() {
       if err := recover(); err != nil {
           fmt.Println(err)
       }
   }()
   file, err := os.Open("path")
   if err != nil {
       panic(err)
   }
   defer file.Close()
   //todo
   //...
   return
   //defer执行时机
}

输出日志 等收尾工作


func test3() {
   t1 := time.Now()
   defer func() {
       fmt.Printf("耗时: %f s", time.Now().Sub(t1).Seconds())
   }()
   //todo
   //...
   return
   //defer执行时机
}

2. 复杂的defer

当我们已经深深记住defer的执行时机并打算翻过这一页时,事情的发展又开始偏离初始。请看如下代码


func test() {
   i := 0
   defer fmt.Println(i)                       //输出 0
   defer func(x int) { fmt.Println(x) }(i)    //输出 0
   defer func(x *int) { fmt.Println(*x) }(&i) //输出 1
   defer func() { fmt.Println(i) }()          //输出 1
   i++
   //todo
   //...
   fmt.Println(i) //输出 1
   return  
}

output:

1

1

1

0 // 值未修改

0 // 值未修改

通常认为defer就像真的被挪到了return之后。

但defer的本质依然是函数调用。当执行到defer定义时,首先会对参数进行求值,然后参数被压入函数调用栈,此时不会进入defer函数体,而是直到函数返回时才调用defer函数体。

参数被压入函数调用栈时,如果参数是值类型,那么将复制值,如果参数是指针,那么将复制指针而不是复制指针指向的值。

defer函数体内的变量是在return后执行因此不受影响。

因此我们在使用defer时一定要明确函数的参数类型(如果有的话),其次要明确defer函数体内的变量引用是否正确。

以下是常见错误


func test4() error {
   f, err := os.Open("A.txt")
   if err != nil {
       return err
   }
   defer func() { f.Close() }()//错误: 关闭是B文件,f引用被重新赋值
   f, err = os.Open("B.txt")
   if err != nil {
       return err
   }
   defer func() { f.Close() }() //关闭是B文件
   list := []int{1, 2}
   for _, i := range list {
       defer fmt.Println(i) //输出 2 1 //i为值类型参数被复制
       defer func() { fmt.Println(i) }() //错误: 输出 2 2 //函数体内对i引用,留最终值
   }
   return nil
}

3. 更复杂点的defer

且看如下代码


type Test struct {
   name string
}
func (this *Test) Point() { // this  为指针
   fmt.Println(this.name)
}
func (this  Test) Value() { //this  为值类型
   fmt.Println(this.name)
}
func test5() {
   ts := []Test{{"a"}, {"b"}, {"c"}}
   for _, t := range ts {
       defer t.Point() //输出 c c c
       defer t.Value() //输出 c b a
   }
}

看似相同的代码却输出了完全不同的结果。要理解这种差别还是得从调用函数的本质来说。golang对struct 的方法调用是这样的


defer func (this Type, para) result

struct 的方法在定义时this 采用的值类型还是指针决定defer在调用时首个参数(隐藏)的复制的不同。

上述代码中Point方法定义时使用了指针作为this,因此输出是for循环最终赋值的t引用。

Value方法定义时使用值类型作为this,因此输出是for循环执行的每步复制后的t。

最终只有理解了以上出现的问题,到此defer所带来困扰才能远离我们。

补充:Golang中defer的三个实战要点

Golang中的defer是使用频次比较高的,能创造出延迟生效特效的一种方式。

defer也有自己的矫情,需要注意的。

本文将从通过代码的方式来说明defer的三点矫情。

1.defer的生效顺序

先说结论:defer的执行顺序是倒序执行(同入栈先进后出)


func main() {
defer func() {
 fmt.Println("我后出来")
}()
defer func() {
 fmt.Println("我先出来")
}()
}

执行后打印出:

我先出来

我后出来

2.defer与return,函数返回值之间的顺序

先说结论:return最先执行->return负责将结果写入返回值中->接着defer开始执行一些收尾工作->最后函数携带当前返回值退出

返回值的表达方式,我们知道根据是否提前声明有两种方式:一种是func test() int 另一种是 func test() (i int),所以两种情况都来说说


func test() int
func main() {
fmt.Println("main:", test())
}
func test() int {
var i int
defer func() {
 i++
 fmt.Println("defer2的值:", i)
}()
defer func() {
 i++
 fmt.Println("defer1的值:", i)
}()
return i
}

输出:

defer1的值: 1

defer2的值: 2

main: 0

详解:return的时候已经先将返回值给定义下来了,就是0,由于i是在函数内部声明所以即使在defer中进行了++操作,也不会影响return的时候做的决定。


func test() (i int)
func main() {
fmt.Println("main:", test())
}
func test() (i int) {
defer func() {
 i++
 fmt.Println("defer2的值:", i)
}()
defer func() {
 i++
 fmt.Println("defer1的值:", i)
}()
return i
}

输出:

defer1的值: 1

defer2的值: 2

main: 2

详解:由于返回值提前声明了,所以在return的时候决定的返回值还是0,但是后面两个defer执行后进行了两次++,将i的值变为2,待defer执行完后,函数将i值进行了返回。

3.defer定义和执行两个步骤,做的事情

先说结论:会先将defer后函数的参数部分的值(或者地址)给先下来【你可以理解为()里头的会先确定】,后面函数执行完,才会执行defer后函数的{}中的逻辑


func test(i *int) int {
return *i
}
func main(){
var i = 1
// defer定义的时候test(&i)的值就已经定了,是1,后面就不会变了
defer fmt.Println("i1 ="  , test(&i))
i++
// defer定义的时候test(&i)的值就已经定了,是2,后面就不会变了
defer fmt.Println("i2 ="  , test(&i))
// defer定义的时候,i就已经确定了是一个指针类型,地址上的值变了,这里跟着变
defer func(i *int) {
 fmt.Println("i3 ="  , *i)
}(&i)
// defer定义的时候i的值就已经定了,是2,后面就不会变了
defer func(i int) {
 //defer 在定义的时候就定了
 fmt.Println("i4 ="  , i)
}(i)
defer func() {
 // 地址,所以后续跟着变
 var c = &i
 fmt.Println("i5 ="  , *c)
}()
// 执行了 i=11 后才调用,此时i值已是11
defer func() {
 fmt.Println("i6 ="  , i)
}()
i = 11
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持

来源:https://blog.csdn.net/dengming0922/article/details/80946420

0
投稿

猜你喜欢

  • 前言随着网站的内容的增多和用户访问量的增多,网站加载会越来越慢,受限于带宽和服务器同一时间的请求次数的限制,,我们往往需要在此时对我们的网站
  • 网页制作中需要把握好很多原则和细节,今天我们来谈谈网页设计中的平衡、对比、连贯和留白。一、平衡如果你的页面是平衡的,当用户浏览这个页面的时候
  • 互联网时代数据是 * 式增长,我们常常需要把结构化数据和非结构化数据(如文档,演示文稿,视频,音频,图像)存储在一起。通常有几种方案: 1。在
  • jQuery之所以如此流行并被从大公司到个人博客的几乎每个人都广泛使用,是因为它上手和使用相当简单,而且为我们提供了一些人都不知道的相当棒的
  • Web Accessibility Initiative Accessible Rich Internet Applications认识AR
  • 如何在约定时间显示特定的提示信息?<%Function Greeting()    
  • 部署网站前查看一下系统是否已经安装CGI1、启动iis服务器,打开IIS服务器打开IIS服务器,点击网站,右击“添加网站”2、创建网站点击“
  • 防止Application对象在多线程访问中出现错误asp代码处理代码如下(VB):<%Application.Lock()Appli
  • 1.现在我本机系统已内置python2.62.下载进行源码安装复制链接下载到/root/mypackage,解压接着mkdir /usr/l
  • 一般我们是利用Session对象来防止通过“刷新”增加计数器的访问量的,看看下面的做法;<%If IsEmpty(Sessi
  • 一 MySQL WorkbenchMySQL Workbench提供DBAs和developers一个集成工具环境:1)数据库设计和建模2)
  • 原来的题目设想为界面视觉效果的统一性,但是“统一”这个词似乎有点敏感,怕触动萌点无数,而我也无意去设定一个什么什么的统一性来侃侃而谈,极为专
  • QQ登录Banner增加了剧情的概念之后,已经推出了春节和情人节两期。这之后设想能围绕Banner做的更加丰富,对传统文化的体现也能更为深入
  • Google网站管理员工具的验证方式有两种,一个是通过html文件,或一种是通过meta标签。通常情况下,这两个方式直接就可以通过验证了。但
  • 英文的文档在这里,详细全面,本文仅为自己的学习笔记,只是试图通过转述加深自己的学习,不详细不全面。由于浏览器之间的差异,所以在JS中监听事件
  • 对于一个设了自动增长标识的数据表来说,它的字段的值是由数据库自动设置的;这在导数据时很麻烦。 当我们导数据时,我们往往想想将标识字段的数据也
  • 使用Access数据库生成申报数据与读入数据的实例方法:示例:Sub Mwrite()On Error GoTo thiserrDim rs
  • 前两天,编辑建议我去当当和卓越申请个用户,在网站上放上我的书的链接,这样还可以拿到一些反点儿,于是我兴冲冲地跑到几个网站上去看,却只在卓越(
  • SQL Server服务器的配置选项属于那种人们了解较少且经常误用的选项。当一个技术支持人员要求你按照某种方式调整一个选项、而另一个技术支持
  • 简单介绍下:CSS 图片拼合 (CSS sprites) 可有效降低图片文件的 HTTP 连接请求数. 多个图片将以一定间距合并为一个大图片
手机版 网络编程 asp之家 www.aspxhome.com