Golang学习笔记(04-2-闭包和defer)

321 阅读7分钟

1. 函数作为参数和返回值

1.1. 函数的类型

1.1.1. 函数名的本质

  • 不同的函数存在不同的类型,其形参和返回值为函数类型的一部分
  • 函数名是一个指针,指向了函数的内存地址
package main

import (
	"fmt"
)

func f1() {
	return
}

func f2(a, b int) {
	return
}

func f3(a, b int) (x, y int) {
	return 1, 2
}

func main() {
	fmt.Printf("f1: %T\t\t\t%v\n", f1, f1)
	fmt.Printf("f2: %T\t\t%v\n", f2, f2)
	fmt.Printf("f3: %T\t%v\n", f3, f3)
}
e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\04-func_type>go run main.go
f1: func()                      0x49f5f0
f2: func(int, int)              0x49f600
f3: func(int, int) (int, int)   0x49f610

1.1.2. 定义一个函数类型

package main

import "fmt"

func f0(n, m int) int {
	return m + n
}

func f1(n int) {
	fmt.Println(n + 1)
}

func main() {
	type funType func(int, int) int // 定义一种函数类型,func(int, int) int
	var ft0 funType = f0

	fmt.Printf("10+20=%d\n", ft0(10, 20))
	// ft0 = f1 // cannot use f1 (type func(int)) as type funType in assignment
	// ft0(10)  // not enough arguments in call to ft0
}

1.1.3. 函数赋值给变量

package main

import "fmt"

func f0(n, m int) int {
	return m + n
}

func f1(n int) string {
	ret := fmt.Sprintf("n+1=%d", n+1) // 数字转换为字符串
	return ret
}

func main() {
	v0, v1 := f0, f1
	fmt.Printf("f0--> type:%T\tvalue:%v\n", f0, f0(1, 2))
	fmt.Printf("v0--> type:%T\tvalue:%v\n", v0, v0(1, 2))
	fmt.Printf("f1--> type:%T\tvalue:%v\n", f1, f1(100))
	fmt.Printf("v1--> type:%T\tvalue:%v\n", v1, v1(100))
}
e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\04-func_type>go run main.go
f0--> type:func(int, int) int   value:3
v0--> type:func(int, int) int   value:3
f1--> type:func(int) string     value:n+1=101
v1--> type:func(int) string     value:n+1=101

1.2. 函数作为参数

package main

import "fmt"

func f0(n,m int) int {
	return m+n
}

func f1(f func(int, int) int, n, m int) int { 
	return f(n, m)
}

func main()  {
	fmt.Printf("f1: type:%T\tvalue:%v\n", f1, f1)
	fmt.Println(f1(f0,10,20))
}
$ go run 05-func_args/main.go
f1: type:func(func(int, int) int, int, int) int value:0x49f900
30

1.3. 函数作为返回值

package main

import (
	"fmt"
)

func addFunc(n ...int) (ret int) {
	for _, v := range n {
		ret += v
	}
	return
}

func divFunc(m ...int) (ret int) {
	ret = 0
	for _, v := range m {
		ret -= v
	}
	return
}

func do(s string) (ret func (n ...int) int){
	switch s {
	case "+":
		ret = addFunc
	case "-":
		ret = divFunc
	}
    return // return 需要放到switch外面,又因为代码块的局部变量问题,不能再case内部使用 ret := addFunc
}

func main()  {
	f0 := do("+")
	f1 := do("-")
	fmt.Printf("f0  type:%T\tvalue:%v\tres:%v\n",f0, f0, f0(1,2,4,5))
	fmt.Printf("f1  type:%T\tvalue:%v\tres:%v\n",f1, f1, f1(1,2,4,5))
}
$ go run 05-func_args/main.go
f0  type:func(...int) int       value:0x49f5f0  res:12
f1  type:func(...int) int       value:0x49f620  res:-12

2. 闭包

内部函数引用了外包函数的变量,称为闭包函数,闭包函数通常返回的是一个内部函数地址,参考 **Python的闭包和装饰器 **章节。闭包是内部函数与外部函数中变量组成的一个整体!

2.1. 案例一

package main

import "fmt"

func f0(n int) func(int) int {
	// 闭包函数
	return func(m int) int {
		return m + n
	}
}

func main()  {
	f, g := f0(100), f0(200)
	fmt.Println(f(10), g(100))
	fmt.Println(f(20), g(200))
	fmt.Println(f(30), g(300))
}
[root@heyingsheng day03]# go run 06-func/main.go
110 300
120 400
130 500

