程式設計書說的 “Go 程式設計師應該讓聚合型別的零值也具有意義” 是在講什麼

KevinYan發表於2019-11-25

在《Go語言程式設計》這本書和很多其他Go 程式設計教程中很多都提到過“Go程式設計師應該讓一些聚合型別的零值也具有意義”的概念,我們這篇文章主要說一下有意義的零值這個話題。

在 Go 中宣告變數時如果初始化表示式被省略:

var 變數名字 型別 = 表示式

那麼將用零值初始化變數。

以下是 Go 官方的語言參考對零值初始化機制的解釋:

When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the zero value for its type: false for booleans, 0 for numeric types, "" for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.

當通過變數宣告、呼叫new函式或者是通過符合字面量([]string{}, structType{}等形式)、呼叫 make 函式建立新值並且未顯式的提供初始化時,變數或者值將被賦予預設值。變數或者值的每個元素將被賦予其型別的零值:布林值為false,數字型別為0,字串為“”,指標,函式,介面,切片,通道和對映為nil。該初始化是遞迴完成的,因此,例如,未指定任何值,一個結構體陣列的每個元素的欄位都將設定為欄位型別的零值。

Go始終將值設定為已知預設值的特性對於程式的安全性和正確性很重要,也使Go程式更簡單,更緊湊。這就是Go程式設計師在說“給你的結構體一個有用的零值”時談論的內容。

下面是一個使用sync.Mutex的示例,該示例設計為無需顯式初始化即可使用。 sync.Mutex包含兩個未匯出的整數字段:

type Mutex struct {
    state int32
    sema  uint32
}

由於零值機制的存在,每當宣告sync.Mutex時,這些欄位將被設定為0。

package main

import "sync"

type MyInt struct {
        mu sync.Mutex
        val int
}

func main() {
        var i MyInt

        // i.mu is usable without explicit initialisation.
        i.mu.Lock()      
        i.val++
        i.mu.Unlock()
}

有用的零值的型別的另一個示例是bytes.Buffer。你可以在宣告瞭一個 bytes.Buffer 型別的變數後,無需顯式初始化即可開始讀取或寫入。

package main

import "bytes"
import "io"
import "os"

func main() {
        var b bytes.Buffer
        b.Write([]byte("Hello world"))
        io.Copy(os.Stdout, &b)
}

切片型別的零值為nil。這意味著你無需顯式建立切片,只需宣告它即可。

package main

import "fmt"
import "strings"

func main() {
        // s := make([]string, 0)
        // s := []string{}
        var s []string

        s = append(s, "Hello")
        s = append(s, "world")
        fmt.Println(strings.Join(s, " "))
}

注意:var s [] string與它上面的兩條註釋行相似,但是不相同。可以通過程式檢測出nil切片值與具有零長度的切片值之間的差別。以下程式碼將輸出false。

package main

import "fmt"
import "reflect"

func main() {
        var s1 = []string{}
        var s2 []string
        fmt.Println(reflect.DeepEqual(s1, s2))
}

對於 nil 指標來說,你可以讓你的程式允許在具有nil值的型別上呼叫方法。這可以用來簡單地為方法提供有意義的預設返回值。比如下面的程式在 nil 指標上呼叫 Path方法是返回了/usr/home ,示例為了好理解只是簡單輸出了一下呼叫結果,但是在很多比示例更復雜的功能方法來說這比直接返回 string 的零值空字元對程式更有意義。

package main

import "fmt"

type Config struct {
        path string
}

func (c *Config) Path() string {
        if c == nil {
                return "/usr/home"
        }
        return c.path
}

func main() {
        var c1 *Config
        var c2 = &Config{
                path: "/export",
        }
        fmt.Println(c1.Path(), c2.Path())
}

公眾號:網管叨bi叨 | Golang、PHP、Laravel、Docker等學習經驗分享

相關文章