スライスは、同じ型の値をいくつでも格納することができ、インデックス番号でその値(要素)を管理するという点は配列と同じです。しかし次の点で配列とは異なります。
配列は、要素の追加ができませんが、スライスは、要素の追加ができます。
配列は値型ですが、スライスは参照型です。つまりスライス名はポインターとして使えます。
配列を関数の実引数に指定した場合は、値渡しになりますが、スライスを関数の実引数に指定した場合は、参照渡しになります。
package main
import "fmt"
func main() {
var s1 [] int // 要素 0 個のスライスを定義しています
var a1 [0] int // 要素 0 個の配列を定義しています
s2 := [] int {1, 2, 3} // スライスの定義と同時に初期化しています
a2 := [...] int {1, 2, 3} // 配列の定義と同時に初期化しています
println(s1) // println関数はスライスの値、つまりアドレスを表示します
println(&a1) // println関数は配列のアドレスを表示します
println(s2)
println(&a2)
fmt.Println(s1) // fmt.Println関数はスライスの指し示すアドレスの値が表示します
fmt.Println(a1) // fmt.Println関数は配列の値が表示します
fmt.Println(s2) // fmt.Println関数はスライスの指し示すアドレスの値が表示します
fmt.Println(a2) // fmt.Println関数は配列の値が表示します
}
[0/0]0x0
0xc000064f10
[3/3]0xc000078000
0xc000064f10
[]
[]
[1 2 3]
[1 2 3]
var s1 [] int
[ と ] の間に何も記述しない場合は、スライスを定義したことになります。初期値も与えない場合は、空のスライスが定義されます。
var a1 [0] int
[ と ] の間に要素数を記述した場合は、配列を定義したことになります。[ と ] の間に 0 記述すると、空の配列が定義されます。
s2 := [] int {1, 2, 3}
{ と }(波括弧)で囲んで初期値を与えると、要素を持ったスライスが定義されます。[ と ](大括弧)の間には何も記述しないことに注意してください。
a2 := [...] int {1, 2, 3}
初期値を与えて配列を定義する場合、要素数は [...] と記述して省略することができます。[ と ] の間に何も記述しないと、スライスの定義になりますので、注意してください。
println(s1)
println関数の引数に、スライスや配列を渡した場合は、そのコレクションのアドレスが表示されます。空のスライスのアドレスを表示した場合は、[0/0]0x0 と、実際には、どこのアドレスも指し示していないことが分かります。
println(&a1)
println関数の引数に、コレクションを渡した場合は、そのコレクションのアドレスが表示されます。引数として配列を渡す場合は、&配列名としなければなりません。
println(s2)
要素のあるスライスのアドレスを表示すると、0xc000064f10 と、実際のアドレスを指し示していることが分かります。
fmt.Println(s1)
fmt.Println関数の引数が、スライスの場合は、そのスライスの指し示すアドレスの値が表示されます。空のスライスは [ ] と表示されます。
fmt.Println(a1)
fmt.Println関数の引数が、配列の場合は、その配列の値が表示されます。
以上をまとめると次のようになります。
コレクションを定義する場合に、[ と ] の間に何も記述しないとスライスが定義されます。
[ と ] の間に要素数か ... を記述すると配列が定義されます。
println関数で表示されるのはコレクションのアドレスです。スライスの場合は、スライス名をそのまま記述すれば良いのですが、配列の場合は、&配列名としなければなりません。
fmt.Println関数で表示されるのはコレクションの値です。スライスの場合でも、*スライス名としなくても、スライスの指し示すアドレスの値が表示されます。逆に、*スライス名とするとエラーになりますので注意が必要です。
package main
import "fmt"
func main() {
println("-----Append-----")
s1 := [] int {1, 2, 3}
s2 := append(s1, 4, 5, 6)
fmt.Println(s1)
fmt.Println(s2)
println(s1)
println(s2)
println("------Copy------")
s_ := make([] int, len(s1))
fmt.Println(s_)
copy(s_, s2)
fmt.Println(s_)
s_ = make([] int, 4)
n := copy(s2, s_)
fmt.Println(s2)
println("コピーした数は", n, "個です")
}
-----Append-----
[1 2 3]
[1 2 3 4 5 6]
[3/3]0xc000082000
[6/6]0xc000084000
------Copy------
[0 0 0]
[1 2 3]
[0 0 0 0 5 6]
コピーした数は 4 個です
s2 := append(s1, 4, 5, 6)
append 関数は、第1引数のスライスに、第2引数以降の値を持つ要素を追加した、新しいスライスを作ります。もとのスライスに要素が追加されるわけではありません。
fmt.Println(s1)
fmt.Println(s2)
println(s1)
println(s2)
実際に、s1 の値はもとのままですし、s1 と s2 のアドレスは違うのものです。
s_ := make([] int, len(s1))
make 関数は、値のない複数の要素を持つスライスを作成できます。第1引数に型を、第2引数に、要素の個数を指定します。len 関数は、引数のコレクションの要素数を取得できます。
fmt.Println(s_)
初期化されていない int 型は 0 という値になっています。
copy(s_, s2)
fmt.Println(s_)
copy 関数は、第1引数のスライスに、第2引数のスライスをコピーします。第1引数のスライスと第2引数のスライスの要素の個数が違う場合は、先頭からコピーできる個数だけコピーします。エラーにはなりません。
n := copy(s2, s_)
copy 関数は、コピーした要素の個数を戻り値として返します。copy 関数の戻り値は使わなくてもエラーになりません。
スライスは配列から作ることもできます。
import "fmt"
func main() {
a := [...] int {1, 2, 3, 4, 5}
s1 := a[:]
s2 := a[1:4]
println(&a)
println(s1)
println(s2)
fmt.Println( a)
fmt.Println(s1)
fmt.Println(s2)
a [1] = 200
s1[2] = 300
s2[2] = 400
fmt.Println( a)
fmt.Println(s1)
fmt.Println(s2)
println(len( a), cap( a))
println(len(s1), cap(s1))
println(len(s2), cap(s2))
println("-----cap-----")
s3 := make([] int, 5, 100)
println(s3)
fmt.Println(s3)
}
0xc000084000
[5/5]0xc000084000
[3/4]0xc000084008
[1 2 3 4 5]
[1 2 3 4 5]
[2 3 4]
[1 200 300 400 5]
[1 200 300 400 5]
[200 300 400]
5 5
5 5
3 4
-----cap-----
[5/100]0xc000098000
[0 0 0 0 0]
s1 := a[:]
配列名[ : ]と記述すると、その配列をスライスに変換したものが作れます。
s2 := a[1:4]
[ : ] の : の前に、配列の何番目からスライスするかをインデックス番号で指定できます。省略した場合は、配列の最初を指定したことになります。: の後ろには、配列の何番目までスライスするかをインデックス番号で指定できます。終了地点の指定は、指定されたインデックス番号の 1 つ前までになります。省略した場合は、配列の最後までを指定したことになります。この例では、配列 a の 2 番目から 4 番目を指定したことになります。
println(&a)
println(s1)
println(s2)
それぞれのアドレスを比べると、配列 a とスライス s1 は同じものであることが分かります。スライス s2 は、8 バイト多いアドレスになっていると思います。8 バイトは、int 型 1 個分のメモリ容量です。つまり配列 a の 2 番目の要素のアドレスであることが分かります。
fmt.Println(s2)
スライス s2 の値は、[2 3 4] です。配列 a の 2 番目から 4 番目を指定できたことが分かります。
a [1] = 200
s1[2] = 300
s2[2] = 400
配列 a の 2 番目の要素に 200 を、スライス s1 の 3 番目の要素に 300 を、スライス s2 の 3 番目の要素に 400 を代入しています。s2 の 3 番目は、a や s1 の 4 番目と同じです。
fmt.Println( a)
fmt.Println(s1)
fmt.Println(s2)
もう 1 度値を表示すると、どの配列やスライスの値を変更しても、すべての配列とスライスで値が変更されていることが分かります。
println(len( a), cap( a))
println(len(s1), cap(s1))
println(len(s2), cap(s2))
len 関数は、要素数を取得します。cap 関数は、配列やスライスの容量を取得します。cap は capacity(容量)の略だと思えば良いと思います。今までスライスのアドレスを表示した際に、[5/5]0xc000084000などと表示されていたのは、[5/5]で要素数と容量を表示していました。
s3 := make([] int, 5, 100)
println(s3)
fmt.Println(s3)
make 関数には、省略できる第 3 引数があります。この第 3 引数で cap(容量)を指定することができます。省略した場合は、要素数と同じ容量になります。
[ : ] という記法で、配列から作成したスライスは、もとの配列のポインターであることに注意してください。
スライスを使った繰り返し文は、配列の場合と同じです。
cap については、何のためにあるのか、私自身もまだ分かっていません。詳しい説明は省略させていただきます。