2.2. 案例二

package main

import (
	"fmt"
	"strings"
)

func addSuffix(suffix string) func(...string) []string  {
	// 根据提供的后缀名生成函数,内部函数通过文件名来判断是否需要加后缀
	return func(names ...string) []string {
		resSlice := make([]string, 0, len(names))
		for _, name := range names{
			if ! strings.HasSuffix(name, suffix){  // 如果没有后缀则添加
				name = name + suffix
			}
			resSlice = append(resSlice, name)
		}
		return resSlice
	}
}

func main()  {
	txtFunc, pngFunc := addSuffix(".txt"), addSuffix(".png")
	ret1 := txtFunc("ssh","ftp","ntp.txt","dns.txt")
	ret2 := pngFunc("Python.png","Java","Lua","Go","Bash")
	fmt.Println(ret1)
	fmt.Println(ret2)
}
[root@heyingsheng day03]# go run 06-func/main.go
[ssh.txt ftp.txt ntp.txt dns.txt]
[Python.png Java.png Lua.png Go.png Bash.png]

2.3. 案例三

package main

import "fmt"

func f0(x int) func(oper string, y int) int{
	return func(oper string, y int) int {
		if oper == "+" {
			x += y
			return x
		} else {
			x -= y
			return x
		}
	}
}

func main()  {
	x := 10
	f1 := f0(x) // f1 := f0(10)
	fmt.Println(f1("+", 1), f1("-", 2)) // x=10 --> 11; 9
	fmt.Println(f1("+", 3), f1("-", 4)) // x=9  --> 12; 8
	x = 15 // 当前x为main函数的局部变量,而f0中的x已经在 line 19中传递后变成了f0中局部变量
	fmt.Println(f1("+", 5), f1("-", 6)) // x=8  --> 13; 7
}

3. Defer

defer 语句在函数中用于处理收尾工作,比如关闭文件描述符,关闭socket连接等。

  • 函数的 return ret 并不是原子操作,而是分为两部分: 返回值赋值,执行RET将返回值返回
  • defer 语句会在返回值赋值后,函数执行RET(返回返回值)之前执行
  • defer 语句的延迟调用遵循 先入后出 原则
  • defer 语句如果跟的是函数代码,会先处理参数(将实参带入),然后挂起,等待返回值赋值后执行函数代码

3.1. 案例一

package main

import "fmt"

func f1() int {
	x := 5
	defer func() {
		x++
	}()
	return x  // 1. 返回值=5  2. x++ 3. RET 返回值 --> 5
}

func f2() (x int) {
	defer func() {
		x++
	}()
	return 5 // 1. x=5  2. x++  3. RET x  --> 6
}

func f3() (y int) {
	x := 5
	defer func() {
		x++
	}()
	return x  // 1. y=5  2. x++  3. RET y  --> 5
}
func f4() (x int) {
	defer func(x int) {
		x++
	}(x)
	return 5 // 1. x=5 2. x++(局部变量)  3.RET X --> 5
}
func main() {
	fmt.Println(f1())  // 5
	fmt.Println(f2())  // 6
	fmt.Println(f3())  // 5
	fmt.Println(f4())  // 5
}

3.2. 案例二

package main

import "fmt"

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
}
/*
1. 	line 12;line 13 --> x=1;y=2
2. 	line 14 --> defer calc("AA", 1, calc("A", 1, 2)) --> defer calc("AA", 1, 3)
				fmt.Println("A", 1, 2, 3)
3.  line 15 --> x=10
4.  line 16 --> defer calc("BB", 10, calc("B", 10, 2)) --> defer calc("BB", 10, 12)
				fmt.Println("B", 10,2,12)
5.  line 17 --> y=20
6.  line 24 --> defer calc("BB", 10, 12)
				fmt.Println("BB", 10,12,22)
7.  line 21 --> defer calc("AA", 1, 3)
				fmt.Println("BB", 1,3,4)
*/
[root@heyingsheng day03]# go run 09-defer/main.go
A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4

3.3. defer使用场景

// 伪代码: defer 用于关闭文件句柄
func func01()  {
	// 打开文件
	fp := Open(文件名)
	defer fp.Close()
	// 对文件句柄处理的逻辑代码
    ......
}
// 伪代码: defer 用于关闭数据库连接
func func01()  {
	// 连接数据库
    connection := db.Connect(数据库)
	defer connection.Close()
	// 对数据库连接处理的逻辑代码
    ......
}

