Go言語の配列は要素数を増やせませんが「スライス」を使えば増やせます。
まずは配列の定義の復習とスライスの定義を見ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
package main import ( "fmt" ) func main() { /* 配列の定義、初期値設定の例 */ var a1 [3]int fmt.Println(a1) //[0 0 0] var a2 [3]int = [3]int{1, 2, 3} fmt.Println(a2) //[1 2 3] var a3 = [3]int{1, 2, 3} fmt.Println(a3) //[1 2 3] //(:=)は関数内のみ使える定義 a4 := [3]int{1, 2, 3} fmt.Println(a4) //[1 2 3] //...で要素数は入れなくてもよい a5 := [...]int{1, 2, 3} fmt.Println(a5) //[1 2 3] /* スライスの定義、初期値設定の例 */ //スライスは長さを変えられるので、配列のように要素数は入れない var s1 []int fmt.Println(s1) //[] var s2 []int = []int{1, 2, 3} fmt.Println(s2) //[1 2 3] var s3 = []int{1, 2, 3} fmt.Println(s3) //[1 2 3] //(:=)は関数内のみ使える定義 s4 := []int{1, 2, 3} fmt.Println(s4) //[1 2 3] } |
配列は[数字]intまたは[…]intと書きますが、スライスは[]intというように書きます。
スライスに要素を追加したい場合は組み込み関数appendを使う
スライスはappend関数でサイズを増やせます(要素数を増やせます)。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import ( "fmt" ) func main() { var s1 []int s1 = append(s1, 1) s1 = append(s1, 2) s1 = append(s1, 3) fmt.Println(s1) } |
スライスは参照
配列とスライスの大きな違いは上記の通りサイズを増やせるかどうかですが、他にも違いがありまして、それはスライスは参照であるということです。
参照の意味を理解するのは難しいですが、下の例を見ていただくと多少わかりやすいかも知れません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
package main import ( "fmt" ) func main() { //配列a var a = [3]int{1, 2, 3} //スライスs var s = []int{1, 2, 3} //出力してみると共に同じ fmt.Println(a) //[1 2 3] fmt.Println(s) //[1 2 3] /* 配列 */ //配列aをa2に代入 a2 := a //a2は当然aと同じ fmt.Println(a2) //[1 2 3] //a2の要素を変更する a2[0] = 100 //変更したので当然変わる fmt.Println(a2) //[100 2 3] //ここでaを再度見てみると[1 2 3]で変わっていない fmt.Println(a) //[1 2 3] /* スライス */ //スライスsをs2に代入 s2 := s //s2は当然sと同じ fmt.Println(s2) //[1 2 3] //s2の要素を変更する s2[0] = 50 //変更したので当然変わる fmt.Println(s2) //[50 2 3] //ここでsを再度見てみると[50 2 3]と変わっています fmt.Println(s) //[50 2 3] } |
参照とは同じメモリアドレスを参照しているということです。
変数はメモリ上に確保された領域(メモリアドレス)で、そこに値を格納しています。
配列の場合、以下の代入でa2にaのコピーが渡ります。
a2 := a
a2は、aとは違うメモリアドレスで、そこにaの値がコピーされています。
a2を変更してもaには影響出ません。
スライスの場合、以下の代入でs2にsの参照しているメモリアドレスが渡ります。
s2 := s
sとs2は同じメモリアドレスを参照しているので、s2を変更したらsも変わります。
それではスライスの値はどこに保存されているのでしょうか?
それは、見えないところで配列が作られていてそこに値があるわけです。
スライスを作ると裏で配列が作られ、スライスはそのメモリアドレスを保持している。
他の言語で言うところのスライス
Pythonなどの他の言語でスライスと言えば、シーケンス(順番に処理するためのデータ構造で文字列や配列など)要素の一部分を取り出すための構文という意味で使われますが、Go言語ではそれもスライスと呼んだりスライシングと呼んだりしていますが、ここでは混同しないようにスライシングと言います。
配列をスライシングしてスライスを作る例です。
なお変数にアンド(&付き変数)をつけるとメモリアドレスが表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package main import ( "fmt" ) func main() { //これは配列 a := [...]int{1, 2, 3, 4, 5} fmt.Printf("%T %v\n", a, a) //[5]int [1 2 3 4 5] //メモリアドレス fmt.Println(&a[0]) //0x10458000 //スライシングしてスライスを作成 s := a[:] fmt.Printf("%T %v\n", s, s) //[]int [1 2 3 4 5] //配列aのメモリアドレスと一緒 fmt.Println(&s[0]) //0x10458000 //sはスライスなのでサイズを増やせる s = append(s, 6) fmt.Printf("%T %v\n", s, s) //[]int [1 2 3 4 5 6] //配列aのメモリアドレスと違う! fmt.Println(&s[0]) //0x10450030 } |
appendでaのサイズを増やした後は違うメモリアドレスになっています。
ここでは裏で新しい配列が作られて、そのメモリアドレスを参照しています。
makeでスライスを作成
あらかじめ長さと容量を決めて空のスライスを作成したい場合はmake関数を使います。
s := make([]int, length, capacity)
make関数の第1引数([]int)がスライスの型、第2引数(length)がスライスの長さ、第3引数(capacity)が(スライスの裏で作成される配列の長さ)容量です。
容量は省略可能で、省略した場合長さ=容量になります。
容量はcap()という関数でも知ることが出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package main import ( "fmt" ) func main() { //make関数で作成 a := make([]int, 5, 10) //ゼロ値で初期化される fmt.Println(a) //[0 0 0 0 0] //代入 a[0] = 1 a[1] = 2 a[2] = 3 a[3] = 4 a[4] = 5 //表示してみる fmt.Println(a) //[1 2 3 4 5] //メモリアドレス確認 fmt.Println(&a[0]) //0x10450030 //要素を追加してみる a = append(a, 6, 7, 8, 9, 10) //表示してみる fmt.Println(a) //[1 2 3 4 5 6 7 8 9 10] //メモリアドレス確認(同じ) fmt.Println(&a[0]) //0x10450030 //現在の容量確認 fmt.Println(cap(a)) //10 //更に要素を追加(容量の10を超える) a = append(a, 11) //表示してみる fmt.Println(a) //[1 2 3 4 5 6 7 8 9 10 11] //メモリアドレス確認(違う) fmt.Println(&a[0]) //0x1045a000 //現在の容量確認(倍に増えている) fmt.Println(cap(a)) //20 } |
appendでスライスの長さが多くなり、それが容量を超えた場合、容量が倍になって新しい配列が裏にできて、スライスはそれを参照するようになります。