問題の歴史:
for-range構文はGoでコレクション(スライス、配列、map、文字列)の反復の方法として登場しました。Goの開発者は最適化を導入しました:ループの各反復ごとに値がコピーされ、参照が直接使用されることはなく、特にループ変数に関して不明瞭なエラーを引き起こすことがあります。
問題:
多くの人はrange内で変数のアドレスを取得しようとして(例えば、&v)、コレクションの要素のアドレスを取得していると考えていますが、実際にはローカル変数のアドレスを取得しています。
解決策:
for-rangeループでは、各反復ごとにイテレーターの新しいコピー(key、value)が作成されます。基本的な型にとっては問題ありませんが、構造体の場合、要素のポインタを保持するときに予期しない結果になることがあります — それは常に同じ変数を指すことになり、スライスの異なる要素を指すわけではありません。
コード例:
people := []Person{{Name: "Ivan"}, {Name: "Oleg"}} ptrs := make([]*Person, 0) for _, p := range people { ptrs = append(ptrs, &p) // すべてのptrsは同じpを参照します }
主な特長:
range内でvalue変数への参照を保存すると何が起こりますか?
すべての参照は同じメモリを指し、valueは一時的な変数であるためです。
for _, v := range someSlice { ptrs = append(ptrs, &v) } // すべてのptrsは同じ変数への参照を含みます!
range内のvalueを介してコレクションの要素を参照で変更できますか?
いいえ、valueを変更してもコレクションの元の要素には影響しません。変更するにはインデックスを参照する必要があります。
for _, v := range arr { v.Field = 10 // arrは変更されません } for i := range arr { arr[i].Field = 10 // 正しい }
mapのfor-rangeは反復の順序を保証しますか?
いいえ、Goのmapの反復順序は未定義であり、アプリケーションの実行ごとに異なる可能性があります。
開発者がrangeを介して構造体の要素へのリンクのリストをシリアル化しようとし、&valueを別のスライスに保存します。結果として同じアドレスのスライスが得られます。
利点:
欠点:
インデックスで反復し、配列の必要な要素へのポインタを保存します:
for i := range arr { ptrs = append(ptrs, &arr[i]) }
利点:
欠点: