Go言語のキャッチアップ その2 ~配列~
Goの配列(Array)
Goの配列は、同じ型の値を決まった個数だけ格納するデータ構造。 RubyのArrayと似ているが、重要な違いがある。
Goの配列とは
たとえば、整数を3つ格納する配列を作る。
package main
import "fmt"
func main() {
numbers := [3]int{10, 20, 30}
fmt.Println(numbers)
// [10, 20, 30]
}
この[3]intの意味はこんな感じ。
- 3: 要素数
- int: 各要素の型
{10, 20, 30}: 格納する値
この3つの値は、3つのint型の値が連続したメモリ領域に格納される。
ちなみにRubyならこんな感じ。
numbers = [10, 20, 30]
なんと簡単な書き方。 しかしRubyの配列は要素を自由に追加・削除できるため、どこかのタイミングで意図せず値が追加されていたり、削除されていたりする場合もある。
Goの配列は違う。
Goの配列は要素数を変更できない
package main
func main() {
numbers := [3]int{10, 20, 30}
numbers[0] = 100 // OK
// 4つ目の要素を追加することはできない。
numbers[3] = 40
}
// ./main.go:9:10: invalid argument: index 3 out of bounds [0:3]
つまり、要素の値は変更できるが、要素数は変更できない。 また、Goでは配列の長さも型の一部となる。
package main
import (
"fmt"
"reflect"
)
func main() {
a := [3]int{10, 20, 30}
b := [4]int{10, 20, 30, 40}
fmt.Println(reflect.TypeOf(a))
// [3]int
fmt.Println(reflect.TypeOf(b))
// [4]int
}
こんな感じでaとbは異なる型になる。 (※reflect.TypeOf()は型をチェックする関数) これはRubyにはない特徴。
配列とメモリの関係
Goの配列は、要素がメモリ上に連続して配置される。
たとえば、intが8バイトの環境なら、以下のようになる。
このように、それぞれの値が連続した領域に格納される。
Goの配列は代入するとコピーされる
Rubyの場合
a = [10, 20, 30]
b = a
b[0] = 999
p a # [999, 20, 30]
p b # [999, 20, 30]
このようにaとbが同じ配列オブジェクトを参照する。
一方Goの場合
package main
import "fmt"
func main() {
a := [3]int{10, 20, 30}
b := a
b[0] = 999
fmt.Println(a)
// [10 20 30]
fmt.Println(b)
// [999 20 30]
}
Goでは配列を別の変数に代入すると、配列全体の値がコピーされる。
関数の引数に配列を渡す場合もコピーされる
前述の通り、Goでは配列を別の変数に代入すると配列全体がコピーされる。 これは、関数の引数として配列を渡した場合も同様の事が起こる。
package main
import "fmt"
func update(numbers [3]int) {
numbers[0] = 999
fmt.Println(numbers)
// [999 20 30]
}
func main() {
numbers := [3]int{10, 20, 30}
update(numbers)
fmt.Println(numbers)
// [10 20 30]
}
update関数の中では、配列の先頭要素を999に変更している。 しかしmain関数で出力すると元の値のままになっている。 これは関数に配列を渡した際に、配列全体の値がコピーされているため。 つまり、update関数が変更しているのは元の配列ではなくコピーされた配列ということになる。
なぜGoでは配列をコピーするのか?
Goの配列は値型であり、代入や関数への引数渡しでは配列全体の値がコピーあsれる。 そのため、コピー先の配列を変更しても、元の配列には影響しない。
ただし、大きな配列の場合はコピーするデータ量も増えるため、実際のGoの開発では、より柔軟に扱えるスライスがよく使われる(らしい) 次回はスライス!