Go语言|零切片、空切片、nil切片的区别

1,476 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

零切片、空切片、nil切片

Go语言中的零切片、空切片、nil切片这三者是不同的东西,切片的创建方式有很多种,不同方式创建出来的切片也不一样。

总结:零切片长度和容量不为0,切片内部数组的元素都是零值;nil切片的长度和容量都为0,和nil比较结果为true;空切片的长度和容量为0,但是和nil的比较结果为false

  • 零切片

    我们把切片内部数组的元素都是零值或者底层数组的内容全是 nil的切片叫做零切片,使用make创建的、长度、容量都不为0的切片就是零值切片:

    slice1 := make([]int,5) // 0 0 0 0 0
    slice2 := make([]*int,5) // nil nil nil nil nil
    
  • nil切片

    nil切片(nil slice)的长度和容量都为0,并且和nil比较的结果为true,采用直接创建切片的方式、new创建切片的方式都可以创建nil切片:

    var slice3 []int
    var slice4 = *new([]int)
    

    这种情况可以用于需要返回slice的函数,当函数出现异常的时候,保证函数依然会有nil的返回值。

    但是错误的用法,会报数组越界的错误,因为只是声明了slice,却没有给实例化的对象。

    slice3[1] = 0 //runtime error: index out of range [1] with length 0
    
  • 空切片

    空切片(empty slice)的长度和容量也都为0,但是和nil的比较结果为false,因为所有的空切片的数据指针都指向同一个地址 0xc42003bda0;使用字面量、make可以创建空切片:

    var slice5 = []int{}
    var slice6 = make([]int, 0)
    

    空切片指向的 zerobase 内存地址是一个神奇的地址,从 Go 语言的源代码中可以看到它的定义:

    // base address for all 0-byte allocations
    var zerobase uintptr
    
    // 分配对象内存
    func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
            ...
            if size == 0 {
                    return unsafe.Pointer(&zerobase)
            }
      ...
    }
    
    

    当我们查询或者处理一个空的切片的时候,这非常有用,它会告诉我们返回的是一个切片,但是切片内没有任何值。

测试代码:

func TestDefineSlice(t *testing.T) {
   //零切片
   slice1 := make([]int,5) // 0 0 0 0 0
   slice2 := make([]*int,5) // nil nil nil nil nil
   t.Log(slice1, len(slice1),cap(slice1),slice1==nil)
   t.Log(slice2,len(slice2),cap(slice2),slice2==nil)

   //nil切片
   var slice3 []int
   var slice4 = *new([]int)
   t.Log(slice3, len(slice3),cap(slice3),slice3==nil)
   t.Log(slice4, len(slice4),cap(slice4),slice4==nil)
   //slice3[1] = 0 //runtime error: index out of range [1] with length 0

   //空切片
   var slice5 = []int{}
   var slice6 = make([]int, 0)
   t.Log(slice5, len(slice5),cap(slice5),slice5==nil)
   t.Log(slice6, len(slice6),cap(slice6),slice6==nil)
}

运行结果:

=== RUN   TestDefineSlice
    slice_test.go:79: [0 0 0 0 0] 5 5 false
    slice_test.go:80: [<nil> <nil> <nil> <nil> <nil>] 5 5 false
    slice_test.go:85: [] 0 0 true
    slice_test.go:86: [] 0 0 true
    slice_test.go:91: [] 0 0 false
    slice_test.go:92: [] 0 0 false
--- PASS: TestDefineSlice (0.00s)

面试题

最后我们再来看一道面试题:

var a interface{}
fmt.Println(a == nil) // true, the interface doesn't point to anything
var someNilSlice []int
fmt.Println(someNilSlice == nil) // true, just some nil slice
a = someNilSlice
fmt.Println(a == nil) // false, now the interface does point to something