Go言語に限らずポインタが存在する言語では「ポインタなのか?値なのか?」ということに常に気を配る必要があります。
さらにスライスになると「中身がポインタなのか?」「容れ物がポインタなのか?」「どちらもポインタなのか?」というように、注意すべきことが増え、気を抜くと簡単にランタイムエラーになってしまいます。
このエントリでは、スライスとスライスのポインタで異なる初期化方法や要素の追加についてまとめます。
初期化では&と*の使い分けに注意します。
type Person struct {
Name string
}
var persons1 []*Person // Person(ポインタ)のスライス
persons1 = []*Person {{Name: "Tom"}, {Name:"John"}}
var persons2 *[]Person // Person(値)のスライスのポインタ
persons2 = &[]Person {{Name: "Tom"}, {Name: "John"}}
var persons3 *[]*Person // Person(ポインタ)のスライスのポインタ
persons3 = &[]*Person {{Name: "Tom"}, {Name: "John"}} スライスの要素の追加はappendを使います。
var persons1 []*Person
persons1 = append(persons1, &Person{Name: "Tom"}) スライスのポインタの場合は注意が必要です。
下記の例はSEGVを引き起こします。なぜでしょうか?
// これは間違い
var persons2 *[]Person
*persons2 = append(*persons2, Person{Name: "Tom"}) // => SEGV これはスライスとスライスのポインタで、初期化時のnilが指す対象が異なるためです。
- スライス
[]*Personがnil→ 要素がない - スライスのポインタ
[]Personがnil→ スライスへの参照がない
var persons1 []*Personは「要素がない」スライスを初期化しているのに対し、var persons2 *[]Person は「スライスへの参照がない」ポインタを初期化しているためです。
参照がnilなので*persons2のようにデリファレンスできず nil pointer dereference のランタイムエラーになってしまうというわけです。
要はスライスへの参照があれば良いので、appendの前にスライスへの参照を与えると動作します。
var persons2 *[]Person
persons2 = &[]Person{{Name: "John"}}
*persons2 = append(*persons2, Person{Name: "Tom"}) スライスとスライスのポインタに対し、繰り返し(iteration)を使って要素を追加する例です。
下記の例では*[]Personを繰り返して[]*Studentに要素を追加しています。
先に挙げたappendで追加していく方法と、前もって長さの決まった容れ物を作って(make)から代入する方法があります。
type Student struct {
Name string
}
var persons *[]Person
persons = &[]Person {{Name: "Tom"}, {Name: "John"}}
var students []*Student
// make() して要素を確保してから代入する方法
students = make([]*Student, len(*persons))
for i, person := range *persons {
students[i] = &Student{Name: person.Name}
}
// make() せずに append() しても良い
for _, person := range *persons {
students = append(students, &Student{Name: person.Name})
} 以上です。
このエントリでは、スライスとスライスのポインタで異なる初期化方法や要素の追加についてまとめました。
Goの配列とスライスを復習したいという方はぜひ下記の記事をご覧になってください。
コメントを送る
コメントはブログオーナーのみ閲覧できます