4. 递归

递归是函数自己调用自己,在文件遍历方面就很常用,但是递归是不断开辟新的内存空间,性能较差。如果可以用循环来替代,则尽量考虑使用循环替代。

package main

import (
	"fmt"
	"time"
)

// 兔子数列问题  : F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2) , (n ≥ 3,n ∈ N*)
func f0(n uint64) uint64 {  // 递归方式求兔子数列
	if n == 0 {
		return 0
	}else if n == 1 {
		return 1
	} else if n == 2 {
		return 1
	} else {
		return f0(n-1) + f0(n-2)
	}
}

// 使用循环解决兔子数列问题
func f1(n uint64) uint64 {
	var (
		x uint64 = 1
		y uint64 = 1
	)
	for i:=uint64(1); i <= n; i++ {
		if i > 2{
			y, x = y+x, y
		}
	}
	return y
}


func main()  {
	startT := time.Now()
	ret1 := f0(40)
	t1 := time.Since(startT)
	startT = time.Now()
	ret2 := f1(40)
	t2 := time.Since(startT)
	fmt.Printf("兔子数列递归: n=40 --> ret=%d ; 耗时: %v \n", ret1, t1)
	fmt.Printf("兔子数列循环: n=40 --> ret=%d ; 耗时: %v \n", ret2, t2)
}
[root@heyingsheng 11-recursive]# go run main.go # python 的递归耗时21s
兔子数列递归: n=40 --> ret=102334155 ; 耗时: 349.3038ms
兔子数列循环: n=40 --> ret=102334155 ; 耗时: 0s

5. 重试函数

在项目中,部分场景会要求不断重试,并且重试的时间还要逐步延长,如重启服务,这个过程可能会失败,需要重试多次,每次重试要求间隔时间翻一翻。

// 失败重试函数:
// retries为重试次数,负数表示永远重试; sleep 为初始休眠时间,每次翻倍
// name 为函数名,用于日志打印,比使用反射效率高且安全
// ctx 超时的ctx
func Retry(retries, sleep int, fn func() error, name string, ctx context.Context) (err error) {
	errChan := make(chan error, 1)
	go func() {
		for retries != 0 {
			select {
			case <- ctx.Done():
				errChan <- errors.New("timeout")
			default:
				err = fn()
				if s, ok := err.(Stop); ok {
					errChan <- s.error
					return
				}
				if err == nil {
					errChan <- nil
					return
				}
				fmt.Printf("retry fun %s failed, err:%#v, retry after %d second\n", name, err.Error(), sleep)
				time.Sleep(time.Second * time.Duration(sleep))
				retries = retries -1
				sleep = sleep * 2
			}
		}
	}()
	select {
	case <- ctx.Done():
		return errors.New("timeout")
	case err = <- errChan:
		return
	}
}

type Stop struct {
	error
}

func NewStop(err error) Stop {
	return Stop{err}
}
// 测试函数
func main() {
	logger.InitLog()
	content := map[string]string{"name": "张三"}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*64)
	defer cancel()
	err := retry.Retry(-1, 1, writeData("/root/aaa.txt", content, 0644), "write data", ctx)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	fmt.Println("success")
}

// 构造闭包函数
func writeData(path string, content interface{}, mode int) func() error {
	return func() error {
		pwd := path[:strings.LastIndex(path, string(os.PathSeparator))]
		if err := os.MkdirAll(pwd, 0755); err != nil {
			return err
		}
		marshal, err := yaml.Marshal(content)
		if err != nil {
			return err
		}
		err = ioutil.WriteFile(path, marshal, os.FileMode(mode))
		return err
	}
}
[root@duduniao ~]# time /tmp/retry
success

real    0m0.002s
user    0m0.002s
sys     0m0.000s

[root@duduniao ~]# su - duduniao sh -c "time /tmp/retry"
WARN[2021-01-14T11:47:25+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 1 second
WARN[2021-01-14T11:47:26+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 2 second
WARN[2021-01-14T11:47:28+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 4 second
WARN[2021-01-14T11:47:32+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 8 second
WARN[2021-01-14T11:47:40+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 16 second
WARN[2021-01-14T11:47:56+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 32 second
WARN[2021-01-14T11:48:28+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 64 second
timeout

real    1m4.002s
user    0m0.000s
sys     0m0.005s