这是我在Golang的第一天,当我尝试切片操作时,就像append()
一样,有一件事让我很困惑:
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
a:= s[2:4];
a = append(a, 1000, 1001);
a[1] = 100;
printSlice("a:", a)
printSlice("s:", s)
}
func printSlice(title string, s []int) {
fmt.Printf("%s len=%d cap=%d %v\n", title, len(s), cap(s), s)
}
当我只向a
添加两个数字时,例如:
a = append(a, 1000, 1001);
......结果是:
a: len=4 cap=4 [5 100 1000 1001]
s: len=6 cap=6 [2 3 5 100 1000 1001]
我认为,a
显示s
作为对a = append(a, 1000, 1001, 1002);
的引用。
但是当我将这行代码更改为:
a: len=5 cap=8 [5 100 1000 1001 1002]
s: len=6 cap=6 [2 3 5 7 11 13]
......结果变为:
a
我认为,s
已被重新分配给另一段内存,以保存整个内容,并将引用分离为slice
。
这是如此不一致,让我如此困惑,有时很容易犯这个错误(就像你有一个随机数量的值要追加)。
为什么Golang这样设计?如果我只想要JavaScript push
和package.json
之类的操作,怎么可以避免这种情况?
答案 0 :(得分:5)
这与在Go中实现切片的方式有关。
slice
's struct看起来像:
type slice struct {
array unsafe.Pointer
len int
cap int
}
因此,切片具有长度和容量。如果您尝试将项目附加到切片以使其超过当前容量,则会在下面创建一个新数组以保存新数据,但由于之前的子数据仍可能指向较旧的数组,因此它将保持原样直到有没有更多的参考资料。
现在假设我们有一个切片A
:[1, 2, 3, 4, 5, 6]
和一个子切片B
,它指向A
中的最后3项:[4,5,6]。
[1, 2, 3, 4, 5, 6]
^ ^
| |
| B------
|
A---------------
现在,如果我们将一个项目追加到B
然后根据您的预期行为,它应该更新A
,因此会创建一个新数组。如果子片段的大小与实际数组相比较小(用于附加从原始数组中复制1000个项目的1个项目),则效率低下。
另外为了保持一致,指向旧数组的所有其他引用(子句)必须更新以指向新数组中的适当位置,这意味着我们必须在切片中存储其他信息,如启动指数。如果我们有一个子片的子片,这可能会变得棘手。
因此,目前的实施是有道理的。
这里推荐的方法是复制子片而不是直接处理它以防止此类问题。拥有副本的另一个好处是,如果原始切片很大并且不再有引用,那么它可以被垃圾收集,但是如果它有一个子切片,那么原始数组将保持它的内存,直到子切片仍然引用它。