• GO—函数


    Go 语言支持普通函数、匿名函数和闭包,从设计上对函数进行了优化和改进,让函数使用起来更加方便。

    Go 语言的函数属于“一等公民”(first-class),也就是说:

    • 函数本身可以作为值进行传递。
    • 支持匿名函数和闭包(closure)。
    • 函数可以满足接口。

    函数定义:

    1. func function_name( [parameter list] ) [return_types] {
    2. 函数体
    3. }
    • func:函数由 func 开始声明
    • function_name:函数名称,函数名和参数列表一起构成了函数签名。
    • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
    • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
    • 函数体:函数定义的代码集合。

    返回值可以为多个:

    1. func test(x, y int, s string) (int, string) {
    2. // 类型相同的相邻参数,参数类型可合并。 多返回值必须用括号。
    3. n := x + y
    4. return n, fmt.Sprintf(s, n)
    5. }

    1.1 函数做为参数

    函数做为一等公民,可以做为参数传递。

    1. func fn() int {
    2. return 300
    3. }
    4. func test(fn func() int) int {
    5. return fn()
    6. }
    7. func main() {
    8. //这是直接使用匿名函数
    9. s := test(func() int { return 200 })
    10. fmt.Println(s) //200
    11. //传入一个函数
    12. s2 := test(fn)
    13. fmt.Println(s2) //300
    14. }

    在将函数做为参数的时候,我们可以使用类型定义,将函数定义为类型,这样便于阅读

    1. type myFunc func(s string, x, y int) string
    2. func format(fn myFunc, s string, x, y int) string {
    3. return fn(s, x, y)
    4. }
    5. func formatFunc(s string, x, y int) string {
    6. return fmt.Sprintf(s, x, y)
    7. }
    8. func main() {
    9. s2 := format(formatFunc, "%d %d", 10, 20)
    10. fmt.Println(s2)
    11. }

    1.2 函数返回值

    函数返回值可以有多个,同时Go支持对返回值命名

    1. func main() {
    2. var a, b = 10, 20
    3. fmt.Println(sum(a, b))
    4. }
    5. //多个返回值 用括号扩起来
    6. func sum(a, b int) (int, int) {
    7. return a, b
    8. }

    .3 参数

    函数定义时指出,函数定义时有参数,该变量可称为函数的形参。

    形参就像定义在函数体内的局部变量。

    但当调用函数,传递过来的变量就是函数的实参,函数可以通过两种方式来传递参数:

    1. 值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
    1. func swap(x, y int) int {
    2. ... ...
    3. }

    1. 引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
    1. package main
    2. import (
    3. "fmt"
    4. )
    5. /* 定义相互交换值的函数 */
    6. func swap(x, y *int) {
    7. *x,*y = *y,*x
    8. }
    9. func main() {
    10. var a, b int = 1, 2
    11. /*
    12. 调用 swap() 函数
    13. &a 指向 a 指针,a 变量的地址
    14. &b 指向 b 指针,b 变量的地址
    15. */
    16. swap(&a, &b)
    17. fmt.Println(a, b)
    18. }

    map、slice、chan、指针、interface默认以引用的方式传递。

    不定参数传值

    不定参数传值 就是函数的参数不是固定的,后面的类型是固定的。(可变参数)

    Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。

    在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片。

    格式:

    1. func test1(args ...int) { //0个或多个参数
    2. }
    3. func test2(a int, args ...int) int { //1个或多个参数
    4. }
    5. func test3(a int, b int, args ...int) int { //2个或多个参数
    6. }

    注意:其中args是一个slice,我们可以通过arg[index]依次访问所有参数,通过len(arg)来判断传递参数的个数.

    1. func main() {
    2. a := []int{10, 20, 10}
    3. fmt.Println(test("sum: %d", a...)) // slice... 展开slice
    4. }
    5. func test(s string, n ...int) string {
    6. var x = 0
    7. for _, v := range n {
    8. x += v
    9. }
    10. return fmt.Sprintf(s, x)
    11. }

    2. 匿名函数

    匿名函数是指不需要定义函数名的一种函数实现方式。

    在Go里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。

    匿名函数由一个不带函数名的函数声明和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量,不必声明。

    匿名函数的定义格式如下:

    1. func(参数列表)(返回参数列表){
    2. 函数体
    3. }

    1. func main() {
    2. //这里将一个函数当做一个变量一样的操作。
    3. getSqrt := func(a float64) float64 {
    4. return math.Sqrt(a)
    5. }
    6. fmt.Println(getSqrt(4.123))
    7. }

    在定义时调用匿名函数

    匿名函数可以在声明后调用,例如:

    1. func main() {
    2. func(a int) {
    3. fmt.Println(a)
    4. }(100) //(100),表示对匿名函数进行调用,传递参数为 100。
    5. }

    返回多个匿名函数

    1. package main
    2. import "fmt"
    3. func FGen(x, y int) (func() int, func(int) int) {
    4. //求和的匿名函
    5. sum := func() int {
    6. return x + y
    7. }
    8. // (x+y) *z 的匿名函数
    9. avg := func(z int) int {
    10. return (x + y) * z
    11. }
    12. return sum, avg
    13. }
    14. func main() {
    15. f1, f2 := FGen(1, 2)
    16. fmt.Println(f1())
    17. fmt.Println(f2(3))
    18. }

    3. 闭包

    所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

    闭包=函数+引用环境

    1. package main
    2. import "fmt"
    3. func main() {
    4. // 创建一个玩家生成器
    5. generator := playGen("码神")
    6. // 返回玩家的名字和血量
    7. name, hp := generator()
    8. // 打印值
    9. fmt.Println(name, hp)
    10. generator1 := playGen2()
    11. name1, hp1 := generator1("码神")
    12. // 打印值
    13. fmt.Println(name1, hp1)
    14. }
    15. // 创建一个玩家生成器, 输入名称, 输出生成器
    16. func playGen(s string) func() (string, int) {
    17. // 血量一直为150
    18. hp := 150
    19. // 返回创建的闭包
    20. return func() (string, int) {
    21. // 将变量引用到闭包中
    22. return s, hp
    23. }
    24. }
    25. // 创建一个玩家生成器, 输入名称, 输出生成器
    26. func playGen2() func(name string) (string, int) {
    27. hp := 150
    28. return func(name string) (string, int) {
    29. return name, hp
    30. }
    31. }

    . 延迟调用

    Go语言的 defer 语句会将其后面跟随的语句进行延迟处理

    defer特性:

    1. 关键字 defer 用于注册延迟调用。
    2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
    3. 多个defer语句,按先进后出的方式执行。
    4. defer语句中的变量,在defer声明时就决定了。

    defer的用途:

    1. 关闭文件句柄
    2. 锁资源释放
    3. 数据库连接释放

    go 语言的defer功能强大,对于资源管理非常方便,但是如果没用好,也会有陷阱。

    1. package main
    2. import (
    3. "log"
    4. "time"
    5. )
    6. func main() {
    7. start := time.Now()
    8. log.Printf("开始时间为:%v", start)
    9. defer log.Printf("时间差:%v", time.Since(start)) // Now()此时已经copy进去了
    10. //不受这3秒睡眠的影响
    11. time.Sleep(3 * time.Second)
    12. log.Printf("函数结束")
    13. }
    • Go 语言中所有的函数调用都是传值的
    • 调用 defer 关键字会立刻拷贝函数中引用的外部参数 ,包括start 和time.Since中的Now
    • defer的函数在压栈的时候也会保存参数的值,并非在执行时取值。

    如何解决上述问题:使用defer func()

    1. func main() {
    2. start := time.Now()
    3. defer func() {
    4. log.Printf("开始调用defer")
    5. log.Printf("时间差:%v", time.Since(start))
    6. log.Printf("结束调用defer")
    7. }()
    8. time.Sleep(3 * time.Second)
    9. log.Printf("函数结束")
    10. }

    因为拷贝的是函数指针,函数属于引用传递

    另一个问题:

    1. func main() {
    2. var whatever = [5]int{1, 2, 3, 4, 5}
    3. for i, _ := range whatever {
    4. //函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4.
    5. defer func() { fmt.Println(i) }()
    6. }
    7. }

    解决:

    1. func main() {
    2. var whatever = [5]int{1, 2, 3, 4, 5}
    3. for i, _ := range whatever {
    4. i := i
    5. defer func() { fmt.Println(i) }()
    6. }
    7. }

    5. 异常处理

    Go语言中使用 panic 抛出错误,recover 捕获错误。

    异常的使用场景简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。

    panic:

    1. 内置函数
    2. 假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
    3. 返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行
    4. 直到goroutine整个退出,并报告错误

    recover:

    1. 内置函数
    2. 用来捕获panic,从而影响应用的行为

    golang 的错误处理流程:当一个函数在执行过程中出现了异常或遇到 panic(),正常语句就会立即终止,然后执行 defer 语句,再报告异常信息,最后退出 goroutine。如果在 defer 中使用了 recover() 函数,则会捕获错误信息,使该错误信息终止报告。

    注意:

    1. 利用recover处理panic指令,defer 必须放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。
    2. recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。
    3. 多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。

    1. func main() {
    2. test()
    3. }
    4. func test() {
    5. defer func() {
    6. if err := recover(); err != nil {
    7. log.Println(err) //2024/02/28 23:03:38 print out panic
    8. }
    9. }()
    10. panic("print out panic")
    11. }

    由于 panic、recover 参数类型为 interface{},因此可抛出任何类型对象。

    1. func panic(v interface{})
    2. func recover() interface{}

    延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获:

    1. func test() {
    2. defer func() {
    3. // defer panic 会打印
    4. fmt.Println(recover()) //defer panic
    5. }()
    6. defer func() {
    7. panic("defer panic")
    8. }()
    9. panic("test panic")
    10. }
    11. func main() {
    12. test()
    13. }

    除用 panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态:

    1. type error interface {
    2. Error() string
    3. }

    标准库 errors.New 和 fmt.Errorf 函数用于创建实现 error 接口的错误对象。通过判断错误对象实例来确定具体错误类型。

    1. package main
    2. import (
    3. "errors"
    4. "fmt"
    5. "log"
    6. )
    7. var divError = errors.New("div error:分母不能为0")
    8. func div(x, y int) (int, error) {
    9. if y == 0 {
    10. return 0, divError
    11. }
    12. return x / y, nil
    13. }
    14. func main() {
    15. defer func() {
    16. if err := recover(); err != nil {
    17. log.Fatalln(err) //2024/02/28 23:17:14 div error:分母不能为0
    18. }
    19. }()
    20. res, err := div(8, 0)
    21. switch err {
    22. case nil:
    23. fmt.Println(res)
    24. case divError:
    25. panic(err)
    26. }
    27. }

  • 相关阅读:
    SpringCloudGateway 入门
    企业电子招投标采购系统——功能模块&功能描述+数字化采购管理 采购招投标
    PHP基于thinkphp的网上书店管理系统#毕业设计
    MYSQL数据库之用户管理
    dockerDesktop使用方法
    这个macOS神器,让爱怀旧的人直呼:“爷青回!”
    JDBC 在性能测试中的应用
    Java基础(程序控制结构篇)
    Docker技术入门| Part02:Docker基础用法(Docker镜像使用、操作容器、数据管理)
    Kubeadm 在线快速部署 1.23 单master集群 【实验用】
  • 原文地址:https://blog.csdn.net/weixin_51595939/article/details/136390424