2016-11-22 19 views
1

这是我在Golang第一天,当我尝试它的切片操作,如append(),有一两件事让我很困惑:为什么Golang的切片操作这么复杂

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的slicepush这样的操作,那怎么能够避免呢?

+2

我认为这个想法是,你只是不'追加到视图共享数据。如果你想采取子碟并追加它,你可以复制一份。 – user2357112

+7

如果您不理解切片背后的简单语义,它只会显得不一致。如果你不确定发生了什么,你可以阅读很多资源:[Go Slices:usage and internals](https://blog.golang.org/go-slices-usage-and-internals),[A游览之旅:切片](https://tour.golang.org/moretypes/7),[切片类型](https://golang.org/ref/spec#Slice_types)和[附加任何复制切片](https://golang.org/ref/spec#Appending_and_copying_slices),[Go By Example:Slices](https://gobyexample.com/slices)和[Slice Tricks](https://github.com/) golang/go/wiki/SliceTricks) – JimB

+0

@ user2357112谢谢,现在我很清楚。 – Kuan

回答

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以及因此一个新数组归因于被创建。如果子切片的大小与实际数组相比较小(用于追加1个项目从原始数组复制1000个项目),这可能是低效的。

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

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


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

+0

'如果我们追加一个项目到B然后从你预期的行为,它应该更新A,因此,一个新的数组将被创建,因为这个。“不,这不是我的预期行为,我的预期行为应该只是创建一个新的数组,并复制一切B指的是新的数组并添加新项目。答应永远不会碰(因为无论如何,会有一个新的阵列创建)。我不得不说这个实现很糟糕 – Kuan

相关问题