• Golang基础3-函数、nil相关


    函数

      • 需要声明原型
      • 支持不定参数 func sum(numbers ...int)int
      • 支持返回多值
      • 支持递归
      • 支持命名返回参数
    1. // 命名返回参数
    2. func add(a, b int) (sum int) {
    3. sum = a + b
    4. return // 这里不需要显式地写出返回值,因为已经在函数签名中声明了命名返回参数
    5. }
      • 支持匿名函数、闭包
      • 函数也是一种类型,函数可以赋值给变量(本质函数指针)
      • 一个包中能有名字一样的函数
      • 不支持:重载(==,!=等等均不支持),默认参数

    简单demo

    1. package main
    2. import "fmt"
    3. //测试函数
    4. func test(x, y int, s string) (int, string) {
    5. n := x + y
    6. return n, fmt.Sprintf("%s,%d\n", s, n)
    7. }
    8. func main() {
    9. a, b := test(1, 2, "你好")
    10. // _可以忽略某些值的返回
    11. fmt.Println(a)
    12. fmt.Println(b)
    13. }

    回调函数demo

    回调函数本质其实就是函数指针作为形参,传递给了函数,增加了代码的灵活度。

    1. package main
    2. import "fmt"
    3. // 回调函数1
    4. func testFunc(fn func() int) int {
    5. return fn()
    6. }
    7. // 定义函数类型
    8. type FormatFunc func(s string, x, y int) string
    9. func format(fn FormatFunc, s string, x, y int) string {
    10. return fn(s, x, y)
    11. }
    12. func formatHelper(s string, x, y int) string {
    13. return fmt.Sprintf(s, x, y)
    14. }
    15. func main() {
    16. s1 := testFunc(func() int {
    17. return 100
    18. })
    19. fmt.Println(s1) //100
    20. //匿名函数,回调进行格式化返回string
    21. //s2 := format(func(s string, x, y int) string {
    22. // return fmt.Sprintf(s, x, y)
    23. //}, "%d %d", 10, 20)
    24. s2 := format(formatHelper, "%d %d", 10, 20)
    25. fmt.Println(s2)
    26. }

    闭包demo

    闭包很简单,可以理解为返回值是一个函数指针,其他的再看就很好理解了。

    https://juejin.cn/post/6844903793771937805

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. //返回函数指针 func()int
    6. func a() func() int {
    7. i := 0
    8. b := func() int {
    9. i++
    10. fmt.Println(i)
    11. return i
    12. }
    13. return b
    14. }
    15. func main() {
    16. //a执行完返回func() int 的这个函数指针,其中赋值给c,
    17. //那么这里面保存有这个b匿名函数的所有信息,实现了自增,可以不用定义全局变量
    18. c := a()
    19. c()
    20. c()
    21. c()
    22. //因为这个是函数指针
    23. a() //不会输出i
    24. }

    递归demo

    1. package main
    2. import "fmt"
    3. // 递归1,求阶乘
    4. func Factorial(n int) int {
    5. if n <= 1 {
    6. return 1
    7. }
    8. return n * Factorial(n-1)
    9. }
    10. // 递归2,斐波那契数列
    11. func Fibonaci(n int) int {
    12. if n == 0 {
    13. return 0
    14. }
    15. if n == 1 {
    16. return 1
    17. }
    18. return Fibonaci(n-1) + Fibonaci(n-2)
    19. }
    20. func main() {
    21. fmt.Println("5!=", Factorial(5))
    22. fmt.Println("前10项斐波那契数列:")
    23. for i := 0; i < 10; i++ {
    24. fmt.Printf("%d\t", Fibonaci(i))
    25. }
    26. }

    异常处理demo

    参考文档:异常处理 · Go语言中文文档

    使用panic抛出错误,defer捕获错误,一般panic中抛出异常,defer中捕获异常,之后正常处理。

    panic:

    内置函数,panic后的代码不执行, interface{},直到goroutine整个退出并报告错误,

    recover:

    1.利用recover处理panic指令,defer 必须放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。

    2.recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。

    3.多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。

    painc处理demo

    painc会导致程序直接退出,平时开发中尽量不随便使用。

    一般场景:我的服务想要启动,必须依赖某些服务、日志、mysql能联通,配置文件没问题,那么才能启动的时候,直接使用panic

    一旦服务启动,这时你的某行代码不小心触发panic,那么这就是重大事故(比如别人请求,你直接挂了)

    但是架不住有些地方被动触发panic,这时就引入了recover来捕获panic

    1. package main
    2. import "fmt"
    3. // painc部分后面代码不执行
    4. func test() {
    5. defer func() {
    6. if err := recover(); err != nil {
    7. println("recover panic:", err.(string))
    8. }
    9. }()
    10. panic("panic错误测试!")
    11. //panic后的代码不执行
    12. //fmt.Println("panic后代码")
    13. }
    14. func main() {
    15. test()
    16. fmt.Println("main")
    17. }

    error处理 demo
    1. package main
    2. import (
    3. "errors"
    4. "fmt"
    5. )
    6. func A() (int, error) {
    7. return 2, errors.New("this is an error")
    8. }
    9. func main() {
    10. if _, err := A(); err != nil {
    11. fmt.Println(err)
    12. }
    13. }

    recover捕获panic的demo

    recover需要延迟调用,也就是必须在defer的函数内部,否则返回nil

    1. package main
    2. import "fmt"
    3. func except() {
    4. fmt.Println("except延迟函数调用!")
    5. fmt.Println("except延迟函数recover:", recover())
    6. }
    7. func recoveryDemo() {
    8. //等效于下面的匿名延迟函数
    9. defer except()
    10. //延迟调用,recover在函数内部
    11. defer func() {
    12. if err := recover(); err != nil {
    13. fmt.Println("有效", err.(string))
    14. }
    15. }()
    16. //defer recover() //无效,不是延迟调用,nil
    17. defer fmt.Println(recover()) //无效,空
    18. defer func() {
    19. func() {
    20. fmt.Println("defer inner")
    21. fmt.Println("defer inner", recover()) //无效
    22. }()
    23. }()
    24. panic("panic错误测试!")
    25. //不会执行
    26. fmt.Println("End of test!")
    27. }
    28. func main() {
    29. recoveryDemo()
    30. fmt.Println("main")
    31. }

    总结:需要recover捕获panic时defer延迟函数进行接受,并且第一个有效的recover只能捕获最后一个painc(如果多个panic),之后有效的recover也返回nil。

    defer的使用

    defer延迟调用,一般释放资源和连接、关闭文件、释放锁等等。类似于java的finally和c++中析构函数,不过defer一般跟在函数或方法中。

    参考博客:【Golang】Go语言defer用法大总结(含return返回机制)_golang defer return-CSDN博客

    多个defer满足后进先出

    defer跟无参、有参函数、方法
    1. package main
    2. import "fmt"
    3. //无返回值
    4. func test(a int) {
    5. defer fmt.Println("defer1 = ", a)//方法
    6. defer func(v int) {
    7. fmt.Println("defer2 = ", v)
    8. }(a)//有参函数
    9. defer func() {
    10. fmt.Println("defer3 = ", a)
    11. }()//无参函数
    12. a += 100
    13. }
    14. func main() {
    15. test(0)
    16. }

    defer满足后进先出,其次,有参情况下a会先传递进入,最后等a+=100之后执行完了再输出。

    可读取函数返回值(return返回机制)

    先return结构写入返回值,后defer收尾,最后携带返回值退出.

    无名返回值,有名返回值的区别,见下面demo

    无名有名返回值defer的demo

    函数返回值可以无名、有名,这个是方便理解的不全代码,有名res的话本质局部变量,因此defer后会可能会影响res的返回值。而int这个返回值就直接定了。这个很容易引起bug,因此看下面例子:

    1. package main
    2. import "fmt"
    3. // 无名返回值
    4. func UnNamed(n int) int {
    5. n += 100
    6. defer func() {
    7. n += 100
    8. fmt.Println("UnNamed defer1", n)
    9. }()
    10. return n
    11. }
    12. // 有名返回值
    13. func Named(n int) (res int) {
    14. res = n + 100
    15. defer func() {
    16. res += 100
    17. fmt.Println("UnNamed defer1", res)
    18. }()
    19. // 返回res局部变量,因此受defer中的res的逻辑影响
    20. return res
    21. }
    22. func main() {
    23. n := 0
    24. fmt.Println("main UnNamed return:", UnNamed(n))
    25. fmt.Println("main Named return:", Named(n))
    26. }

    对于第一无名返回值,先执行return保存返回值100,之后defer输出200,最后返回到main函数为100.

    第二有名返回值,先执行return知道返回的是res(此时100),之后defer修改输出res200,最终返回到main为200.

    同理可以更复杂,defer可以传入形参的无名有名函数,可以进行分析。

    1. package main
    2. import "fmt"
    3. // 无名返回值
    4. func UnNamed(n int) int {
    5. n += 100
    6. defer func(n int) { //传入100,输出110
    7. n += 10
    8. fmt.Println("UnNamed defer2", n)
    9. }(n)
    10. defer func() { //200
    11. n += 100
    12. fmt.Println("UnNamed defer1", n)
    13. }()
    14. return n //100
    15. }
    16. // 有名返回值
    17. func Named(n int) (res int) {
    18. res = n + 100 //100
    19. defer func(res int) { //传入100并且注意是值拷贝,并且入栈,110
    20. res += 10
    21. fmt.Println("UnNamed defer2", res)
    22. }(res)
    23. defer func() { //入栈
    24. res += 100
    25. fmt.Println("UnNamed defer1", res)
    26. }()
    27. return res //100->200
    28. }
    29. func main() {
    30. n := 0
    31. fmt.Println("main UnNamed return:", UnNamed(n)) //100
    32. fmt.Println()
    33. fmt.Println("main Named return:", Named(n)) //200
    34. }

    因此传入指针等等,defer函数,有无名返回值均会影响main函数中接收到的最终return的值,请注意。

      • 调用os.Exit时defer不会被执行defer
      • 与panic进行配合处理异常

    nil相关

    nil代表某些数据类型的零值

    不同类型0值

    bool false

    number 0

    string ""

    slice、map、channel、pointer、interface{} nil

    如果是结构体,那么它的零值是,内部所有属性的零值的集合

    nil 和empty的区别

    这里分析了slice、map中的nil和empty的区别。

    1. package main
    2. import "fmt"
    3. type Student struct {
    4. name string
    5. age int
    6. }
    7. func main() {
    8. // nil slice,其实可以创建,append对nil slice进行了处理,但是map就不行
    9. var s1 []Student
    10. //s1 = append(s1, Student{"Bob", 19})
    11. fmt.Println(s1)
    12. if s1 == nil {
    13. fmt.Println("s1==nil")
    14. }
    15. // 不是nil slice,其实本质上是创建了,内部ptr指向一个空间为0的数组
    16. var s2 = make([]Student, 0)
    17. if s2 == nil {
    18. fmt.Println("s2==nil")
    19. }
    20. // nil map
    21. var m1 map[int]Student
    22. if m1 == nil {
    23. fmt.Println("m1==nil")
    24. }
    25. //可以查询,但是无法添加键值对,panic(assignment to entry in nil map)
    26. //m1[1] = Student{"hhh", 123}
    27. //if val, ok := m1[1]; ok {
    28. // fmt.Println("ok", val)
    29. //} else {
    30. // fmt.Println("not ok")
    31. //}
    32. fmt.Println(m1)
    33. //不是nil map,已经初始化了,0个空间的map
    34. var m2 = make(map[int]Student, 0)
    35. //可以查询,插入数据了
    36. m2[1] = Student{"hhh", 123}
    37. if val, ok := m2[1]; ok {
    38. fmt.Println("ok", val)
    39. } else {
    40. fmt.Println("not ok")
    41. }
    42. fmt.Println(m2)
    43. }
  • 相关阅读:
    Linux 基本指令大全。(万字详解)
    pytorch深度学习实战lesson22
    Kafka从安装使用到集成Springboot详细教程
    性能测试:工具篇:jmeter-命令行使用
    【面试】C/C++面试八股
    【C++】undered_set与undered_map
    vivo数据中心网络链路质量监测的探索实践
    Python中的enum的食用方法
    Unity WebSocket-Server
    多功能电子密码锁的设计与制作
  • 原文地址:https://blog.csdn.net/m0_61973596/article/details/138141847