Golang中的defer

这篇具有很好参考价值的文章主要介绍了Golang中的defer。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

面试常问之defer()的执行次序


情形1

package main

func main() {

    defer print(123)
 defer_call()
 defer print(789//panic之后的代码不会被执行
 print("不会执行到这里")
}

func defer_call() {
 defer func() {
  print("打印前")
 }()
 defer func() {
  print("打印中")
 }()

 defer print("打印后")

 panic("触发异常")

    defer print(666)   //IDE会有提示: Unreachable code
    
}

结果为:

打印后打印中打印前123panic: 触发异常

goroutine 1 [running]:
main.defer_call()
 /Users/shuangcui/explore/panicandrecover.go:19 +0xe5
main.main()
 /Users/shuangcui/explore/panicandrecover.go:6 +0x51

可见:

  • panic之后的defer()不会被执行
  • panic之前的defer(),按照 先进后出的次序执行,最后输出panic信息

(defer机制底层,是用链表实现的一个栈)

再如:

func main() {

 fmt.Println(123)

 defer fmt.Println(999)

 subfunc()

}

func subfunc() {

 defer fmt.Println(888)

 for i := 0; i > 10; i++ {
  fmt.Println("当前i为:", i)
  panic("have a bug")
 }

 defer fmt.Println(456)

}

结果为:

123
456
888
999

defer会延迟到当前函数执行 return 命令前被执行, 多个defer之间按LIFO先进后出顺序执行




情形2 (在defer内打印defer之外的主方法里操作的变量)

package main

import "fmt"

func main() {
 foo()
}

func foo() {
 i := 0
 defer func() {
  //i--
  fmt.Println("第一个defer", i)
 }()

 i++
 fmt.Println("+1后的i:", i)

 defer func() {
  //i--
  fmt.Println("第二个defer", i)
 }()

 i++
 fmt.Println("再+1后的i:", i)

 defer func() {
  //i--
  fmt.Println("第三个defer", i)
 }()

 i++
 fmt.Println("再再+1后的i:", i)

 i = i + 666

 fmt.Println("+666后的i为:", i)

}

输出为:

+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 669
第二个defer 669
第一个defer 669


情形3 (在defer内外操作同一变量)

package main

import "fmt"

func main() {
 foo()
}

func foo() {
 i := 0
 defer func() {
  i--
  fmt.Println("第一个defer", i)
 }()

 i++
 fmt.Println("+1后的i:", i)

 defer func() {
  i--
  fmt.Println("第二个defer", i)
 }()

 i++
 fmt.Println("再+1后的i:", i)

 defer func() {
  i--
  fmt.Println("第三个defer", i)
 }()

 i++
 fmt.Println("再再+1后的i:", i)

 i = i + 666

 fmt.Println("+666后的i为:", i)

}

输出为:

+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 668
第二个defer 667
第一个defer 666

情形4! (发生了参数传递!---传递参数给defer后面的函数, defer内外同时操作该参数)

package main

import "fmt"

func main() {
 foo2()
}

func foo2() {
 i := 0
 defer func(k int) {
  //k--
  fmt.Println("第一个defer", k)
 }(i)

 i++
 fmt.Println("+1后的i:", i)

 defer func(k int) {
  //k--
  fmt.Println("第二个defer", k)
 }(i)

 i++
 fmt.Println("再+1后的i:", i)

 defer func(k int) {
  //k--
  fmt.Println("第三个defer", k)
 }(i)

 i++
 fmt.Println("再再+1后的i:", i)

 i = i + 666

 fmt.Println("+666后的i为:", i)

}

输出为:

+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 2
第二个defer 1
第一个defer 0

如果取消三处k--的注释, 输出为:

+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 1
第二个defer 0
第一个defer -1

等同于:

package main

import "fmt"

func main() {
 foo3()
}

func foo3() {
 i := 0
 defer f1(i)

 i++
 fmt.Println("+1后的i:", i)


 defer f2(i)

 i++
 fmt.Println("再+1后的i:", i)

 defer f3(i)
 i++
 fmt.Println("再再+1后的i:", i)

 i = i + 666

 fmt.Println("+666后的i为:", i)

}

func f1(k int) {
 k--
 fmt.Println("第一个defer", k)
}

func f2(k int) {
 k--
 fmt.Println("第二个defer", k)
}

func f3(k int) {
 k--
 fmt.Println("第三个defer", k)
}

defer指定的函数的参数在 defer 时确定,更深层次的原因是Go语言都是值传递。


情形5! (传递指针参数!---传递参数给defer后面的函数, defer内外同时操作该参数)

package main

import "fmt"

func main() {

 foo5()
}

func foo5() {
 i := 0
 defer func(k *int) {
  fmt.Println("第一个defer", *k)
 }(&i)

 i++
 fmt.Println("+1后的i:", i)

 defer func(k *int) {
  fmt.Println("第二个defer", *k)
 }(&i)

 i++
 fmt.Println("再+1后的i:", i)

 defer func(k *int) {
  fmt.Println("第三个defer", *k)
 }(&i)

 i++
 fmt.Println("再再+1后的i:", i)

 i = i + 666

 fmt.Println("+666后的i为:", i)
}

输出为:

+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 669
第二个defer 669
第一个defer 669

作如下修改:

package main

import "fmt"

func main() {

 foo5()
}

func foo5() {
 i := 0
 defer func(k *int) {
  (*k)--
  fmt.Println("第一个defer", *k)
 }(&i)

 i++
 fmt.Println("+1后的i:", i)

 defer func(k *int) {
  (*k)--
  fmt.Println("第二个defer", *k)
 }(&i)

 i++
 fmt.Println("再+1后的i:", i)

 defer func(k *int) {
  (*k)--
  fmt.Println("第三个defer", *k)
 }(&i)

 i++
 fmt.Println("再再+1后的i:", i)

 i = i + 666

 fmt.Println("+666后的i为:", i)
}

输出为:

+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 668
第二个defer 667
第一个defer 666

总结一下

  • 如果传参进defer后面的函数(无论是闭包func(){}(i)方式还是子方法f(i)方式,或是直接跟如fmt.Println(i)),defer回溯时均以当时传参时i的值去计算

  • 反之,defer回溯时,以最后i的值带入计算;(参考下面的例子).

参考:

Go面试题答案与解析[1]




几种写法之间的归类与区别


package main

import "fmt"

func main() {
 rs := foo6()
 fmt.Println("in main func:", rs)
}

func foo6() int {
 i := 0
 defer fmt.Println("in defer :", i)

 //defer func() {
 // fmt.Println("in defer :", i)
 //}()

 i = 1000
 fmt.Println("in foo:", i)
 return i+24
}

输出为:

in foo: 1000
in defer : 0
in main func: 1024

如果改为:

package main

import "fmt"

func main() {
 rs := foo6()
 fmt.Println("in main func:", rs)
}

func foo6() int {
 i := 0
 //defer fmt.Println("in defer :", i)
 defer func() {
  fmt.Println("in defer :", i)
 }()

 i = 1000
 fmt.Println("in foo:", i)
 return i+24
}

输出为:

in foo: 1000
in defer : 1000
in main func: 1024

也可见,

defer fmt.Println("in defer :", i)

相当于

defer func(k int) {
  fmt.Println(k)
    }(i)

func f(k int){
 fmt.Println(k)
}

这时的参数,都是传递时的值

而如

 defer func() {
  fmt.Println("in defer :", i)
    }()

这时的参数,为最后return之前那一刻的值




defer会影响返回值吗?


函数的return value 不是原子操作, 在编译器中实际会被分解为两部分:返回值赋值return 。而defer刚好被插入到末尾的return前执行(即defer介于二者之间)。故可以在defer函数中修改返回值

package main

import (
 "fmt"
)

func main() {
 fmt.Println(doubleScore(0))    //0
 fmt.Println(doubleScore(20.0)) //40
 fmt.Println(doubleScore(50.0)) //50
}
func doubleScore(source float32) (rs float32) {
 defer func() {
  if rs < 1 || rs >= 100 {
   //将影响返回值
   rs = source
  }
 }()
 rs = source * 2
 return
 //或者
 //return source * 2
}

输出为:

0
40
50

再如:

func main() {

    fmt.Println("foo return :", foo2())

}

func foo() map[string]string {

    m := map[string]string{}

    defer func() {
        m["a"] = "b"
    }()

    return m
}

输出为:

foo return : map[a:b]

又如:

package main


import "fmt"

func main() {
 fmt.Println("foo return :", foo())
}

func foo() int {
 i := 0

 defer func() {
  i = 10086
 }()

 return i + 5
}

输出为:

foo return : 5

若作如下修改:


func foo() (i int) {
 i = 0
 defer func() {
  i = 10086
 }()

 return i + 5
}

则返回为:

foo return : 10086

return之后的语句先执行,defer后的语句后执行


return value拆解为两步: 确定value值,然后return..即如果return 后面是个方法或者复杂表达式,且有某个值i,会先计算.完成后defer再执行,如果defer里面也有对i的改动,是可以影响返回值的

(给函数返回值申明变量名, 这时, 变量的内存空间空间是在函数执行前就开辟出来的,且该变量的作用域为整个函数,return时只是返回这个变量的内存空间的内容,因此defer能够改变返回值)

defer不影响返回值,除非是map、slice和chan这三种引用类型,或者返回值定义了变量名




参考:

Golang研学:如何掌握并用好defer[2]--存疑("引用传递"那里明显错误)

Golang中的Defer必掌握的7知识点


参考资料

[1]

Go面试题答案与解析: https://yushuangqi.com/blog/2017/golang-mian-shi-ti-da-an-yujie-xi.html

[2]

Golang研学:如何掌握并用好defer: https://segmentfault.com/a/1190000019063371#comment-area

本文由 mdnice 多平台发布文章来源地址https://www.toymoban.com/news/detail-639699.html

到了这里,关于Golang中的defer的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Go 基础篇】Go语言中的defer关键字:延迟执行与资源管理

    在Go语言中, defer 是一种用于延迟执行函数调用的。它提供了一种简洁而强大的方式,用于在函数返回之前执行一些必要的清理操作或者释放资源。 defer 的灵活性和易用性使得它在Go语言中广泛应用于资源管理、错误处理和代码结构优化等方面。🚀🚀🚀 本篇博客将详

    2024年02月11日
    浏览(57)
  • golang实现webgis后端开发

    目录 前言 二、实现步骤 1.postgis数据库和model的绑定 2.将pg库中的要素转换为geojson (1)几何定义 (2)将wkb解析为几何类型 (3)定义geojson类型 (4)数据转换 (5)数据返回  2.前端传入的geojson储存到数据库 3、其他功能实现 总结         停更了接近一个月都在研究一门新语言gola

    2024年02月08日
    浏览(50)
  • 【后端学习笔记·Golang】邮箱邮件验证

    流程: 接收用户请求后生成随机验证码,并将验证码存入Redis中,并设置TTL 通过gomail发送验证码给用户邮箱 接收用户输入的验证码,与Redis中存放的验证码进行比对 ​ 随机种子通过 time.Now().UnixNano() 进行设置,以确保对于同一个用户每次请求都使用不同的种子。然后,定义

    2024年04月26日
    浏览(50)
  • golang iris框架 + linux后端运行

    打包应用 开启服务 关闭后台 杀死进程 通过 ps -ef|grep main 找到应用出现 找到应用的( PID(一般是第一个数字) )

    2024年02月07日
    浏览(56)
  • 新版以太坊Ethereum库ethersV5.0配合后端Golang1.18实时链接区块链钱包(Metamask/Okc)以及验签操作

    区块链去中心化思想无处不在,比如最近使用个体抗原自检替代大规模的中心化核酸检测,就是去中心化思想的落地实践,避免了大规模聚集导致的交叉感染,提高了检测效率,本次我们使用Ethereum最新的ethersV5.0以上版本链接去中心化区块链钱包,并且通过后端Golang1.18服务进

    2024年02月02日
    浏览(62)
  • Golang 中的 map 详解

    1、map 的定义   在计算机科学里,被称为相关数组、map、符号表或者字典,是由一组 key, value 对组成的抽象数据结构,并且同一个 key 只会出现一次。   两个关键点:map 是由 key-value 对组成的;key 只会出现一次。   map 的设计也被称为 “The dictionary problem(字典问题)

    2024年02月14日
    浏览(52)
  • Golang 中的接口详解

    什么是接口:接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。 在Go语言中接口(interface)是一种类型,一种抽象的类型。 interface是一组method的集合,是duck-type programming的一种体现。 接口做的事情就像是定义一个协议(规则

    2024年02月13日
    浏览(39)
  • Golang中的协程(上)

    在Golang中,协程(Coroutine)是一种轻量级的执行单位,可以理解为独立的并发任务。在本篇博客中,我们将详细分析介绍Golang中的协程,包括协程的概念、存在的原因、实现方法、运行方式、案例讲解以及与主线程的关系等内容。 协程是一种轻量级的线程,拥有自己的堆栈和

    2024年02月15日
    浏览(38)
  • golang中的循环依赖

    作为 Golang 开发人员,您可能遇到过导入周期。Golang 不允许导入循环。如果 Go 检测到代码中的导入循环,则会抛出编译时错误。在这篇文章中,让我们了解导入周期是如何发生的以及如何处理它们。 导入周期 假设我们有两个包, p1 并且 p2 。当 package p1 依赖于 package p2 且

    2024年01月23日
    浏览(77)
  • Golang 中的流程控制

    Go 语言中最常用的流程控制有 if 和 for ,而 switch 和 goto 主要是为了简化代码、降低重复 代码而生的结构,属于扩展类的流程控制。 if 语句 if 语句 由一个布尔表达式后紧跟一个或多个语句组成。 1.Go 编程语言中 if 语句的语法如下: if 在布尔表达式为 true 时,其后紧跟的语

    2024年02月13日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包