Goにおけるスライス演算子(slice operator)を改めて調べ直し、3-INDEX記法などを学んだ。
TL;DR
- スライスは
make
などのほかにスライス演算子(slice operator)による初期化も存在する - スライス演算子でもcapacityを指定することができる。
- 他にも珍しいスライスの宣言方法があった。
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s := arr[2:5] // s = [2 3 4] cap(s) = 8
s = arr[2:5:7] // s = [2 3 4] cap(s) = 5
s = arr[:0] // s = [] cap(s) = 10
s = arr[:0:7] // s = [] cap(s) = 7
s = make([]int, 10)[:5] // [0 0 0 0 0] make ([]int, 5, 10)と同じ
記事中のサンプルコードをまとめたplaygroundは以下。
スライス
Goにおけるスライスは、基底配列(underlying array)の要素の部分列(あるいは全部)へアクセスする軽量なデータ構造だ。
- Slice expressions | The Go Programming Language Specification
詳細なデータ構造についてはGo Blogが参考になる。
- Go Slices: usage and internals | The Go Blog
スライスは大雑把に以下のような情報を持つ。容量を超える数の値が入る場合はメモリアロケーションが走る。
- 基底配列の特定の場所を指すポインタ。スライスの先頭要素になる場所
- 長さ。スライスにふくまれている値の数。
len
関数で取得できる値 - 容量(capacity)。そのスライスの最大容量。
cap
関数で取得できる値
まったく新しいスライスを宣言するときはmake
などを使う。
make([]int, 5) // [0 0 0 0 0]
make([]int, 5, 10) // [0 0 0 0 0] あらかじめ容量が10ある
[]int{} // [0 0 0 0 0]
[]int{3:2, 5:1} // [0 0 0 2 0 1]
詳細はプログラミング言語Go 4.2スライスに記載されている(上記も大半が引用)。
スライス演算子(slice operator)
そしてある配列(あるいは別のスライス)から新たなスライスを生成するのがスライス演算子(slice operator)だ。
基底配列(あるいは別のスライス)のオブジェクトarray
に対してarray[1:2]
のように宣言する。
例えば以下のようにスライス演算子でスライスを生成すると、各情報は以下のようになる。
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // array(not slice)
s := arr[2:5] // slice operator a[ low : high ]
fmt.Println("s =", s) // s = [2 3 4]
fmt.Println("len =", len(s)) // len = 3
fmt.Println("cap =", cap(s)) // cap = 8
上記の例でいうと、「基底配列の2番目の要素」(law
)から「基底配列の(5-1)番目の要素」(high
)までのスライスが用意される。
容量は基底配列(あるいは元のスライス)で決まるので、基底配列の長さ - スライスの開始位置(len(arr) - low))
になる。
スライス演算子の省略記法
スライス演算子ではlow
やhigh
を明示的に書かずに省略することができる。
a[2:] // a[2 : len(a)] と書くのと同じ
a[:3] // a[0 : 3] と書くのと同じ
a[:] // a[0 : len(a)] と書くのと同じ
3-INDEX記法でスライス演算子利用時でも容量を指定する
あまり見ない気がするが、3つインデックスを指定する方法も存在する。
- Three-index slices | Go 1.2 Release Notes
make
を使ってスライスを生成するとき(make([]int, len, cap)
)のように、容量を3つ目の値として指定する。
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s = arr[2:5:7]
fmt.Println("s =", s) // s = [2 3 4]
fmt.Println("len =", len(s)) // len = 3
fmt.Println("cap =", cap(s)) // cap = 5
3-INDEX記法でも最初のパラメータだけを省略することができる。2番目と3番目のパラメータは省略することができなくなる。
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s = arr[:0:7]
fmt.Println("s =", s) // s = []
fmt.Println("len =", len(s)) // len = 0
fmt.Println("cap =", cap(s)) // cap = 7
// s = arr[::7] // middle index required in 3-index slice
// s = arr[:5:] // final index required in 3-index slice
// s = arr[5:7:5] // invalid slice index: 7 > 5
最後に
スライスの宣言について仕様を再確認していたら結構知らなかったことがあったので、まとめた。
3-INDEX記法はいつから入ったんだ?と思ったが1.2からだったのでほぼ最初から入っていたのに使ったことがなかった。
普段メモリアロケーションなど細部を気をつけてコーディングできていないので状況に応じては使えるようになった(そんな状況がいつあるか?はちょっと想像できていないが…)。
基底配列(underlying array)やa[low:high]
という書き方を slice operator と呼ぶこともわかってよかった。ただ、今回調べていて一番びっくりした書き方は以下だったりする。
// [0 0 0 0 0] make ([]int, 5, 10)と同じ
make([]int, 10)[:5]
参考
- Slice expressions | The Go Programming Language Specification
- Three-index slices | Go 1.2 Release Notes
- Go Slices: usage and internals | The Go Blog