12. Go 语言流程控制:defer 延迟语句

Hi,你们好,我是明哥。java

在本身学习 Golang 的这段时间里,我写了详细的学习笔记放在个人我的微信公众号 《Go编程时光》,对于 Go 语言,我也算是个初学者,所以写的东西应该会比较适合刚接触的同窗,若是你也是刚学习 Go 语言,不防关注一下,一块儿学习,一块儿成长。python

个人在线博客:http://golang.iswbm.com
个人 Github:github.com/iswbm/GolangCodingTimegit


Go里的流程控制方法仍是挺丰富,整理了下有以下这么多种:github

  • if - else 条件语句
  • switch - case 选择语句
  • for - range 循环语句
  • goto 无条件跳转语句
  • defer 延迟执行

今天是最后一篇讲控制流程了,内容是 defer 延迟语句,这个在其余编程语言里好像没有见到。应该是属于 Go 语言里的独有的关键字,但即便如此,阅读后这篇文章后,你能够发现 defer 在其余编程语言里的影子。golang

1. 延迟调用

defer 的用法很简单,只要在后面跟一个函数的调用,就能实现将这个 xxx 函数的调用延迟到当前函数执行完后再执行。编程

defer xxx()

这是一个很简单的例子,能够很快帮助你理解 defer 的使用效果。数组

import "fmt"

func myfunc() {
	fmt.Println("B")
}

func main() {
	defer myfunc()
	fmt.Println("A")
}

输出以下微信

A
B

固然了,对于上面这个例子能够简写为成以下,输出结果是一致的编程语言

import "fmt"

func main() {
	defer fmt.Println("B")
	fmt.Println("A")
}

2. 即时求值的变量快照

使用 defer 只是延时调用函数,此时传递给函数里的变量,不该该受到后续程序的影响。函数

好比这边的例子

import "fmt"

func main() {
	name := "go"
	defer fmt.Println(name) // 输出: go

	name = "python"
	fmt.Println(name)      // 输出: python
}

输出以下,可见给 name 从新赋值为 python,后续调用 defer 的时候,仍然使用未从新赋值的变量值,就好在 defer 这里,给全部的这是作了一个快照同样。

python
go

3. 多个defer 反序调用

当咱们在一个函数里使用了 多个defer,那么这些defer 的执行函数是如何的呢?

作个试验就知道了

import "fmt"

func main() {
	name := "go"
	defer fmt.Println(name) // 输出: go

	name = "python"
	defer fmt.Println(name) // 输出: python

	name = "java"
	fmt.Println(name)
}

输出以下,可见 多个defer 是反序调用的,有点相似栈同样,后进先出。

java
python
go

3. defer 与 return 孰先孰后

至此,defer 还算是挺好理解的。在通常的使用上,是没有问题了。

在这里提一个稍微复杂一点的问题,defer 和 return 究竟是哪一个先调用?

使用下面这段代码,能够很容易的观察出来

import "fmt"

var name string = "go"

func myfunc() string {
	defer func() {
		name = "python"
	}()

	fmt.Printf("myfunc 函数里的name:%s\n", name)
	return name
}

func main() {
	myname := myfunc()
	fmt.Printf("main 函数里的name: %s\n", name)
	fmt.Println("main 函数里的myname: ", myname)
}

输出以下

myfunc 函数里的name:go
main 函数里的name: python
main 函数里的myname:  go

来一块儿理解一下这段代码,第一行很直观,name 此时仍是全局变量,值仍是go

第二行也不难理解,在 defer 里改变了这个全局变量,此时name的值已经变成了 python

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

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

4. 为何要有 defer?

看完上面的例子后,不知道你是否和我同样,对这个defer的使用效果感到熟悉?貌似在 Python 也见过相似的用法。

虽然 Python 中没有 defer ,可是它有 with 上下文管理器。咱们知道在 Python 中可使用 defer 实现对资源的管理。最经常使用的例子就是文件的打开关闭。

你可能会有疑问,这也没什么意义呀,我把这个放在 defer 执行的函数放在 return 那里执行不就行了。

当然能够,可是当一个函数里有多个 return 时,你得多调用好屡次这个函数,代码就臃肿起来了。

如果没有 defer,你能够写出这样的代码

func f() {
    r := getResource()  //0,获取资源
    ......
    if ... {
        r.release()  //1,释放资源
        return
    }
    ......
    if ... {
        r.release()  //2,释放资源
        return
    }
    ......
    if ... {
        r.release()  //3,释放资源
        return
    }
    ......
    r.release()     //4,释放资源
    return
}

使用了 defer 后,代码就显得简单直接,无论你在何处 return,都会执行 defer 后的函数。

func f() {
	r := getResource()  //0,获取资源
	
	defer r.release()  //1,释放资源
    ......
    if ... {
		...
        return
    }
    ......
    if ... {
		...
        return
    }
    ......
    if ... {
		...
        return
    }
    ......
    return
}

系列导读

01. 开发环境的搭建(Goland & VS Code)

02. 学习五种变量建立的方法

03. 详解数据类型:****整形与浮点型

04. 详解数据类型:byte、rune与string

05. 详解数据类型:数组与切片

06. 详解数据类型:字典与布尔类型

07. 详解数据类型:指针

08. 面向对象编程:结构体与继承

09. 一篇文章理解 Go 里的函数

10. Go语言流程控制:if-else 条件语句

11. Go语言流程控制:switch-case 选择语句

12. Go语言流程控制:for 循环语句

13. Go语言流程控制:goto 无条件跳转

14. Go语言流程控制:defer 延迟调用

15. 面向对象编程:接口与多态

16. 关键字:make 和 new 的区别?

17. 一篇文章理解 Go 里的语句块与做用域

18. 学习 Go 协程:goroutine

19. 学习 Go 协程:详解信道/通道

20. 几个信道死锁经典错误案例详解

21. 学习 Go 协程:WaitGroup

22. 学习 Go 协程:互斥锁和读写锁

23. Go 里的异常处理:panic 和 recover

24. 超详细解读 Go Modules 前世此生及入门使用

25. Go 语言中关于包导入必学的 8 个知识点

26. 如何开源本身写的模块给别人用?

27. 说说 Go 语言中的类型断言?

28. 这五点带你理解Go语言的select用法


相关文章
相关标签/搜索