LoginSignup
3
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-05-30

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]

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

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

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

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

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

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

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

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

fmt.Println(b)

まとめ

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

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2