我是一个围棋编程的新手。我在围棋编程书中读到过,那片由三样东西组成:数组指针、长度和容量。我渐渐糊涂了:
请问有没有人能分辨出nil和空切片是否是相同的东西?如果它们都是不同的,那么请告诉这两者之间的区别是什么?如何测试一个切片是否是空的?还有,在长度和容量都为零的非nil切片中,指针持有什么值?
3vpjnl9f1#
nil和空切片(容量为0)并不相同,但它们的可观察行为是相同的(几乎一直如此)。
nil
len()
cap()
for range
请参阅以下简单示例(一个nil切片和两个非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
(Note对x1M6 N1 x切片进行切片产生x1M7 N1 x切片,对非x1M8 N1 x切片进行切片产生非x1M9 N1 x切片。除了一个例外,您只能通过比较切片值和预先声明的标识符nil来区分它们,它们在其他方面的行为都是相同的。但是请注意,许多包确实将切片与nil进行比较,并可能基于此而采取不同的行为(例如encoding/json和fmt包)。唯一的区别是将切片转换为数组指针(在Go 1.17中添加到语言中)。将非nil切片转换为数组指针将导致非nil指针,将nil切片转换为数组指针将导致nil指针。要判断切片是否为空,只需将其长度与0进行比较:无论它是nil片还是非nil片,也无论它是否具有正容量;如果没有元素,则为空。
encoding/json
fmt
0
s := make([]int, 0, 100) fmt.Println("Empty:", len(s) == 0, ", but capacity:", cap(s))
打印效果(在Go Playground上试用):
Empty: true , but capacity: 100
引擎盖下切片值由reflect.SliceHeader中定义的结构表示:
reflect.SliceHeader
type SliceHeader struct { Data uintptr Len int Cap int }
在nil切片的情况下,该结构体将具有其零值,即其所有字段都将是其零值,即:0 .如果一个非nil切片的容量和长度都等于0,那么Len和Cap字段肯定是0,但是Data指针可能不是。它 * 不会 * 是,这就是它与nil切片的区别。它将指向一个大小为零的底层数组。注意Go规范允许大小为0的不同类型的值具有相同的内存地址。系统注意事项:尺寸和对齐保证:如果结构或数组类型不包含大小大于零的字段(或元素),则其大小为零。两个不同的零大小变量在内存中可能具有相同的地址。让我们检查一下,为此我们调用unsafe包的帮助,并“获取”切片值的reflect.SliceHeader结构体“view”:
Len
Cap
Data
unsafe
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)))
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}
我们看到了什么?
s2
s3
2wnc66cl2#
从字面意义上讲,slice可以是nil:
var n []int n == nil // true
这是唯一容易的情况。“空”切片的概念没有很好地定义:一个包含len(s) == 0的切片s肯定是空的,不管它的容量有多大。忽略底层的实现,我们永远不需要知道切片在内部是如何表示的,重要的是切片的定义行为。如何测试切片是否为空?“slice s is empty”最合理的定义是一个不包含元素的切片,它可以转换为len(s) == 0。这个定义适用于nil以及非nil切片。有人能告诉我们nil和空切片是不是同一个东西吗?如果两者不同,那么请告诉我们两者之间的区别是什么?从技术上讲,nil切片和非nil切片是不同的(一个是== nil,另一个是!= nil),但这种区别通常并不重要,因为您可以将append转换为nil切片,nil切片上的len和cap返回0
len(s) == 0
s
append
var n []int len(n) == cap(n) == 0 // true n = append(n, 123) len(n) == 1 // true
请阅读Go语言中有关零值的内容,了解更多信息。nil切片就像nil通道或nil贴图:它是未初始化的。你可以通过make ing或者一个字面量来初始化它们。如上所述,没有理由去考虑底层的表示。另外,在长度和容量都为零的非nil切片中,指针的值是什么?这是一个实现细节,可能会因编译器的不同,甚至版本的不同而不同。没有人需要知道这一点来编写正确的和可移植的Go语言程序。
make
zqdjd7g93#
var s1 []int // nil slice s2 := []int{} // non-nil, empty slice s3 := make([]int, 0) // non-nil, empty slice
警告,如果处理JSON,nil切片将编码为null而不是[],这可能会破坏一些(javascript)客户端,这些客户端试图在不可迭代的null上迭代(没有null检查)。
3条答案
按热度按时间3vpjnl9f1#
可观察到的行为
nil
和空切片(容量为0)并不相同,但它们的可观察行为是相同的(几乎一直如此)。len()
和cap()
函数for range
(将是0次迭代)请参阅以下简单示例(一个
nil
切片和两个非nil
空切片):输出(在Go Playground上试用):
(Note对x1M6 N1 x切片进行切片产生x1M7 N1 x切片,对非x1M8 N1 x切片进行切片产生非x1M9 N1 x切片。
除了一个例外,您只能通过比较切片值和预先声明的标识符
nil
来区分它们,它们在其他方面的行为都是相同的。但是请注意,许多包确实将切片与nil
进行比较,并可能基于此而采取不同的行为(例如encoding/json
和fmt
包)。唯一的区别是将切片转换为数组指针(在Go 1.17中添加到语言中)。将非
nil
切片转换为数组指针将导致非nil
指针,将nil
切片转换为数组指针将导致nil
指针。要判断切片是否为空,只需将其长度与
0
进行比较:无论它是nil
片还是非nil
片,也无论它是否具有正容量;如果没有元素,则为空。打印效果(在Go Playground上试用):
引擎盖下
切片值由
reflect.SliceHeader
中定义的结构表示:在
nil
切片的情况下,该结构体将具有其零值,即其所有字段都将是其零值,即:0
.如果一个非
nil
切片的容量和长度都等于0
,那么Len
和Cap
字段肯定是0
,但是Data
指针可能不是。它 * 不会 * 是,这就是它与nil
切片的区别。它将指向一个大小为零的底层数组。注意Go规范允许大小为0的不同类型的值具有相同的内存地址。系统注意事项:尺寸和对齐保证:
如果结构或数组类型不包含大小大于零的字段(或元素),则其大小为零。两个不同的零大小变量在内存中可能具有相同的地址。
让我们检查一下,为此我们调用
unsafe
包的帮助,并“获取”切片值的reflect.SliceHeader
结构体“view”:输出(在Go Playground上试用):
我们看到了什么?
nil
片具有0
数据指针s2
和s3
片具有相同的数据指针,共享/指向相同的大小为0的内存值2wnc66cl2#
从字面意义上讲,slice可以是nil:
这是唯一容易的情况。“空”切片的概念没有很好地定义:一个包含
len(s) == 0
的切片s
肯定是空的,不管它的容量有多大。忽略底层的实现,我们永远不需要知道切片在内部是如何表示的,重要的是切片的定义行为。如何测试切片是否为空?
“slice
s
is empty”最合理的定义是一个不包含元素的切片,它可以转换为len(s) == 0
。这个定义适用于nil以及非nil切片。有人能告诉我们nil和空切片是不是同一个东西吗?如果两者不同,那么请告诉我们两者之间的区别是什么?
从技术上讲,nil切片和非nil切片是不同的(一个是== nil,另一个是!= nil),但这种区别通常并不重要,因为您可以将
append
转换为nil切片,nil切片上的len和cap返回0请阅读Go语言中有关零值的内容,了解更多信息。nil切片就像nil通道或nil贴图:它是未初始化的。你可以通过
make
ing或者一个字面量来初始化它们。如上所述,没有理由去考虑底层的表示。另外,在长度和容量都为零的非nil切片中,指针的值是什么?
这是一个实现细节,可能会因编译器的不同,甚至版本的不同而不同。没有人需要知道这一点来编写正确的和可移植的Go语言程序。
zqdjd7g93#
警告,如果处理JSON,nil切片将编码为null而不是[],这可能会破坏一些(javascript)客户端,这些客户端试图在不可迭代的null上迭代(没有null检查)。