3.2 スライス
スライスは可変長の配列。
スライスの型にはサイズが含まれないため、配列のような制限はない。
スライスと配列の使い方には違いがある。
宣言時に大きさを指定しない
code:go
var x = []int{10, 20, 30}
var y []int
スライスのゼロ値はnil
nilについては「6章 ポインタ」で詳しく説明
比較不可能
code:go
package main
import "fmt"
func main() {
{
var x []int
var y []int
fmt.Println(x==y) // コンパイルエラー
}
}
3.2.1 len
スライスのサイズが分かる組み込み関数。
この関数は他の型も引数で受け取ることができる。
3.2.2 append
スライスの要素を増やすときに使う組み込み関数。
code:go
var x []int
x = append(x, 10)
var x = []int{1, 2, 3}
x = append(x, 4)
y := []int{20, 30, 40}
x = append(x, y...) // 「...」演算子はスライスの個々の値を展開する
appendも戻り値を変数に代入せずに無視するとコンパイルエラーとなる。
詳しくは「5 章 関数」にて解説。
3.2.3 スライスのキャパシティ
スライスはキャパシティを持っており、あらかじめ一定個の連続する領域が確保されている。
スライスのサイズがキャパシティに達すると、Goのランタイムがより大きなキャパシティを持つ領域を確保してくれる。この時のより大きなキャパシティを持つスライスは、元のスライスがコピーされたもの。
組み込み関数のcapで、スライスのキャパシティが分かる。
code:go
package main
import "fmt"
func main() {
var x []int
fmt.Println(x, len(x), cap(x)) // [] 0 0
x = append(x, 10)
fmt.Println(x, len(x), cap(x)) // 10 1 1 x = append(x, 20)
fmt.Println(x, len(x), cap(x)) // 10 20 2 2 x = append(x, 30)
fmt.Println(x, len(x), cap(x)) // 10 20 30 3 4 x = append(x, 40)
x = append(x, 50)
}
appendによってスライスのサイズが大きくなってくると、コピーのための時間がかかるようになる。
3.2.4 make
make関数を使うことで、スライス宣言時にキャパシティを指定できる。
code:go
x := make([]int, 5, 10) // 長さ5キャパシティ10のスライス
// 長さ0キャパシティ10のスライス。
// 長さは0なので、x0、x1 などの要素指定はできない y := make([]int, 0, 10)
y = append(y, 5, 6, 7, 8) // 値は5 6 7 8 になり、長さは4に変わる 3.2.5 スライスのクリア
clear関数で指定したスライスの全要素をゼロ値にできる。
スライスのサイズは変わらない。
code:go
s := []string{"first", "second", "third"}
clear(s)
fmt.Println(s, len(s)) // 3
fmt.Printf("s0=|%s|, s1=|%s|, s2=|%s|\n", s0, s1, s2) 3.2.6 スライスの生成方法の選択
スライスが全く大きくならない可能性がある場合は、nilスライス
code:go
var data []int // スライスのゼロ値nilで初期化される(nilスライス)
fmt.Println(data == nil) // true
fmt.Println(data) // []
fmt.Println(len(data)) // 0
これが有用なケースはスライスをJSONに変換するとき。詳しくは「13.3.4 JSON ストリーミングのエンコードとデコード」にて解説
初期値がある、またはスライスの値が変化しない場合は、スライスリテラル
code:go
data := []int{2, 4, 6, 8}
実行すればスライスのサイズが予想できるが、プログラムを書いている時は分からない場合は、make。ただしここで問題なのが、サイズを正の長さにするべきか、サイズは0にしてキャパシティを正の値にするべきか
スライスをバッファとして利用する場合は、サイズを正の整数で指定する
バッファについては「13.1 入出力」にて解説
必要なサイズが正確に分かっている場合は、そのサイズを指定する
上記以外の状況の場合は、サイズが0でキャパシティを指定
3.2.7 スライスのスライス
スライス式はスライスから別のスライス(サブスライス)を作ることができる。
[{開始オフセット}:{終了オフセット}]の形式で書く。
開始オフセットを指定しないと0が、終了オフセットを指定しないとスライスの最後となる。
code:go
x := []string{"a", "b", "c", "d"}
fmt.Println("y:", y) // y: a b fmt.Println("z:", z) // z: b c d fmt.Println("d:", d) // d: b c 3.2.7.1 スライスの記憶領域の共有
スライスからサブスライスを作り出すときはデータのコピーを作っておらす、メモリが共有されている。
スライスの要素を変更すると、その要素を共有している全てのスライスが影響を受ける。
code:go
x := []string{"a", "b", "c", "d"}
fmt.Println("y:", y) // y: x y fmt.Println("z:", z) // z: y z d appendが加わると更にややこしくなる。
code:go
x := []string{"a", "b", "c", "d"}
y := x:2 // 先頭から2つ。つまりx0とx1。残りもyのキャパシティに含まれている fmt.Println("y:", y) // y: a b y = append(y, "z")
y = append(y, "1")
y = append(y, "2")
code:go
x := make([]string, 0, 5)
x = append(x, "a", "b", "c", "d")
fmt.Println("y:", y) // y: a b fmt.Println("z:", z) // z: c d fmt.Println(cap(x), cap(y), cap(z)) // 5 5 3
y = append(y, "i", "j", "k")
fmt.Println("z:", z) // z: i j x = append(x, "x")
fmt.Println("z:", z) // z: i j z = append(z, "y")
fmt.Println("z:", z) // z: i j y こうしたスライスの複雑な状況を避けるために、サブスライスはappendを使わないか、以下のように以下のようにフルスライス式を用いてappendで上書きが発生しないようにする。