没有切片与非零切片与Go语言中的空切片

nil slices vs non-nil slices vs empty slices in Go language

我是一个新手去编程。我在Go编程书中读到,切片由三个部分组成:指向数组的指针、长度和容量。

我在nil切片(slice没有要指向的基础数组,len=0,cap=0)和非nil切片(其中len=0,cap=0和空切片)之间感到困惑。

有人能告诉我零片和空片是不是一样的吗?如果两者都不一样,请告诉我两者有什么区别?

如何测试切片是否为空?

另外,指针在长度和容量为零的非零片中的值是什么?


可观察行为

nil和空切片(容量为0)不同,但它们的可观察行为是相同的。我的意思是:

  • 您可以将它们传递给内置的len()cap()函数
  • 您可以对它们执行for range(将是0次迭代)
  • 您可以对它们进行切片(不违反spec:slice表达式中概述的限制;因此结果也将是一个空切片)
  • 由于它们的长度为0,因此无法更改其内容(附加值将创建新的切片值)
  • 小精灵

    参见这个简单的例子(一个nil片和两个非nil空片):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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 {}

    输出(在游乐场上尝试):

    1
    2
    3
    s1 0 0 true [] true
    s2 0 0 false [] false
    s3 0 0 false [] false

    (请注意,切片nil切片会导致nil切片,切片非nil切片会导致非nil切片。)

    您只能通过将切片值与预先声明的标识符nil进行比较来区分差异,它们在其他方面的行为都相同。

    要判断切片是否为空,只需将其长度与0len(s) == 0进行比较。不管是nil片还是非nil片,也不管它是否具有正容量;如果它没有元素,它是空的。

    1
    2
    s := make([]int, 0, 100)
    fmt.Println("Empty:", len(s) == 0,", but capacity:", cap(s))

    印刷品(在游乐场上试用):

    1
    Empty: true , but capacity: 100

    。引擎盖下面

    切片值由reflect.SliceHeader中定义的结构表示:

    1
    2
    3
    4
    5
    type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
    }

    如果是nil片,这个结构将有它的零值,即它的所有字段都是零值,即:0

    如果非nil片的容量和长度都等于0LenCap字段肯定是0字段,但Data指针可能不是。它不会,这就是它与nil片的区别。它将指向一个零大小的底层数组。

    请注意,go规范允许具有0大小的不同类型的值具有相同的内存地址。规格:系统注意事项:尺寸和对准保证:

    A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory.

    让我们检查一下。为此,我们调用unsafe包的帮助,并"获取"切片值的reflect.SliceHeader结构"视图":

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var s1 []int
    s2 := []int{}
    s3 := make([]int, 0)

    fmt.Printf("s1 (addr: %p): %+8v
    ",
        &s1, *(*reflect.SliceHeader)(unsafe.Pointer(&s1)))
    fmt.Printf("s2 (addr: %p): %+8v
    ",
        &s2, *(*reflect.SliceHeader)(unsafe.Pointer(&s2)))
    fmt.Printf("s3 (addr: %p): %+8v
    ",
        &s3, *(*reflect.SliceHeader)(unsafe.Pointer(&s3)))

    输出(在游乐场上尝试):

    1
    2
    3
    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数据指针
    • s2s3片具有相同的数据指针,共享/指向相同的0大小内存值
    • 小精灵


      从字面意义上讲,切片可以为零:

      1
      2
      var n []int
      n == nil // true

      这是唯一容易的情况。"空"切片的概念没有很好的定义:一个带有len(s) == 0的切片s肯定是空的,不管它的容量如何。最明智的做法是:忽略底层实现,不需要知道切片是如何在内部表示的。重要的是切片的定义行为。

      How to test whether a slice is empty or not?

      "slice s是空的"最合理的定义是一个不包含转换为len(s) == 0的元素的切片。这个定义既适用于零切片,也适用于非零切片。

      Can anyone please tell whether nil and empty slices are same things? If they both are different, then please tell what is the difference between those two?

      从技术上讲,零片和非零片是不同的(一个等于零,另一个等于零!=nil),但这种区别通常并不重要,因为可以将cx1〔35〕转换为nil切片,nil切片上的len和cap返回0

      1
      2
      3
      4
      var n []int
      len(n) == cap(n) == 0 // true
      n = append(n, 123)
      len(n) == 1 // true

      有关详细信息,请参阅Go中的零值。一个零切片就像一个零通道或一个零映射:它是未初始化的。您可以用make对它们进行初始化,也可以用文字进行初始化。如上所述,没有理由考虑基础代表性。

      Also, what value does the pointer holds in non-nil slices, whose length and capacity are zero?

      这是一个实现细节,可能因编译器而异,甚至因版本而异。没有人需要知道这一点来编写正确的便携式GO程序。