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
<a name="WopxA"></a>#### 1.1.2. 定义一个函数类型```gopackage mainimport "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) intvar ft0 funType = f0fmt.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 mainimport "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, f1fmt.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.gof0--> type:func(int, int) int value:3v0--> type:func(int, int) int value:3f1--> type:func(int) string value:n+1=101v1--> type:func(int) string value:n+1=101
1.2. 函数作为参数
package mainimport "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.gof1: type:func(func(int, int) int, int, int) int value:0x49f90030
1.3. 函数作为返回值
package mainimport ("fmt")func addFunc(n ...int) (ret int) {for _, v := range n {ret += v}return}func divFunc(m ...int) (ret int) {ret = 0for _, v := range m {ret -= v}return}func do(s string) (ret func (n ...int) int){switch s {case "+":ret = addFunccase "-":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.gof0 type:func(...int) int value:0x49f5f0 res:12f1 type:func(...int) int value:0x49f620 res:-12
2. 闭包
内部函数引用了外包函数的变量,称为闭包函数,闭包函数通常返回的是一个内部函数地址,参考 Python的闭包和装饰器 章节。闭包是内部函数与外部函数中变量组成的一个整体!
2.1. 案例一
package mainimport "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.go110 300120 400130 500
2.2. 案例二
package mainimport ("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 mainimport "fmt"func f0(x int) func(oper string, y int) int{return func(oper string, y int) int {if oper == "+" {x += yreturn x} else {x -= yreturn x}}}func main() {x := 10f1 := f0(x) // f1 := f0(10)fmt.Println(f1("+", 1), f1("-", 2)) // x=10 --> 11; 9fmt.Println(f1("+", 3), f1("-", 4)) // x=9 --> 12; 8x = 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. 案例一
```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 }
<a name="wQF6x"></a>### 3.2. 案例二```gopackage mainimport "fmt"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}/*1. line 12;line 13 --> x=1;y=22. 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=104. 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=206. 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.goA 1 2 3B 10 2 12BB 10 12 22AA 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 mainimport ("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 = 1y 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 超时的ctxfunc 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.errorreturn}if err == nil {errChan <- nilreturn}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 -1sleep = 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/retrysuccessreal 0m0.002suser 0m0.002ssys 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 secondWARN[2021-01-14T11:47:26+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 2 secondWARN[2021-01-14T11:47:28+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 4 secondWARN[2021-01-14T11:47:32+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 8 secondWARN[2021-01-14T11:47:40+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 16 secondWARN[2021-01-14T11:47:56+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 32 secondWARN[2021-01-14T11:48:28+08:00] retry fun write data failed, err:"open /root/aaa.txt: permission denied", retry after 64 secondtimeoutreal 1m4.002suser 0m0.000ssys 0m0.005s
