プログラミング
記事内に商品プロモーションを含む場合があります

Go言語のfor~rangeの注意点(ポインタ参照について)

tadanori

Go言語のfor~rangeでリストの内容を更新できないかと考えてチェックしていると、いろいろ気づきがありましたので記事にまとめました。

for~rangeの使い方

for~rangeの基本的な使い方

Go言語でfor~rangeを使う場合、基本的な書き方は以下のようになると思います。

for~rangeの場合、1つめは要素のインデックスで、2つめが要素の値になります。

あわせて読みたい
Go言語入門|プログラミングに必須な知識を一気に解説
Go言語入門|プログラミングに必須な知識を一気に解説

下記のプログラムでは、インデックスは受け取らずに、要素の値だけを受け取っています。

package main

import (
	"fmt"
)

func main() {
	a := []int{1, 2, 3, 4}

	for _, e := range a {
		fmt.Println(e)
	}
}
実行結果
1
2
3
4

aの値を更新したい(NGな例

ループ中でaの値を更新したい場合、以下のように書きたくなるかもしれません。

package main

import "fmt"

func main() {
	a := []int{1, 2, 3, 4}

	for _, e := range a {
		e += 10
	}
	fmt.Println(a) // NG: aの値は更新されない
}

実際に実行してみると、結果は以下のようになりaの値は更新されません

実行結果
[1 2 3 4]

つまり、配列aとループ中のeは別の変数のようです。

アドレスを渡したい

C++のように&eとして参照にできないかと考えますが、これはエラーになります。

package main

import "fmt"

func main() {
	a := []int{1, 2, 3, 4}

	for _, &e := range a { // &eはエラー
		fmt.Println(e)
	}
}

残念ながらGo言語では、for~rangeで&を使うことはできないようです。

そもそも、アドレスはどうなっているのか?

そもそも、ループ中のeのアドレスはどうなっているのでしょうか?

確認のために以下のようなコードを書いてみました。

package main

import "fmt"

func main() {
	a := []int{1, 2, 3, 4}

	for i, e := range a {
		fmt.Println(e, &e, &a[i])
	}
}

実行結果をみるとeのアドレスとa[i]のアドレスが異なっていることがわかります。

また、実行して気付いたのですが、eのアドレスは常に同じです(これによるトラブルは番外編で解説します)。

実行結果
1 0xc0000120e0 0xc0000220a0
2 0xc0000120e0 0xc0000220a8
3 0xc0000120e0 0xc0000220b0
4 0xc0000120e0 0xc0000220b8

ということで、for~rangeループでは、eの中身を毎回書き換えていることがわかります。

元の配列の値を変更する場合の書き方

では、元の配列aの中身を変更したい場合はどうすれば良いかというと、for~rangeを使う場合は以下のようになります。

package main

import "fmt"

func main() {
	a := []int{1, 2, 3, 4}

	for i := range a {
		a[i] += 10
	}
	fmt.Println(a)
}

結局、for i:=0; i < len(a); i++ {...}と書くのとあまり変わらない気もしますが、for~rangeを使った方が少しだけ記述量が少なくなります。

番外編

今回調べて1つだけ注意点に気付いたのでメモしておきます。

ポインタに注意

以下のようなコードを考えます。bはポインタ配列で、eの値をコピーせずにアドレスを記録しています。

package main

import "fmt"

func main() {
	a := []int{1, 2, 3, 4}
	b := []*int{}

	for _, e := range a {
		b = append(b, &e)
	}
	for i := 0; i < len(b); i++ {
		fmt.Println(*b[i])
	}
}

このプログラムを実行すると以下のような結果になります。

実行結果
4
4
4
4

eが同じアドレスを使い回していることを思い出せば理解できると思いますが、bに格納されるアドレスは全て同じになり、全て最後の値になります

整数配列などでこういうミスをすることはないかと思いますが、例えば構造体などの場合はコピーのコストを削減するためにアドレスだけ参照するということをやりがちなので注意が必要です。

まとめ

for~rangeも便利ですが、注意して使わないと思わぬミスを起こしてしまうことに気づきました。注意が必要です。

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

記事URLをコピーしました