匿名函数:
没有函数名的函数就是匿名函数
匿名函数的定义格式如下:
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
注意:
recover()
必须搭配defer
使用。defer
一定要在可能引发panic
的语句之前定义。(否则defer 语句不可达,如下图所示)