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

huangapple go评论84阅读模式
英文:

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

问题

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

我对以下几点感到困惑:

  • 空切片(slice没有底层数组指向,长度为0,容量为0)
  • 非空切片,只有长度为0,容量为0
  • 空切片

请问有人可以告诉我nil切片和空切片是否相同?如果它们不同,那么请告诉我它们之间的区别是什么?如何测试一个切片是否为空?此外,在长度和容量都为零的非空切片中,指针保存的是什么值?

英文:

I am a newbie to Go programming. I have read in go programming book that slice consists of three things: a pointer to an array, length and capacity.

I am getting confused between:

  • nil slices (slice has no underlying array to point to, len = 0, cap=0)
  • non-nil slices where only len = 0, cap = 0
  • empty slices.

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? How to test whether a slice is empty or not? Also, what value does the pointer holds in non-nil slices, whose length and capacity are zero?

答案1

得分: 89

可观察行为

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

  • 你可以将它们传递给内置的 len()cap() 函数。
  • 你可以使用 for range 对它们进行迭代(迭代次数为0)。
  • 你可以对它们进行切片操作(不违反 规范:切片表达式 中的限制条件,结果也将是一个空切片)。
  • 由于它们的长度为0,你无法更改它们的内容(追加一个值会创建一个新的切片值)。

看下面这个简单的例子(一个 nil 切片和两个非 nil 的空切片):

var s1 []int         // nil 切片
s2 := []int{}        // 非 nil 的空切片
s3 := make([]int, 0) // 非 nil 的空切片

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 进行比较来区分它们,在其他方面它们的行为是相同的。但请注意,许多包会将切片与 nil 进行比较,并根据此进行不同的操作(例如 encoding/jsonfmt 包)。

唯一的区别是通过将切片转换为数组指针(这是在 Go 1.17 中添加到语言中的)。将非 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 的非 nil 切片,LenCap 字段很可能也是 0,但 Data 指针可能不是。它不会是 nil 切片的特点。它将指向一个大小为零的底层数组。

请注意,Go 规范允许具有零大小的不同类型的值具有相同的内存地址。规范:系统考虑因素:大小和对齐保证:

如果结构体或数组类型不包含大小大于零的字段(或元素),则其大小为零。两个不同的零大小变量在内存中可能具有相同的地址。

我们来验证一下。为此,我们使用 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
  • s2s3 切片具有相同的数据指针,共享/指向相同的大小为零的内存值。
英文:

Observable behavior

nil and empty slices (with 0 capacity) are not the same, but their observable behavior is the same (almost all the time). By this I mean:

  • You can pass them to the builtin len() and cap() functions
  • You can for range over them (will be 0 iterations)
  • You can slice them (by not violating the restrictions outlined at Spec: Slice expressions; so the result will also be an empty slice)
  • Since their length is 0, you can't change their content (appending a value creates a new slice value)

See this simple example (a nil slice and 2 non-nil empty slices):

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 {}

Output (try it on the Go Playground):

s1 0 0 true [] true
s2 0 0 false [] false
s3 0 0 false [] false

(Note that slicing a nil slice results in a nil slice, slicing a non-nil slice results in a non-nil slice.)

Besides an exception, you can only tell the difference by comparing the slice value to the predeclared identifier nil, they behave the same in every other aspect. Do note however that many packages do compare slices to nil and may act differently based on that (e.g. encoding/json and fmt packages).

The only difference is by converting the slice to an array pointer (which was added to the language in Go 1.17). Converting a non-nil slice to an array pointer will result in a non-nil pointer, converting a nil slice to an array pointer will result in a nil pointer.

To tell if a slice is empty, simply compare its length to 0: len(s) == 0. It doesn't matter if it's the nil slice or a non-nil slice, it also doesn't matter if it has a positive capacity; if it has no elements, it's empty.

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

Prints (try it on the Go Playground):

Empty: true , but capacity: 100

Under the hood

