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

1.1. 函数的类型

1.1.1. 函数名的本质

  • 不同的函数存在不同的类型,其形参和返回值为函数类型的一部分
  • 函数名是一个指针,指向了函数的内存地址 ```go 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. <a name="WopxA"></a>
  2. #### 1.1.2. 定义一个函数类型
  3. ```go
  4. package main
  5. import "fmt"
  6. func f0(n, m int) int {
  7. return m + n
  8. }
  9. func f1(n int) {
  10. fmt.Println(n + 1)
  11. }
  12. func main() {
  13. type funType func(int, int) int // 定义一种函数类型,func(int, int) int
  14. var ft0 funType = f0
  15. fmt.Printf("10+20=%d\n", ft0(10, 20))
  16. // ft0 = f1 // cannot use f1 (type func(int)) as type funType in assignment
  17. // ft0(10) // not enough arguments in call to ft0
  18. }

1.1.3. 函数赋值给变量

  1. package main
  2. import "fmt"
  3. func f0(n, m int) int {
  4. return m + n
  5. }
  6. func f1(n int) string {
  7. ret := fmt.Sprintf("n+1=%d", n+1) // 数字转换为字符串
  8. return ret
  9. }
  10. func main() {
  11. v0, v1 := f0, f1
  12. fmt.Printf("f0--> type:%T\tvalue:%v\n", f0, f0(1, 2))
  13. fmt.Printf("v0--> type:%T\tvalue:%v\n", v0, v0(1, 2))
  14. fmt.Printf("f1--> type:%T\tvalue:%v\n", f1, f1(100))
  15. fmt.Printf("v1--> type:%T\tvalue:%v\n", v1, v1(100))
  16. }
  1. e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\04-func_type>go run main.go
  2. f0--> type:func(int, int) int value:3
  3. v0--> type:func(int, int) int value:3
  4. f1--> type:func(int) string value:n+1=101
  5. v1--> type:func(int) string value:n+1=101

1.2. 函数作为参数

  1. package main
  2. import "fmt"
  3. func f0(n,m int) int {
  4. return m+n
  5. }
  6. func f1(f func(int, int) int, n, m int) int {
  7. return f(n, m)
  8. }
  9. func main() {
  10. fmt.Printf("f1: type:%T\tvalue:%v\n", f1, f1)
  11. fmt.Println(f1(f0,10,20))
  12. }
  1. $ go run 05-func_args/main.go
  2. f1: type:func(func(int, int) int, int, int) int value:0x49f900
  3. 30

1.3. 函数作为返回值

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func addFunc(n ...int) (ret int) {
  6. for _, v := range n {
  7. ret += v
  8. }
  9. return
  10. }
  11. func divFunc(m ...int) (ret int) {
  12. ret = 0
  13. for _, v := range m {
  14. ret -= v
  15. }
  16. return
  17. }
  18. func do(s string) (ret func (n ...int) int){
  19. switch s {
  20. case "+":
  21. ret = addFunc
  22. case "-":
  23. ret = divFunc
  24. }
  25. return // return 需要放到switch外面,又因为代码块的局部变量问题,不能再case内部使用 ret := addFunc
  26. }
  27. func main() {
  28. f0 := do("+")
  29. f1 := do("-")
  30. fmt.Printf("f0 type:%T\tvalue:%v\tres:%v\n",f0, f0, f0(1,2,4,5))
  31. fmt.Printf("f1 type:%T\tvalue:%v\tres:%v\n",f1, f1, f1(1,2,4,5))
  32. }
  1. $ go run 05-func_args/main.go
  2. f0 type:func(...int) int value:0x49f5f0 res:12
  3. f1 type:func(...int) int value:0x49f620 res:-12

2. 闭包

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

2.1. 案例一

  1. package main
  2. import "fmt"
  3. func f0(n int) func(int) int {
  4. // 闭包函数
  5. return func(m int) int {
  6. return m + n
  7. }
  8. }
  9. func main() {
  10. f, g := f0(100), f0(200)
  11. fmt.Println(f(10), g(100))
  12. fmt.Println(f(20), g(200))
  13. fmt.Println(f(30), g(300))
  14. }
  1. [root@heyingsheng day03]# go run 06-func/main.go
  2. 110 300
  3. 120 400
  4. 130 500

