Go言語(golang) スライスと配列の使い方
Go言語にはslice(スライス)とarray(配列)があります。
以下、スライスと配列それぞれの基本的な使い方や違いについて
目次
スライスの初期化
var s []int var s []int = []int{10, 20, 30} var s = []int{10, 20, 30} s := []int{} s := []int{10, 20, 30} // 多重スライス var s [][]int s := [][]int{ {10, 20, 30}, {10, 20, 30}, }
make を使った初期化
make の引数は make([型/type], [長さ/len], [容量/cap])
長さ = len = length = レングス
容量 = cap = capacity = キャパシティ
- 長さ が 0 、 容量 が 10 で初期化
s := make([]int, 0, 10) fmt.Printf("%#v, len: %d, cap: %d\n", s, len(s), cap(s)) // []int{}, len: 0, cap: 10
- 長さに 1 以上の数値を指定すると、その分がゼロ値(int型の場合は0)で初期化される
s := make([]int, 3, 10) fmt.Printf("%#v, len: %d, cap: %d\n", s, len(s), cap(s)) // []int{0, 0, 0}, len: 3, cap: 10
package main import "fmt" func main() { // 以下は s := []int{0, 0, 0} で初期化したことと同じ s := make([]int, 3) fmt.Println(s, len(s), cap(s)) // [0 0 0] 3 3 // 当然、値を追加すると末尾に追加される s = append(s, 100) fmt.Println(s, len(s), cap(s)) // [0 0 0 100] 4 6 }
- 容量に長さより小さい数値を指定した場合はエラー
s := make([]int, 5, 3) // error len larger than cap in make([]int)
- 容量は省略可能でその場合は長さと同じになる
s = make([]int, 3) fmt.Printf("%#v, len: %d, cap: %d\n", s, len(s), cap(s)) // []int{0, 0, 0}, len: 3, cap: 3
- 容量(cap)
以下のように、長さは追加した要素分増えていくのに対して、容量はそれを超えたときに倍の容量が自動で確保される
package main import "fmt" func main() { s := make([]int, 0, 5) for i := 1; i >= 11; i++ { s = append(s, i) fmt.Printf("%2d, len: %2d, cap: %2d\n", i, len(s), cap(s)) // 1, len: 1, cap: 5 // 2, len: 2, cap: 5 // 3, len: 3, cap: 5 // 4, len: 4, cap: 5 // 5, len: 5, cap: 5 // 6, len: 6, cap: 10 // 7, len: 7, cap: 10 // 8, len: 8, cap: 10 // 9, len: 9, cap: 10 // 10, len: 10, cap: 10 // 11, len: 11, cap: 20 } }
makeで長さと容量を指定するメリット
スライスに値を100個入れたベンチマーク
package main import "testing" const N = 100 // make を使わない func BenchmarkA(b *testing.B) { for i := 0; i < b.N; i++ { var a []int for j := 0; j < N; j++ { a = append(a, j) } } } // make を使って 0 を指定する func BenchmarkB(b *testing.B) { for i := 0; i < b.N; i++ { a := make([]int, 0, 0) for j := 0; j < N; j++ { a = append(a, j) } } } // make を使って容量のみ指定する func BenchmarkC(b *testing.B) { for i := 0; i < b.N; i++ { a := make([]int, 0, N) for j := 0; j < N; j++ { a = append(a, j) } } } // make を使って長さ、容量を指定する func BenchmarkD(b *testing.B) { for i := 0; i < b.N; i++ { a := make([]int, N, N) for j := 0; j < N; j++ { a[j] = j } } }
ベンチマーク結果
実行回数 一回の実行時間 メモリサイズ メモリ再割当て BenchmarkA-2 1486426 795 ns/op 2040 B/op 8 allocs/op BenchmarkB-2 1534741 790 ns/op 2040 B/op 8 allocs/op BenchmarkC-2 17227060 70.1 ns/op 0 B/op 0 allocs/op BenchmarkD-2 30811446 39.3 ns/op 0 B/op 0 allocs/op
make を使って長さと容量を指定した場合(D)が最も処理速度が速く、その次に make を使って容量のみ(C)。他は同じで A と B は遅い
予め数がわかっている場合は make を使って長さと容量を指定して初期化したほうがいい。
値の変更
package main import "fmt" func main() { s := []int{10, 20, 30} fmt.Println(s) // [10 20 30] s[0] = 100 // 0 番目の値 10 を 100 に変更 // 1 番目の値 20 を 200 に変更 // 2 番目の値 30 を 300 に変更 s[1], s[2] = 200, 300 fmt.Println(s) // [100 200 300] }
値の取得
package main import "fmt" func main() { s := []string{"A", "B", "C", "D", "E"} fmt.Println(s[0]) // 0 番目の値 A を取得 fmt.Println(s[1]) // 1 番目の値 B を取得 fmt.Println(s[2]) // 2 番目の値 C を取得 fmt.Println(s[3]) // 3 番目の値 D を取得 fmt.Println(s[4]) // 4 番目の値 E を取得 // 範囲を指定して取得する。[start:end] fmt.Println(s[1:4]) // 1 番目から 3 番目まで [B C D] fmt.Println(s[2:]) // 2 番目から 最後(4番目まで) [C D E] fmt.Println(s[:3]) // 0 番目から 2 番目まで [A B C] fmt.Println(s[:]) // 最初から最後まで [A B C D E] // end に start 位置より小さい数を指定した場合はエラー fmt.Println(s[2:1]) // invalid slice index: 2 > 1 }
- 先頭の値を取り出して残りを元のスライスに入れる
package main import "fmt" func main() { s := []string{"A", "B", "C", "D", "E"} v, s := s[0], s[1:] fmt.Println(v, s) // A [B C D E] }
- 末尾の値を取り出して残りを元のスライスに入れる
package main import "fmt" func main() { s := []string{"A", "B", "C", "D", "E"} v, s := s[len(s)-1], s[:len(s)-1] fmt.Println(v, s) // E [A B C D] }
- 先頭、末尾以外の値を取り出して残りを元のスライスに入れる
package main import "fmt" func main() { s := []string{"A", "B", "C", "D", "E"} // 0 から数えて 2 番目 C を抜き出す v := s[2] // それ以外を元のスライスに入れる s = append(s[:2], s[3:]...) fmt.Println(v, s) // C [A B D E] }
値の削除
package main import "fmt" func main() { s := []string{"A", "B", "C", "D", "E"} // 先頭を削除 s = s[1:] fmt.Println(s) // [B C D E] // 末尾を削除 s = s[:len(s)-1] fmt.Println(s) // [B C D] // 途中の値を削除 s = append(s[:1], s[2:]...) fmt.Println(s) // [B D] }
容量を指定して削除
- 通常、削除するとキャパシティは、そのスライスの容量になる。以下の場合は5
s := []string{"A", "B", "C", "D", "E"} s = s[:1] fmt.Println(s, len(s), cap(s)) // [A] 1 5
三番目にキャパシティを指定できる
s := []string{"A", "B", "C", "D", "E"} // 元の容量(ここでは5)より大きい数値を指定するとエラー s = s[:3:3] fmt.Println(s, len(s), cap(s)) // [A B C] 3 3
スライス自体の削除
- nilを代入することで全て削除できる
s := []int{10, 20, 30} s = nil fmt.Println(s, len(s), cap(s)) // [] 0 0
- 確保しているキャパシティはそのままで削除する
s := []int{10, 20, 30} s = s[:0] fmt.Println(s, len(s), cap(s)) // [] 0 3
値の追加
- 一つまたは複数の値を末尾に追加する
package main import "fmt" func main() { s := make([]string, 0, 5) fmt.Println(s) // [] // 値を追加 s = append(s, "A") fmt.Println(s) // [A] // 複数の値を追加 s = append(s, "B", "C") fmt.Println(s) // [A B C] // スライスで追加 s = append(s, []string{"D", "E"}...) fmt.Println(s) // [A B C D E] }
- マップと違ってスライスは初期化しなくても追加できる
map
の場合は初期化しないとエラーになるけどスライスは初期化しなくてもエラーにならない
var s []string fmt.Println(s == nil) // true s = append(s, "A") fmt.Println(s) // [A]
- 値を先頭に追加する
package main import "fmt" func main() { s := []string{"A", "B", "C", "D", "E"} s = append([]string{"Hello", "World"}, s...) fmt.Println(s) // [Hello World A B C D E] }
- 値を挿入する
package main import "fmt" func main() { s := []string{"A", "B", "C", "D", "E"} s = append(s[:2], append([]string{"Hello", "World"}, s[2:]...)...) fmt.Println(s) // [A B Hello World C D E] }
スライスのコピー
- 単純に別の変数に代入するだけだと、シャローコピーなので値のアドレスは同じ
s := []int{10, 20, 30} // 別の変数に入れる _s := s // 当然、変数自体のアドレスは違う fmt.Printf("%p, %p\n", &_s, &s) // 0xc00000c080, 0xc00000c060 // 値のアドレスは同じなので、どちらかの値を変更するともう片方も変更される fmt.Printf("%p, %p\n", &_s[0], &s[0]) // 0xc000014420, 0xc000014420 _s[0] = 1000 fmt.Println(s[0]) // 1000
- copy を使ったディープコピー
s := []int{10, 20, 30} _s := make([]int, len(s)) copy(_s, s) fmt.Printf("%p, %p\n", &_s[0], &s[0]) // 0xc0000a0020, 0xc0000a0000 _s[0] = 1000 fmt.Println(s[0]) // 10
- appendでもディープコピーできる
s := []int{10, 20, 30} var _s []int _s = append(_s, s...) fmt.Printf("%p, %p\n", &_s[0], &s[0]) // 0xc000014440, 0xc000014420
配列 (スライスとは異なる)
- 配列の初期化
[3]int
のように長さを指定するか [...]int
で初期化できる
var a [3]int fmt.Println(a, len(a), cap(a)) // [0 0 0] 3 3 var a = [2]int{} fmt.Println(a, len(a), cap(a)) // [0 0] 2 2 var a = [4]int{10} fmt.Println(a, len(a), cap(a)) // [10 0 0 0] 4 4 a := [...]int{10, 20, 30} fmt.Println(a, len(a), cap(a)) // [10 20 30] 3 3 a := [3]int{10, 20, 30} fmt.Println(a, len(a), cap(a)) // [10 20 30] 3 3
- 追加するとエラー
値の変更はできるけど配列は固定長なので追加するとエラーになる
a := [3]int{10, 20, 30} a = append(a, 40) // エラー
- 配列のアドレス
package main import "fmt" func main() { // スライス s := []int{10, 20, 30} _s := s fmt.Println(&s[0], &_s[0]) // 0xc000096000 0xc000096000 _s[0] = 1000 // スライスの場合はアドレスが同じなので値が変わる fmt.Println(s[0], _s[0]) // 1000 1000 // 配列 a := [3]int{10, 20, 30} _a := a fmt.Println(&a[0], &_a[0]) // 0xc000014420 0xc000014440 _a[0] = 1000 // 配列の場合はアドレスが違うので変更されない fmt.Println(a[0], _a[0]) // 10 1000 }
- 配列のスライス化
package main import ( "fmt" ) func ArraySet1000(a [3]int) { a[0] = 1000 } func SliceSet1000(s []int) { s[0] = 1000 } func main() { a := [3]int{10, 20, 30} ArraySet1000(a) fmt.Println(a[0]) // 10 // 値は変わらない s := []int{10, 20, 30} SliceSet1000(s) fmt.Println(s[0]) // 1000 // スライスだと値は変わる // 配列は渡せない。エラー SliceSet1000(a) // スライス化して渡す SliceSet1000(a[:]) fmt.Println(a[0]) // 1000 // 値が変わる // スライス化すると当然appendできる s = a[:] s = append(s, 40) fmt.Println(s) // [1000 20 30 40] }