11.1 Go类型的零值

作为一个C程序员出身的人,我总是喜欢将在使用C语言时“受过的苦”与使用Go语言中得到的“甜头”做比较,从而来证明Go语言设计者在当初设计Go语言时是经过了充分考量的。

在C99规范中,有一段是否对栈上局部变量进行自动清零初始化的描述:

未被显式初始化且具有自动存储持续时间的对象,其值是不确定的。

规范的用语总是晦涩难懂的,这句话大致的意思是:如果一个变量是在栈上分配的局部变量,且在声明时未对其进行显式初始化,那么它的值是不确定的。比如:

// chapter3/sources/varinit.c
#include <stdio.h>

static int cnt;

void f() {
    int n;
    printf("local n = %d\n", n);

    if (cnt > 5) {
        return;
    }

    cnt++;
    f();
}

int main() {
    f();
    return 0;
}

编译上面的程序并执行:

// 环境:CentOS; GCC 4.1.2
// 注意:在你的环境中执行上述代码,输出的结果很大可能与这里有所不同
$ gcc varinit.c
$ ./a.out

local n = 0
local n = 10973
local n = 0
local n = 52
local n = 0
local n = 52
local n = 52

我们看到分配在栈上的未初始化变量的值是不确定的。虽然一些编译器的较新版本提供了一些命令行参数选项用于对栈上变量进行零值初始化,比如GCC就提供如下命令行选项:

-finit-local-zero
-finit-derived
-finit-integer=n
-finit-real=<zero|inf|-inf|nan|snan>
-finit-logical=<true|false>
-finit-character=n

但这并不能改变C语言原生不支持对未显式初始化局部变量进行零值初始化的事实。资深C程序员深知这个陷阱带来的问题有多严重。因此出身于C语言的Go设计者们在Go中对这个问题进行了彻底修复和优化。下面是Go语言规范[2]中关于变量默认值的描述:

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

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

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

另外,Go的零值初始是递归的,即数组、结构体等类型的零值初始化就是对其组成元素逐一进行零值初始化。