nil 在 Go 中的真实意义
大多数类型的零值
除了数值类型的默认值是 0,布尔类型的默认值是 false,字符串类型的默认值是空串之外,其余所有的内置类型的如 指针,slice,map,channel 甚至是函数的零值和接口皆为 nil
仅仅是标识符,而不是关键字
有意思的是,在不同类型中的 nil 有其不同的含义
在指针中
nil 即表示空指针,表示没有引用任何的地址
在切片 slice 中
slice的本质其实是一个结构体,类似
type mySlice struct {
ptr *elem // 真实的数据
len int // 真实的元素个数
cap int // 容量
}它的 nil 值其实是结构体的零值,即 mySlice{nil, 0, 0}
channel, map, function 中
它们的 nil 值其实就是空指针即未初始化,在不为 nil 的时候,会指向具体的不同实现初始化返回的对象指针
在 interface 中
情况又有所不同,interface 的本质是一个结构体,里面有两个字段 Type 和 Value
type _interface struct {
dynamicTypeInfo *_implementation
dynamicValue unsafe.Pointer // 通用指针类型
}
type _implementation struct {
itype *_type // 接口类型
dtype *_type // 运行时类型,必须实现 itype
methods []*_func // 实现了对应 itype 的方法的函数指针
}
type _func struct {
name string
methodSig uint // 相同签名的两个函数拥有相同的方法 ID
funcSig uint // 接收者参数的ID
// 其他信息 ...
}因此 interface 的 nil 值,也就是未进行赋值的时候为 struct{nil, nil}
在 Go 的错误处理中,我们经常会见到 if err != nil 这样的代码,error 其实也是一个接口,如果我们返回的是具体的自定义错误类型的空指针例如 ptr,那么在返回的时候,就会被包装成 error 的 interface 类型即一个结构体 struct{p, nil} ,所有的关于 nil 相等的判断结果都将为 false,因为 p 是动态类型 _implementation 指针,因此这就告诉了我们不要向 error 返回具体的错误类型,即便这个自定义结构体类型的值为 nil
nil 值的真实情况需要具体分析
有时候 nil 并不是我们所认为的 nil,例如空指针nil 和 空接口nil 就根本不相等
空指针接收者
在 C++的印象中,如果对象指针为空,我们是无法调用它的成员函数的,但是在 Go 中我们却可以通过判断接收者指针是否为 nil 来在函数中采取不同的行为
nil slice
向空切片 append 的时候运行时会向 slice 其中的空指针分配一部分空间
nil map
map 的 nil 值是空且只读的,因此与其向参数中传入空的 map 字面量声明,我们还可以传 nil
nil channel
当已经初始化 channel 被关闭时,如果此时还继续从 channel 中取值的话得到的就会是零值,因此我们应该在接收的时候判断一下 channel 是否继续开放,如果 channel 已经关闭,我们直接将接收 channel 赋值为 nil,让 select 直接跳过,从而避免了在循环 select 多通道的情况下重复判断通道抢占CPU的操作
nil func
空函数可以用于设置默认函数或者用户传入的自定义函数
nil interface
nil 接口,可以用来表示使用默认操作
其他
struct{} 所占的存储空间为 0 字节 all zero sized值的地址为0x1beeb0,因此如果我们取两个空结构体的地址,会发现它们的地址一样