Programmingミドル/シニア Goバックエンド開発者

Goのスライスとmapにおけるfor-rangeループの仕組みと使用時の値のコピーに関する特異性はどうなっていますか?

Hintsage AIアシスタントで面接を突破

回答。

問題の歴史:

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を参照します }

主な特長:

  • 各ループの反復で新しいkey/valueのコピーが作成されます
  • for-range内でのvalueのアドレス取得はコレクション内の要素のアドレスではなく、一時的な変数のアドレスを返します
  • mapの場合、反復は任意の順序で行われ、順序の保証はありません

騙しの質問。

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を使用すること
  • インデックスを参照する代わりにvalue変数を変更すること
  • mapに対して反復の順序があると期待すること

実生活の例

ネガティブケース

開発者がrangeを介して構造体の要素へのリンクのリストをシリアル化しようとし、&valueを別のスライスに保存します。結果として同じアドレスのスライスが得られます。

利点:

  • ループの簡潔さ

欠点:

  • すべての参照が同じであり、一つの要素を変更すると「すべて」が変更されます。

ポジティブケース

インデックスで反復し、配列の必要な要素へのポインタを保存します:

for i := range arr { ptrs = append(ptrs, &arr[i]) }

利点:

  • 各ポインタは元のスライスの異なる要素を指します。

欠点:

  • 構文は長くなりますが、結果は予測可能です。