1. Go 类型的零值

当通过声明或调用new为变量分配存储空间,或者通过复合文字字面量或make调用创建新值, 并且还不提供显式初始化的情况下,Go会为变量或值提供默认值。

Go 语言的每种原生类型都有其默认值,这个默认值就是这个类型的零值。下面是 Go 规范定义的内置原生类型的默认值(零值)。

  1. 所有整型类型:0
  2. 浮点类型:0.0
  3. 布尔类型:false
  4. 字符串类型:""
  5. 指针、interfaceslicechannelmapfunctionnil
  • Go 的零值初始是递归的,即诸如数组、结构体等类型的零值初始化就是对其组成元素逐一进行零值初始化。

2. 零值可用

第一个例子是关于 slice 的:

  1. var zeroSlice []int
  2. zeroSlice = append(zeroSlice, 1)
  3. fmt.Println(zeroSlice) // 输出:[1]

按传统的思维,对于值为 nil 这样的变量我们要给其赋上合理的值后才能使用。但是 Go 具备零值可用的特性,我们可以直接对其使用 append 操作,并且不会出现引用 nil 的错误。

第二个例子是通过 nil 指针调用方法的:

  1. // callmethodthroughnilpointer.go
  2. package main
  3. import (
  4. "fmt"
  5. "net"
  6. )
  7. func main() {
  8. var p *net.TCPAddr
  9. fmt.Println(p) //输出:<nil>
  10. }

我们在标准输出上输出该变量,fmt.Println 会调用 p.String()。我们来看看 TCPAddr 这个类型的 String 方法实现:

  1. // $GOROOT/src/net/tcpsock.go
  2. func (a *TCPAddr) String() string {
  3. if a == nil {
  4. return "<nil>"
  5. }
  6. ip := ipEmptyString(a.IP)
  7. if a.Zone != "" {
  8. return JoinHostPort(ip+"%"+a.Zone, itoa(a.Port))
  9. }
  10. return JoinHostPort(ip, itoa(a.Port))
  11. }

sync.Mutex:

  1. var mu sync.Mutex
  2. mu.Lock()
  3. mu.Unlock()

bytes.Buffer:

  1. // bytesbufferwrite.go
  2. package main
  3. import (
  4. "bytes"
  5. )
  6. func main() {
  7. var b bytes.Buffer
  8. b.Write([]byte("Effective Go"))
  9. fmt.Println(b.String()) // 输出:Effective Go
  10. }

3. 小结

没有提供零值可用的例子:

  1. var s []int
  2. s[0] = 12 // 报错!
  3. s = append(s, 12) // OK
  1. var m map[string]int
  2. m["tonybai"] = 1 // 报错!
  3. m1 := make(map[string]int
  4. m1["tonybai"] = 1 // OK

注意尽量避免值拷贝:

  1. var mu sync.Mutex
  2. mu1 := mu // Error: 避免值拷贝
  3. foo(mu) // Error: 避免值拷贝