2016-11-22 13 views
1

それはGolangで私の最初の日だ、と私はappend()のように、そのスライス操作をしようとすると、私はとても混乱になり、一つのことがあります:私は2つのだけ番号が追加するとなぜ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] 

sへの参照として、aが表示されていると思います。

しかし、私は、コードの行を変更する場合:

a = append(a, 1000, 1001, 1002); 

...結果は次のようになります。

aは、メモリの他のセグメントに再割り当てされた、私が思うに、
a: len=5 cap=8 [5 100 1000 1001 1002] 
s: len=6 cap=6 [2 3 5 7 11 13] 

、全体を保持し、sへの参照を切り離します。

これは非常に矛盾しているので、混乱させてしまいます。追加する値の乱数がある場合など、このエラーを起こすのは本当に簡単なことがあります。

なぜGolangはこのように設計されていますか? JavaScriptのslicepushのような操作をしたいのであれば、これを避ける方法はありますか?

+2

私は、共有データへのビューには「追加」しないという考えがあると思います。サブスライスを付けてそれに追加する場合は、コピーを作成します。 – user2357112

+7

スライスの背後にあるシンプルなセマンティクスを理解していないと矛盾しているように見えます。何が起こっているのかわからない場合は、[Go Slices:usage and internals](https://blog.golang.org/go-slices-usage-and-internals)、[A Go:Slices](https://tour.golang.org/moretypes/7)、[Slice Types](https://golang.org/ref/spec#Slice_types)および[Copying Slicesを追加する](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 
} 

、スライスは、長さと能力を有しています。現在の容量を超えるようにスライスにアイテムを追加しようとすると、新しいデータを保持するために新しい配列が作成されますが、以前のサブスライスがまだ古い配列を指している可能性があるまで保持されますそれ以上の参照は残っていません。 [1, 2, 3, 4, 5, 6]Aで3つの項目を最後に指摘subslice B:[4、5、6]


今の私たちは、スライスAを持っているとしましょう。私たちはあなたの期待される動作から、その後Bに項目を追加する場合

[1, 2, 3, 4, 5, 6] 
^  ^
|  | 
|  B------ 
| 
A--------------- 

は、今では新しい配列が原因それに作成されますので、同様Aを更新する必要があります。サブリスのサイズが実際の配列に比べて小さい場合(1つの項目を追加して元の配列から1000項目をコピーする場合)、これは非効率的です。

プラス一貫性を維持する古いアレイを指す他のすべての参照(サブスライス)は、新しい配列の適切な位置を指すように更新する必要があります。つまり、スライスに追加情報を格納する必要があります。開始インデックス。また、サブスライスのサブスライスがある場合、これは難しいことがあります。

したがって、現在の実装は意味があります。


ここで推奨されるアプローチではなく、このような問題を防止するために、直接それに取り組んでsubsliceのコピーを作成することです。コピーを持つもう1つの利点は、元のスライスが巨大であり、もはや参照がない場合はガーベッジ・コレクションできますが、サブスライスがあった場合は、元の配列はサブスライスが参照されるまでメモリに保持されますそれに。

+0

'Bにアイテムを追加するとあなたの期待された動作から、それも同様に新しい配列が作成されます。いいえ、それは私の期待された動作ではありません、私の期待される動作は、新しい配列を作成し、その新しい項目を追加します。 Aは決して触れてはいけません(とにかく、新しい配列が作成されるため)。私はこの実装が嫌なことを言わなければならない – Kuan

関連する問題