可观察的行为
nil 和空切片(容量为 0)不一样,但它们的可观察行为是一样的。我的意思是:
看这个简单的例子(一个nil切片和2个非nil空切片):
var s1 []int // nil slice
s2 := []int{} // non-nil, empty slice
s3 := make([]int, 0) // non-nil, empty slice
fmt.Println("s1", len(s1), cap(s1), s1 == nil, s1[:], s1[:] == nil)
fmt.Println("s2", len(s2), cap(s2), s2 == nil, s2[:], s2[:] == nil)
fmt.Println("s3", len(s3), cap(s3), s3 == nil, s3[:], s3[:] == nil)
for range s1 {}
for range s2 {}
for range s3 {}
输出(在Go Playground上试试):
s1 0 0 true [] true
s2 0 0 false [] false
s3 0 0 false [] false
(请注意,切片nil 切片会产生nil 切片,切片非nil 切片会产生非nil 切片。)
您只能通过将切片值与预先声明的标识符nil 进行比较来区分它们,它们在其他方面的行为相同。
要判断一个切片是否为空,只需将其长度与0:len(s) == 0 进行比较。不管是nil 切片还是非nil 切片,它是否具有正容量也没关系;如果没有元素,则为空。
s := make([]int, 0, 100)
fmt.Println("Empty:", len(s) == 0, ", but capacity:", cap(s))
打印(在Go Playground 上试用):
Empty: true , but capacity: 100
引擎盖下
切片值由reflect.SliceHeader中定义的结构体表示:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
如果是nil 切片,则此结构将具有其零值,即其所有字段都将为零值,即:0。
拥有一个容量和长度都等于0、Len 和Cap 的非nil 字段肯定是0,但Data 指针可能不是。 不会不是,这就是它与nil 切片的区别所在。它将指向一个大小为零的底层数组。
请注意,Go 规范允许大小为 0 的不同类型的值具有相同的内存地址。 Spec: System considerations: Size and alignment guarantees:
如果结构或数组类型不包含大小大于零的字段(或元素),则它的大小为零。 两个不同的零大小变量在内存中可能具有相同的地址。
让我们检查一下。为此,我们调用 unsafe 包的帮助,并“获取”我们切片值的 reflect.SliceHeader 结构“视图”:
var s1 []int
s2 := []int{}
s3 := make([]int, 0)
fmt.Printf("s1 (addr: %p): %+8v\n",
&s1, *(*reflect.SliceHeader)(unsafe.Pointer(&s1)))
fmt.Printf("s2 (addr: %p): %+8v\n",
&s2, *(*reflect.SliceHeader)(unsafe.Pointer(&s2)))
fmt.Printf("s3 (addr: %p): %+8v\n",
&s3, *(*reflect.SliceHeader)(unsafe.Pointer(&s3)))
输出(在Go Playground 上试试):
s1 (addr: 0x1040a130): {Data: 0 Len: 0 Cap: 0}
s2 (addr: 0x1040a140): {Data: 1535812 Len: 0 Cap: 0}
s3 (addr: 0x1040a150): {Data: 1535812 Len: 0 Cap: 0}
我们看到了什么?
- 所有片(片头)都有不同的内存地址
-
nil 切片具有 0 数据指针
-
s2 和 s3 切片确实有相同的数据指针,共享/指向相同的 0 大小的内存值