copy関数でスライスをコピーして使用してみよう!

Go言語
この記事は約9分で読めます。
Go初学者
Go初学者

あれ?スライスを普段通りコピーして値を更新したら、コピー元の値も変わってしまったよ…

もりぴ
もりぴ

スライスなどの参照型のコピーは注意が必要ですよ!

この記事ではスライスのコピー操作について理解できます!
  • 基本型とスライス(参照型)の値のコピーの違いについて
  • スライスをコピーしてみる(copy関数)

基本型(int型やstring型など)の変数をコピーするのとは違い、参照型(スライスmap)の変数のコピーは基本型のコピー方法とは違います。

今回は、基本型の変数のコピーを復習し、参照型(特にスライス)のコピーについてお伝えしていきます。

もりぴ
もりぴ

スライスのコピーについて知りたい方は、ここからジャンプ!

もりぴ
この記事を書いた人

XHTML1.0時代にHTML&CSSを勉強した経験あり。無趣味だった私が2020年5月からプログラミング学習を開始し現在も挫折せずに趣味で学習を楽しんでいる51歳。プログラミングの楽しさをブログを通してお伝えしていきます。

もりぴをフォローする

基本型の変数をコピーしてみる

スライスのコピー操作に入る前に、基本型(今回はint型とint型の配列)のコピーを復習します。

そして、コピー先とコピー元の値の変化も確認していきましょう。

int型の変数をコピーして動きを確認する

// int型の変数をコピー
package main

import "fmt"

func main() {
    // int型の変数を宣言
    var a int = 10

    // 変数aをコピー
    a2 := a

    // それぞれの値を確認
    fmt.Println("変数aは", a, "です。")
    fmt.Println("変数a2は", a2, "です。")

    // 変数a2の値を書き換える
    a2 = 1000

    // それぞれの値を確認
    fmt.Println("変数aは", a, "です。")
    fmt.Println("変数a2は", a2, "です。")
}
// 出力結果
$ go run main.go 
変数aは 10 です。
変数a2は 10 です。
変数aは 10 です。
変数a2は 1000 です。

int型の値が入った 変数a をコピーされた 変数a2 のそれぞれ値が 10 代入されています。

そして、変数a2 の値を 1000 に変更しても、変数a の値には影響していないことが確認できます。

int型の配列をコピーして動きを確認する

では、配列ではどうでしょうか。

// int型の配列をコピー
package main

import "fmt"

func main() {
    // int型の配列を宣言
    var b [3]int = [3]int{1, 2, 3}

    // 配列bをコピー
    b2 := b

    // それぞれの値を確認
    fmt.Println("配列bは", b, "です。")
    fmt.Println("配列b2は", b2, "です。")

    // 変数a2の値を書き換える
    b2[0] = 1000

    // それぞれの値を確認
    fmt.Println("配列bは", b, "です。")
    fmt.Println("配列b2は", b2, "です。")
}
// 出力結果
$ go run main.go 
配列bは [1 2 3] です。
配列b2は [1 2 3] です。
配列bは [1 2 3] です。
配列b2は [1000 2 3] です。

配列に関しても、配列b2[0] の値を 1000 に変更しても、配列b には影響していないことが確認できます。

これは、基本型は値渡し値をメモリ上にコピーしているからです。

スライスをコピーする

スライスのコピーについて、ゆっくりとお伝えしていきます。

スライスを基本型と同じようにコピーしてみる

スライスを基本型と同様にコピーしたら、どういう動きになるか確認してみましょう。

// スライスを基本型と同じようにコピー
package main

import "fmt"

func main() {
    // スライスを宣言
    sl := []int{100, 200}

    // sl2にslをコピー
    sl2 := sl

    // sl2[0]の値を変更する
    sl2[0] = 1000

    // それぞれの値を確認
    fmt.Println("sl =", sl)
    fmt.Println("sl2 = ", sl2)
}
// 出力結果
$ go run main.go 
sl = [1000 200]
sl2 =  [1000 200]
Go初学者
Go初学者

あれ? sl2[0] に値を変えたのに、sl[0] も変わってますね。

もりぴ
もりぴ

スライスは参照型なので、アドレスの値をコピーしているからです。

参照型では値をコピーしているのではなく、アドレスをコピーしている参照渡し)のです。

では、slsl2 のアドレスを確認してみましょう。

// スライスを基本型と同じようにコピー
package main

import "fmt"

