defer概览

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

defer用法

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

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

defer数据结构

type _defer struct {siz       int32started   boolopenDefer boolsp        uintptrpc        uintptrfn        *funcval_panic    *_paniclink      *_defer
}
  • _defer结构体中的link字段 指向下一个defer结构体地址的指针;
  • _defer结构体是 延迟调用链表中的一个元素,所有的结构体都是通过 link字段串联成链表;
  • 使用链表方式存储,
  • 代码从上到下编译,遇到的 defer都会放到链表头部,后面执行的执行按照链表顺序从头到尾执行
  • defer编译及执行顺序 就是 栈的入栈出栈的顺序

defer必须要了解的特性

多defer执行顺序

结论

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

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

结论

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

示例代码

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

package mainimport "fmt"func A(v int) int {fmt.Println("A函数,入参为", v)v += 1return 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 += 5fmt.Println("UseA执行return代码前的最后一行")return v
}func main() {fmt.Println("UseA执行结果", UseA(1)) //6return
}

代码执行结果

defer与return关键字执行顺序

结论

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

示例代码

package mainimport "fmt"func A(v int) int {fmt.Println("A函数,入参为", v)v += 1return v
}func UseOtherA(v int) int {defer fmt.Println("defer执行")return A(v)
}func main() {fmt.Println("UseOtherA执行结果",UseOtherA(1))return
}

代码执行结果

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

示例代码


package mainimport "fmt"func A(v int) int {fmt.Println("A函数,入参为", v)v += 1return v
}
func B(v int) (result int) {result = vdefer 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 mainimport "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 mainimport "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) //0return v             //0
}func test2() (v int) {defer func() {fmt.Println(v) //3}()return 3 //3
}func test3() (v int) {defer func() {fmt.Println(v)}() //4defer fmt.Println(v)                    //0defer func(v int) { fmt.Println(v) }(v) //0v = 3return 4 //4
}func test4() (v int) {defer func(n int) {fmt.Println(n) //0}(v)return 5 //5
}

总结

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

Go defer用法相关推荐

  1. Go 延迟调用 defer 用法详解

    引子 package counterimport ("log""sync" )type Counter struct {mu *sync.MutexValue ...

  2. Go语言 defer

    引言 Go 语言中的 defer 语句是 UNIX 之父 Ken Thompson 大神发明的,是完全正交的设计. 也正因为 Go 语言遵循的是正交的设计, 所以才有了: "少是指数级的多/ ...

  3. c语言memcpy是什么,C语言memcpy函数的用法

    介绍 memcpy是memory copy的缩写,意为内存复制,在写C语言程序的时候,我们常常会用到它.它的函原型如下: void *memcpy(void *dest, const void *sr ...

  4. 秋招面试准备 JS1

    1几个很实用的BOM属性对象方法 参考27.BOM的属性对象方法_y_xiuzhu的博客-CSDN博客 1 BOM的属性对象方法 BOM的对象用于访问浏览器的功能,提供了当前窗口中加载的文档有关的信息 ...

  5. RxJava 2.x入门教程

    前言 首先来说一下rxjava1和rxjava2的区别吧,附带一些RxJava 1升级到RxJava 2过程中踩过的一些"坑",RxJava 对大家而言肯定不陌生,其受欢迎程度不言 ...

  6. Golang 基础之基础语法梳理 (一)

    大家好,今天将梳理出的 Go语言基础语法内容,分享给大家. 请多多指教,谢谢. 本次<Go语言基础语法内容>共分为三个章节,本文为第一章节 Golang 基础之基础语法梳理 (一) Gol ...

  7. Go语言实现HTTP压测工具(2)——Golang语言基础学习和使用

    文章目录 0 前言 1. 数据类型 2. 函数 3. 控制结构 3.1 for range用法 3.2 defer用法 4. 数组.切片(Slice)与映射(Map) 5. 结构体(Struct)与方 ...

  8. Go语言中 defer 的用法

    文章目录 Go语言中 defer 的用法 一.defer触发时机 二.defer执行逻辑 1. 多个defer语句按先进后出的方式执行 2.defer声明时,对应的参数会实时解析 3.defer.re ...

  9. 关于defer 的用法

    要是以前,我铁定整天到处找教程看,光说不练,现在觉悟了,看教程看得最多,不一定能看完,看完了不一定能比作者更明白,看明白了不一定能用得好.所以看教程其实好处不大,只能作为小小的参考.很多东西看别人的始 ...

最新文章

  1. 查询远程或本地计算机的登录账户
  2. windows phone7---MVVM模式
  3. linux之vim操作快速跳到下一个空格和上一个空格命令
  4. CSDN博客标题和目录的一点思考
  5. 菜鸟教程c语言题目,C 练习实例40
  6. Ubuntu安装tensorflow报错:tensorflow-xx.whl not a supported wheel on this platform
  7. 最短路径之Dijkstra算法和Floyd-Warshall算法
  8. load runner
  9. 采用nettcp绑定的wcf宿主到iis7
  10. 邮局只能寄指定大小的箱子
  11. 跟小丸子学基础口语16-20
  12. Android屏幕适配(SmallestWidth适配 sw限定符)最新步骤解析
  13. 计算机在中医方剂中的应用,计算机中医应用(精).docx
  14. PIC单片机入门教程(三)—— 安装编译器(MPLAB XC Compilers)
  15. 基于BP神经网络PID控制+Simulink仿真
  16. 大规模定制生产模式及其关键技术
  17. 圣诞礼物|2020年送这些礼物你就能拥有一个程序员男朋友
  18. diff比较两个目录时,如何略过特定目录或文件
  19. 5个国内优秀网站设计案例分享
  20. 【前端技术】一篇文章搞掂:WeX5

热门文章

  1. as3+php+测试地址,m3u8 视频测试地址
  2. 小程序未来几年的一个发展趋势!
  3. Android开发: 分享利用好Kotlin的特点提高开发效率
  4. 2、shell入门命令练习及文本处理三剑客
  5. Android 应用跳转到指定QQ临时聊天界面
  6. 素描纸制品绘画攻略,掌握规律变简单~
  7. LaTeX设置 --- itemize 与 enumerate
  8. 神经网络每次结果不一样,神经网络预测问题
  9. Go by Example
  10. 正则表达式:一张图入门级了解正则表达式