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

Go 试图更好地理解切片的行为,go,slice,Go,Slice,当我读到围棋中的片段时,它们似乎足够合理。 我知道切片结构具有基于底层数组和当前包含元素长度的容量,并且切片引用底层数组 然而,当我在玩围棋的时候, 我不明白为什么以下情况会降低底层阵列的容量 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]

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

然而,当我在玩围棋的时候, 我不明白为什么以下情况会降低底层阵列的容量

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时容量会发生变化?

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

Originally:

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

After reslicing:

| <------- array -------> |          
| 2 | 3 | 5 | 7 | 11 | 13 |
    | <----- slice -----> |          
当使用s=s[1:5]重新切片时,实际上忽略了底层数组的第一个元素,因此剩下五个元素,这就是切片的新容量

Originally:

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

After reslicing:

| <------- array -------> |          
| 2 | 3 | 5 | 7 | 11 | 13 |
    | <----- slice -----> |          
切片下面的数组可以延伸到切片的末尾。容量是对该范围的度量:它是切片长度和超出切片的数组长度之和

Originally:

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

After reslicing:

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


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

基础数组最初的长度为6,这也是片的容量

Originally:

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

After reslicing:

| <------- array -------> |          
| 2 | 3 | 5 | 7 | 11 | 13 |
    | <----- slice -----> |          
当使用s=s[1:5]重新切片时,实际上忽略了底层数组的第一个元素,因此剩下五个元素,这就是切片的新容量

Originally:

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

After reslicing:

| <------- array -------> |          
| 2 | 3 | 5 | 7 | 11 | 13 |
    | <----- slice -----> |          
切片下面的数组可以延伸到切片的末尾。容量是对该范围的度量:它是切片长度和超出切片的数组长度之和

Originally:

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

After reslicing:

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

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

As,无论是使用旧的s[low:high]语法还是新的s[low:high:max]重新选择语法永远不会更改基础数组本身。调用新语法时,max表达式添加了一个完整的切片表达式

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

有一个切片头,它保留一个指向数组的指针,并提供长度和容量;和 在某个地方有一些底层数组。 你可以:

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

调用任何函数,将切片作为参数传递,都将向其传递切片头的副本。底层数组仍然在它所在的任何位置,但是您调用的函数会获取切片头的副本。注意,在您自己的函数中,您可以直接或间接地检查这个切片头;注意

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

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

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

如果append或您调用的任何函数决定,gee,旧的底层数组太小,因此分配了一个新的数组并将数据复制到它,那么仍然拥有旧的切片头的人就不会受到任何干扰 nyone使用新的切片头对底层数组进行修改。因此,代码有时可能会工作,有时会失败,这取决于是追加还是决定重用现有数组,还是创建新数组

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

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

as,无论是使用旧的s[low:high]语法,还是使用新的s[low:high:max]语法永远不会更改基础数组本身。调用新语法时,max表达式添加了一个完整的切片表达式

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

有一个切片头,它保留一个指向数组的指针,并提供长度和容量;和 在某个地方有一些底层数组。 你可以:

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

调用任何函数,将切片作为参数传递,都将向其传递切片头的副本。底层数组仍然在它所在的任何位置,但是您调用的函数会获取切片头的副本。注意,在您自己的函数中,您可以直接或间接地检查这个切片头;注意

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

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

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

如果append或您调用的任何函数决定,gee,旧的基础数组太小,因此分配了一个新的数组并将数据复制到它,那么仍然拥有旧切片标头的人都可以安全地避免任何人使用新切片标头对基础数组进行任何干预。因此,代码有时可能会工作,有时会失败,这取决于是追加还是决定重用现有数组,还是创建新数组

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


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

上限是从切片的开始到备份阵列的结束。它不是备份阵列的大小。这里也不能人为地降低上限。好的,换句话说,因为片容量从片的开始计算到阵列的结束,如果有足够的多余长度,则无法检索切片之前的元素,而切片之后的元素则可以检索?否。切片边界之外的元素是不可访问的,也不可访问。@Volker,这不是真的。容量可以通过一个完整的切片表达式来增加,从而可以访问切片末尾以外的元素:啊,当然,对不起。这个奇特的新东西:-封盖是从片的开始到背衬阵列的结束。它不是备份阵列的大小。这里也不能人为地降低上限。好的,换句话说,因为片容量从片的开始到阵列的结束都有计数,所以
d无法检索切片之前的元素,而如果长度足够长,则可以检索切片之后的元素?否。切片边界之外的元素是不可访问的,也不可访问。@Volker,这不是真的。容量可以通过一个完整的切片表达式来增加,从而可以访问切片末尾以外的元素:啊,当然,对不起。这个奇特的新东西:-谢谢,虽然这有道理,但我真的不明白为什么这与第二个和第三个打印切片之间的步骤不同。这是如何设置切片的长度而不是容量的?容量是从数组的第一个可见元素到其末端测量的,即使是不可见的。s[:0]不会更改第一个元素,因此容量也不会更改。但是s[1:5]确实改变了第一个元素,所以容量减少了。谢谢,虽然这是有道理的,我真的不明白为什么这与第二个和第三个打印切片之间的步骤不同。这是如何设置切片的长度而不是容量的?容量是从数组的第一个可见元素到其末端测量的,即使它是不可见的。s[:0]不会更改第一个元素,因此容量也不会更改。但是s[1:5]确实改变了第一个元素,因此容量减小。