nil 是什么
在源码builtin.go
定义中,nil
是一个Type
类型的变量
var nil Type
type Type int
nil
适用于指针,函数,interface,map,slice,channel这6种类型
变量定义:
分配变量指定大小的内存,确定一个变量名称。Go 确保分配出来的内存块里面是全 0 数据
所以,记住这句话,nil
是编译器识别行为的一个触发点而已,看到这个 nil
会触发编译器的一些特殊判断和操作。
slice
定义
- 第一种
var
的方式定义变量纯粹真的是变量定义,如果逃逸分析之后,确认可以分配在栈上,那就在栈上分配这 24 个字节,如果逃逸到堆上去,那么调用newobject
函数进行类型分配。 - 第二种
make
方式则略有不同,如果逃逸分析之后,确认分配在栈上,那么也是直接在栈上分配 24 字节,如果逃逸到堆上则会导致调用makeslice
函数来分配变量。
nil
判断
-
指针值为 0 的,也就是说这个动态数组没有实际数据的时候。
-
只对首字段
array
做非 0 判断,len,cap 字段不做判断。
map
定义
// 变量定义
var m1 map[string]int
// 定义 & 初始化
var m2 = make(map[string]int)
- 第一种方式仅仅定义了 m1 变量本身;
- 第二种方式则是分配 m2 的内存,还会调用
makehmap
函数(不一定是这个函数,要看逃逸分析的结果,如果是可以栈上分配的,会有一些优化)来创建某个结构,并且把这个函数的返回值赋给 m2;
nil 判断
搞懂了变量本身和管理结构的区别就很简单了,这里的 nil
值判断也仅仅是针对变量本身的判断,只要是非 0 指针,那么就是非 nil
。也就是说 m1
只要是一个非 0 的指针,就不会是非nil
的。
interface
变量本身
type iface struct {
tab *itab
data unsafe.Pointer
}
type eface struct {
_type *_type
data unsafe.Pointer
}
其中,iface
就是通常定义的 interface 类型,eface
则是通常人们常说的空接口
对应的数据结构。
不管内部怎么样,这两个结构体占用内存是一样的,都是一个正常的指针类型和一个无类型的指针类型( Pointer
),总共占用 16 个字节。
也就是说,如果你声明定义一个 interface
类型,无论是空接口,还是具体的接口类型,都只是分配了一个 16 字节的内存块给你,注意是置 0 分配哦。
nil
赋值
和上面类似,如果对一个 interface
变量赋值 nil
的话,发生的事情也仅仅是把变量本身这 16 个字节的内存块置 0 而已。
nil
值判断
判断 interface
是否是 nil
?这个跟 slice
类似,也仅仅是判断首字段(指针类型)是否为 0 即可。因为如果是初始化过的,首字段一定是非 0 的。