猿问

试图更好地理解切片的行为

当我阅读 Go 中的 slice 时,它们似乎很合理。我知道切片结构的容量基于底层数组和当前包含的元素的长度,切片也引用了下面的数组。


但是,当我在玩 Go 的“A Tour of Go”时,我不明白为什么以下内容会降低底层数组的容量。


package main


import "fmt"


func main() {

    s := []int{2, 3, 5, 7, 11, 13}

    printSlice(s)


    s = s[1:5]

    printSlice(s)


    s = s[:0]

    printSlice(s)


    s = s[0:5]

    printSlice(s)

}


func printSlice(s []int) {

    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)

}

结果:


len=6 cap=6 [2 3 5 7 11 13]

len=4 cap=5 [3 5 7 11]

len=0 cap=5 []

len=5 cap=5 [3 5 7 11 13]

为什么第一次和第二次调用之间的容量会发生变化printSlice(s)?


慕沐林林
浏览 199回答 2
2回答

慕田峪7331174

正如Peter 所说,重新切片——无论是使用旧s[low:high]语法还是新s[low:high:max]语法——永远不会改变底层数组本身。(Go 规范调用新语法,max表达式添加了一个完整的切片表达式。)使用切片要注意的主要事项也有助于您考虑使用切片。请记住,对于任何切片,实际上都有两个部分:有一个切片头,它保存一个指向数组的指针并提供长度和容量;和某处有一些底层数组。您可以自己提供底层数组:var space [10]intsomeSlice := space[:]或者您可以使用make(或本身调用的函数make)让运行时以某种方式分配数组。1 同时,编译器会为你分配切片头。调用任何函数,将切片作为参数传递,都会将切片头的副本传递给它。底层数组仍然在任何地方,但您调用的函数会获取切片标头的副本。请注意,在您自己的函数中,您可以直接或间接地检查此切片头;请参阅icza对如何检查切片标头的回答?.您调用的函数可能会自行决定:天哪,这个切片标头讨论了一个数组,它对于我想放入数组中的东西来说太小了。我将在某处分配一个新数组,通过旧切片头复制所有旧数组的值,并将新切片头与新数组一起使用来完成我的工作。 内置append正是这样做的。做这种事情的函数往往并不总是这样做,而只是有时:有时,切片头会谈到一个已经足够大的数组,所以它可以在数组中放入更多的东西。执行此操作的函数通常会为您返回一个新的切片标头。你应该抓住这个新值并使用它。新的切片头将具有新的指针、长度和容量。新指针可能与旧指针相同!如果是这样,请注意此函数或任何其他函数(可能是外部或内部递归调用),它试图使用旧切片头或新切片头来修改底层数组,因为其他函数可能不会期望这种修改。如果append(或您调用的任何函数)决定,哎呀,旧的底层数组太小,因此分配了一个新数组并将数据复制到它,那么仍然拥有旧切片头的人是“安全的”,不受任何人的干预使用新切片头的底层数组。因此,代码有时可能会起作用,然后有时会失败,这取决于append(或其他)是否决定重新使用现有数组或制作一个新数组。这就是您需要注意的,这就是为什么您需要注意谁拥有哪些切片标头以及这些标头可能使用的底层数组。1这种分配背后有一定的“魔力” make,归根结底只是对包的一些非常小心的使用,unsafe以便编译器和运行时可以串通起来产生有用的结果。由于 Go 运行时源代码可供所有人使用,因此您可以查看内部结构,但 Go 作者保留在未来更改内部结构的权利,如果更新使 Go 代码更快或更好等等。

哈士奇WWW

底层数组最初的长度为六,这也是切片的容量。当你重新切片时,s = s[1:5]实际上是“忽略”了底层数组的第一个元素,所以你只剩下五个元素,这就是切片的新容量。Originally:| <------- array -------> |&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;| 2 | 3 | 5 | 7 | 11 | 13 || <------- slice -------> |&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;After reslicing:| <------- array -------> |&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;| 2 | 3 | 5 | 7 | 11 | 13 |&nbsp; &nbsp; | <----- slice -----> |&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;切片底层的数组可能会超出切片的末尾。容量是该范围的度量:它是切片长度与切片之外的数组长度之和。https://golang.org/ref/spec#Slice_types请注意,它表示超出切片(在这种情况下,超出切片的数组长度为零)。在切片开始之前可能存在的任何数组元素都不计入切片的容量。事实上,这些元素是完全无法访问的,除非存在另一个引用该数组区域的切片。为了将容量保持在 6,Go 必须创建一个大小为 6 的新数组,并将原始数组的最后五个元素复制到新数组的开头,但重新切片永远不会改变底层数组。
随时随地看视频慕课网APP

相关分类

Go
我要回答