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の配列とスライスを復習したいという方はぜひ下記の記事をご覧になってください。
コメントを送る
コメントはブログオーナーのみ閲覧できます