post Image
Go言語の可変長配列(slice)を使う上での注意

どのようなプログラミング言語を利用していても、可変長配列はよく使います。

Go 言語においては、 slice という可変長配列が用意されています。

slice を用意するためには make 関数を利用します。

length := 5
capacity := 10
array := make([]int, length, capacity)

make 関数の第 1 引数([]int)が型、第 2 引数(length)が 長さ 、第 3 引数(capacity)が 容量 を意味しています。

今回は、 長さ容量 のそれぞれが、どのような役割なのかを、サンプルコードをつかって確認しました。

準備

slice の長さと容量を求める関数

slice長さlen 関数、 容量cap 関数で求めることが出来ます。

  1. サンプルコード1:

    package main
    
    import "fmt"
    
    func main() {
            lenght := 5
            capacity := 10
            array := make([]int, lenght, capacity)
    
            fmt.Printf("初期の長さ          -> %d\n", len(array))
            fmt.Printf("初期の容量          -> %d\n", cap(array))
    }
    
  2. 出力結果:

    初期の長さ          -> 5
    初期の容量          -> 10
    

    今後のサンプルコードでも len 関数と cap 関数を使って様子を観測します。

slice の各要素の初期値を観測する

slice の初期値を、添字を使って確認します。

長さ分の初期値

  1. サンプルコード2:

    package main
    
    import "fmt"
    
    func main() {
    
            lenght := 5
            capacity := 10
            array := make([]int, lenght, capacity)
    
            fmt.Printf("初期の長さ          -> %d\n", len(array))
            fmt.Printf("初期の容量          -> %d\n", cap(array))
    
            fmt.Println()
            fmt.Println("長さ分の各要素")
            for i := 0; i < len(array); i++ {
                    fmt.Printf("array[%d] -> %d\n", i, array[i])
            }
    
    }
    
  2. 出力結果:

    初期の長さ          -> 5
    初期の容量          -> 10
    
    長さ分の各要素
    array[0] -> 0
    array[1] -> 0
    array[2] -> 0
    array[3] -> 0
    

    長さ分の要素には、 int 型の初期値である 0 が代入されています。

容量分の初期値

  1. サンプルコード3:

    package main
    
    import "fmt"
    
    func main() {
            lenght := 5
            capacity := 10
            array := make([]int, lenght, capacity)
    
            fmt.Printf("初期の長さ          -> %d\n", len(array))
            fmt.Printf("初期の容量          -> %d\n", cap(array))
    
            fmt.Println()
            fmt.Println("容量分の各要素")
            for i := 0; i < cap(array); i++ {
                    fmt.Printf("array[%d] -> %d\n", i, array[i])
            }
    }
    
  2. 出力結果:

    初期の長さ          -> 5
    初期の容量          -> 10
    
    容量分の各要素
    array[0] -> 0
    array[1] -> 0
    array[2] -> 0
    array[3] -> 0
    array[4] -> 0
    panic: runtime error: index out of range
    
    goroutine 1 [running]:
    main.main()
            /home/vagrant/go/src/sample/sample3.go:16 +0x49c
    
    goroutine 2 [runnable]:
    runtime.forcegchelper()
            /usr/local/go/src/runtime/proc.go:90
    runtime.goexit()
            /usr/local/go/src/runtime/asm_amd64.s:2232 +0x1
    
    goroutine 3 [runnable]:
    runtime.bgsweep()
            /usr/local/go/src/runtime/mgc0.go:82
    runtime.goexit()
            /usr/local/go/src/runtime/asm_amd64.s:2232 +0x1
    
    goroutine 4 [runnable]:
    runtime.runfinq()
            /usr/local/go/src/runtime/malloc.go:712
    runtime.goexit()
            /usr/local/go/src/runtime/asm_amd64.s:2232 +0x1
    exit status 2
    

    長さ 以上の要素に、添字を使ってアクセスした時に pannic が発生しています。

    これで添字でアクセス出来るのは、長さ分までということが分かりました。

slice を可変長配列として利用する

slice に要素を追加するには append 関数を利用します。

append 関数で要素を追加すれば、可変長配列として slice を利用できます。

容量の変化

