为什么 golang 切片内部设计成这样?
Why golang slices internal designed like this?
代码:
func main() {
a := []int{1, 2}
printSlice("a", a)
b := a[0:1]
printSlice("b origin", b)
b = append(b, 9)
printSlice("b after append b without growing capacity", b)
printSlice("a after append b without growing capacity", a)
b = append(b, 5, 7, 8)
printSlice("a after append b with grown capacity", a)
printSlice("b after append b with grown capacity", b)
b[0] = 1000
printSlice("b", b)
printSlice("a", a)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
输出:
a len=2 cap=2 [1 2]
b origin len=1 cap=2 [1]
b after append b without growing capacity len=2 cap=2 [1 9]
a after append b without growing capacity len=2 cap=2 [1 9]
a after append b with grown capacity len=2 cap=2 [1 9]
b after append b with grown capacity len=5 cap=6 [1 9 5 7 8]
b len=5 cap=6 [1000 9 5 7 8]
a len=2 cap=2 [1 9]
有趣的是最后两行。我已经知道切片只是底层数组的 window。当在容量范围内重新切片时,这两个切片共享相同的底层数组,但是当我重新切片以超出其容量时,这两个切片具有不同的底层数组。但是为什么golang的设计者选择不把原切片的底层数组改成新切片的底层数组,让两个切片仍然有相同的底层数组呢?在当前状态下,当我更改新重新切片的某些元素的值时,我必须检查我是否更改了底层数组以确定此操作是否对它支持的其他切片有副作用(请参阅输出的最后两行)。我认为这很尴尬。
But why golang designers choose not to change the underlying array of the origin slice to the underlying array of the new slice, so as to make both slices still have the same underlying array?
主要是,同一数组的切片完全可以出现在程序中的任何地方——完全不同的函数、程序包等等。鉴于切片在内存中的布局方式,Go 必须 "find" 所有共享数组的切片才能更新它们;没办法。
一些其他数组列表实现(如 Python 列表)的方法是,你传递的实际上是一个 指针 指向类似 Go 切片的东西,如果两个变量保持 "the same list",则当您查看另一个变量时,使用一个变量的追加也会出现。这也有一些效率成本——另一个指针查找要做 a[0]
。在那些你真的需要在这里的追加来充当那里的追加的情况下,你可以使用指向切片的指针。
如果您需要,指向切片的指针会为您提供别名,但不提供子切片——要获得您要求的一切,您需要一种不同的安排,我可以想不到from in the wild的例子(offset, length, and pointer to struct { capacity int; firstElem *type }
).
代码:
func main() {
a := []int{1, 2}
printSlice("a", a)
b := a[0:1]
printSlice("b origin", b)
b = append(b, 9)
printSlice("b after append b without growing capacity", b)
printSlice("a after append b without growing capacity", a)
b = append(b, 5, 7, 8)
printSlice("a after append b with grown capacity", a)
printSlice("b after append b with grown capacity", b)
b[0] = 1000
printSlice("b", b)
printSlice("a", a)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
输出:
a len=2 cap=2 [1 2]
b origin len=1 cap=2 [1]
b after append b without growing capacity len=2 cap=2 [1 9]
a after append b without growing capacity len=2 cap=2 [1 9]
a after append b with grown capacity len=2 cap=2 [1 9]
b after append b with grown capacity len=5 cap=6 [1 9 5 7 8]
b len=5 cap=6 [1000 9 5 7 8]
a len=2 cap=2 [1 9]
有趣的是最后两行。我已经知道切片只是底层数组的 window。当在容量范围内重新切片时,这两个切片共享相同的底层数组,但是当我重新切片以超出其容量时,这两个切片具有不同的底层数组。但是为什么golang的设计者选择不把原切片的底层数组改成新切片的底层数组,让两个切片仍然有相同的底层数组呢?在当前状态下,当我更改新重新切片的某些元素的值时,我必须检查我是否更改了底层数组以确定此操作是否对它支持的其他切片有副作用(请参阅输出的最后两行)。我认为这很尴尬。
But why golang designers choose not to change the underlying array of the origin slice to the underlying array of the new slice, so as to make both slices still have the same underlying array?
主要是,同一数组的切片完全可以出现在程序中的任何地方——完全不同的函数、程序包等等。鉴于切片在内存中的布局方式,Go 必须 "find" 所有共享数组的切片才能更新它们;没办法。
一些其他数组列表实现(如 Python 列表)的方法是,你传递的实际上是一个 指针 指向类似 Go 切片的东西,如果两个变量保持 "the same list",则当您查看另一个变量时,使用一个变量的追加也会出现。这也有一些效率成本——另一个指针查找要做 a[0]
。在那些你真的需要在这里的追加来充当那里的追加的情况下,你可以使用指向切片的指针。
如果您需要,指向切片的指针会为您提供别名,但不提供子切片——要获得您要求的一切,您需要一种不同的安排,我可以想不到from in the wild的例子(offset, length, and pointer to struct { capacity int; firstElem *type }
).