引入

defer是Go语言中的一个关键字(延迟调用),一般用于释放资源和连接、关闭文件、释放锁等。和defer类似的有java的finally和C++的析构函数,这些语句一般是一定会执行的(某些特殊情况后文会提到),不过析构函数析构的是对象,而defer后面一般跟函数或方法。

1、 多个defer语句,按先进后出的方式执行

package mainimport "fmt"func main() {var whatever [5]struct{}for i := range whatever {defer fmt.Println(i)}
}

输出:

    43210

所有的defer语句会放入栈中,在入栈的时候会进行相关的值拷贝(也就是下面的“对应的参数会实时解析”)。

2、defer声明时,对应的参数会实时解析

简单示例:

package mainimport "fmt"func main() {i := 1fmt.Println("i =", i)defer fmt.Print(i)
}

输出:

i = 1
1

defer后面的语句最后才会执行,后面会讲当defer存在时return的执行逻辑。

辨析:defer后面跟无参函数、有参函数和方法:

package mainimport "fmt"func test(a int) {//无返回值函数defer fmt.Println("1、a =", a) //方法defer func(v int) { fmt.Println("2、a =", v)} (a) //有参函数defer func() { fmt.Println("3、a =", a)} () //无参函数a++
}
func main() {test(1)
}

输出:

3、a = 2
2、a = 1
1、a = 1

解释:

方法中的参数a,有参函数中的参数v,会请求参数,直接把参数代入,所以输出的都是1。a++变成2之后,3个defer语句以后声明先执行的顺序执行,无参函数中使用的a现在已经是2了,故输出2。

3、可读取函数返回值(return返回机制)

defer、return、返回值三者的执行逻辑应该是:
return最先执行,return负责将结果写入返回值中;
接着defer开始执行一些收尾工作;
最后函数携带当前返回值(可能和最初的返回值不相同)退出。

当defer语句放在return后面时,就不会被执行。如下:

package mainimport "fmt"func f(i int) int{return idefer fmt.Print("i =", i)return i+1
}func main() {f(1)
}

没有输出,因为return i之后函数就已经结束了,不会执行defer。
(1)无名返回值:

package mainimport ("fmt"
)func a() int {var i intdefer func() {i++fmt.Println("defer2:", i) }()defer func() {i++fmt.Println("defer1:", i) }()return i
}func main() {fmt.Println("return:", a())
}

输出:

defer1: 1
defer2: 2
return: 0

解释:
返回值由变量i赋值,相当于返回值=i=0。第二个defer中i++ = 1, 第一个defer中i++ = 2,所以最终i的值是2。但是返回值已经被赋值了,即使后续修改i也不会影响返回值。最终返回值返回,所以main中打印0。

(2)有名返回值:

package mainimport ("fmt"
)func b() (i int) {defer func() {i++fmt.Println("defer2:", i)}()defer func() {i++fmt.Println("defer1:", i)}()return i //或者直接写成return
}func main() {fmt.Println("return:", b())
}

输出:

defer1: 1
defer2: 2
return: 2

解释:
这里已经指明了返回值就是i,所以后续对i进行修改都相当于在修改返回值,所以最终函数的返回值是2。

(3)函数返回值为地址

package mainimport ("fmt"
)func c() *int {var i intdefer func() {i++fmt.Println("defer2:", i)}()defer func() {i++fmt.Println("defer1:", i)}()return &i
}func main() {fmt.Println("return:", *(c()))
}

输出:

defer1: 1
defer2: 2
return: 2

解释:
此时的返回值是一个指针(地址),这个指针=&i,相当于指向变量i所在的地址,两个defer语句都对i进行了修改,那么返回值指向的地址的内容也发生了改变,所以最终的返回值是2。

再看一个例子:

func f() (r int) {defer func(r int) {r = r + 5}(r)return 1
}