容量に収まる様に要素を追加したときの変化

  1. サンプルコード4:

    package main
    
    import "fmt"
    
    func main() {
            lenght := 5
            capacity := 10
            array := make([]int, lenght, capacity)
    
            fmt.Printf("初期の長さ          -> %d\n", len(array))
            fmt.Printf("初期の容量          -> %d\n", cap(array))
    
            appendCount := 3
            for i := 0; i < appendCount; i++ {
                    array = append(array, i+1)
            }
    
            fmt.Println()
            fmt.Printf("append 後の長さ     -> %d\n", len(array))
            fmt.Printf("append 後の容量     -> %d\n", cap(array))
    
            fmt.Println()
            fmt.Println("append 後の長さ分の各要素")
            for i := 0; i < len(array); i++ {
                    fmt.Printf("array[%d] -> %d\n", i, array[i])
            }
    }
    
  2. 出力結果:

    初期の長さ          -> 5
    初期の容量          -> 10
    
    append 後の長さ     -> 8
    append 後の容量     -> 10
    
    append 後の長さ分の各要素
    array[0] -> 0
    array[1] -> 0
    array[2] -> 0
    array[3] -> 0
    array[4] -> 0
    array[5] -> 1
    array[6] -> 2
    array[7] -> 3
    

    append した分、初期の長さ分の後に追加されています。 容量 は変化しないことが分かりました。

容量以上になる様に要素を追加したときの変化

  1. サンプルコード5:

    package main
    
    import "fmt"
    
    func main() {
            lenght := 5
            capacity := 10
            array := make([]int, lenght, capacity)
    
            fmt.Printf("初期の長さ          -> %d\n", len(array))
            fmt.Printf("初期の容量          -> %d\n", cap(array))
    
            appendCount := 6
            for i := 0; i < appendCount; i++ {
                    array = append(array, i+1)
            }
    
            fmt.Println()
            fmt.Printf("append 後の長さ     -> %d\n", len(array))
            fmt.Printf("append 後の容量     -> %d\n", cap(array))
    
            fmt.Println()
            fmt.Println("append 後の長さ分の各要素")
            for i := 0; i < len(array); i++ {
                    fmt.Println(i, "->", array[i])
            }
    }
    
  2. 出力結果:

    初期の長さ          -> 5
    初期の容量          -> 10
    
    append 後の長さ     -> 11
    append 後の容量     -> 20
    
    append 後の長さ分の各要素
    0 -> 0
    1 -> 0
    2 -> 0
    3 -> 0
    4 -> 0
    5 -> 1
    6 -> 2
    7 -> 3
    8 -> 4
    9 -> 5
    10 -> 6
    

    append した分、初期の長さ分の後に追加されています。

    それに加え、容量が初期の 10 から 20 に増えています。

    これにより、初期に確保した容量とは関係なく、要素を追加出来ることが分かりました。

アドレスの変化

slice のアドレスを観測することにより、容量以上に要素を追加した時の振る舞いを観測します。

容量に収まる様に要素を追加したときの変化

  1. サンプルコード6:

    package main
    
    import "fmt"
    
    func main() {
            lenght := 5
            capacity := 10
            array := make([]int, lenght, capacity)
    
            fmt.Printf("初期のアドレス      -> %p\n", array)
    
            appendCount := 3
            for i := 0; i < appendCount; i++ {
                    array = append(array, i+1)
            }
    
            fmt.Printf("append 後のアドレス -> %p\n", array)
    
    }
    
  2. 出力結果:

    初期のアドレス      -> 0xc2080480a0
    append 後のアドレス -> 0xc2080480a0
    

    アドレスが同じです。

容量に収まる様に要素を追加したときの変化

  1. サンプルコード7:

    package main
    
    import "fmt"
    
    func main() {
            lenght := 5
            capacity := 10
            array := make([]int, lenght, capacity)
    
            fmt.Printf("初期のアドレス      -> %p\n", array)
    
            appendCount := 7
            for i := 0; i < appendCount; i++ {
                    array = append(array, i+1)
            }
    
            fmt.Printf("append 後のアドレス -> %p\n", array)
    
    }
    
  2. 出力結果:

    初期のアドレス      -> 0xc2080480a0
    append 後のアドレス -> 0xc20804c000
    

    アドレスが変わっています。

    容量以上の要素数を追加するときに、 append 関数内部で領域の再確保が発生し、新しい領域のアドレスが戻り値として返します。

    データは再確保された領域にコピーされます。データの量が多いと、計算コストがかかります。

