菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
0
0

编程书说的 “Go 程序员应该让聚合类型的零值也具有意义” 是在讲什么

原创
05/13 14:22
阅读数 310

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

发表评论

0/200
0 点赞
0 评论
收藏
为你推荐 换一批