Go言語のスライスの基本について解説します。
- Go言語のスライスの基本がわかる
- 配列との違いがわかる
- Go 1.17
定義方法
定義方法は主に3つあります。
var
を使うケースと:=
(省略変数宣言)で説明します。
makeを使わずに定義
配列との定義の違いのポイントは、要素数を指定するかしないかです。
var slice1 []int
fmt.Printf("%v", slice1)
// 出力結果
[]
var slice2 = []int{}
fmt.Printf("%v", slice2)
// 出力結果
[]
slice3 := []int{1, 2, 3, 4, 5}
fmt.Printf("%v", slice3)
// 出力結果
[1 2 3 4 5]
makeを使って定義
make
は、make([]T, len, cap)
で定義できます。
lenは必須で、要素数を定義します。
capはオプションで、スライスの容量を定義します。渡さない場合は、lenと同じになります。
capを指定することで、メモリ領域を確保することができます。
領域を指定せずにスライスを拡張した場合は、元のスライスが格納していたデータを新しい領域に丸ごとコピーします。この処理にはコストがかかります。
つまり拡張される要素数がわかる場合は、capを指定することでパフォーマンスよく実行できるので、実装には理解と注意が必要です。
var slice4 = make([]int, 5)
fmt.Printf("%v", slice4)
// 出力結果
[0 0 0 0 0]
slice5 := make([]int, 3, 5)
fmt.Printf("%v", slice5)
// 出力結果
[0, 0, 0]
// 組み込み関数のlenとcapで要素数と容量を取得できる
fmt.Println(len(slice5)) // 3
fmt.Println(cap(slice5)) // 5
簡易スライス式を使って定義
配列やスライスをもとに新しいスライスを生成するSimple slice expressions
という機能がありそれを使います。
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:3]
fmt.Printf("%v", slice)
// 出力結果
[2 3]
スライス式には様々な記述方法があります。
arr := [5]int{1, 2, 3, 4, 5}
fmt.Printf("%v", arr[0:3]) // [1 2 3] インデックス0から3まで
fmt.Printf("%v", arr[3:]) // [4 5] インデックス3から全て
fmt.Printf("%v", arr[:3]) // [1 2 3] インデックス0から3まで
fmt.Printf("%v", arr[:]) // [1 2 3 4 5] 全て
スライスの要素の型と初期値
定義の例では、int
型を使っていましたが、要素の型はGoで定義された全ての型を定義できます。
初期値は、それぞれの型の初期値が入ります。
int => 0
string => “”
bool => false
// 例
slice1 := make([]int, 3) // [0, 0, 0]
slice2 := make([]uint, 3) // [0, 0, 0]
slice3 := make([]bool, 3) // [false, false, false]
slice4 := make([]float64, 3) // [0, 0, 0]
slice5 := make([]string, 3) // ["", "", ""]
要素へのアクセス
要素へのアクセスは、インデックスを使ってアクセスします。
slice := []int{1, 2, 3, 4, 5}
fmt.Printf("%v", slice[0])
// 出力結果
1
インデックスの範囲が間違っているとエラーが発生します。
slice := []int{1, 2, 3, 4, 5}
fmt.Printf("%v", slice[5])
// 出力結果
index out of range
要素への代入
インデックスで代入したい要素にアクセスして代入します。
slice := []int{1, 2, 3, 4, 5}
slice[0] = 6
slice[1] = 7
fmt.Printf("%v", slice)
// 出力結果
[6 7 3 4 5]
要素の型に合わない型の要素を代入しようとすると、エラーが発生します。
要素を追加
スライスは可変長なので、要素を追加することができます。
append
を使って要素を追加
append
はappend(s S, x...T)
と定義されているので、第二引数にはスライスの要素型の要素を任意の数だけ指定できます。
slice := []int{1, 2}
slice = append(slice, 3, 4)
fmt.Println(slice)
// 出力結果
[1 2 3 4]
可変長引数の定義と渡し方
Goではよく...int
のような記述が使われます。
これは、内部的にint型のスライスにまとめる
動作がされます。
スライスを引数に渡す場合は、slice...
のように渡します。
func sum(s ...int) int {
total := 0
for _, value := range s {
total += value
}
return total
}
fmt.Println(sum(1, 2, 3)) // 6
slice := []int{1, 2, 3}
fmt.Println(sum(slice...)) // 6
参照型のスライスと配列の動作の違い
配列と配列・スライスとスライスを代入したケースを例に違いを解説します。
配列の場合
配列の場合は、値そのものが新しい領域にコピーされるので、a
を変更してもb
に影響がありません。
a := [3]int{1, 2, 3}
b := [3]int{4, 5, 6}
a = b
fmt.Printf("%v", a) // [4, 5, 6]
a[0] = 7
a[1] = 8
a[2] = 9
fmt.Printf("%v", a) // [7, 8, 9]
fmt.Printf("%v", b) // [4, 5, 6]
スライスの場合
配列の場合は、メモリ領域が参照されるので、a
を変更するとb
も変更されます。
a := []int{1, 2, 3}
b := []int{4, 5, 6}
a = b
fmt.Printf("%v", a) // [4, 5, 6]
a[0] = 7
a[1] = 8
a[2] = 9
fmt.Printf("%v", a) // [7, 8, 9]
fmt.Printf("%v", b) // [7, 8, 9]
モグモグさん
この違いはとても重要なので、理解しておきましょう。
スライスと配列の違い
ここまで解説してきた通り、スライスの大きな特徴は下記2つです。
- サイズが可変(可変長)
- メモリ領域を参照
配列は、参照型ではないのでスライスの特徴の逆になります。
- サイズが固定(固定長)
- 値を新しくコピー
まとめ
Go言語の配列の基本について解説しました。
特に、配列はスライスと似ている構造なので、違いを理解して使っていくことが重要です。