2.2. 案例二

  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. func addSuffix(suffix string) func(...string) []string {
  7. // 根据提供的后缀名生成函数,内部函数通过文件名来判断是否需要加后缀
  8. return func(names ...string) []string {
  9. resSlice := make([]string, 0, len(names))
  10. for _, name := range names{
  11. if ! strings.HasSuffix(name, suffix){ // 如果没有后缀则添加
  12. name = name + suffix
  13. }
  14. resSlice = append(resSlice, name)
  15. }
  16. return resSlice
  17. }
  18. }
  19. func main() {
  20. txtFunc, pngFunc := addSuffix(".txt"), addSuffix(".png")
  21. ret1 := txtFunc("ssh","ftp","ntp.txt","dns.txt")
  22. ret2 := pngFunc("Python.png","Java","Lua","Go","Bash")
  23. fmt.Println(ret1)
  24. fmt.Println(ret2)
  25. }
  1. [root@heyingsheng day03]# go run 06-func/main.go
  2. [ssh.txt ftp.txt ntp.txt dns.txt]
  3. [Python.png Java.png Lua.png Go.png Bash.png]

2.3. 案例三

  1. package main
  2. import "fmt"
  3. func f0(x int) func(oper string, y int) int{
  4. return func(oper string, y int) int {
  5. if oper == "+" {
  6. x += y
  7. return x
  8. } else {
  9. x -= y
  10. return x
  11. }
  12. }
  13. }
  14. func main() {
  15. x := 10
  16. f1 := f0(x) // f1 := f0(10)
  17. fmt.Println(f1("+", 1), f1("-", 2)) // x=10 --> 11; 9
  18. fmt.Println(f1("+", 3), f1("-", 4)) // x=9 --> 12; 8
  19. x = 15 // 当前x为main函数的局部变量,而f0中的x已经在 line 19中传递后变成了f0中局部变量
  20. fmt.Println(f1("+", 5), f1("-", 6)) // x=8 --> 13; 7
  21. }

3. Defer

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

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

    3.1. 案例一

    ```go 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 }

  1. <a name="wQF6x"></a>
  2. ### 3.2. 案例二
  3. ```go
  4. package main
  5. import "fmt"
  6. func calc(index string, a, b int) int {
  7. ret := a + b
  8. fmt.Println(index, a, b, ret)
  9. return ret
  10. }
  11. func main() {
  12. x := 1
  13. y := 2
  14. defer calc("AA", x, calc("A", x, y))
  15. x = 10
  16. defer calc("BB", x, calc("B", x, y))
  17. y = 20
  18. }
  19. /*
  20. 1. line 12;line 13 --> x=1;y=2
  21. 2. line 14 --> defer calc("AA", 1, calc("A", 1, 2)) --> defer calc("AA", 1, 3)
  22. fmt.Println("A", 1, 2, 3)
  23. 3. line 15 --> x=10
  24. 4. line 16 --> defer calc("BB", 10, calc("B", 10, 2)) --> defer calc("BB", 10, 12)
  25. fmt.Println("B", 10,2,12)
  26. 5. line 17 --> y=20
  27. 6. line 24 --> defer calc("BB", 10, 12)
  28. fmt.Println("BB", 10,12,22)
  29. 7. line 21 --> defer calc("AA", 1, 3)
  30. fmt.Println("BB", 1,3,4)
  31. */
  1. [root@heyingsheng day03]# go run 09-defer/main.go
  2. A 1 2 3
  3. B 10 2 12
  4. BB 10 12 22
  5. AA 1 3 4

3.3. defer使用场景

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

