Go言語のスライスのcopy関数とスライス操作の基本テクニック
生徒
「先生、Go言語のスライスって便利だけど、copy関数って何をするんですか?」
先生
「copy関数は、あるスライスの中身を別のスライスにコピーするための関数です。スライス同士でデータを移し替えるときに使います。」
生徒
「スライスを操作するときに他に覚えておくといいテクニックはありますか?」
先生
「はい、スライスの基本操作やよく使うテクニックも一緒に説明しますね!」
1. copy関数とは?
copy関数は、あるスライスの中身を「そのまま別のスライスへ写す」ための標準関数です。元データは変更されないので、バックアップを作ったり、加工用に別の入れ物を用意したいときに役立ちます。ファイルを複製してから編集するイメージに近いですね。
使い方はシンプルで「copy(コピー先, コピー元)」と書きます。戻り値として「実際に写せた要素数」が返ってくるため、「ちゃんとコピーできたか」を数で確認できます。まずは最小の例で動きを見てみましょう。
package main
import "fmt"
func main() {
src := []int{1, 2, 3} // コピー元(元データ)
dst := make([]int, len(src)) // コピー先(同じ大きさを用意)
n := copy(dst, src) // dstへ中身を写す
fmt.Println("コピーした要素数:", n)
fmt.Println("src:", src) // 元データはそのまま
fmt.Println("dst:", dst) // 複製されたデータ
}
このサンプルでは、srcは一切変わらず、dstだけが複製された内容になります。「安全に複製してから作業する」――これがcopyの基本的な役割です。
2. copy関数の基本的な使い方
ここでは、実際にcopy関数を動かしてみましょう。使い方はとても簡単で、「コピー先のスライス」と「コピー元のスライス」を指定するだけです。コピー先は、あらかじめ必要な要素数ぶんのサイズを確保しておく必要があります。そうしないと、入り切らない要素はコピーされません。
src := []int{1, 2, 3} // コピー元のスライス
dst := make([]int, 3) // コピー先のスライスを3つ分確保
n := copy(dst, src) // src → dstへコピー
fmt.Println("コピーした要素数:", n)
fmt.Println("src:", src)
fmt.Println("dst:", dst)
実行すると、srcのデータがそのままdstへ複製され、戻り値として「何個コピーできたか」が表示されます。元のsrcが変わらず、dstだけが書き換わっている様子を見ると、コピーが安全に行われていることが分かります。まずはこの基本形をしっかり覚えておくと、応用でも迷わず使えます。
生徒
「先生、コピーしたあとに src を変えても dst には影響しないんですか?」
先生
「そうです。copy は中身を『複製』するので、別々のスライスになります。だからバックアップを取りたいときにも便利ですよ。」
生徒
「なるほど!ファイルをコピーして編集するみたいな感覚ですね。」
3. copy関数のポイント
- コピーされる要素数は、コピー元とコピー先のスライスの小さい方に合わせます。
- コピー先のスライスは、コピーする分の容量(要素数)を持っている必要があります。
copyは新しいスライスを作るのではなく、既存のスライスにデータを上書きします。
4. スライス操作の基本テクニック
スライスは配列のように使えますが、動的にサイズが変えられます。ここではよく使う操作を紹介します。
4-1. スライスの一部分を取り出す(スライス操作)
スライスから特定の範囲だけ取り出したいときは次のように書きます。
nums := []int{10, 20, 30, 40, 50}
sub := nums[1:4] // インデックス1から3まで取り出す(4は含まない)
fmt.Println(sub) // [20 30 40]
スライスの範囲指定は [開始:終了] で、開始は含み、終了は含みません。
4-2. スライスの先頭から指定位置まで取り出す
start := nums[:3] // インデックス0から2まで
fmt.Println(start) // [10 20 30]
4-3. スライスの指定位置から最後まで取り出す
end := nums[2:] // インデックス2から最後まで
fmt.Println(end) // [30 40 50]
5. copy関数とスライス操作を使った応用例
copy関数でスライスの一部分だけを別のスライスにコピーする例を見てみましょう。
src := []int{100, 200, 300, 400, 500}
dst := make([]int, 2)
copy(dst, src[1:3]) // srcのインデックス1から2までをdstにコピー
fmt.Println(dst) // [200 300]
ここではsrc[1:3]でスライスの一部分を取り出し、それをdstにコピーしています。
6. スライスの容量と長さについて
スライスには「長さ」と「容量」があります。長さは現在の要素数、容量は拡張できる最大要素数を表します。
nums := []int{1, 2, 3, 4, 5}
sub := nums[1:3]
fmt.Println(len(sub)) // 長さは2
fmt.Println(cap(sub)) // 容量は4
容量はスライスが元の配列からどこまで続いているかを示します。appendで容量を超えると新しい配列が作られます。
7. スライスのコピーと参照の違い
スライスは配列の一部を参照しているので、直接代入すると中身を共有します。これを避けるにはcopyで中身を複製しましょう。
a := []int{1, 2, 3}
b := a // aとbは同じ配列を参照
b[0] = 100
fmt.Println(a) // [100 2 3] 変更がaにも反映される
c := make([]int, len(a))
copy(c, a) // cはaのコピー
c[0] = 200
fmt.Println(a) // [100 2 3] 変更なし
fmt.Println(c) // [200 2 3]
まとめ
Go言語のスライスとcopy関数について学んできましたが、改めて振り返ってみると、単にコピーをするだけの関数だと思った人ほど、実際に使ってみると便利さを強く感じるはずです。スライスという仕組みは、配列を柔軟に扱うために生まれたとても便利な仕掛けであり、プログラミング初心者でも理解しやすく、それでいて実務的な開発でも頻繁に使われる重要な概念です。
今回のページでは、スライスの基本的な操作やcopy関数の使い方、参照による影響や部分的な抽出など、初心者でも無理なく理解できるように、段階を踏みながら丁寧に整理してきました。
スライスは内部で配列を参照しているため、別の変数に代入しても同じ配列を共有するという点がとても大切です。
初心者でも覚えやすいルールとして、「copyはコピー先のスライスに上書きする」という点がとても重要です。新しくスライスを作るわけではなく、あらかじめ用意しておいたスライスに中身を写すという仕組みが基本になります。
さらに、スライスの基本操作では、一部分だけを切り出して新しいスライスを作ることができました。この機能は配列にはない柔軟な要素であり、データの中から必要な部分だけを取り出したいときにとても重宝します。
長さは「現在入っている要素の数」、容量は「今後増やせる余裕」を示していました。
それでは、最後に簡単なサンプルプログラムを使いながら、今回学んだ内容をあらためて確認してみましょう。
スライスとcopy関数を使った確認用サンプル
package main
import "fmt"
func main() {
prices := []int{150, 200, 300, 400}
backup := make([]int, len(prices))
n := copy(backup, prices)
fmt.Println("コピーした要素数:", n)
fmt.Println("元のスライス:", prices)
fmt.Println("コピーしたスライス:", backup)
prices[0] = 999
fmt.Println("元を変更:", prices)
fmt.Println("コピーは変化なし:", backup)
}
ここでは、商品価格のリストをコピーし、元のスライスを変更してもコピー先が影響を受けないことを確認しています。このような処理は、データ分析や一時的な計算のときにとても役に立ちます。実際に数字を変えて動かすと、違いがより分かりやすくなるでしょう。 また、スライスの一部分を取り出すサンプルも見てみましょう。
一部分だけをコピーするサンプル
package main
import "fmt"
func main() {
numbers := []int{10, 20, 30, 40, 50}
part := make([]int, 2)
copy(part, numbers[1:3])
fmt.Println("元のスライス:", numbers)
fmt.Println("コピーした一部分:", part)
}
このように、スライスの一部分を別のスライスにコピーすることで、必要なデータだけをきれいに抽出できます。とても単純な機能ですが、経験を積むほど、この操作の便利さが分かってきます。
生徒
「先生、今日の内容を振り返ってみると、思ったよりもスライスって便利なんですね。配列と違って長さを気にせず使える感じがすごくありがたいです。」
先生
「そうですね。特に、現実のデータは数が変わりやすいので、スライスの柔軟性はとても役に立ちますよ。copy関数を使えば、安全に複製して、元のデータを壊さずに加工できます。」
生徒
「直接代入すると同じところを参照してしまうというところが、最初は怖いと思ったけど、copyを使えば安心ですね。」
先生
「そのとおりです。プログラムは目に見えないところでデータが動いているので、参照の仕組みを知らないとトラブルになりがちですが、ちゃんと理解していれば何も心配いりません。」
生徒
「部分的なコピーもすごく便利ですね。必要なところだけ抜き出して、新しい処理をすることが簡単にできそうです。」
先生
「はい。今回の内容は、どのレベルの開発でも役に立つ大切な考え方ですから、ぜひしっかり覚えておいてください。」