func main() {
    // スライスを宣言
    sl := []int{100, 200}

    // sl2にslをコピー
    sl2 := sl

    // sl2[0]の値を変更する
    sl2[0] = 1000

    // それぞれの値を確認
    fmt.Println("sl =", sl)
    fmt.Println("sl2 = ", sl2)

    // アドレスを確認
    fmt.Printf("sl = %p\n", sl)
    fmt.Printf("sl2 = %p\n", sl2)
}
// 出力結果
$ go run main.go 
sl = [1000 200]
sl2 =  [1000 200]
sl = 0xc000014100
sl2 = 0xc000014100

slsl2アドレスが一緒です。

スライスを基本型のようにコピーすると値がコピーされるのではなく、アドレスがコピーされるので sl2[0] = 1000 のような操作をすると直接アドレスの値が変更され sl[0] にも影響が出ます。

copy関数を使用してスライスをコピーする

スライスをコピーするには、make関数copy関数 を使用します。

copy関数は下記のように記述します。

コピーに成功した要素数を表示する変数 := copy(コピー先, コピー元)

では、実際にプログラムで確認していきましょう。

// copy関数を使用してスライスをコピー
package main

import "fmt"

func main() {
    // スライスを宣言する
    sl3 := []int{1, 2, 3, 4, 5}
    // sl3を出力する
    fmt.Println(sl3)

    // スライスのメモリ領域を確保する
    sl4 := make([]int, 5, 5)
    // sl4の現状を出力する
    fmt.Println(sl4)

    // スライスはcopy関数を使用する
    // 変数cはコピーに成功した要素数が格納される
    c := copy(sl4, sl3)
    fmt.Println(c, sl4)

    // アドレスを確認
    fmt.Printf("sl3 = %p\n", sl3)
    fmt.Printf("sl4 = %p\n", sl4)

    // sl4[0]の値を変更する
    sl4[0] = 1000

    // それぞれのスライスを出力
    fmt.Println(sl3)
    fmt.Println(sl4)
}
// 出力結果
$ go run main.go 
[1 2 3 4 5]
[0 0 0 0 0]  // メモリにスライスの領域を確保
5 [1 2 3 4 5]  // コピーに成功した値とコピーされたスライス
sl3 = 0xc00001a2d0 // アドレスの違いを確認できる
sl4 = 0xc00001a300
[1 2 3 4 5]
[1000 2 3 4 5]  // sl4[0]の値が変更されている

make関数 でスライスのメモリに領域を確保してから、コピー元の sl3 のスライスの要素が sl4 にきちんとコピーされ、アドレスの違いも確認できます。

そして、sl4[0] の値を変更しても sl3 には影響していないことが確認できました。

要素数の違うスライスをコピーする

先程のスライスのコピーは、どちらも要素数が同じでした。

では、要素数の違うスライスをコピーすると、どういう動きになるのか確認してみましょう。

// 要素数の違うスライスをコピー
package main

import "fmt"

func main() {
    // スライスを宣言する
    sl5 := []int{10, 20, 30, 40, 50}
    // sl5を出力する
    fmt.Println(sl5)

    // スライスのメモリ領域を確保する
    sl6 := make([]int, 3, 5)
    // sl6の現状を出力する
    fmt.Println(sl6)

    // スライスはcopy関数を使用する
    // 変数cはコピーに成功した要素数が格納される
    d := copy(sl6, sl5)
    fmt.Println(d, sl6)
}
// 出力結果
$ go run main.go 
[10 20 30 40 50]
[0 0 0]
3 [10 20 30]

make関数 で要素数を 3 で指定したので、スライスのコピーに成功したのは 3 でした。

この場合、左から(インデックス番号0から)3つの要素がコピーされます。

【まとめ】スライスをコピーする(copy関数)

今回は、スライスのコピーについてお伝えしてきました。

それでは、まとめにはいります。

この記事で覚えておくべきことのまとめ!
  • スライスなどの参照型は基本型のようにコピーできない
  • スライスをコピーには make関数 でメモリ領域を確保してから copy関数 でコピーする
  • copy関数 の記述方法は…
    コピーに成功した要素数を表示する変数 := copy(コピー先, コピー元)
  • 要素数の違うスライスのコピーは make関数 で指定した要素数分がコピーされる(インデックス番号0の要素から)

以上です。

値渡しと参照渡しが理解できていれば、スライスのコピーに関しては問題なく理解できたと思います。

実際にプログラムのコードを書いて、いろんなパターンの動きを確かめてみましょう。

comment

タイトルとURLをコピーしました