Go言語 での配列コピー方法について解説
この記事では、Go言語における配列のコピー方法を解説します。
Goの配列は値渡しで動作するため、単純な代入だけでは意図した動作が得られない場合もあります。
簡単な例を用いながら、効率的なコピー手法について説明します。
配列の基本知識
配列の定義と値渡しの特性
Go言語では、配列は固定長のデータ構造であり、各要素は同じ型で統一されています。
配列を定義する際は、サイズと要素の型を明確に指定します。
たとえば、var numbers [5]int と記述すると、整数型の要素を5個持つ配列が作成されます。
配列は値渡しが基本となるため、関数に渡す際や別の変数に代入する際、配列全体がコピーされます。
このため、一方の内容を変更しても、元の配列には影響が及びません。
また、Goではサイズが配列型の一部であるため、サイズが異なる配列同士の代入はエラーとなる点に注意してください。
配列とSliceの違い
配列とSliceは似たデータ構造ですが、いくつか大きな違いがあります。
- 配列は固定長であり、サイズが型の一部として扱われます。
 - Sliceは動的長のデータ構造で、内部には配列の参照が保持されています。
 - Sliceは参照型であるため、スライスを関数に渡すと元のデータが参照され、変更が反映される場合があります。
 
これらの違いにより、用途に応じて配列とSliceを使い分ける必要があります。
単純代入による配列コピー
単純代入の動作確認
Go言語では、配列同士の代入は単純代入でコピーが行われます。
たとえば、srcという配列の内容をdstに代入すると、srcの各要素がdstへコピーされます。
代入演算子(=)を使った場合、配列全体が値渡しとなるため、コピー元とコピー先は独立して存在します。
以下のサンプルコードは、単純代入による配列コピーの動作を確認する例です。
package main
import "fmt"
func main() {
	// 配列の宣言と初期化
	src := [5]int{1, 2, 3, 4, 5}
	// 単純代入によるコピー
	dst := src
	// srcの一部を変更
	src[0] = 100
	// 結果の表示
	fmt.Println("src:", src) // 配列srcの出力
	fmt.Println("dst:", dst) // 配列dstの出力(変更されていない)
}src: [100 2 3 4 5]
dst: [1 2 3 4 5]コピー結果の検証方法
単純代入を用いてコピーした後、コピー元とコピー先の内容が独立しているかどうかを検証できます。
上記サンプルコードでは、srcの先頭要素を変更した際に、dstには元の値が残っている点から、深いコピーが行われたことが確認できます。
検証は、変更後の各配列を標準出力やデバッガを使ってチェックする方法が一般的です。
ループ処理による手動コピー実装
ループ処理による各要素のコピー
単純代入以外にも、forループを用いて各要素を個別にコピーする方法があります。
この手法は、配列の一部の要素だけをコピーする場合や、特定の条件に基づくコピーを行いたいときに有効です。
コード例の概要
以下のサンプルコードは、forループを利用して配列srcから配列dstへ要素を手動でコピーする例です。
配列のサイズが同じである前提として、インデックスを明示的に使って各要素を代入しています。
package main
import "fmt"
func main() {
	// コピー元の配列
	src := [5]int{10, 20, 30, 40, 50}
	// コピー先の配列(デフォルト初期化)
	var dst [5]int
	// 各要素をforループでコピー
	for i := 0; i < len(src); i++ {
		dst[i] = src[i]
	}
	// 結果の表示
	fmt.Println("src:", src)
	fmt.Println("dst:", dst)
}src: [10 20 30 40 50]
dst: [10 20 30 40 50]注意すべきポイント
- 配列のサイズを正確に把握し、ループの回数を配列の長さに合わせることが大切です。
 - ループ処理によるコピーの場合、コピー元とコピー先が同じサイズである必要があります。異なるサイズの場合は、インデックスの範囲に注意する必要があります。
 - 単純代入と比べパフォーマンス面での差は通常小さいですが、柔軟性を考慮した場合に有用です。
 
Slice変換とcopy関数を利用した配列コピー
配列からSliceへの変換方法
Go言語では、配列からSliceへの変換は非常に簡単です。
変換は、配列に対して[:]のスライス記法を使うことで可能となります。
この方法により、配列全体を参照するSliceが生成され、以降、Sliceとして柔軟に操作できるようになります。
変換手順の詳細
以下のサンプルコードは、配列を[:]記法でSliceに変換する例です。
変換後のSliceは配列の一部だけを参照することも可能です。
package main
import "fmt"
func main() {
	// 配列の宣言と初期化
	arr := [5]int{5, 10, 15, 20, 25}
	// 配列全体をSliceに変換
	slice := arr[:]
	// Sliceを利用して値の変更
	slice[0] = 50
	// 結果の表示
	fmt.Println("arr:", arr)
	fmt.Println("slice:", slice)
}arr: [50 10 15 20 25]
slice: [50 10 15 20 25]copy関数を用いたコピー手法
利用例の解説
copy関数は、Slice同士のコピーを行うための標準関数です。
この関数は、コピー先のSliceとコピー元のSliceに適用され、要素数が多い方に合わせてコピーが行われます。
以下のサンプルコードは、copy関数を用いてsrcスライスの内容をdstスライスにコピーする例です。
package main
import "fmt"
func main() {
	// コピー元の配列とSlice変換
	srcArray := [5]int{100, 200, 300, 400, 500}
	srcSlice := srcArray[:]
	// コピー先のスライス(長さはコピー元と同じ)
	dstSlice := make([]int, len(srcSlice))
	// copy関数を利用してスライスをコピー
	numCopied := copy(dstSlice, srcSlice)
	// 結果の表示
	fmt.Printf("コピーされた要素数: %d\n", numCopied)
	fmt.Println("srcSlice:", srcSlice)
	fmt.Println("dstSlice:", dstSlice)
}コピーされた要素数: 5
srcSlice: [100 200 300 400 500]
dstSlice: [100 200 300 400 500]利用時の注意点
copy関数は、コピー先のスライスの容量に依存してコピーが行われるため、コピー先のサイズが十分であることを確認してください。- コピーできる要素数は、コピー元とコピー先のうち短い方の長さとなります。
 - 配列から直接
copy関数を用いる場合は、まずSliceに変換する必要がある点に注意してください。 
配列コピー時の留意点
値コピーと参照コピーの違い
配列は値型であり、代入や関数引数として渡す際に全体がコピーされます。
一方、Sliceは参照型であるため、同じ底層の配列を共有することになります。
このため、配列の単純代入は独立したコピーを作成しますが、Sliceを使ったコピーでは、コピー先とコピー元が同じデータ領域を参照するケースがあるため、予期しない動作を引き起こす可能性があります。
意図しない動作を防ぐ対策
- 配列の場合、単純代入や手動コピーにより、意図した内容が正しくコピーされているか確認することが大切です。
 - Sliceを使う際は、変換するタイミングやコピー方法を慎重に選び、必要に応じて
copy関数を利用して独立したデータを作成するようにしましょう。 - また、デバッグ時に変更前後のデータの状態を標準出力で確認することは、意図しない動作を防ぐ上で効果的です。
 - インデックスの範囲や、配列とSliceの型・サイズの整合性にも注意してください。
 
まとめ
この記事では、配列とSliceの基本知識や、単純代入、ループ処理、copy関数を用いた配列コピー方法を詳しく解説しましたでした。
配列は値渡し、Sliceは参照渡しで動作が異なるため、状況に応じた適切なコピー手法を選ぶことが重要である、と総括できます。
ぜひ実際にコードを書いて、最適なコピー方法を体験してみてください。