首页 > 其他分享 >Go 语言中闭包与defer

Go 语言中闭包与defer

时间:2023-09-09 15:11:06浏览次数:34  
标签:defer 中闭 fmt Println int func Go main

匿名函数:

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

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

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 语句不可达,如下图所示)

标签:defer,中闭,fmt,Println,int,func,Go,main
From: https://www.cnblogs.com/lxing-go/p/17689493.html

相关文章

  • PGO in Go 1.21
    原文在这里。由MichaelPratt发布于2023年9月5日在2023年早些时候,Go1.20发布了供用户测试的概要版本的基于性能分析的优化(PGO)。经过解决预览版已知的限制,并得益于社区反馈和贡献的进一步改进,Go1.21中的PGO支持已经准备好供一般生产使用!请查阅性能分析优化用户指南以获取......
  • 一次简单的golang栈溢出
    一次简单的golang栈溢出https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458516910&idx=1&sn=aa8b791884ff0f2993235e84f04451c9&chksm=b18ecd2486f94432b1fa09ecfdf7ea00db9cc87659e02b66de4d0186c3386f91208bc0ec4cc3&mpshare=1&scene=1&srci......
  • googlectf2023 gradebook 复现(TOCTOU)
    Gradebook结构根据函数sub_2247开头的部分可以推测出gradebook的结构0x4year0x8gradebookname(32bytes)0x28studentname(32bytes)0x48sizeofthisgradebook(uint)0x50firstgradestructureoffset0x58newgradestructureoffset每一条记录grade......
  • Go - ERROR: # command-line-arguments undefined
    zzh@ZZHPC:/zdata/MyPrograms/Go/aaa/Ch05/05_04$gorunhttpd.go#command-line-arguments./httpd.go:15:9:undefined:DB./httpd.go:58:16:undefined:NewDBzzh@ZZHPC:/zdata/MyPrograms/Go/aaa/Ch05/05_04$gorun*.go2023/09/0823:29:15error:sql:unknowndriver......
  • MongoDB 入门
    0x01概述MongoDB是一个基于分布式文件存储的开源数据库,由C++语言编写,提供了一个可扩展的高性能数据存储解决方案MongoDB是一个文档型数据库,属于非关系型数据库(NoSQL)的一种,其数据是以文档的形式来存储的文档(Document)在MongoDB中是一个非常重要的概念,类似MySQL中的......
  • Go每日一库之2:go-flags
    简介在上一篇文章中,我们介绍了flag库。flag库是用于解析命令行选项的。但是flag有几个缺点:不显示支持短选项。当然上一篇文章中也提到过可以通过将两个选项共享同一个变量迂回实现,但写起来比较繁琐;选项变量的定义比较繁琐,每个选项都需要根据类型调用对应的Type或TypeVar函数;......
  • 接口文档,jwt介绍和构成,jwt签发与认证,base64编码,drf-jwt使用,django-rest-framewor
    1接口文档#作为后端,接口写好了#作为前端,需要使用我们写的接口(移动端,web,桌面端)#后端需要写接口文档#接口文档的展现形式: 1word,md,写好传到公司的某个平台---》前端可以下载2自动生成接口文档---》后端通过配置--》把所写的接口都自动生成---》地址--》访问......
  • GO语言中import GitHub的包 会影响加载速度吗
    在Go语言中使用GitHub的包不会影响加载速度。在Go语言中,所有包都是静态导入的,因此使用import关键字导入GitHub的包时,Go编译器会将包中的代码文件解压缩到您的项目目录中,并在运行时直接调用这些文件,而不是通过网络下载它们。这意味着import语句不会增加项目的启动时间,而且使用import......
  • RunnerGo:性能测试领域的领跑者
    随着软件行业的飞速发展,性能测试已经成为确保应用程序稳定性和可靠性的重要环节。RunnerGo,作为一款由国内开发者基于Go语言自主研发的性能压测工具,正在受到越来越多人的关注。本文将详细介绍RunnerGo的优势、应用场景以及与其他测试工具的比较,进一步阐明为何RunnerGo成为性能测试领......
  • 【回顾】Google Cloud Next '23 引入GKE Enterprise——容器平台的下一阶段发展
    【CloudAce是GoogleCloud全球战略合作伙伴,在亚太地区、欧洲、南北美洲和非洲拥有二十多个办公室。CloudAce在谷歌专业领域认证及专业知识目前排名全球第一位,并连续多次获得GoogleCloud各类奖项。作为谷歌云托管服务商,我们提供谷歌云、谷歌地图、谷歌办公套件、谷歌云认证......