Go 为什么戈朗';他的切片操作如此复杂

Go 为什么戈朗';他的切片操作如此复杂,go,Go,这是我在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 prin

这是我在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
的引用

这是如此的不一致,让我非常困惑,以至于有时候很容易犯这个错误(比如当你有一个随机数的值要附加时)


为什么Golang设计成这样?如果我只想在JavaScript的
slice
push
中执行类似的操作,那么如何避免这种情况呢?

这是一个关于如何在Go中实现slice的问题

看起来像:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
因此,切片具有长度和容量。如果尝试将项目附加到切片,使其超过当前容量,则会在下面创建一个新数组来保存新数据,但由于以前的子切片可能仍然指向旧的数组,因此它会保持原样,直到不再有对它的引用为止


现在让我们假设我们有一个切片
a
[1,2,3,4,5,6]
和一个子切片
B
,它指向
a
:[4,5,6]中的最后3项

[1, 2, 3, 4, 5, 6]
 ^        ^
 |        |
 |        B------
 |
 A---------------  
现在,如果我们在
B
中附加一个项,那么根据您的预期行为,它也应该更新
A
,因此将因此创建一个新数组。如果子片的大小与实际数组相比很小(用于从原始数组中复制1000个项来追加1个项),则这可能是低效的

另外,为了保持一致,指向旧数组的所有其他引用(子片)都必须更新为指向新数组中的适当位置,这意味着我们必须在切片中存储额外的信息,如开始索引。如果我们有一个子片的子片,这会变得很棘手

因此,当前的实现是有意义的



这里推荐的方法是复制子片,而不是直接处理子片,以防止此类问题。拥有副本的另一个好处是,如果原始切片很大并且不再有引用,那么它可以被垃圾收集,但是如果它有子切片,那么原始数组将被保留在内存中,直到子切片仍然引用它。

这是一个关于如何在Go中实现切片的问题

看起来像:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
因此,切片具有长度和容量。如果尝试将项目附加到切片,使其超过当前容量,则会在下面创建一个新数组来保存新数据,但由于以前的子切片可能仍然指向旧的数组,因此它会保持原样,直到不再有对它的引用为止


现在让我们假设我们有一个切片
a
[1,2,3,4,5,6]
和一个子切片
B
,它指向
a
:[4,5,6]中的最后3项

[1, 2, 3, 4, 5, 6]
 ^        ^
 |        |
 |        B------
 |
 A---------------  
现在,如果我们在
B
中附加一个项,那么根据您的预期行为,它也应该更新
A
,因此将因此创建一个新数组。如果子片的大小与实际数组相比很小(用于从原始数组中复制1000个项来追加1个项),则这可能是低效的

另外,为了保持一致,指向旧数组的所有其他引用(子片)都必须更新为指向新数组中的适当位置,这意味着我们必须在切片中存储额外的信息,如开始索引。如果我们有一个子片的子片,这会变得很棘手

因此,当前的实现是有意义的



这里推荐的方法是复制子片,而不是直接处理子片,以防止此类问题。拥有副本的另一个好处是,如果原始切片很大并且不再有引用,那么它可以被垃圾收集,但是,如果存在子片,则原始数组将保留在内存中,直到子片仍然引用它。

我认为这样做的目的是不
将视图附加到共享数据中。如果你想获取一个子片并附加到它,你就要制作一个副本。只有当你不理解片背后的简单语义时,它才会显得不一致。如果您不确定发生了什么,您可以阅读大量资源:、和上的规范、、和@user2357112谢谢,现在我已经清楚了。@JimB谢谢,我将阅读它们。这篇文章:我认为您的想法是不将视图附加到共享数据中。如果你想获取一个子片并附加到它,你就要制作一个副本。只有当你不理解片背后的简单语义时,它才会显得不一致。如果您不确定发生了什么,您可以阅读大量资源:、和上的规范、、和@user2357112谢谢,现在我很清楚了。@JimB谢谢,我将阅读它们。这篇文章: