スライス

スライスは、同じ型のデータを並べて格納したもの(可変長)です。

  • スライス内のひとつひとつのデータを『要素』と呼ぶ
  • 要素を識別する通し番号を『添字』と呼ぶ
  • 添字は0から順に採番される
  • スライスの長さは可変
  • スライスの型にはスライスの長さを含まない(例:[]int
  • 配列の長さとは別に容量(キャパシティ)を持つ
  • スライス内部では配列を持つ

スライスの宣言と初期化

これはint型の要素を格納できるスライスを宣言したものです。

// 形式: var 変数名 []要素の型
var a []int

スライスの宣言と代入を一緒にできます。

// 形式: var 変数名 = []要素の型{初期値1, 初期値2...}
var a = []int{3, 1}
// varの代わりに:=を使用
b := []int{3, 1}

初期値が決まっていない場合は、make関数を使ったスライスの宣言と代入もできます。 引数にはmake(型, 長さ, 容量)を指定します。容量は省略できます。代入される値は各要素のゼロ値です。 容量については、この後で説明します。

a := make([]int, 2)    // [0 0] 長さ:2、容量:2
b := make([]int, 2, 3) // [0 0] 長さ:2、容量:3

スライスのゼロ値

スライスのゼロ値はnilです。

var a []int // a == nil

『未代入のスライス』と『長さゼロのスライス』には違いがあります。 違いは次の通りです。

  • 未代入のスライス:スライスそのものがない(nilである)
  • 長さゼロのスライス:スライスはあるが、スライスの要素がない(nilでない)
var a []int         // a == nil
b := make([]int, 0) // b != nil

スライスの操作

配列との共通点は次の通りです。

  • スライスの要素を取得/設定するには[]の中に添字を指定
  • スライスの長さはlen関数で確認できる

配列と異なる点は次の通りです。

  • スライスの容量はcap関数で確認できる
  • スライスの長さを増やすにはappend関数を使用
  • append関数は長さや容量が足りなくなると自動で追加される
  • スライスを別の変数に代入すると同一のスライスが参照される
  • スライス式で配列やスライスの一部をスライスとして抽出できる

対応するサンプルコードを次に示します。

a := make([]int, 2)
// スライスの要素を設定
a[0] = 3
a[1] = 1
// スライスの要素を取得
fmt.Println(a[0]) // 3
fmt.Println(a[1]) // 1
// スライスの長さを取得
fmt.Println(len(a)) // 2
// スライスの容量を取得
fmt.Println(cap(a)) // 2
// スイラスの要素を追加
// 長さと容量は自動で追加される
a = append(a, 5, 6)
fmt.Println(a, len(a), cap(a)) // [3 1 5 6] 4 4
// スライス式(配列やスライスの一部をスライスとして扱う)
fmt.Println(a[1:3])   // [1 5]
fmt.Println(a[:3])    // [3 1 5]
fmt.Println(a[3:])    // [6]
fmt.Println(a[:])     // [3 1 5 6]
fmt.Println(a[1:3:3]) // [1 5]
// スライス式でスライスの要素を削除する(添字1の要素を削除)
fmt.Println(append(a[:1], a[2:]...)) // [3 5 6]

スライスの容量

スライスの容量とは何でしょうか。 容量を扱う背景を知ると理解しやすいでしょう。

  • スライスは可変長のため、長さが変わるとデータの記憶領域を確保し直す必要がある
  • 毎回確保し直すのは非効率なので、あらかじめスライスは実際の長さよりも大きな領域を確保する
  • これを容量と呼ぶ

つまり、スライスの容量は、スライスを効率よく扱うために確保された記憶領域のサイズのことです。

スライスをコピーする方法

スライスを別の変数に代入すると、代入元と代入先で同一のスライスを扱う場合があります(代入後、appendによってスライスの容量が変更されると、別物のスライスになることがあります)。

次のサンプルの通り、変数aを変数bに代入して変数bでスライスを操作するとその結果が変数aにも反映されていることが確認できます。

このように同一の実体を扱うのではなく、各要素をコピーした別物のスライスを作るにはcopy関数を使います。 次のサンプルの変数cは変数aをコピーして別物のスライスを作成しています。

a := []int{3, 1}

// スライスの代入(参照コピー)
var b []int
b = a
// bの操作がaに影響する(aとbは同一)
b[0] = 2
fmt.Println(a) // [2 1]
fmt.Println(b) // [2 1]

// スライスの各要素の値をコピー
c := make([]int, len(a))
copy(c, a)
// cの操作がaに影響しない(aとcは別物)
c[0] = 5
fmt.Println(a) // [2 1]
fmt.Println(c) // [5 1]