最初返回值r的值是1,虽然defer语句中函数的参数名也叫r(这里我记作r’),但传参的时候相当于r‘=r(值传递),函数内的语句相当于r’=r‘+5,所以返回值r并没有被修改,最终的返回值仍是1。

4、defer与闭包

package mainimport "fmt"type Test struct {name string
}
func (t *Test) pp() {fmt.Println(t.name)
}
func main() {ts := []Test{{"a"}, {"b"}, {"c"}}for _, t := range ts {defer t.pp()}
}

输出:

c
c
c

解释:
for结束时t.name=“c”,接下来执行的那些defer语句中用到的t.name的值均为”c“。

修改代码为:

package mainimport "fmt"type Test struct {name string
}
func pp(t Test) {fmt.Println(t.name)
}
func main() {ts := []Test{{"a"}, {"b"}, {"c"}}for _, t := range ts {defer pp(t)}
}

输出:

c
b
a

解释:
defer语句中的参数会实时解析,所以在碰到defer语句的时候就把该时的t代入了。

再次修改代码:

package mainimport "fmt"type Test struct {name string
}
func (t *Test) pp() {fmt.Println(t.name)
}func main() {ts := []Test{{"a"}, {"b"}, {"c"}}for _, t := range ts {tt := tprintln(&tt)defer tt.pp()}
}

输出:

0xc000010200
0xc000010210
0xc000010220
c
b
a

解释:

:=用来声明并赋值,连续使用2次a:=1就会报错,但是在for循环内,可以看出每次tt:=t时,tt的地址都不同,说明他们是不同的变量,所以并不会报错。每次都有一个新的变量tt:=t,所以每次在执行defer语句时,对应的tt不是同一个(for循环中实际上生成了3个不同的tt),所以输出的结果也不相同。

5、defer用于关闭文件和互斥锁

文件:

unc ReadFile(filename string) ([]byte, error) {f, err := os.Open(filename)if err != nil {return nil, err}defer f.close()return ReadAll()
}

互斥锁:

var mu sync.Mutex
var m = make(map[string]int)func lookup(key string) int {mu.Lock()defer mu.Unlock()return m[key]
}

6、“解除”对所在函数的依赖

package mainimport "fmt"
import "time"type User struct {username string
}func (this *User) Close() {fmt.Println(this.username, "Closed !!!")
}func main() {u1 := &User{"jack"}defer u1.Close()u2 := &User{"lily"}defer u2.Close()time.Sleep(10 * time.Second)fmt.Println("Done !")}

输出:

Done !
lily Closed !!!
jack Closed !!!

解释:
defer后面跟无参函数,u1.Close()和u2.Close()要等sleep和fmt.Println(“Done !”)之后才可以执行,也就是在函数最终返回之前执行。

修改代码为:

package mainimport "fmt"
import "time"type User struct {username string
}func (this *User) Close() {fmt.Println(this.username, "Closed !!!")
}func f(u *User) {defer u.Close()
}func main() {u1 := &User{"jack"}f(u1)u2 := &User{"lily"}func() { defer u2.Close() }()time.Sleep(10 * time.Second)fmt.Println("Done !")
}

输出:

jack Closed !!!
lily Closed !!!
Done !

