Go函数下篇:defer和闭包

简介: Go函数下篇:defer和闭包

defer

  1. 使用defer注册的匿名函数(延迟调用)还可以给它传参,不过是值拷贝
package main
import "fmt"
func work() int {
 num := 10
 defer func(i int) {
  i += 20
  println("defer内的结果:", i)
 }(num)
 return num
}
func main() {
 ret := work()
 fmt.Println(ret)
}

输出:

defer内的结果: 30
10

解析:在work函数内,变量num传递给了通过defer注册了的匿名函数,在匿名函数内做了加20,但它的结果并未影响到外面的num变量,这就是值拷贝。

  1. 当主动调用os.Exit退出程序后,defer会不会执行?试试再说
package main
import (
 "fmt"
 "os"
)
func main() {
 defer func() {
  fmt.Println("释放锁...")
 }()
 fmt.Println("hello")
 os.Exit(1)
}

输出:

hello

答案很明显了,居然不会。

  1. 注册自定义的函数为延迟调用,模拟个小栗子:打开文件,处理,处理完后关闭该文件
package main
import (
 "fmt"
)
func closeFile() {
 println("关闭文件")
}
func work() bool {
 defer closeFile()
 fmt.Println("打开文件")
 fmt.Println("正在处理...")
 fmt.Println("处理完成!")
 return true
}
func main() {
 status := work()
 fmt.Println(status)
}

输出:

打开文件
正在处理...
处理完成!
true
关闭文件

所以说,defer的应用场景可以是释放资源或者关闭连接、关闭文件、释放锁等等,说白了就是:它就是做收尾工作的家伙。

闭包

  1. 闭包初体验

闭包的本质其实返回的是一个函数,但这个函数有点特殊,为啥说特殊呢?因为在这个函数里面,还有一个函数,这个函数是匿名函数,且在这个匿名函数里面还可以引用外部变量,当被反复调用时,这里引用的外部变量只会初始化一次。因此:闭包=函数+函数里面的匿名函数+匿名函数引用的外部变

「接下来一步一步进行解剖」

刚才说到闭包的本质其实返回的是一个函数,之前提到过,匿名函数是可以作为函数的返回值的,看下面代码:

func bibao() func() {
}

上面的栗子中,定义了一个函数Bibao,这个函数没有入参,返回值的类型是函数类型,也就是定义成func(),所以它要返回一个函数才行呢,现在函数体是空的,别急,一步一步解剖。

刚才说到在这个函数里面,还有一个函数,这个函数是匿名函数,且还要返回这个匿名函数,看下面的代码:

func bibao() func() {
 return func() {
  fmt.Println("hello")
 }
}
func main() {
 b := bibao()
 b()
}

输出:

hello

在bibao函数体里,返回了一个匿名函数,这个匿名函数的功能是输出了字符串“hello”,调用bibao()把它赋值给变量b,此刻,变量b就是一个函数(它接收的就是返回的匿名函数),变量b既然是函数,那就可以进行调用了b(),所以执行后,输出的是“hello”。

刚才说到且在这个匿名函数里面还可以引用外部变量,当被反复调用时,这里引用的外部变量只会初始化一次,看下面代码:

package main
import "fmt"
func bibao(name string) func() string {
 base := "hello"
 return func() string {
  base = base + " " + name
  return base
 }
}
func main() {
 b := bibao("ttr")
 fmt.Println(b()) // 第1次调用
 fmt.Println(b()) // 第2次调用
 fmt.Println(b()) // 第3次调用
 fmt.Println(b()) // 第4次调用
}

输出:

hello ttr //第1次调用的结果输出
hello ttr ttr //第2次调用的结果输出
hello ttr ttr ttr //第3次调用的结果输出
hello ttr ttr ttr ttr //第4次调用的结果输出

