为什么Golang切片的内部设计是这样的?

3

代码:

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]

有趣的事情出现在最后两个打印行。我已经知道切片只是底层数组的一个窗口。当在其容量内重新切片时,那么两个切片共享相同的底层数组,但是当我将其重新切片以扩大其容量时,这两个切片具有不同的底层数组。但是,为什么golang设计者选择不更改原始切片的底层数组为新切片的底层数组,使得两个切片仍然具有相同的底层数组呢?在当前状态下,当我更改新重新切片的某些元素的值时,我必须检查是否更改了底层数组,以决定此操作是否会对由它支持的其他切片产生副作用(请参见输出的最后两行)。我认为这很尴尬。

如果你担心“我必须检查这个操作是否对其他切片产生了副作用”,那么你做错了。 - Dave C
1
我不理解为什么会有人踩这个问题。这个问题 a) 完全合法 b) 准备充分(输入代码几乎可运行,输出结果)c) 表述清晰 d) 问题的答案很简单。对于那些踩这个问题的人,请写下你们的理由。这将有助于提问者。 - topskip
1个回答

3
但是为什么golang的设计者选择不将原始切片的基础数组更改为新切片的基础数组,以使两个切片仍具有相同的基础数组呢?主要原因是,同一数组的切片可以出现在程序的任何地方——完全不同的函数、包等。鉴于切片在内存中的布局方式,Go必须“查找”所有共享该数组的切片以更新它们;它没有办法。其他一些数组列表实现(例如Python列表)的方法是,您传递的实际上是类似于Go切片的指针,如果两个变量持有“相同的列表”,则使用一个变量进行追加时,当您查看另一个变量时也会显示出来。这也有一些效率成本——需要另一个指针查找来执行a [0]。在那些真正需要在此处进行追加以作为追加的情况下,您可以使用切片指针。切片指针可以为您提供别名(如果您需要),但不提供子切片功能——要获取您请求的所有内容,您需要使用不同的排列方式,我无法想到野外的示例(偏移量、长度和指向struct { capacity int; firstElem *type }的指针)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接