首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Go语言中的nil切片与非nil切片与空切片

Go语言中的nil切片与非nil切片与空切片
EN

Stack Overflow用户
提问于 2017-06-01 18:32:34
回答 3查看 14.9K关注 0票数 39

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

我混淆了nil切片(切片没有指向的底层数组,len = 0,cap=0),只有len = 0,cap =0的非nil切片和空切片。

有没有人能说清楚nil和空片是不是一样的东西?如果它们是不同的,那么请告诉我这两者之间的区别是什么?

如何测试切片是否为空?

另外,在长度和容量都为零的非nil切片中,指针持有什么值?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2017-06-01 19:09:14

可观察的行为

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

您可以将它们传递给内置的len()cap()函数

  • 您可以对它们进行切片(将为0 iterations)

  • You可以对它们进行切片(通过不违反Spec: Slice expressions中概述的限制;因此结果也将是一个空切片)

  • 由于它们的长度为0,因此您无法更改其内容(追加一个值将创建一个新的切片值)

请看这个简单的示例(一个nil切片和两个非nil空切片):

代码语言:javascript
复制
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上试用):

代码语言:javascript
复制
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切片,它的容量是否为正也无关紧要;如果它没有元素,它就是空的。

代码语言:javascript
复制
s := make([]int, 0, 100)
fmt.Println("Empty:", len(s) == 0, ", but capacity:", cap(s))

打印(在Go Playground上试用):

代码语言:javascript
复制
Empty: true , but capacity: 100

在引擎盖下

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

代码语言:javascript
复制
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

nil切片的情况下,这个结构将有它的零值,即它的所有字段都将是它们的零值,即:0

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

请注意,Go规范允许大小为0的不同类型的值具有相同的内存地址。Spec: System considerations: Size and alignment guarantees:

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

让我们来看看这个。为此,我们调用unsafe包的帮助,并“获得”我们的切片值的reflect.SliceHeader结构“视图”:

代码语言:javascript
复制
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上试用):

代码语言:javascript
复制
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}

我们看到了什么?

  • 所有存储片(存储片标题)具有不同的内存大小数据存储片具有相同的数据指针,共享/指向相同的0大小内存值
票数 65
EN

Stack Overflow用户

发布于 2017-06-01 19:09:34

从字面上看,一个切片可以是零:

代码语言:javascript
复制
var n []int
n == nil // true

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

如何测试切片是否为空?

“slice s is empty”最合理的定义是不包含任何元素的slice,也就是len(s) == 0。该定义适用于nil以及非nil切片。

谁能告诉我nil和空片是不是一样的东西?如果它们是不同的,那么请告诉我这两者之间的区别是什么?

从技术上讲,nil片和非nil片是不同的(一个是== nil,另一个是!= nil),但这种区别通常无关紧要,因为您可以对nil片执行append操作,并且len和cap on nil片返回0。

代码语言:javascript
复制
var n []int
len(n) == cap(n) == 0 // true
n = append(n, 123)
len(n) == 1 // true

请阅读Go中有关零值的内容以获取更多信息。nil切片类似于nil通道或nil映射:它是未初始化的。您可以通过对它们执行make或通过文字进行初始化。如上所述,没有理由考虑底层表示。

还有,在长度和容量都为零的非nil切片中,指针持有什么值?

这是一个实现细节,可能会因编译器而异,甚至因版本而异。没有人需要知道这一点,才能编写正确和可移植的Go程序。

票数 1
EN

Stack Overflow用户

发布于 2020-10-23 05:58:23

代码语言:javascript
复制
var s1 []int         // nil slice
s2 := []int{}        // non-nil, empty slice
s3 := make([]int, 0) // non-nil, empty slice

警告,如果处理JSON,nil切片将编码为null而不是[],这可能会破坏一些试图在不可迭代的null上迭代(不进行null检查)的(javascript)客户端。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/44305170

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档