なんとなく配列代わりに使ったりするスライス。
ちょっと深堀りしてどんなものか考えてみます。
ちゃんと理解すれば中身が変わったり変わらなかったりで混乱することはないはず。
スライスの正体と配列との違い
配列は指定した型を指定した個数入れられる実体。
対してスライスは配列へのポインタを持つ構造体です。
ソースを見るとポインタ、長さ、キャパシティーを持っています。
1 2 3 4 5 |
type slice struct { array unsafe.Pointer len int cap int } |
スライスを代入したときに参照コピーになるのはarrayポインタが代入されるからです(ディープコピーにはcopy(dst, src []Type)
が用意されている)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
a1 := [2]int{1, 2} //配列 s1 := []int{1, 2} //スライス a2 := a1 s2 := s1 //どちらも入れ物そのものは違うもの fmt.Printf("%t %p %p\n", &a1 == &a2, &a1, &a2) fmt.Printf("%t %p %p\n", &s1 == &s2, &s1, &s2) //配列は中身も入れ物と同じアドレスでそれぞれ別アドレスになる //スライスは同じアドレスを指している fmt.Printf("%t %p %p\n", &a1[0] == &a2[0], &a1[0], &a2[0]) fmt.Printf("%t %p %p\n", &s1[0] == &s2[0], &s1[0], &s2[0]) /* Result: false 0xc000064090 0xc0000640b0 false 0xc00005a420 0xc00005a440 false 0xc000064090 0xc0000640b0 true 0xc0000640a0 0xc0000640a0 */ |
スライスの定義
1 2 3 |
s1 := []int{} //空の構造体として s2 := s1[:] //配列、スライスの部分スライス化 s3 := make([]int, 0) //makeを使った初期化 |
部分的なスライスを作る場合は元の配列(スライス)の参照であることに注意。
1 2 3 4 5 6 7 8 9 10 11 |
s1 := []int{0, 1, 2, 3, 4, 5} s2 := s1[1:3] fmt.Printf("%#v\n", s1) //[]int{0, 1, 2, 3, 4, 5} fmt.Printf("%#v\n", s2) //[]int{1, 2} //部分スライスの変更をすると元の配列/スライスも変更される s2[0] = 9 fmt.Printf("%#v\n", s1) //[]int{0, 9, 2, 3, 4, 5} //つまりこの参照先は同じ fmt.Printf("%t\n", &s1[1] == &s2[0]) //true |
スライスの追加
append(slice []Type, elems ...Type)
で確保領域を追加できる。
キャパシティーを超える追加では別の領域を新たに確保する点に注意。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
s1 := make([]int, 5) fmt.Printf("%#v\n", s1) //[]int{0, 0, 0, 0, 0} s2 := append(s1, 9) fmt.Printf("%#v\n", s2) //[]int{0, 0, 0, 0, 0, 9} s3 := append(s2, 8) fmt.Printf("%#v\n", s3) //[]int{0, 0, 0, 0, 0, 9, 8} fmt.Printf("%d , %d , %d\n", len(s1), len(s2), len(s3)) //5 , 6 , 7 fmt.Printf("%d , %d , %d\n", cap(s1), cap(s2), cap(s3)) //5 , 10 , 10 //別の領域を指している fmt.Printf("%t\n", &s1[0] == &s2[0]) //false //同じ領域を指している fmt.Printf("%t\n", &s2[0] == &s3[0]) //true |
s1
からs2
へappend
するとき、現キャパシティーの倍の10を新規に確保している。
s2
からs3
へappend
するときはキャパシティーが足りているので同じ領域に追加。
スライスの検索
for文などで回しながら走査するしかない。
数値でソート済みであればバイナリサーチで多少早くできる。
ただ基本的にインデックス指定できないものはmapにした方が早い。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
s1 := []int{10, 11, 12, 13, 14, 15, 16, 17, 18, 19} //一般的なfor文 for i := 0; i < len(s1); i++ { if s1[i] == 15 { println(s1[i]) } } //range iterates for _, v := range s1 { if v == 16 { println(v) } } //バイナリサーチ/二分探索 ss := sort.Search(len(s1), func(i int) bool { return s1[i] >= 17 }) if ss < len(s1) { println(s1[ss]) } |
重複なしで順番に意味がない場合はmapを使うのがベター。
1 2 3 4 5 6 7 8 9 10 |
m := map[int]struct{}{ 10: struct{}{}, 12: struct{}{}, 14: struct{}{}, 16: struct{}{}, } _, ok := m[12] fmt.Println(ok) //true _, ok = m[13] fmt.Println(ok) //false |
(struct{}
は0byteなので使っているだけでbool
でもなんでもよい)