Go のスライスの操作(append・copy)

スライスに要素を追加したり、別のスライスにコピーしたりする操作は、Go のスライス操作の中でも特に頻繁に使用します。appendcopy の使い方をマスターしましょう。

append で要素を追加

append 関数は、スライスの末尾に要素を追加した新しいスライスを返します。

numbers := []int{1, 2, 3}
numbers = append(numbers, 4)
fmt.Println(numbers) // [1 2 3 4]

append は元のスライスを変更するのではなく、新しいスライスを返します。そのため、戻り値を変数に代入する必要があります。

複数の要素を一度に追加

append の第2引数以降に複数の値を渡すことで、一度に複数の要素を追加できます。

numbers := []int{1, 2, 3}
numbers = append(numbers, 4, 5, 6)
fmt.Println(numbers) // [1 2 3 4 5 6]

スライス同士を結合

... 演算子を使うと、スライス同士を結合できます。

a := []int{1, 2, 3}
b := []int{4, 5, 6}
a = append(a, b...)
fmt.Println(a) // [1 2 3 4 5 6]

b... と書くことで、スライス b の各要素を展開して append に渡しています。

容量の自動拡張

append で要素を追加する際、容量が足りなくなると Go が自動的に新しい配列を確保します。

s := make([]int, 0, 2)
fmt.Printf("len=%d cap=%d\n", len(s), cap(s)) // len=0 cap=2

s = append(s, 1, 2)
fmt.Printf("len=%d cap=%d\n", len(s), cap(s)) // len=2 cap=2

s = append(s, 3) // 容量を超える
fmt.Printf("len=%d cap=%d\n", len(s), cap(s)) // len=3 cap=4

容量が足りなくなると、通常は現在の容量の2倍程度の新しい配列が確保されます。この処理にはコストがかかるため、要素数が予測できる場合は make で十分な容量を確保しておくと効率的です。

copy でスライスをコピー

copy 関数は、スライスの要素を別のスライスにコピーします。

src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)

n := copy(dst, src)
fmt.Println(dst) // [1 2 3]
fmt.Println(n)   // 3(コピーされた要素数)

copy はコピー先とコピー元の短い方の長さ分だけコピーします。この例では dst の長さが 3 なので、3 要素がコピーされました。

copy で独立したコピーを作る

スライスを単に代入すると、背後の配列を共有してしまいます。完全に独立したコピーを作りたい場合は copy を使います。

代入(浅いコピー)

背後の配列を共有する。一方を変更すると他方にも影響する。

copy(深いコピー)

新しい配列にコピーする。元のスライスとは独立している。

original := []int{1, 2, 3}

// 浅いコピー(共有)
shallow := original
shallow[0] = 999
fmt.Println(original[0]) // 999(影響を受ける)

// 深いコピー(独立)
original = []int{1, 2, 3}
deep := make([]int, len(original))
copy(deep, original)
deep[0] = 999
fmt.Println(original[0]) // 1(影響を受けない)

意図しない変更を防ぐため、スライスを他の関数に渡す際や、長期間保持する際には copy で独立したコピーを作ることを検討してください。