Go 语言中闭包与defer

发布时间 2023-09-09 15:11:51作者: 小星code

匿名函数:

没有函数名的函数就是匿名函数

匿名函数的定义格式如下:

func(参数)(返回值){
    函数体
}

基本使用:

func main() {
	// 将匿名函数保存到变量
	add := func(x, y int) {
		fmt.Println(x + y)
	}
	add(10, 20) // 通过变量调用匿名函数

	//自执行函数:匿名函数定义完加()直接执行
	func(x, y int) {
		fmt.Println(x + y)
	}(10, 20)
}

闭包

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境

func adder() func(int) int {
	var x int  //adder函数的变量
	return func(y int) int {
		x += y  //这里先从匿名函数内部找变量 x,如果没有,再到外层函数找
		return x
	}
}
func main() {
	var f = adder()  // 此时 f 就是一个闭包
	fmt.Println(f(10)) //10
	fmt.Println(f(20)) //30
	fmt.Println(f(30)) //60

	f1 := adder()
	fmt.Println(f1(40)) //40
	fmt.Println(f1(50)) //90
}

变量f是一个函数,并且它引用了其外部作用域中的x变量,此时f就是一个闭包。 在f的生命周期内,变量x也一直有效。

闭包进阶示例1:

func adder2(x int) func(int) int { //x 作为参数传进来
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var f = adder2(10)
	fmt.Println(f(10)) //20
	fmt.Println(f(20)) //40
	fmt.Println(f(30)) //70

	f1 := adder2(20)
	fmt.Println(f1(40)) //60
	fmt.Println(f1(50)) //110
}

闭包进阶示例2:

func makeSuffixFunc(suffix string) func(string) string {
	return func(name string) string {
         //strings.HasSuffix 判断字符串的后缀
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}
		return name
	}
}

func main() {
	jpgFunc := makeSuffixFunc(".jpg")
	txtFunc := makeSuffixFunc(".txt")
	fmt.Println(jpgFunc("test")) //test.jpg
	fmt.Println(txtFunc("test")) //test.txt
}

闭包进阶示例3:

func calc(base int) (func(int) int, func(int) int) {  // calc 返回值是两个函数
	add := func(i int) int { 
		base += i
		return base
	}
	sub := func(i int) int {
		base -= i
		return base
	}
	return add, sub
}

func main() {
	f1, f2 := calc(10)
	fmt.Println(f1(1), f2(2)) //11 9
	fmt.Println(f1(3), f2(4)) //12 8
	fmt.Println(f1(5), f2(6)) //13 7
}

总结:判断一个函数是否是闭包,只需要看在该函数内,是否存在外部函数变量的引用,存在即是闭包,不存在就不是闭包;

defer语句

Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。

举个例子:

func main() {
	fmt.Println("start")
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println("end")
}
//start
//end
//3
//2
//1

因此,由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。

defer执行时机

在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。具体如下图所示:

defer经典案例

案例1

package main

import "fmt"

func f1() int {
	x := 5
	defer func() {
		fmt.Println("f1x1 ", x)
		x++
		fmt.Println("f1x2 ", x)
	}()
	return x 
	//1.先给返回值变量 x 赋值为5,
	//2. 执行defer 语句,变量x++ 变成6 (这个与返回值变量不是同一个)
	//3. 返回 (5)
}

func f2() (x int) {
	defer func() {
		fmt.Println("f2x1 ", x)
		x++
		fmt.Println("f2x2 ", x)
	}()
	return 5
	//1.先给返回值变量 x 赋值为5,
	//2. 执行defer 语句,返回值变量x++ 变成6 (此处 defer 操作的就是返回值变量)
	//3. 返回 (6)
}

func f3() (y int) {
	x := 5
	defer func() {
		x++
	}()
	return x
	//1.先给返回值变量 y 赋值为5,
	//2. 执行defer 语句,将f3内的变量 x 变为6
	//3. 返回 (5,返回的变量是y)
}
func f4() (x int) {
	defer func(x int) {
		x++
	}(x)
	return 5
	//1.先给返回值变量 x 赋值为5,
	//2. 执行defer 语句,将返回值变量x作为参数传进去(此处因为是值传递,所以不影响外边真实的返回值x的变化)
	//3. 返回 (5)
}

func main() {
	fmt.Printf("f1:%d\n", f1()) //f1:5
	fmt.Printf("f2:%d\n", f2()) //f2:6
	fmt.Printf("f3:%d\n", f3()) //f3:5
	fmt.Printf("f4:%d\n", f4()) //f4:5
}

案例2

func calc(index string, a, b int) int {
	ret := a + b
	fmt.Println(index, a, b, ret)
	return ret
}

func main() {
	x := 1
	y := 2
	defer calc("AA", x, calc("A", x, y))
	x = 10
	defer calc("BB", x, calc("B", x, y))
	y = 20
}
/*结果:
A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4 
*/

本题关键:defer注册要延迟执行的函数时该函数所有的参数都需要确定其值


panic/recover

Go语言中目前(Go1.12)是没有异常机制,但是使用panic/recover模式来处理错误。 panic可以在任何地方引发,但recover只有在defer调用的函数中有效。 首先来看一个例子:

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	panic("panic in B")
}

func funcC() {
	fmt.Println("func C")
}
func main() {
	funcA()
	funcB()
	funcC()
}

运行结果:

func A
panic: panic in B                                                     
                                                                      
goroutine 1 [running]:                                                
main.funcB(...)                                                       
        G:/Desktop/All_code/Go_code/AcWing/bagu/defer/main.go:68      
main.main()                                                           
        G:/Desktop/All_code/Go_code/AcWing/bagu/defer/main.go:76 +0x66

程序运行期间funcB中引发了panic导致程序崩溃,异常退出了。这个时候我们就可以通过recover将程序恢复回来,继续往后执行

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	defer func() {
		err := recover()
		//如果程序出出现了panic错误,可以通过recover恢复过来
		if err != nil {
			fmt.Println("recover in B")
		}
	}()
	panic("panic in B")
}

func funcC() {
	fmt.Println("func C")
}
func main() {
	funcA()
	funcB()
	funcC()
}

运行结果:

func A
recover in B
func C

注意:

  1. recover()必须搭配defer使用。
  2. defer一定要在可能引发panic的语句之前定义。(否则defer 语句不可达,如下图所示)