当前位置: 首页 > 知识库问答 >
问题:

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

宋高扬
2023-03-14

当我读到围棋中的片段时,它们似乎足够合理。我知道切片结构具有基于底层数组和当前包含元素长度的容量,并且切片引用底层数组。

然而,当我在玩Go的“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之间容量发生变化?

共有2个答案

轩辕煌
2023-03-14

正如彼得所说,重新切片——无论是使用旧的s[low: high]语法,还是新的s[low: high: max]语法——永远不会改变基础数组本身。Go规范调用新语法,其中max表达式添加了一个完整的切片表达式。)

使用切片时要注意的主要事项还有助于您考虑使用切片。记住,对于任何切片,实际上有两个部分:

  • 有一个切片头,它保留一个指向数组的指针,并提供长度和容量;和

您可以自己提供底层数组:

var space [10]int

someSlice := space[:]

或者您可以使用make(或本身调用make的函数)让运行时以某种方式分配数组1同时,编译器将为您分配切片头。

调用任何函数,将切片作为参数传递,都会将切片头的副本传递给它。底层数组仍然在它所在的地方,但是您调用的函数会获得切片头的副本。请注意,在您自己的函数中,您可以直接或间接地检查这个片头;请参阅icza对如何检查片头的回答?。

您调用的函数可能会自行决定:天哪,这个切片头讨论的是一个数组,它太小了,无法容纳我想要放入数组中的内容。我将在某个地方分配一个新数组,通过旧切片头复制所有旧数组的值,并将新切片头与新数组一起使用来完成我的工作。内置的append就是这样做的。

做这类事情的函数往往不总是这样做,但只是有时:有时,切片头谈论一个已经足够大的数组,因此它可以在数组中放入更多的内容。

通常,执行此操作的函数将为您返回一个新的切片标头。您应该抓住这个新值并使用它。新的切片标头将具有新的指针、长度和容量。新指针可能与旧指针相同!如果是这样,请注意此函数或任何其他函数,可能是一个外部或内部递归调用,它试图使用旧的或新的切片头来修改底层数组,因为其他函数可能不希望进行这种修改。

如果append(或者你调用的任何函数)认为,哎呀,旧的底层数组太小了,因此分配了一个新的数组并将数据复制到其中,那么仍然拥有旧的片头的人是“安全的”使用新片头的基础数组。因此,代码可能有时工作,然后有时失败,这取决于是否append(或其他)决定重新使用现有数组,或创建一个新的数组。

这就是您需要注意的,这就是为什么您需要注意谁拥有哪些切片头以及这些头可能使用的底层数组。

1在这make分配的背后有一定程度的“魔力”,最终,这只是非常小心地使用不安全的包,以便html" target="_blank">编译器和运行时能够联合起来产生有用的结果。当所有人都可以使用Go运行时源代码时,您可以查看内部代码,但是Go作者保留在将来更改内部代码的权利,如果更新使Go代码更快或更好,或者其他什么。

宣冥夜
2023-03-14

底层数组最初的长度为6,这也是片的容量。

当您使用s=s[1:5]重新切片时,实际上是在“忽略”基础数组的第一个元素,因此剩下五个元素,这就是切片的新容量。

Originally:

| <------- array -------> |              
| 2 | 3 | 5 | 7 | 11 | 13 |
| <------- slice -------> |              

After reslicing:

| <------- array -------> |          
| 2 | 3 | 5 | 7 | 11 | 13 |
    | <----- slice -----> |          

切片下面的数组可以延伸到切片的末尾。容量是对该范围的度量:它是切片长度和超出切片的数组长度之和。

https://golang.org/ref/spec#Slice_types

请注意,它表示超出切片(在本例中,超出切片的数组长度为零)。片开始之前可能存在的任何数组元素都不计入片的容量。事实上,这些元素是完全不可访问的,除非存在引用该数组区域的另一个切片。

要将容量保持在6,Go必须创建一个大小为6的新数组,并将原始数组的最后五个元素复制到新数组的开头,但重新选择不会更改基础数组。

 类似资料:
  • rank ▲ ✰ vote url 29 569 323 918 url 理解Python切片 对于Python的切片有什么好的资料吗?对我来说理解切片有点难度.它看起来非常有用,但是我还是不能理解它,我正在找有什么好的资料. 非常简单: a[start:end] # 从start开始到end-1结束 a[start:] # 从start开始直到末尾 a[:end] # 从头部开始

  • 问题内容: 可以将切片用作键吗? 我有尝试: 编译器给我一个错误。因此,要么不可能,要么我错误地声明了它(如果可以,那么正确的方法是什么?)。 问题答案: 不可以,切片没有定义相等性,因此无法用作映射键。

  • 问题内容: 是否有更简化的方法来执行以下操作? 我正在寻找更接近这个的东西。 问题答案: 不,没有,但是受Objective-C NSDictionary类的启发,我写了一种方法来做到这一点:

  • 问题内容: 我只是想知道哪种方法更好(或者我是否缺少更好的方法)。我试图确定一个单词的第一个字母和最后一个字母是否相同,并且有两个明显的解决方案。 要么 据我了解,第一个只是提取字符串的片段并进行字符串比较,而第二个则是从任一端提取字符并将其作为字节进行比较。 我很好奇两者之间是否存在性能差异,并且是否有任何“可取的”方式来做到这一点? 问题答案: 如果用字母表示您的符文,请使用: 如果您的意思是

  • 在Go中,有没有更简单/更好的方法从地图中获取一部分密钥? 目前,我正在遍历地图并将关键点复制到一个切片:

  • 问题内容: 我正在尝试使用地图和这些地图的切片来存储从数据库查询返回的行。但是我在row.Next()以及最终的每次迭代中得到的都是查询中同一行的切片。似乎问题与我存储的存储位置相同有关,但直到现在我仍无法解决。 我在这里想念的是什么: 源代码如下: 问题答案: 这是因为您存储在切片中的是指向地图的指针,而不是地图的副本。 从实际运行的Go地图中: 地图类型是引用类型,例如指针或切片… 由于您是在