这次对bibao函数做了点小改造,它需要接收一个参数name。在bibao函数中,对于匿名函数内部来说,它的外部变量就是bibao函数里的base变量。调用函数bibao并传入"ttr",在匿名函数中进行了字符串拼接,bibao("ttr")初始化了一次,b()反复调用了4次,在匿名函数中引用的外部变量base,每次调用时返回的是外部变量base的多个副本,因为在每次调用时都会为局部变量分配内存(对于整个程序来说,在函数里的base变量是局部变量,而对于在bibao函数里的匿名函数来说,base变量是外部变量。)

还可以再这样改造,效果也是一样的:

package main
import "fmt"
func bibao() func(name string) string {
 base := "hello"
 return func(name string) string {
  base = base + " " + name
  return base
 }
}
func main() {
 b := bibao()
 fmt.Println(b("ttr"))
 fmt.Println(b("ttr1"))
 fmt.Println(b("ttr2"))
}

输出:

hello ttr
hello ttr ttr1
hello ttr ttr1 ttr2

好了,到此为止,闭包到底在哪呢?下面这块就是闭包,bibao函数返回了这个闭包:

base := "hello"
return func(name string) string {
 base = base + " " + name
 return base
}

闭包是由函数和与其相关的引用环境组合而成的实体,这里所说的函数就是匿名函数,所说的引用环境就是外部变量base,他们一起组合成了一个实体并返回,这就是闭包。

突然想到如果把base变量放到匿名函数里面,它还是不是一个闭包?看看效果:

package main
import "fmt"
func bibao() func(name string) string {
 return func(name string) string {
  base := "hello "
  base = base + " " + a + " " + name
  return base
 }
}
func main() {
 b := bibao()
 fmt.Println(b("ttr"))
 fmt.Println(b("ttr1"))
 fmt.Println(b("ttr2"))
}

输出:

hello ttr
hello ttr1
hello ttr2
  1. 来一个小栗子

闭包的好处在于,可以减少全局变量,在函数调用的过程中隐式的传递共享变量,这更具有封装性,更安全了,使其不能随意访问到共享变量。

package main
import (
 "fmt"
)
func bankcard(name string) func() (string, float32) {
 amount := 10000.0
 return func() (string, float32) {
  return name, float32(amount)
 }
}
func main() {
 b := bankcard("xiaoming")
 fmt.Println(b())
}
相关文章
|
26天前
|
JSON 安全 网络协议
go语言使用内置函数和标准库
【10月更文挑战第18天】
16 3
|
3月前
|
Go
go函数
go函数
34 10
|
3月前
|
编译器 Go C++
Go to Learn Go之函数
Go to Learn Go之函数
29 0
|
3月前
|
编译器 Go 索引
Go数组、多维数组和切片(动态数组),及常用函数len(),cap(),copy(),append()在切片中的使用
本文介绍了Go语言中数组、多维数组和切片(动态数组)的基本概念和操作,包括数组的定义、初始化、访问,多维数组的定义和访问,以及切片的创建、使用和扩容。同时,还讲解了切片中常用的函数len()、cap()、copy()和append()的使用方法。
|
4月前
|
Prometheus Cloud Native Go
Go 1.22 标准库 slices 新增函数和一些旧函数增加新特性
Go 1.22 标准库 slices 新增函数和一些旧函数增加新特性
|
4月前
|
存储 Go
掌握 Go 语言的 defer 关键字
掌握 Go 语言的 defer 关键字
|
4月前
|
设计模式 Java 数据库连接
|
4月前
|
Go 开发者
|
4月前
|
Go
实验深度理解Go中try...catch...的panic、defer、recover用法
文章通过实验代码演示了Go语言中如何使用panic、defer和recover函数来模拟try...catch...的异常处理机制,并详细解释了每个函数的作用和在异常处理中的使用场景。
36 0
|
4月前
|
存储 机器学习/深度学习 设计模式
Go从入门到放弃之函数
Go从入门到放弃之函数