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. 定义一个函数类型
```go
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. 案例一
```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. 案例二
```go
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