脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - Golang之defer 延迟调用操作

Golang之defer 延迟调用操作

2021-03-19 00:56程序猿编码 Golang

这篇文章主要介绍了Golang之defer 延迟调用操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

前言

defer语句被用于预定对一个函数的调用。我们把这类被defer语句调用的函数称为延迟函数。而defer 延迟语句在其他编程语言里好像没有见到。应该是属于 Go 语言里的独有的关键字。但用法类似于面向对象编程语言 Java 和 C# 的 finally 语句块。

下面对defer进行介绍。

defer特性

 

1. 关键字 defer 用于注册延迟调用。

2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。

3. 多个defer语句,按先进后出的方式执行。

1.延迟调用

 

用法很简单,只需要在函数前面加上 defer就行,就能实现将这个 该函数的调用延迟到当前函数执行完后再执行。例如:

?
1
2
3
4
5
6
7
8
9
10
11
package main
import (
 "fmt"
)
func myFunc(){
 fmt.Println("minger")
}
func main(){
 defer myFunc() //等价于defer fmt.Println("minger")
 fmt.Println("程序猿编码")
}

编译运行:

Golang之defer 延迟调用操作

2.defer 与 return 孰先孰后

 

defer 和 return 到底是哪个先调用?先看看例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import (
 "fmt"
)
var name string = "go"
func myFunc() string {
  defer func() {
    name = "python"
  }()
  fmt.Println("myFunc 函数里的name:", name)
  return name
}
func main() {
  myName := myFunc()
  fmt.Println("main 函数里的name: ", name)
  fmt.Println("main 函数里的myname: ", myName )

编译运行:

Golang之defer 延迟调用操作

来看看打印信息,第一行输出,name 此时还是全局变量,值还是go

第二行输出,在 defer 里改变了全局变量,此时name的值已经变成了 python

重点在第三行,为什么输出的是 go ?

解释只有一个,那就是 defer 是return 后才调用的。所以在执行 defer 前,myName 已经被赋值成 go 了。

3.多个defer 逆序执行

 

还是老规矩先来上代码,看看输出信息,例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
package main
import (
 "fmt"
)
func main(){
 name := "go"
 defer fmt.Println(name)
 name = "C/C++"
 defer fmt.Println(name)
 name = "Python"
 fmt.Println(name)
}

编译输出:

Golang之defer 延迟调用操作

可见 多个defer 是它们会以逆序执行(类似栈,即后进先出)。

defer官方的解释

Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the “defer” statement is executed.

翻译一下:

每次defer语句执行的时候,会把函数“压栈”,函数参数会被拷贝下来;当外层函数(非代码块,如一个for循环)退出时,defer函数按照定义的逆序执行;如果defer执行的函数为nil, 那么会在最终调用函数的产生panic.

为什么需要defer?

 

往往我们在编程的时候,经常需要打开一些资源,比如数据库连接、文件、锁等,这些资源需要在用完之后释放掉,否则会造成内存泄漏。

因此我们有时会忘记关闭这些资源。Golang直接在语言层面提供defer关键字,在打开资源语句的下一行,就可以直接用defer语句来注册函数结束后执行关闭资源的操作。

defer用途

1. 关闭文件句柄

2. 锁资源释放

3. 数据库连接释放

defer的使用其实非常简单,来看看一个简单用途:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
 "log"
 "os"
)
func main() {
 f, err := os.OpenFile("text.txt", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) //文件没有就创建,文件存在就追加
 if err != nil {
 log.Fatal(err)
 }
 defer f.Close()
 f.WriteString("程序猿编码\n")
}

编译输出:

Golang之defer 延迟调用操作

在打开文件的语句附近,用defer语句关闭文件。这样,在函数结束之前,会自动执行defer后面的语句来关闭文件。

当然,defer会有小小地延迟,对时间要求特别特别特别高的程序,可以避免使用它。

总结

defer 语句经常使用于成对的操作,比如打开和关闭,连接和断开,加锁和解锁,即便是再复杂的控制流,资源在任何情况下都能够正确释放。

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

前言

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

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

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

1.defer的生效顺序

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

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

正文

1.defer的生效顺序

 

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

?
1
2
3
4
5
6
7
8
func main() {
 defer func() {
 fmt.Println("我后出来")
 }()
 defer func() {
 fmt.Println("我先出来")
 }()
}

执行后打印出:

我先出来

我后出来

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

 

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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的时候做的决定。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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后函数的{}中的逻辑

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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/chen1415886044/article/details/105022265

延伸 · 阅读

精彩推荐
  • Golanggolang json.Marshal 特殊html字符被转义的解决方法

    golang json.Marshal 特殊html字符被转义的解决方法

    今天小编就为大家分享一篇golang json.Marshal 特殊html字符被转义的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 ...

    李浩的life12792020-05-27
  • GolangGolang通脉之数据类型详情

    Golang通脉之数据类型详情

    这篇文章主要介绍了Golang通脉之数据类型,在编程语言中标识符就是定义的具有某种意义的词,比如变量名、常量名、函数名等等,Go语言中标识符允许由...

    4272021-11-24
  • Golanggo语言制作端口扫描器

    go语言制作端口扫描器

    本文给大家分享的是使用go语言编写的TCP端口扫描器,可以选择IP范围,扫描的端口,以及多线程,有需要的小伙伴可以参考下。 ...

    脚本之家3642020-04-25
  • GolangGolang中Bit数组的实现方式

    Golang中Bit数组的实现方式

    这篇文章主要介绍了Golang中Bit数组的实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    天易独尊11682021-06-09
  • Golanggolang的httpserver优雅重启方法详解

    golang的httpserver优雅重启方法详解

    这篇文章主要给大家介绍了关于golang的httpserver优雅重启的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    helight2992020-05-14
  • Golanggolang如何使用struct的tag属性的详细介绍

    golang如何使用struct的tag属性的详细介绍

    这篇文章主要介绍了golang如何使用struct的tag属性的详细介绍,从例子说起,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看...

    Go语言中文网11352020-05-21
  • Golanggo日志系统logrus显示文件和行号的操作

    go日志系统logrus显示文件和行号的操作

    这篇文章主要介绍了go日志系统logrus显示文件和行号的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    SmallQinYan12302021-02-02
  • Golanggolang 通过ssh代理连接mysql的操作

    golang 通过ssh代理连接mysql的操作

    这篇文章主要介绍了golang 通过ssh代理连接mysql的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    a165861639710342021-03-08