Go语言基础:闭包_defer_recover

2024-03-12 17:40

本文主要是介绍Go语言基础:闭包_defer_recover,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

主要借鉴七米的博客

文章目录

  • 闭包
  • defer关键字
    • defer执行时机
    • 练习
    • 面试题
  • 内置函数介绍
      • panic/recover
  • 练习

闭包

先看一个例子:

package mainimport "fmt"func sum012(x,y int)func()int{return func() int {return x+y}
}func main() {f:=sum012(5,6)fmt.Println(f()) //11fmt.Println(sum012(7,8)()) //15
}

再这个例子中,使用了sum012函数进行求和,而返回的是一个匿名函数

在该匿名函数中的作用域里面并没有x和y,于是匿名函数就向上寻找,在参数传递找到了x和y

把函数赋值给f,此时f就是一个闭包.

注意调用的时候的匿名函数的特殊性

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

package mainimport "fmt"func sum013(x int)func(int)int{return func(y int)int{x+=yreturn x}
}func main() {var f=sum013(30)fmt.Println(f(20)) //50fmt.Println(f(30)) //80fmt.Println(f(40)) //120var s = sum013(10) //此时f生命周期暂停,s生命周期开始fmt.Println(s(10)) //20
}

实例2:

package mainimport ("fmt""strings"
)// 这是一个添加后缀名的例子
func add13(s string)func (string)string{return func(s2 string)string{if !strings.HasSuffix(s2,s){// 该函数判断的是s是否是在s2的尾部// s是后缀名,s2是文件名return s2+s}else{return s2}}
}
func main() {var f=add13(".jpg")fmt.Println(f("123"))fmt.Println(f("456.jpg"))var z=add13(".txt")fmt.Println(z("hhh.txt"))
}
//123.jpg
//456.jpg
//hhh.txt

示例三

本示例由于一个函数有两个返回值,所以需要两个变量来共同接收:

package mainimport "fmt"func calculate15(x int)(func (int)int,func(int)int){add:=func (y int)int{return x+y}sub:=func(y int)int{return x-y}return add,sub
}func main() {var f1,f2=calculate15(10)fmt.Println(f1(5),f2(10)) //15 0fmt.Println(f1(10),f2(20)) //20 -10
}

闭包其实并不复杂,只要牢记闭包=函数+引用环境

defer关键字

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

package mainimport "fmt"func main() {fmt.Println("Start")defer fmt.Print(1)defer fmt.Print(2)defer fmt.Print(3)fmt.Println("End")
}
//Start
//End
//321

可见,GO是先执行没有defer的,再按照顺序逆序执行defer后面的语句

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

defer执行时机

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

下面的例子

package mainimport "fmt"func test_defer()string{fmt.Println("这是没有defer的语句")defer fmt.Println("这是defer语句")return "这是return"
}
func main() {fmt.Println(test_defer())
}

由于返回值在函数定义的时候已经将该变量进行定义,在执行return的时候会先执行返回值保存操作,而后续的defer函数会改变这个返回值(虽然defer是在return之后执行的,但是由于使用的函数定义的变量,所以执行defer操作后对该变量的修改会影响到return的值

练习

判断,下面的结果:

package mainimport "fmt"func f1() int {x := 5defer func() {x++}()return x
}func f2() (x int) {defer func() {x++}()return 5
}func f3() (y int) {x := 5defer func() {x++}()return x
}
func f4() (x int) {defer func(x int) {x++}(x)return 5
}
func main() {fmt.Println(f1())fmt.Println(f2())fmt.Println(f3())fmt.Println(f4())
}

输出结果是5 6 5 5

先看f3,一开始y=0,x=5.之后被defer,return的是x=5,之后执行了func,但是y并没有被改变,仍然输出了值为5的y

面试题

func calc(index string, a, b int) int {ret := a + bfmt.Println(index, a, b, ret)return ret
}func main() {x := 1y := 2defer calc("AA", x, calc("A", x, y))x = 10defer calc("BB", x, calc("B", x, y))y = 20
}

问,上面代码的输出结果是?(提示:defer注册要延迟执行的函数时该函数所有的参数都需要确定其值)

A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4

内置函数介绍

内置函数介绍
close主要用来关闭channel
len用来求长度,比如string、array、slice、map、channel
new用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
make用来分配内存,、主要用来分配引用类型,比如chan、map、slice
append用来追加元素到数组、slice中
panic和recover用来做错误处理

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 Bgoroutine 1 [running]:
main.funcB(...).../code/func/main.go:12
main.main().../code/func/main.go:20 +0x98

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

  1. recover()必须搭配defer使用。
  2. defer一定要在可能引发panic的语句之前定义。

例如

package mainimport "fmt"
func First_recover(){fmt.Println("Recover的使用")
}
func A_recover(){defer func() {error := recover()if error != nil {fmt.Println("程序出现了错误,现已恢复")}}()panic("程序有错")
}func main() {First_recover()A_recover()
}

练习

/*
你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
分配规则如下:
a. 名字中每包含1个'e'或'E'分1枚金币
b. 名字中每包含1个'i'或'I'分2枚金币
c. 名字中每包含1个'o'或'O'分3枚金币
d: 名字中每包含1个'u'或'U'分4枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
程序结构如下,请实现 ‘dispatchCoin’ 函数
*/
var (coins = 50users = []string{"Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",}distribution = make(map[string]int, len(users))
)func main() {left := dispatchCoin()fmt.Println("剩下:", left)
}

答案:

/*
你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
分配规则如下:
a. 名字中每包含1个'e'或'E'分1枚金币
b. 名字中每包含1个'i'或'I'分2枚金币
c. 名字中每包含1个'o'或'O'分3枚金币
d: 名字中每包含1个'u'或'U'分4枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
程序结构如下,请实现 ‘dispatchCoin’ 函数
*/
package mainimport "fmt"var (
coins = 50
users = []string{"Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
}
distribution = make(map[string]int, len(users))
)
func dispatchCoin()int{summary:=0for _,s:=range users{sum:=0s_rune:=[]rune(s)for _,c:=range s_rune{f:=string(c)switch f{case "e","E":sum+=1case "i","I":sum+=2case "o","O":sum+=3case "u","U":sum+=4default:continue}}fmt.Printf("%s所花费的coins为:%v\n",s,sum)summary+=sum}return coins-summary
}
func main() {
left := dispatchCoin()
fmt.Println("剩下:", left)
}

运行结果:

Matthew所花费的coins为:1
Sarah所花费的coins为:0
Augustus所花费的coins为:12
Heidi所花费的coins为:5
Emilie所花费的coins为:6
Peter所花费的coins为:2
Giana所花费的coins为:2
Adriano所花费的coins为:5
Aaron所花费的coins为:3
Elizabeth所花费的coins为:4
剩下: 10

这篇关于Go语言基础:闭包_defer_recover的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/802062

相关文章

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

go中空接口的具体使用

《go中空接口的具体使用》空接口是一种特殊的接口类型,它不包含任何方法,本文主要介绍了go中空接口的具体使用,具有一定的参考价值,感兴趣的可以了解一下... 目录接口-空接口1. 什么是空接口?2. 如何使用空接口?第一,第二,第三,3. 空接口几个要注意的坑坑1:坑2:坑3:接口-空接口1. 什么是空接

C#基础之委托详解(Delegate)

《C#基础之委托详解(Delegate)》:本文主要介绍C#基础之委托(Delegate),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 委托定义2. 委托实例化3. 多播委托(Multicast Delegates)4. 委托的用途事件处理回调函数LINQ

C语言中的数据类型强制转换

《C语言中的数据类型强制转换》:本文主要介绍C语言中的数据类型强制转换方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C语言数据类型强制转换自动转换强制转换类型总结C语言数据类型强制转换强制类型转换:是通过类型转换运算来实现的,主要的数据类型转换分为自动转换

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件

C语言实现两个变量值交换的三种方式

《C语言实现两个变量值交换的三种方式》两个变量值的交换是编程中最常见的问题之一,以下将介绍三种变量的交换方式,其中第一种方式是最常用也是最实用的,后两种方式一般只在特殊限制下使用,需要的朋友可以参考下... 目录1.使用临时变量(推荐)2.相加和相减的方式(值较大时可能丢失数据)3.按位异或运算1.使用临时

使用C语言实现交换整数的奇数位和偶数位

《使用C语言实现交换整数的奇数位和偶数位》在C语言中,要交换一个整数的二进制位中的奇数位和偶数位,重点需要理解位操作,当我们谈论二进制位的奇数位和偶数位时,我们是指从右到左数的位置,本文给大家介绍了使... 目录一、问题描述二、解决思路三、函数实现四、宏实现五、总结一、问题描述使用C语言代码实现:将一个整

C语言字符函数和字符串函数示例详解

《C语言字符函数和字符串函数示例详解》本文详细介绍了C语言中字符分类函数、字符转换函数及字符串操作函数的使用方法,并通过示例代码展示了如何实现这些功能,通过这些内容,读者可以深入理解并掌握C语言中的字... 目录一、字符分类函数二、字符转换函数三、strlen的使用和模拟实现3.1strlen函数3.2st

Go语言中最便捷的http请求包resty的使用详解

《Go语言中最便捷的http请求包resty的使用详解》go语言虽然自身就有net/http包,但是说实话用起来没那么好用,resty包是go语言中一个非常受欢迎的http请求处理包,下面我们一起来学... 目录安装一、一个简单的get二、带查询参数三、设置请求头、body四、设置表单数据五、处理响应六、超

C语言中的浮点数存储详解

《C语言中的浮点数存储详解》:本文主要介绍C语言中的浮点数存储详解,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、首先明确一个概念2、接下来,讲解C语言中浮点型数存储的规则2.1、可以将上述公式分为两部分来看2.2、问:十进制小数0.5该如何存储?2.3 浮点