4. 递归

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

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. // 兔子数列问题 : F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2) , (n ≥ 3,n ∈ N*)
  7. func f0(n uint64) uint64 { // 递归方式求兔子数列
  8. if n == 0 {
  9. return 0
  10. }else if n == 1 {
  11. return 1
  12. } else if n == 2 {
  13. return 1
  14. } else {
  15. return f0(n-1) + f0(n-2)
  16. }
  17. }
  18. // 使用循环解决兔子数列问题
  19. func f1(n uint64) uint64 {
  20. var (
  21. x uint64 = 1
  22. y uint64 = 1
  23. )
  24. for i:=uint64(1); i <= n; i++ {
  25. if i > 2{
  26. y, x = y+x, y
  27. }
  28. }
  29. return y
  30. }
  31. func main() {
  32. startT := time.Now()
  33. ret1 := f0(40)
  34. t1 := time.Since(startT)
  35. startT = time.Now()
  36. ret2 := f1(40)
  37. t2 := time.Since(startT)
  38. fmt.Printf("兔子数列递归: n=40 --> ret=%d ; 耗时: %v \n", ret1, t1)
  39. fmt.Printf("兔子数列循环: n=40 --> ret=%d ; 耗时: %v \n", ret2, t2)
  40. }
  1. [root@heyingsheng 11-recursive]# go run main.go # python 的递归耗时21s
  2. 兔子数列递归: n=40 --> ret=102334155 ; 耗时: 349.3038ms
  3. 兔子数列循环: n=40 --> ret=102334155 ; 耗时: 0s

5. 重试函数

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

  1. // 失败重试函数:
  2. // retries为重试次数,负数表示永远重试; sleep 为初始休眠时间,每次翻倍
  3. // name 为函数名,用于日志打印,比使用反射效率高且安全
  4. // ctx 超时的ctx
  5. func Retry(retries, sleep int, fn func() error, name string, ctx context.Context) (err error) {
  6. errChan := make(chan error, 1)
  7. go func() {
  8. for retries != 0 {
  9. select {
  10. case <- ctx.Done():
  11. errChan <- errors.New("timeout")
  12. default:
  13. err = fn()
  14. if s, ok := err.(Stop); ok {
  15. errChan <- s.error
  16. return
  17. }
  18. if err == nil {
  19. errChan <- nil
  20. return
  21. }
  22. fmt.Printf("retry fun %s failed, err:%#v, retry after %d second\n", name, err.Error(), sleep)
  23. time.Sleep(time.Second * time.Duration(sleep))
  24. retries = retries -1
  25. sleep = sleep * 2
  26. }
  27. }
  28. }()
  29. select {
  30. case <- ctx.Done():
  31. return errors.New("timeout")
  32. case err = <- errChan:
  33. return
  34. }
  35. }
  36. type Stop struct {
  37. error
  38. }
  39. func NewStop(err error) Stop {
  40. return Stop{err}
  41. }
  1. // 测试函数
  2. func main() {
  3. logger.InitLog()
  4. content := map[string]string{"name": "张三"}
  5. ctx, cancel := context.WithTimeout(context.Background(), time.Second*64)
  6. defer cancel()
  7. err := retry.Retry(-1, 1, writeData("/root/aaa.txt", content, 0644), "write data", ctx)
  8. if err != nil {
  9. fmt.Println(err.Error())
  10. return
  11. }
  12. fmt.Println("success")
  13. }
  14. // 构造闭包函数
  15. func writeData(path string, content interface{}, mode int) func() error {
  16. return func() error {
  17. pwd := path[:strings.LastIndex(path, string(os.PathSeparator))]
  18. if err := os.MkdirAll(pwd, 0755); err != nil {
  19. return err
  20. }
  21. marshal, err := yaml.Marshal(content)
  22. if err != nil {
  23. return err
  24. }
  25. err = ioutil.WriteFile(path, marshal, os.FileMode(mode))
  26. return err
  27. }
  28. }
  1. [root@duduniao ~]# time /tmp/retry
  2. success
  3. real 0m0.002s
  4. user 0m0.002s
  5. sys 0m0.000s
  6. [root@duduniao ~]# su - duduniao sh -c "time /tmp/retry"
  7. WARN[2021-01-14T11:47:25+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 1 second
  8. WARN[2021-01-14T11:47:26+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 2 second
  9. WARN[2021-01-14T11:47:28+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 4 second
  10. WARN[2021-01-14T11:47:32+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 8 second
  11. WARN[2021-01-14T11:47:40+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 16 second
  12. WARN[2021-01-14T11:47:56+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 32 second
  13. WARN[2021-01-14T11:48:28+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 64 second
  14. timeout
  15. real 1m4.002s
  16. user 0m0.000s
  17. sys 0m0.005s