スライスとは、配列の要素数を可変にすることができるデータ構造のことを指します。スライスには長さと容量があり、これを使用して配列の一部にアクセスし、操作することができます。
配列とは異なり、スライスはサイズが固定されておらず、必要に応じて動的に拡大または縮小することができます。
スライスはいくつかの定義方法があります。
var slice []int
fmt.Println(slice) // []
要素数を指定せずに配列を宣言するとスライスになります。
また要素数を指定せずに初期値を代入してもスライスを定義することができます。
slice := []int{1, 2, 3, 4, 5}
fmt.Println(slice) // [1 2 3 4 5]
配列へのポインタを活用して、スライスを定義することもできます。Goでは、スライスは配列の一部分への参照です。構文[:]を使うと、基礎となる配列全体を参照するスライスを作成することができます。
array := [5]int{1, 2, 3, 4, 5}
slice := array[:]
fmt.Println(slice) // [1 2 3 4 5]
この例では、配列を作成し、[:]を使用して配列全体を参照するスライスを作成します。
[low:high]構文を使用すると、基礎となる配列の特定のサブシーケンスを参照するスライスを作成することができます。たとえば、次のようになります。
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:3]
fmt.Println(slice) // [2 3]
インデックス1番目から2番目までの要素を参照するスライスを作成します。
組み込みのmake関数を使って、スライスを作成することもできます。make関数は、長さと容量を引数として受け取ります。
slice := make([]int, 3, 10)
fmt.Println(slice) // [0 0 0]
この例では、長さ3、デフォルト容量10の整数のスライスが作成されます。スライスはゼロ値で初期化され、プログラムの出力は[0 0 0]となります。
make関数を用いてスライスを作成した場合、長さは必ず指定する必要がありますが、容量は省略することができます。容量を省略した場合は、長さと同じ容量を持ったスライスが作成されます。
slice := make([]int, 3)
fmt.Println(slice) // [0 0 0]
Goのスライスは3つの要素から構成されています。
ポインタは、スライスの基礎となる配列の最初の要素を参照するメモリアドレスです。スライスは配列の一部への参照であり、ポインタはこれを可能にするものです。
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:3]
fmt.Println(slice) // [1 2]
この例では、配列は5つの整数からなる配列で、sliceはその配列のインデックス1番目から3番目までの要素を表すスライスです。これは、スライスが配列の2から3までの要素を参照することを意味します。スライスのポインタはarray[1]のメモリアドレスになります。
Goでは、&演算子を使って変数のメモリアドレスを取得することができます。例えば、配列の要素のメモリアドレスを取得するには、次のように記述します。
array := [5]int{1, 2, 3, 4, 5}
ptr := &array[1]
fmt.Println(ptr) // 0xc0000b8008
これは、array[1]のメモリアドレスを表示します。ptrの型は*intであり、これはintへのポインタであることに注意してください。同様に、スライスの最初の要素のメモリアドレスを取得するには、次のように記述します。
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:3]
ptr := &slice[0]
fmt.Println(ptr) // 0xc0000b8008
これは、スライスの最初の要素のメモリアドレスを表示します。ptrの型は前の例と同じように*intであることに注意してください。
スライスの長さは、そのスライスに現在格納されている要素の数です。
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:3]
fmt.Println(len(slice)) // 2
この例では、配列の2要素を含むので、スライスの長さは2です。
スライスの容量は、基礎となる配列のサイズを変更する前にスライスが保持できる最大要素数のことです。これは、スライスの開始点から基礎となる配列の終了点までの距離として計算されます。
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:3]
fmt.Println(cap(slice)) // 4
この例では、配列の要素(array[1]からarray[4]までの距離)を4つまで格納できるため、スライスの容量は4となります。
別の例を挙げてみましょう。
array := [6]int{1, 2, 3, 4, 5, 6}
slice := array[1:4]
fmt.Println(slice) // [2 3 4]
この例では、arrayは6個の整数からなる配列、sliceは配列のスライスで、要素インデックス1から4までを表しています。
sliceの長さは3です。これは配列arrayから3つの要素を含んでいるためです。スライスの容量は5です。容量は、スライスの最初の要素から基礎となる配列の終端までの距離として計算され、この場合、6-1=5となります。
では、スライスを以下のようにスライスしてみましょう。
slice = slice[:2]
fmt.Println(slice) // [2 3]
fmt.Println(len(slice)) // Output: 2
fmt.Println(cap(slice)) // Output: 5
この場合、スライススライスの長さは2であり、これは配列から2つの要素を含むからです。新しいスライスの容量は依然として5で、これは基礎となる配列から最大5個の要素を保持することができるからです。
これは、スライスの容量は基礎となる配列によって決定され、スライスの長さが変わっても同じであることを実証しています。
スライスへ要素を追加するには、append関数を使用します。これはGoでスライスに要素を追加する最も一般的な方法です。append関数はスライスと1つ以上の要素を受け取り、スライスの末尾に要素を追加した新しいスライスを返します。
slice := []int{1, 2, 3}
slice = append(array, 4, 5)
fmt.Println(array) // [1 2 3 4 5]
この例では、append関数を使用してスライス配列の最後に4と5を追加し、結果を配列に代入しています。
Note: appendは要素追加後のスライスを返すため、appendの結果は変数で受け取る必要があることに注意してください。
append関数を使用してスライスに新しい要素が追加される際、スライスの容量に達した場合、新しい要素を収容するために新しい別のメモリ領域が割り当てられ、ポインタは新しいメモリ領域に確保されます。
それでは例を見てみましょう。
slice := []int{1, 2, 3}
fmt.Println("len:", len(slice)) // len: 3
fmt.Println("cap:", cap(slice)) // cap: 3
fmt.Println("Before:", &slice[0]) // Before: 0xc00001a018
slice = append(slice, 4, 5)
fmt.Println("len:", len(slice)) // len: 5
fmt.Println("cap:", cap(slice)) // cap: 6
fmt.Println("After:", &slice[0]) // After: 0xc000100000
スライスの長さとスライスの容量が等しいときまで、ポインタの値は同じになります。
Go言語のスライスのゼロ値はnilです。その場合、基礎となる配列は存在しませんが、空の配列を示します。
var s []int
fmt.Println(s, len(s), cap(s)) // [] 0 0
fmt.Println(s == nil) // true
一方、make関数で作成されたスライスのゼロ値はnilではありません。make関数は初期化された空のスライスを返します。
s := make([]int, 0)
fmt.Println(s == nil) // false