Golang 中所有没有显示申明值的变量,都会被初始化为默认值,规范中定义的默认值为:

  1. 所有整型类型:0
  2. 浮点类型:0.0
  3. 布尔类型:false
  4. 字符串类型:""
  5. 指针、interface、切片(slice)、channel、map、function:nil

对于一些类型,虽然默认值为 nil,但可以在不初始的情况下进行调用,这种特性被称作零值可用,例如下面这个例子。

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

这个例子中,并没有使用 make([]int) 来显式的初始 zeroSlice,但是我们仍能直接对其使用 append 方法。

另外两个标准库的例子是 bytes.Buffersync.Mutex

var mu sync.Mutex
mu.Lock()
mu.Unlock()
 
var b bytes.Buffer
b.Write([]byte("Effective Go"))

当然,并不是所有的类型和方法都适用,其实原理也比较简单,Golang 调用方法时会自动的转换指针。对于上面例子中,MutexLock, UnlockBufferWrite 都定义在指针上,调用是会自动切换,这样在方法体内可以直接判断如果传入指针为 nil,则对其再进行初始化操作就可以了。比如下面这个例子:

func (a *TCPAddr) String() string {
    if a == nil {
        return "<nil>"
    }
    ip := ipEmptyString(a.IP)
    if a.Zone != "" {
        return JoinHostPort(ip+"%"+a.Zone, itoa(a.Port))
    }
    return JoinHostPort(ip, itoa(a.Port))
}

这里还透露了 Golang runtime 的一个实现细节,对于 var b bytes.Buffer 这个变量,runtime 不仅存储了 b 这个变量的值,也存储了其类型对应方法的函数表。