【Go言語】スライス型の宣言・組み込み関数(append/len/cap/copy)|初心者向け解説

目次

スライスとは?

Go言語におけるスライスは、一連の値を保持するためのデータ構造であり、配列をより柔軟に使うために設計されたデータ型です。

配列とスライスの違い
  • 配列:固定長
  • スライス:可変長
    ⇒より効率的にメモリを管理できる

スライスの宣言

スライスの宣言方法はいくつかあるので、以降ではそれぞれの宣言方法を解説します。

varを使った宣言

スライスの宣言は[]を使って行います。
スライスの各要素は指定された型でなければならないので、宣言時に型を記述する必要があります。

var x = []int{10, 20. 30}   // [10, 20, 30](各要素はint型)

:=を使った宣言

:=を使用することで、省略した形で宣言もできます。

x := []int{10, 20. 30}   // [10, 20, 30](各要素はint型)

インデックスを指定することで、以下のように値を初期化することもできます。

var x = []int{1, 5:4, 6, 10: 100, 15}   // [1, 0, 0, 0, 0, 4, 6, 0, 0, 0, 100, 15]

値が指定されていない要素は「ゼロ値」で埋められます。

ゼロ値とは?
  • 変数を宣言した際に、明示的に初期化しなかった場合に自動的に設定されるデフォルト値のことです。
  • 各データ型におけるゼロ値と具体的な宣言方法については、以下の記事で解説しています。

makeを使った宣言

これまで登場した宣言方法では、キャパシティをあらかじめ指定することはできませんが、makeを使えば方や長さ、キャパシティ(オプション)を指定できます。

x := make([]int, 5, 10)

上記によって長さ5, キャパシティ10のintのスライスが生成されます。
長さが5のため、x[0]x[4]といった表現は有効で、いずれも値として0を有しています。

スライスのキャパシティ

キャパシティ(容量)は、スライスが格納できる要素の最大数を指します。

スライスは動的にサイズが変更可能ですが、その背後にある配列(スライスが参照している)の容量は初期化時に決まります。この容量を超える要素を追加すると、スライスは自動的に新しい配列に拡張されます。

スライスに要素を追加することで、スライスの長さ(len)とキャパシティ(cap)は以下のように変化します。

// 最初のスライス作成 (長さ0, 容量0)
x := []int{}

// 1つ目の要素を追加(長さ1, 容量1)
x = append(x, 1)
// 出力例: After adding 1 element: Length: 1 Capacity: 1

// 2つ目の要素を追加(長さ2, 容量2)
x = append(x, 2)

// 3つ目の要素を追加(長さ3, 容量4)
x = append(x, 3)

// 4つ目の要素を追加(長さ4, 容量4)
x = append(x, 4)

// 5つ目の要素を追加(長さ5, 容量8)
x = append(x, 5)

スライスが自動的にキャパシティを増やしてくれていることが分かりますが、あらかじめ最大のサイズが分かっている場合はmakeで指定しておいた方が効率的です。

スライスのゼロ値

スライスのゼロ値は nil です。
nilのスライスは、長さ(len)が0、容量(cap)も0の状態で、どの配列も参照していません。

var slice []int
fmt.Println(slice)  // nil
fmt.Println(len(slice))  // 0
fmt.Println(cap(slice))  // 0

各要素の参照方法

インデックスを指定して値の取得や変更ができます。

x := []int{10, 20. 30}
fmt.Println(x[2])   // 30

ただし、インデックスの範囲を超える要素の読み書きはできません。

x := []int{10, 20. 30}
fmt.Println(x[5])   // 0

スライスの操作

Go言語ではスライス型の変数を扱うための組み関数が用意されているので、代表的な関数を紹介します。

len

スライスの長さは、len関数を使用して取得できます。

x := []int{1, 2, 3}
len(x)   // 3
  • lenの引数:
    • 第一引数  :取得対象のスライス(ここでは x)。

cap

スライスのキャパシティ(容量)は、cap関数を使用して取得できます。

x := make([]int, 5, 10})
cap(x)   // 10
  • capの引数:
    • 第一引数  :取得対象のスライス(ここでは x)。

append

スライスの要素を増やすには、append関数を使用します。

スライスの容量が足りない場合、append は新しいスライスを作成し、元のスライスの内容を新しいスライスにコピーします。

x := []int
x = append(x, 10, 5)   // [10, 5]
  • append の引数:
    • 第一引数  :追加対象のスライス(ここでは x)。
    • 第二引数以降:追加する要素(ここでは 45)。

copy

copy はスライスの内容を別のスライスにコピーする関数です。
そのため、オリジナルのスライスとはメモリを共有しない、独立したスライスを生成できます。

x := []int{1, 2, 3, 4}   // オリジナルのスライス
y := make([]int, 4)   // 長さ4のスライス
num := copy(y, x)   // xからyにコピー
fmt.Println(y, num)   // [1, 2, 3, 4] 4
  • copy の引数:
    • 第一引数:コピー先のスライス(ここではy
    • 第二引数:コピー元のスライス(ここではx
  • copyの戻り値:
    • コピーされた要素数

コピー先のスライスの長さの方が小さい場合、スライスの長さまでコピーされます。

【補足】スライスの部分コピー

コピー元スライスのインデックスを指定することで、部分コピーが可能です。

x := []int{1, 2, 3, 4}   // オリジナルのスライス
y := make([]int, 2)   // 長さ2のスライス

copy(y, x[1:])   // x[1]からコピー
fmt.println(y)   // [2, 3]

サブスライス

[n:m]の形式で記述することで、スライスから別のスライスを生成できます。

x := []int{10, 20, 30, 40, 50}
s := x[1:4]  // インデックス1から3までの部分スライスを作成
  • 引数:
    • 第一引数  :取得対象のインデックス(ここでは 1:4)。
サブスライスのメモリ共有

スライスからサブスライスを切りだす際に、データのコピーを作成しているわけではなく、2つの変数がメモリを共有しています。

つまりスライスの要素を変更すると、その要素を共有しているすべてのスライスが影響を受けます。

x := []int{1, 2, 3, 4}   // [1, 2, 3, 4]
y := x[:2]   // [1, 2]
z := x[1:]   // [2, 3, 4]

x[1] = 20
y[0] = 10
z[1] = 30

fmt.Println(x)   // [10, 20, 30. 4]
fmt.Println(y)   // [10, 20]
fmt.Println(z)   // [20, 30, 4]

上記のように、以下のような影響が発生します。

  • xを変えるとy・zも変わります。
  • y・zを変えると、xも変わります。

また、サブスライスにappendを利用するとさらに挙動が複雑になるので、できるだけ使用しないようにしましょう。

スライスの注意点

スライス同士での比較はできない

2つのスライスが同じか否かを判定するのに、==!=を使用するとコンパイル時にエラーが発生します。

スライスと比較できるのはnilだけなので、注意が必要です。

var x []int   // xはnilに初期化される
fmt.Println(x == nil)   // true
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次