Go スライスでの予期しない追加動作: 説明
Go では、ポインタのスライスに追加するときに、append 関数が興味深い動作をします。これを説明するには、次のコードを考えてみましょう。
import "fmt" type Foo struct { val int } func main() { var a = make([]*Foo, 1) a[0] = &Foo{0} var b = [3]Foo{Foo{1}, Foo{2}, Foo{3}} for _, e := range b { a = append(a, &e) } for _, e := range a { fmt.Printf("%v ", *e) } }
このコードは {0} {1} {2} {3} を出力するという予想に反して、代わりに {0} {3} { を出力します。 3} {3}。この矛盾の背後にある理由は、for ループの範囲変数の性質にあります。
範囲変数を理解する
Go の for-range ループは、コピーを作成することによって動作します。反復される配列またはスライス内の各要素の。この場合、e は配列 b の要素のコピーです。したがって、e 自体は b の要素ではありません。むしろ、これは要素の値を保持する一時変数です。
スライス a に追加するとき、コードは b の実際の要素のアドレスではなく、e のアドレスを追加します。 e はすべての反復で同じコピーであるため、同じポインタが 3 回 a に追加されます。したがって、e に割り当てられた最後の値 (Foo{3}) は、繰り返し出力される値です。
動作の修正
この動作を修正するには、コードe のアドレスではなく、b の実際の要素のアドレスを追加する必要があります。修正されたループは次のようになります。
for i := range b { a = append(a, &b[i]) }
&e の代わりに &b[i] を追加することで、コードは b の各要素が a に追加されるようにします。その結果、正しい出力 {0} {1} {2} {3} が出力されます。
初期動作の理由
この予期しない動作は、 Go における真の参照の数。 Go にはポインター型と非ポインター型がありますが、参照はありません。範囲変数は単なるローカル変数であり、ポインターまたは非ポインターの値を保持します。参照を保持することはできません。
したがって、範囲変数を操作するときは、要素自体ではなく、値のみを操作することになります。実際の要素を変更するには、値を範囲変数に代入して、値を効果的にコピーする必要があります。この値は次の反復で上書きされます。
以上がfor-range ループからポインターを追加すると、Go の「append」関数が予期しない結果を生成するのはなぜですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。