Go言語では、配列を利用することは少なく、スライスを利用することが多い。なぜなら、スライスは「可変長の配列」のような役割を果たし、非常に便利であるからである。ここでは、Go言語のスライスについて調べていく。
スライスの中身
https://github.com/golang/go/blob/master/src/runtime/slice.go において、sliceは以下のように定義されている。
type slice struct {
array unsafe.Pointer
len int
cap int
}
すなわち、sliceは配列をラップしたものであるということがわかる。
スライスの宣言
スライスの宣言方法は配列にかなり似ているが、重要な違いとしては長さを指定しないことだ。
var x = []int{1, 2, 3}
x := []int{1, 2, 3}
var x = []int{1, 2: 4, 15} // {1, 0, 4, 15}
// 多次元スライス
var x [][]int
var x []int // スライスのゼロ値、すなわちnilが初期値になる
スライスの比較
スライス同士は==や!=で比較することができず、比較できるのはnilだけである。nilとの比較でtrueとなるのはスライスの宣言だけを行なって具体的な値を代入していない状態で、空のスライスとnilを比較してもfalseになるので注意が必要である。なお、Go言語における nil は他の言語の null とは異なり、「型がない」という状態を意味する。
var x []int
var y = []int{}
fmt.Println(x == nil) // true
fmt.Println(y == nil) // false
スライス同士を直接比較できないことは不便に思えるかもしれないが、reflect パッケージの DeepEqual 関数を利用することで比較が可能となる。
x := []int{1, 2, 3}
y := []int{1, 2, 3}
fmt.Println(reflect.DeepEqual(x, y)) // true
y[0] = 1000
fmt.Println(reflect.DeepEqual(x, y)) // false
組み込み関数
len関数とcap関数
len 関数はスライスに含まれる要素の数を、cap 関数はスライスの容量を取得するために利用される。
x := []int{1, 2, 3}
fmt.Println(len(x)) // 3
fmt.Println(cap(x)) // 3
append関数
append関数を利用して、スライスに要素を追加できる。append(x, 5, 6, 7)のように、同時に複数の値も追加することも、演算子「...」を利用してスライスを展開し、マージすることもできる。
x := []int{5, 4}
append(x, 5, 6, 7)
var y = []int{3, 2, 1}
x = append(x, y...)
Go言語において、関数に引数を渡す場合、値のコピーが作成されてから渡される。このルールは、append関数にスライスを渡す場合にも適用される。つまり、append関数に渡されるのはスライスのコピーである。そのため、append関数はスライスのコピーに値を追加し、その結果を返す。
また、append関数を使用する際、スライスが参照する配列に十分なキャパシティがあれば、値の追加は高速に行われる。しかし、キャパシティが不足している場合は、新たな配列に十分なキャパシティを確保するために時間がかかる。このプロセスには、新しい配列の作成、元の配列の値のコピー、および新しい配列へのポインタの付け替えが含まれる。配列は固定長であるため、これらの手順が必要となる。
スライスのスライスおよび、フルスライスについてはこちらの記事で
参考文献
zenn.dev