Go言語 – 11.スライス

2019-02-04Go言語

Go言語 スライス

今回は「スライス」について説明します。
スライスとは、可変長配列を持たない代わりに実装された型。
配列全体のポインタ、配列の長さ(len)、配列の容量(cap)からなるデータ構造を保持している。
配列の部分列を簡単に取り出せる。
Go言語では、配列よりもスライスのほうが多く使われるようです。

スライスの宣言

var hoge []int
var hoge = []int{1, 2, 3}
hoge := []int{1, 2, 3}

[3]とか[...]せずに[]とする。要素数は書かない。

動作の確認

package main

import (
    "fmt"
)

func main() {
    a := [5]string{"A", "B", "C", "D", "E"}
    b := a[:]
    c := a[2:4]
    // d := a[2:6] // invalid slice index 6 (out of bounds for 5-element array)
    fmt.Println(a) // [A B C D E]
    fmt.Println(b) // [A B C D E]
    fmt.Println(c) // [C, D]

    b[1] = "F"
    c[0] = "G"
    fmt.Println(a) // [A F G D E]
    fmt.Println(b) // [A F G D E]
    fmt.Println(c) // [G D]
}

b := a[:]やc := a[2:4]は、配列の要素を指定しスライスに代入している。
範囲外はもちろんコンパイルエラー。
取り出し方は以下のとおり。

取り出し方 説明
a[:] 全要素
a[2:] 2番目から最後まで
a[2:5] 2番目から(5 – 1)番目まで
a[:5] 初めから(5 – 1)番目まで

はじめからとか最後までと指定するときは省略できる。
スライスに渡した部分列の要素は元の配列のメモリ領域が共有される
スライス型で要素を代入するということは、元の配列データに代入ということになる。

※組み込み関数 len()cap()で指定したスライスのlenとcapのデータを参照できる。

関数内での操作

package main

import (
    "fmt"
)

func change1(a []int) {
    a[2] += 100
}

func change2(b []int) {
    b[2] += 200
}

func main() {
    a := []int{1, 2, 3, 4, 5}
    change1(a)
    fmt.Println(a) // [1 2 103 4 5]
    change2(a)
    fmt.Println(a) // [1 2 303 4 5]
}

メモリが共有されているので関数内で操作しても、元のスライスも変更される。
引数として渡してるのは配列の要素ではなく、配列全体のメモリアドレスのようである。

組み込み関数 make() & append()

スライスでは組み込み関数として、make()append()を使うことができる。

make関数

package main

import (
    "fmt"
)

func main() {
    var a []int
    a = make([]int, 5, 10)
    fmt.Println(a) // [0 0 0 0 0]
    fmt.Println(len(a)) // 5
    fmt.Println(cap(a)) // 10
    for i, _ := range a {
        a[i] = i
    }
    fmt.Println(a) // [0 1 2 3 4]

    b := make([]int, 10)
    fmt.Println(b) // [0 0 0 0 0 0 0 0 0 0]
    fmt.Println(len(b)) // 10
    fmt.Println(cap(b)) // 10
}

make(型, 要素数, 容量)として使う。
スライスが初期化され、デフォルトではゼロ値として0が格納されている。
容量を省略すると、要素数==容量となる。

append関数

package main

import (
    "fmt"
)

func main() {
    a := []string{"A", "B", "C", "D", "E"}
    fmt.Println(a) // [A B C D E]
    fmt.Println(len(a)) // 5
    fmt.Println(cap(a)) // 5

    a = append(a, "F", "G")
    fmt.Println(a) // [A B C D E F G]
    fmt.Println(len(a)) // 7
    fmt.Println(cap(a)) // 10

    a = append(a, "H", "J", "K", "L")
    fmt.Println(a) // [A B C D E F G H J K L]
    fmt.Println(len(a)) // 11
    fmt.Println(cap(a)) // 20
}

append(スライス, データ1, データ2, …)として使う。
指定したスライスの末尾に追加していく。
スライスの要素数は自動で増えていく。
容量はいっぱいになった時、現在の容量を倍にして確保してくれる。
この時、配列のメモリアドレスは容量の拡張に伴い変更される。

copy関数

スライスの要素をコピーできる。

package main

import (
    "fmt"
)

func main() {
    a := []string{"A", "B", "C", "D", "E"}
    b := make([]string, 5, 10)
    cLen := copy(b, a)
    fmt.Println(a) // [A B C D E]
    fmt.Println(b) // [A B C D E]
    fmt.Println(cLen) // 5

    b[0] = "F"
    fmt.Println(a) // [A B C D E]
    fmt.Println(b) // [F B C D E]
}

copy(コピー先,コピー元)として扱う。
上記サンプルコードは、スライスbにスライスaのデータをコピーしている。
copy関数ではデータそのものをコピーしているため、共有関係はない。