A slice value is represented by a struct defined in reflect.SliceHeader:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

In case of a nil slice, this struct will have its zero value which is all its fields will be their zero value, that is: 0.

Having a non-nil slice with both capacity and length equal to 0, Len and Cap fields will most certainly be 0, but the Data pointer may not be. It will not be, that is what differentiates it from the nil slice. It will point to a zero-sized underlying array.

Note that the Go spec allows for values of different types having 0 size to have the same memory address. Spec: System considerations: Size and alignment guarantees:

> 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.

Let's check this. For this we call the help of the unsafe package, and "obtain" the reflect.SliceHeader struct "view" of our slice values:

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)))

Output (try it on the 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}

What do we see?

  • All slices (slice headers) have different memory addresses

  • The nil slice has 0 data pointer

  • s2 and s3 slices do have the same data pointer, sharing / pointing to the same 0-sized memory value

答案2

得分: 2

一个切片在字面上可以是nil:

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

这是唯一的简单情况。关于“空”切片的概念并没有明确定义:一个长度为0的切片s肯定是空的,无论其容量如何。最明智的做法是:忽略底层实现,我们不需要知道切片在内部是如何表示的,重要的是切片的定义行为。

如何测试一个切片是否为空?

最明智的定义是:切片s为空,即不包含任何元素,可以表示为len(s) == 0。这个定义对于nil切片和非nil切片都成立。

请问有人能告诉我nil切片和空切片是否相同?如果它们不同,那么它们之间有什么区别?

从技术上讲,nil切片和非nil切片是不同的(一个等于nil,另一个不等于nil),但通常这个区别并不重要,因为你可以向nil切片中追加元素,而且对于nil切片的len和cap返回值都是0。

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

关于Go语言中的零值,请阅读更多信息。nil切片就像是nil通道或nil映射一样:它们都是未初始化的。你可以通过make或字面量来初始化它们。正如前面所说,没有理由去考虑底层表示。

另外,对于长度和容量都为零的非nil切片,指针保存的是什么值?

这是一个实现细节,可能因编译器或版本的不同而有所变化。没有人需要知道这个来编写正确且可移植的Go程序。

英文:

A slice can be nil in a very literal sense:

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

That is the only easy case. The notion of "empty" slice is not well defined: A slice s with len(s) == 0 is certainly empty, regardless of its capacity.
The most sensible thing to do is: Ignore the underlying implementation, one never needs to know how a slice is represented internally. All it matters is the defined behaviour of a slice.

> How to test whether a slice is empty or not?

The most sensible definition of " slice s is empty" is a slice containing no elements which translates to len(s) == 0. This definition holds for nil as well as for non-nil slices.

> 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?

Technically a nil slice and a non-nil slice are different (one being == nil, the other being != nil), but this distinction typically does not matter as you can append to a nil slice and len and cap on nil slices return 0

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

Read about zero values in Go for further information. A nil slice is like a nil channel or a nil map: It is uninitialised. You initialise by either makeing them or by a literal. As said above, there is no reason to think about the underlying representation.

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

This is an implementation detail which may vary from compiler to compiler or even from version to version. Nobody needs to know this to write correct and portable Go programs.

答案3

得分: 2

以下是翻译好的内容:

var s1 []int         // 空切片
s2 := []int{}        // 非空,空切片
s3 := make([]int, 0) // 非空,空切片

警告如果处理 JSON 数据空切片将被编码为 null 而不是 []这可能会导致一些JavaScript客户端在尝试迭代 null不进行空值检查时出现错误因为 null 不能迭代
英文:
var s1 []int         // nil slice
s2 := []int{}        // non-nil, empty slice
s3 := make([]int, 0) // non-nil, empty slice

warning, if dealing with JSON, a nil slice will encode to null instead of [], which can break some (javascript) clients that try to iterate (without a null check) over null which isn't iterable.

huangapple
  • 本文由 发表于 2017年6月1日 18:32:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/44305170.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定