Go言語のキャッチアップ その4 ~スライスのmakeとappend~
make関数
スライスを作成するときに、長さ(len), 容量(cap)を指定できる関数
numbers := make([]int, 3, 5)
これは、
[]int: 整数を扱うスライスを作成する- 3: 長さ(len)は3
- 5: 容量(cap)は5
という意味。 容量の入力を省略することができる。
numbers := make([]int, 3)
// make関数を使ったスライスの作成
package main
import "fmt"
func main() {
numbers := make([]int, 3)
fmt.Println(numbers) // [0 0 0]
fmt.Println(len(numbers)) // 3
fmt.Println(cap(numbers)) // 3
}
具体的な要素の値はまだ決定していないが、長さ(len)や容量(cap)を指定してスライスを作成したいときに使える。
上記の通り、makeで作成した要素にはゼロ値が入る。
numbers := make([]int, 3)
fmt.Println(numbers) // [0 0 0]
なぜ[0 0 0]になるのか。
これは、Goではmakeで作成したスライスの要素が、その型のゼロ値で初期化されるため。
numbers := make([]int, 3)
// [0 0 0]
names := make([]string, 3)
// ["" "" ""]
flags := make([]bool, 3)
// [false false false]
append関数
appendは、スライスの末尾に要素を追加するための関数。
package main
import "fmt"
func main() {
numbers := []int{10, 20, 30}
numbers = append(numbers, 40)
fmt.Println(numbers)
// [10 20 30 40]
}
append(numbers, 40): numbersの末尾に40を追加したスライスを返す
つまり、appendは元のスライス変数を直接書き換えるのではなく、要素を追加した結果のスライスを返す。
makeとappendを組み合わせると?
a := make([]int, 3)
a = append(a, 10)
fmt.Println(a)
// [0 0 0 10]
makeでスライスを作成した時点で、すでに3つの要素が存在する。したがって、appendすると4番目の要素として10が追加される。 一方で、
b := make([]int, 0, 3)
b = append(b, 10)
fmt.Println(b)
// [10]
こちらは長さが0なので、10が最初の要素になる。
appendでcap(容量)を超えるとどうなるか?
package main
import "fmt"
func main() {
numbers := make([]int, 0, 3)
fmt.Println(len(numbers)) // 0
fmt.Println(cap(numbers)) // 3
numbers = append(numbers, 10)
numbers = append(numbers, 20)
numbers = append(numbers, 30)
fmt.Println(numbers) // [10 20 30]
fmt.Println(len(numbers)) // 3
fmt.Println(cap(numbers)) // 3
}
このように長さ0, 容量3のスライスを冒頭で作成したあとに、10, 20, 30をappendしていく。 最後のPrintlnでは、長さ3, 容量3となっていることがわかる。 ここで4つ目の要素をappendしたらどうなるか?
numbers = append(numbers, 40)
fmt.Println(numbers) // [10 20 30 40]
fmt.Println(len(numbers)) // 4
fmt.Println(cap(numbers)) // 4以上(実装による)
このようにappendではcap(容量)を超えてもpanicにならない。 容量が足りなくなると、Goが自動でより大きな基底配列を用意してくれるため。
処理の流れは以下のようになっている。
- 現在の基底配列には3要素分の容量しかない
- append(numbers, 40)で4つ目を追加しようとする
- 容量が足りないので、Goが新しい基底配列を確保する
- 元の要素10,20,30を新しい配列にコピーする
- 40を追加したスライスを返す。
なぜcapは4ではなく6になるのか?
要素を一つ追加するたびに毎回新しい配列を作っていると、コピー処理が何度も発生してしまう。 そのため、Goは必要な容量より多めにメモリを確保することがある。 ただし、容量が何倍に増えるかはGoの言語仕様で保証されていないらしい。 capが必ず6になるわけではない。
範囲外の容量でスライス拡張したときはpanicになっていなかったか?
s = s[:5]
範囲外のスライス拡張をした場合には、panicが発生する。 一方appendは容量が足りなければ新しい基底配列を確保できる。