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語言中的一些標準庫利用零值特性來實現,簡化操作。

  • 可以利用"零值可用"的特性可以提升代碼的結構化、使代碼更簡單、更緊湊。

  • 零值也不是萬能的,有一些場景下零值是不可用的,開發時要注意。

  

  

  

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章