容量の増え方

ついでに 容量 の増え方について観測してみます。

  1. サンプルコード8:

    package main
    
    import "fmt"
    
    func main() {
            lenght := 5
            capacity := 10
            array := make([]int, lenght, capacity)
    
            fmt.Printf("初期の長さ          -> %d\n", len(array))
            fmt.Printf("初期の容量          -> %d\n", cap(array))
    
            fmt.Println()
            appendCount := 100
            for i := 0; i < appendCount; i++ {
                    array = append(array, i+1)
                    fmt.Printf("%3d 個追加後の容量  -> %d\n", i+1, cap(array))
            }
    
    }
    
  2. 出力結果:

    初期の長さ          -> 5
    初期の容量          -> 10
    
      1 個追加後の容量  -> 10
      2 個追加後の容量  -> 10
      3 個追加後の容量  -> 10
      4 個追加後の容量  -> 10
      5 個追加後の容量  -> 10
      6 個追加後の容量  -> 20
      7 個追加後の容量  -> 20
     ...
     14 個追加後の容量  -> 20
     15 個追加後の容量  -> 20
     16 個追加後の容量  -> 40
     17 個追加後の容量  -> 40
     ...
     34 個追加後の容量  -> 40
     35 個追加後の容量  -> 40
     36 個追加後の容量  -> 80
     37 個追加後の容量  -> 80
     ...
     74 個追加後の容量  -> 80
     75 個追加後の容量  -> 80
     76 個追加後の容量  -> 160
     77 個追加後の容量  -> 160
     ...
     99 個追加後の容量  -> 160
    100 個追加後の容量  -> 160
    

    長さ容量 を超えた時に、その時の 容量 の倍の 容量 が新たに確保されることが分かりました。

今回分かったこと

append 関数だけを使って要素を追加していくときには、長さは 0 に指定しておく

append 関数は、既存の要素の最後に新しい要素を追加します。

make 関数で長さを 0 以外の値にしたとき、初期の長さ分の要素を考慮した作りする必要があります。

扱う要素数の検討が付くときには、要素で指定しておく

新しい要素の確保、データのコピーのコストを無視すれば、容量は気にしないい事がわかりました。

しかし、事前に扱う要素素が見当がつく場合は、最初に make 関数で指定しておくほうがいいです。

array := make([]int, 0, capacity)

引数で slice を渡し、関数内で append 関数を利用するときには、必ず戻り値で戻す

要素数が容量を超えたとき、アドレスが新しく割り振られる事がわかりました。

したがって、関数の引数として渡した slice が、関数内部で appende 関数が使われていた場合、気をつけないと期待した効果が得られません。

  1. サンプルコード9:

    package main
    
    import "fmt"
    
    func appendData(a []int) []int {
            appendCount := 100
            for i := 0; i < appendCount; i++ {
                    a = append(a, i+1)
            }
            return a
    }
    
    func main() {
    
            lenght := 0
            capacity := 0
            array := make([]int, lenght, capacity)
    
            fmt.Printf("初期の長さ          -> %d\n", len(array))
            fmt.Printf("初期の容量          -> %d\n", cap(array))
            fmt.Printf("初期のアドレス      -> %p\n", array)
    
            newArray := appendData(array)
    
            fmt.Println()
            fmt.Printf("呼出し後の長さ      -> %d\n", len(array))
            fmt.Printf("呼出し後の容量      -> %d\n", cap(array))
            fmt.Printf("呼出し後のアドレス  -> %p\n", array)
    
            fmt.Println()
            fmt.Printf("戻り値の長さ        -> %d\n", len(newArray))
            fmt.Printf("戻り値の容量        -> %d\n", cap(newArray))
            fmt.Printf("戻り値のアドレス    -> %p\n", newArray)
    
    }
    
  2. 出力結果:

    初期の長さ          -> 0
    初期の容量          -> 0
    初期のアドレス      -> 0x5549e0
    
    呼出し後の長さ      -> 0
    呼出し後の容量      -> 0
    呼出し後のアドレス  -> 0x5549e0
    
    戻り値の長さ        -> 100
    戻り値の容量        -> 128
    戻り値のアドレス    -> 0xc208050000
    

『 Go 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。