🎗️
【Go】for rangeで要素の値が変更されない理由と対処方法
はじめに
Goのスライスなどをfor range
ループさせて、要素の値を変更しようとしたときに、コードの書き方によっては値が変更されない場合があります。
この記事では、その理由と対象方法についてまとめています。
値が変わらない理由
値が書き換わらないコードの例をつぎに示します。
func main() {
s := []int{1, 2, 3}
fmt.Println(s) // [1, 2, 3]
for _, i := range s {
i += 1
}
fmt.Println(s) // [1, 2, 3]
}
このコードは、スライスをfor rangeで回すときに値をインクリメントしていますが、ループを終えた後の結果は、変更前と同じままです。
この理由は、スライスの各要素と、for rangeで登場する変数のアドレスを見るとわかります。
func main() {
s := []int{1, 2, 3}
fmt.Println(s)
for _, i := range s {
i += 1
}
fmt.Println(s)
fmt.Printf("index 0: %p\n", &s[0])
fmt.Printf("index 1: %p\n", &s[1])
fmt.Printf("index 2: %p\n", &s[2])
for i, v := range s {
fmt.Printf("Inside of loop, index %d: %p\n", i, &v)
i += 1
}
fmt.Println(s)
}
上記のコード中のアドレスの出力結果は、つぎのようになります。
index 0: 0x140000180c0
index 1: 0x140000180c8
index 2: 0x140000180d0
Inside of loop, index 0: 0x1400001a0d0
Inside of loop, index 1: 0x1400001a0d0
Inside of loop, index 2: 0x1400001a0d0
ここから、スライスの各要素のアドレスとfor rangeの中で登場する値のアドレスが異なっていることがわかります。また、for range中では、ループごとに値のアドレスが変更されることはなく、同じアドレスを参照しています。
以上のことから、ループさせている変数の要素のアドレスと、for range内での変数のアドレスが異なるため、for range内で要素の値を書き換えたとしても、元の変数の要素の値は変更されないということになります。
対処方法
ここでは、for rangeで値が変更されるようにするためには、どのように書けばいいかの例を示します。
func main() {
s := []int{1, 2, 3}
fmt.Println(s)
for i, _ := range s {
s[i] += 1
}
fmt.Println(s) // [2, 3, 4]
}
理由の方で、for range中で登場する変数のアドレスとループさせている変数の要素のアドレスが異なっている場合に、要素の値が変更されないことを確認しました。
このコードでは、for rangeでインデックスを取得し、ループさせている変数の要素にアクセスして、値を書き換えています。
for rangeの部分は、つぎのようにも書けます。
for i := 0; i < len(s); i++ {
s[i] += 1
}
Discussion