在《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())
}

Kevin
5k 声望1.5k 粉丝