Go言語のスライスはとても便利なのですが、いまいちGo言語のスライスが分からないのはなぜでしょうか。いろいろなプログラムを作って検証してみましょう。
基本
例えば、[]int
のスライス(intの可変配列とも言える)を作成し、適当に初期化して値を一気に表示するプログラムは、以下のようになります。
package main
import "fmt"
func main() {
a := []int{0, 1, 2, 3, 4, 5}
fmt.Printf("%#v\n", a)
}
実行結果:
$ go run a.go
[]int{0, 1, 2, 3, 4, 5}
部分的に取り出す
データを取り出すには、a[要素番号]
。部分スライスを取り出すには、a[i1:i2]
のように書きます。なお、i2には終わりのインデックス+1の値を指定します。
...
func main() {
a := []int{0, 1, 2, 3, 4, 5}
b := a[0:3]
fmt.Println(b)
}
実行結果は[0 1 2]
です。
なお、部分スライスを得る場合、0や末尾の値を省略できます。
-
a[:3]
...a[0:3]
と同じ -
a[3:]
...a[0:末尾+1]
と同じ
スライスへの追加
ここが難しいところですが、配列の追加や削除はあまり直感的ではありません。
末尾への追加は、appendを使います。ポイントなのは、appendの第二引数以降に複数の値を指定してできます。もし、別途スライスを追加したいときは、値...
のように記述します。
a := []int{0, 1, 2, 3, 4, 5}
// 一つ追加
a = append(a, 6)
// 複数の追加
a = append(a, 7, 8, 9)
a = append(a, []int{10, 11, 12}...) // --- (*b)
fmt.Println(a)
実行結果: [0 1 2 3 4 5 6 7 8, 9, 10, 11, 12]
上記(*b)が分かりにくいですね。もう少し分かりやすくしてみます。
a := []int{0, 1, 2, 3, 4, 5}
b := []int{6, 7, 8}
// a + b追加
a = append(a, b...)
fmt.Println(a)
実行結果: [0 1 2 3 4 5 6 7 8]
ちなみに、append(a, b)
とできませんか?
もちろん、型エラーになるのでできませんが、もしできたとするなら、[0,1,2,3,4,5,[6,7,8]]
となってしまいます。b...
と書くことでスライスの内容を引数に展開できるのです。
スライスの一部分を削除
任意の要素を削除するには、以下のようにします。例えば、(0から数えて)3番目の要素を削除してみます。
a := []int{0, 1, 2, 3, 4, 5}
// 3を削除
a = append(a[:3], a[4:]...)
fmt.Println(a)
実行結果: [0 1 2 4 5]
もう少し詳しくスライスを学ぶ
ここまで、基本的な部分を紹介しましたが、スライスをもっと詳しく調べてみましょう。
疑問-スライスを変更するとアドレスは変わる?
appendでスライスの内容を変更できますが、スライスのアドレスは変わってしまうのでしょうか。
// 初期化
a := []int{0, 1, 2, 3, 4, 5}
// 値を追加
b := append(a, 6)
// 値とアドレスを表示
fmt.Printf("--- a = %#v\n", a)
println(&a)
fmt.Printf("--- b = %#v\n", b)
println(&b)
実行結果:
--- a = []int{0, 1, 2, 3, 4, 5}
0xc00009af60
--- b = []int{0, 1, 2, 3, 4, 5, 6}
0xc00009af48
異なる内容のスライスが二つできるのですから、当然、スライスのアドレスは変わっています。
内容を変更してもアドレスが変わらない場合
しかし、内容を変更しても、スライスのアドレスが変わらない場合もあります。
a := []int{0, 1, 2, 3, 4, 5}
fmt.Printf("--- a = %#v\n", a)
println(&a)
a = append(a, 6)
fmt.Printf("--- a = %#v\n", a)
println(&a)
同じ変数に代入した場合はスライスのアドレスは変わりません。以下、実行結果です。
--- a = []int{0, 1, 2, 3, 4, 5}
0xc00009af60
--- a = []int{0, 1, 2, 3, 4, 5, 6}
0xc00009af60
これは、先頭の要素を削除したとしても変わりません。
a := []int{0, 1, 2, 3, 4, 5}
fmt.Printf("--- a = %#v\n", a)
println(&a)
// 先頭の要素を削除した場合
a = a[1:]
fmt.Printf("--- a = %#v\n", a)
println(&a)
結果:
--- a = []int{0, 1, 2, 3, 4, 5}
0xc00005ef60
--- a = []int{1, 2, 3, 4, 5}
0xc00005ef60
スライスを型として定義する
スライスは、typeを利用して異なる型として定義できます。
package main
import "fmt"
type IntSlice []int
func main() {
a := IntSlice{0, 1, 2, 3, 4, 5}
fmt.Printf("--- a = %#v\n", a)
}
実行結果:
--- a = main.IntSlice{0, 1, 2, 3, 4, 5}
スライスを型にしてメソッドを定義
さらに、メソッドを定義することもできます。
package main
import "fmt"
type IntSlice []int
func (p *IntSlice) Sum() int {
c := 0
for _, v := range *p {
c += v
}
return c
}
func main() {
a := IntSlice{0, 1, 2, 3}
println(a.Sum())
}
実行結果: 6
スライス自身を変更するメソッド
そして、スライス自身を変更するメソッドも追加できます。以下は、スライスに値を追加するAppendメソッドを定義してみます。
package main
import "fmt"
type IntSlice []int
func (p *IntSlice) Append(v int) {
*p = append(*p, v)
}
func main() {
a := IntSlice{0, 1, 2, 3}
println(&a)
a.Append(4)
fmt.Printf("%#v\n", a)
println(&a)
}
実行結果:
0xc00005ef60
main.IntSlice{0, 1, 2, 3, 4}
0xc00005ef60
スライスの複製について
スライスaを作成した後、b:=a
のように書いた場合、スライスが複製されるのではなく、bはスライスaのエイリアスのような状態になります。そのため、bの要素を変更した場合、aの要素も変更されてしまいます。
詳しくは、以下が役にたつので参照すると良いでしょう。
簡単なスライスの複製例:
a := []int{0, 1, 2}
// 空のスライスを作成
b := make([]int, len(a))
// 内容をコピー
copy(b, a)
fmt.Println(b)
まとめ
スライスがよく分かると、Goの使い勝手が向上します。スライスの理解は、Goをより使いこなすうえでの第一歩と言うことができるでしょう!