Go语言中的零值坑记

原文链接:http://www.zhoubotong.site/post/45.html

开箱即用

什么叫开箱即用呢?因为Go语言的零值让程序变得更简单了,有些场景我们不需要显示初始化就可以直接用,举几个例子:

切片,他的零值是nil,即使不用make进行初始化也是可以直接使用的,例如:

package main

import (
    "fmt"
    "strings"
)

func main() {
    var s []string

    s = append(s, "love")
    s = append(s, "游戏")
    fmt.Println(strings.Join(s, " ")) // love 游戏
}

但是零值也并不是万能的,零值切片不能直接进行赋值操作:

    var s []string
    s[0] = "love 游戏"

这样的程序就报错了。

  • 方法接收者的归纳:利用零值可用的特性,我们配合空结构体的方法接受者特性,可以将方法组合起来,在业务代码中便于后续扩展和维护:

    package main
    
    import (
        "fmt"
    )
    
    type T struct{}
    
    func (t *T) Run() {
        fmt.Println("love 游戏")
    }
    
    func main() {
        var t T
        t.Run()
    }  

 我在一些开源项目中看到很多地方都这样使用了,这样的代码最结构化。

零值并不是万能

Go语言零值的设计大大便利了开发者,但是零值并不是万能的,有些场景下零值是不可以直接使用的:

  • 未显示初始化的切片、map,他们可以直接操作,但是不能写入数据,否则会引发程序panic:

    func main() {
        var s []string
        s[0] = "周伯通" // panic:runtime error: index out of range [0] with length 0
        var m map[string]bool
        m["love"] = true // panic:assignment to entry in nil map
        fmt.Println(s, m)  
    }
    

    这两种写法使用都是错误的。  

零值的指针

  • 零值的指针就是指向nil的指针,无法直接进行运算,因为是没有无内容的地址:

    func main() {
        var p *uint32
        *p++
        fmt.Println(p) //panic: runtime error: invalid memory address or nil pointer dereference
    }
    

    改成这样才可以

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var p *uint64
        a := uint64(0)
        p = &a
        *p++
        fmt.Println(*p) // 1
    }   

 

  

零值的error类型

error内置接口类型是表示错误条件的常规接口,nil值表示没有错误,所以调用Error方法时类型error不能是零值,否则会引发panic

package main

import (
    "fmt"
)

func main() {
    res := response()
    fmt.Println(res.Error()) //panic: runtime error: invalid memory address or nil pointer dereference
}

func response() error {
    return nil
}

闭包中的nil函数

在日常开发中我们会使用到闭包,但是这其中也隐藏了一个问题,如果我们函数忘记初始化了,那么就会引发panic

package main

var fun func(a, b, c int)

func main() {
    fun(1, 2, 3) // panic: runtime error: invalid memory address or nil pointer dereference
}

怎么解决呢?可以使用带参数闭包或者不带参数的闭包,以下作为参考示例:

package main

import "fmt"

func main() {
    //先调用闭包外面的方法传给变量
    add_func := addNumber(1, 2)
    //再调用里面的方法,因为有了i++ 同一个内存地址 在一次编译中i的值会迭代加1
    fmt.Println(add_func(1, 1)) //1  3  2
    fmt.Println(add_func(0, 0)) //2  3  0
    fmt.Println(add_func(2, 2)) //3  3  4
}

// 闭包使用方法,定义add的传参  和函数差不多,再定义fun 可理解匿名函数
func addNumber(x1, x2 int) func(x3 int, x4 int) (int, int, int) {
    i := 0
    // 这里需要对匿名函数return 理解调用add回调下func,再传参
    return func(x3 int, x4 int) (int, int, int) {
        i++
        //最后return出
        return i, x1 + x2, x3 + x4
    }
}
package main

import "fmt"

func main() {
    /* add 为一个函数,函数 i 为 0 */
    nextNumber := addNumber()

    /* 调用 add 函数,i 变量自增 1 并返回 */
    fmt.Println(nextNumber()) //1
    fmt.Println(nextNumber()) //2
    fmt.Println(nextNumber()) //3
}

func addNumber() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

关于零值不可用的场景先介绍这些,掌握这些才能在日常开发中减少写bug的频率。

总结

总结一下本文叙说的几个知识点:

  • Go语言中所有变量或者值都有默认值,对程序的安全性和正确性起到了很重要的作用。

  • Go语言中的一些标准库利用零值特性来实现,简化操作。

  • 可以利用"零值可用"的特性可以提升代码的结构化、使代码更简单、更紧凑。

  • 零值也不是万能的,有一些场景下零值是不可用的,开发时要注意。

  

  

  

  

posted @ 2022-05-22 15:25  周伯通之草堂  阅读(345)  评论(0编辑  收藏  举报