Go言語「スライスが分からない件」を解決する

Go言語のスライスはとても便利なのですが、いまいちGo言語のスライスが分からないのはなぜでしょうか。いろいろなプログラムを作って検証してみましょう。

# 基本

例えば、``[]int``のスライス(intの可変配列とも言える)を作成し、適当に初期化して値を一気に表示するプログラムは、以下のようになります。

```basic.go
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の値を指定します。

```substring.go
...
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番目の要素を削除してみます。

```remove.go
a := []int{0, 1, 2, 3, 4, 5}
// 3を削除
a = append(a[:3], a[4:]...)
fmt.Println(a)
```

実行結果: ``[0 1 2 4 5]``

- (参考) [go言語のslice操作をまとめてみた（shiftしたりpushしたり）](https://qiita.com/egnr-in-6matroom/items/282aa2fd117aab9469bd)

# もう少し詳しくスライスを学ぶ

ここまで、基本的な部分を紹介しましたが、スライスをもっと詳しく調べてみましょう。

## 疑問-スライスを変更するとアドレスは変わる？

appendでスライスの内容を変更できますが、スライスのアドレスは変わってしまうのでしょうか。

```ShowAddress.go
// 初期化
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
```

異なる内容のスライスが二つできるのですから、当然、スライスのアドレスは変わっています。

## 内容を変更してもアドレスが変わらない場合

しかし、内容を変更しても、スライスのアドレスが変わらない場合もあります。

```ShowAddress2.go
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
```

これは、先頭の要素を削除したとしても変わりません。

```ShowAddress3.go
	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を利用して異なる型として定義できます。

```type1.go
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}
```

## スライスを型にしてメソッドを定義

さらに、メソッドを定義することもできます。

```type2.go
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メソッドを定義してみます。

```type3.go
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の要素も変更されてしまいます。

詳しくは、以下が役にたつので参照すると良いでしょう。

- [スライスの複製に便利な「copy」関数のサンプルと、直接代入した場合の罠](https://qiita.com/riotam/items/06850524450d882e2bd5)

簡単なスライスの複製例：

```slicecopy.go
a := []int{0, 1, 2}

// 空のスライスを作成
b := make([]int, len(a))
// 内容をコピー
copy(b, a)

fmt.Println(b)
```


# まとめ

スライスがよく分かると、Goの使い勝手が向上します。スライスの理解は、Goをより使いこなすうえでの第一歩と言うことができるでしょう！