Go の Append() は新しいスライスを作成するのはいつですか?
Go 言語の append() 関数は、既存のスライスを拡張するために使用されます。組み込み API ドキュメントによると、append() は、元のスライスの容量が不十分な場合に、より大きな容量の新しいスライスを作成する可能性があります。
しかし、再帰アルゴリズムのコンテキストで考えると、この動作には疑問が生じます。特に、次のアルゴリズムはアルファベットの組み合わせを生成します:
<code class="go">package main import ( "fmt" ) func AddOption(c chan []bool, combo []bool, length int) { if length == 0 { fmt.Println(combo, "!") c <- combo return } var newCombo []bool for _, ch := range []bool{true, false} { newCombo = append(combo, ch) AddOption(c, newCombo, length-1) } } func main() { c := make(chan []bool) go func(c chan []bool) { defer close(c) AddOption(c, []bool{}, 4) }(c) for combination := range c { fmt.Println(combination) } }</code>
このコードでは、AddOption 関数がアルファベットのメンバーを再帰的にスライスに追加し、結果をチャネル経由で送信します。ただし、観察によると、チャネルに送信されたスライスは送信後に変更されます。
ドキュメントでは append() が新しいスライスを返す必要があると示唆しているのに、コード内の動作がそうでないことを暗示しているため、矛盾が生じます。この記事では、append() の基礎となるメカニズムを検証し、新しいスライスが作成されるタイミングを明確にします。
スライス表現を理解する
append() の動作を理解するには、次のようにします。スライスの内部表現を理解するために重要です。スライスは、スタンドアロンのように見えますが、自己完結型のデータ構造ではありません。代わりに、実際のデータの基礎となる配列を指す記述子で構成されます。
スライス記述子は、次の 3 つのコンポーネントで構成されます。
Append() の戻り値
append() が使用されると、関数は独自の長さ、容量、およびデータ ポインターを持つ新しいスライス記述子を作成します。これは、append() が「[s] を再割り当てし、[ies] を新しい配列ブロックにコピーする」と記載されているドキュメントと一致しています。
ただし、これにより、なぜスライスに変更が加えられるのかという別の疑問が生じます。チャネルに送信された記述子は元のスライスに残りますか?
共有参照について
この問題を解決する鍵は、チャネル内のデータ ポインタの性質を理解することです。スライス記述子。このポインターは、基礎となるデータのコピーを作成しません。元のスライスと同じデータを指します。
したがって、スライスに対して append() が使用されると、新しいスライス記述子が作成されますが、データ ポインターは同じままになります。これは、どちらかのスライス記述子の要素に加えられた変更は、変更がどこで行われたかに関係なく、両方のスライスに反映されることを意味します。
デモ
この概念を説明するには次のコード スニペットを考えてみましょう:
<code class="go">package main import "fmt" func main() { s := make([]int, 0, 5) s = append(s, []int{1, 2, 3, 4}...) a := append(s, 5) fmt.Println(a) b := append(s, 6) fmt.Println(b) fmt.Println(a) }</code>
このコードが実行されると、次の出力が行われます:
<code class="go">package main import ( "fmt" ) func AddOption(c chan []bool, combo []bool, length int) { if length == 0 { fmt.Println(combo, "!") c <- combo return } var newCombo []bool for _, ch := range []bool{true, false} { newCombo = append(combo, ch) AddOption(c, newCombo, length-1) } } func main() { c := make(chan []bool) go func(c chan []bool) { defer close(c) AddOption(c, []bool{}, 4) }(c) for combination := range c { fmt.Println(combination) } }</code>
この例では、スライス a と b の両方が最初は同じ基礎となるデータを共有します。ただし、 b に新しい値が割り当てられると、新しい基礎となるデータ配列が作成され、 b のデータ ポインターがそれを指すように更新されます。は引き続き同じデータ ポインターを参照するため、古いデータ配列にアクセスし続けます。
スライスの容量を変更することで、再割り当てを回避するのに十分な容量がある場合、スライスが実際に基礎となるデータを共有していることが実証できます。
結論
Go の append() 関数は、新しいスライス記述子を割り当てますが、元のデータ配列への参照は維持します。これは、再帰的アルゴリズム内のスライスへの変更が、同じデータ参照を共有するすべてのスライスに表示されることを意味します。 Go でスライスを効果的に操作するには、この動作を理解することが重要です。
以上がGo の append() 関数はいつ新しいスライスを作成しますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。