这样的使用方式,似乎不太合理,但却有存在的必要性。大多数情况下,可以用于 u1,u2 之类非常消耗内存,或者cpu,其后执行时间过程且没有太多关联的情况。既保留了defer的功能特性,也满足范围精确控制的条件!(算是奇技淫巧吧

【Golang】Go语言defer用法大总结(含return返回机制)相关推荐

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

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

  2. Go语言 defer

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

  3. golang go语言_在Go语言中无需反思即可使用Lodash的好处

    golang go语言 by Tal Kol 通过塔尔科尔 在Go语言中无需反思即可使用Lodash的好处 (The benefits of using Lodash in the Go langua ...

  4. java怎么延迟执行语句_Go语言defer(延迟执行语句)

    Go语言中关键字defer允许我们推迟到函数返回之前(或任意位置执行return语句之后)一刻才执行某个语句或函数(为什么要在返回之后才执行这些语句?因为return语句同样可以包含一些操作,而不是单 ...

  5. Golang.Go语言基础

    Go 一,惊鸿一瞥 1,Hello,World 2,基本知识 3,基本控制结构 4,作用域 二,类型 1,浮点数 2,整数 3,big包 4,多语言文本 5,类型转换 三,构建块 1,函数 2,方法 ...

  6. golang go语言_为什么Go是作为PHP开发人员学习的功能强大的语言

    golang go语言 I've been programming using PHP professionally since 10 years now. After my Computer Eng ...

  7. Go语言的9大优势和3大缺点

    Go的优势 原因 1:性能 Go 极其地快.其性能与 Java 或 C++相似.在我们的使用中,Go 一般比 Python 要快 30 倍.以下是 Go 与 Java 之间的基准比较: 原因 2:语言 ...

  8. 动图图解C语言选择排序算法,含代码分析

    C语言文章更新目录 C语言学习资源汇总,史上最全面总结,没有之一 C/C++学习资源(百度云盘链接) 计算机二级资料(过级专用) C语言学习路线(从入门到实战) 编写C语言程序的7个步骤和编程机制 C ...

  9. c语言getchar用法_C语言 — 关键字

    几十个关键字不多,用得多了自然会记住,相信大家也不会担心.下面是C语言中的 32 个关键字: 一丶C语言关键字 第一个关键字:auto 用来声明自动变量.可以显式的声明变量为自动变量.只要不是声明在所 ...

  10. golang中的defer

    defer在go语言中可以发挥很大的作用,在函数中定义的defer会放在return前执行,defer后面可以放一些资源关闭的操作,以防忘记关闭资源而浪费空间. package mainimport ...

最新文章

  1. 模拟退火算法(TSP问题)
  2. pythonunicode和str_python unicode 和 str 类型的关系
  3. xp怎么删除计算机用户,WinXp系统如何删除用户账户?Xp系统删除用户账号的方法...
  4. 【c++ | 谭浩翔】第四章练习
  5. 西门子plm_西门子PLM组件之形状搜索(支持工业软件国产自主可控)
  6. 皮层之外(从进化的角度看皮层下脑组织:杏仁核,丘脑,纹状体,小脑)
  7. 码流 / 码率 / 比特率 / 帧速率 / 分辨率 / 高清的区别
  8. Oracle或关闭中国研发中心,裁员1600 人
  9. HTML中淡入的动画效果,利用CSS3制作淡入淡出动画效果
  10. Mac-homebrew使用(Mac下包管理工具)
  11. 读书,意味着你还不服输
  12. python 遍历,删除,复制文件夹下所有文件
  13. [USACO20JAN]Loan Repayment S
  14. uni-app获取元素高度
  15. 弹性伸缩定时任务支持Cron表达式 1
  16. PCB设计及制作-数字钟-物联网应用系统设计项目开发
  17. 【我理解Java数的累加累乘的基本思路】
  18. DSP(TMSF280049C)学习笔记1:软件的安装与新工程的建立
  19. vue正则判断输入0-100之间(含0、100)数字,最多两位(多位)小数
  20. jsp504化妆品购物网站商品推荐系统的设计与实现mysql

热门文章

  1. ExtJS应用架构设计(二)
  2. 正则表达式,小于等于180且大于等于0的浮点型
  3. WF4.0 RC 对比 Beta2 的变化
  4. lua把userdata写入mysql_Lua中的userdata
  5. 苹果手机上网很慢_手机信号满格,但上网速度却很慢?来听听通信专家怎么说的...
  6. include vdimgck.php,织梦后台升级后验证码登录一直显示错误的解决方法
  7. 完整的vue-cli3项目创建过程以及各种配置
  8. 2019全球区块链杭州高峰论坛将于5月17日举办!
  9. Python基础学习九 单元测试
  10. Python字符串、元组、列表、字典互相转换的方法