Help us understand the problem. What is going on with this article?

Go言語 スライスの確認

More than 1 year has passed since last update.

スライスとは

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

実際、固定長よりも可変長で配列扱いたい感あるしね。

型の宣言

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

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

こんなもんかな

動作の確認

スライスという名前の通り、予め用意した配列を要素を部分列として取り出すことができる。

ex01.go
package main

import (
    "fmt"
)

func main() {
    a := [5]string{"Haruka", "Chihaya", "Miki", "Yukiho", "Makoto"}
    b := a[:]
    c := a[2:4]
    // d := a[2:6] // invalid slice index 6 (out of bounds for 5-element array)
    fmt.Println(a) // [Haruka Chihaya Miki Yukiho Makoto]
    fmt.Println(b) // [Haruka Chihaya Miki Yukiho Makoto]
    fmt.Println(c) // [Miki, Yukiho]

    b[1] = "Hibiki"
    c[0] = "Yayoi"
    fmt.Println(a) // [Haruka Hibiki Yayoi Yukiho Makoto]
    fmt.Println(b) // [Haruka Hibiki Yayoi Yukiho Makoto]
    fmt.Println(c) // [Yayoi Yukiho]

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

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

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

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

関数で扱う場合

ex02.go
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関数

ex02.go
// 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関数

なかなかに癖のある関数

ex03.go
package main

import (
  "fmt"
)

func main() {
  a := []string{"Haruka", "Chihaya", "Miki", "Yayoi", "Iori"}
  fmt.Println(a)      // [Haruka Chihaya Miki Yayoi Iori]
  fmt.Println(len(a)) // 5
  fmt.Println(cap(a)) // 5

  a = append(a, "Hibiki", "Takane")
  fmt.Println(a)      // [Haruka Chihaya Miki Yayoi Iori Hibiki Takane]
  fmt.Println(len(a)) // 7
  fmt.Println(cap(a)) // 10

  a = append(a, "Makoto", "Yukiho", "Ritsuko", "Azusa")
  fmt.Println(a)      // [Haruka Chihaya Miki Yayoi Iori Hibiki Takane Makoto Yukiho Ritsuko Azusa]
  fmt.Println(len(a)) // 11
  fmt.Println(cap(a)) // 20
}

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

面白い例をみせましょう。

// 十分な容量を確保している場合

ex04.go
package main

import (
    "fmt"
)

func main() {
    a := make([]string, 7, 10) // 要素数 < 容量
    a[0] = "Haruka"
    a[1] = "Chihaya"
    a[2] = "Miki"
    a[3] = "Yayoi"
    a[4] = "Iori"
    a[5] = "Hibiki"
    a[6] = "Takane"
    b := append(a[:5],"Ami", "Mami")
    fmt.Println(a)      // [Haruka Chihaya Miki Yayoi Iori Ami Mami]
    fmt.Println(len(a)) // 7
    fmt.Println(cap(a)) // 10
    fmt.Println(b)      // [Haruka Chihaya Miki Yayoi Iori Ami Mami]
    fmt.Println(len(b)) // 7
    fmt.Println(cap(b)) // 10

    b = append(a[:5],"Hibiki", "Takane", "Ami", "Mami")
    fmt.Println(a)      // [Haruka Chihaya Miki Yayoi Iori Hibiki Takane]
    fmt.Println(len(a)) // 7
    fmt.Println(cap(a)) // 10
    fmt.Println(b)      // [Haruka Chihaya Miki Yayoi Iori Hibiki Takane Ami Mami]
    fmt.Println(len(b)) // 9
    fmt.Println(cap(b)) // 10

    b[5] = "Ritsuko"
    b[6] = "Azusa"
    fmt.Println(a)      // [Haruka Chihaya Miki Yayoi Iori Ritsuko Azusa]
    fmt.Println(len(a)) // 7
    fmt.Println(cap(a)) // 10
    fmt.Println(b)      // [Haruka Chihaya Miki Yayoi Iori Ritsuko Azusa Ami Mami]
    fmt.Println(len(b)) // 9
    fmt.Println(cap(b)) // 10
}

b := append(a[:5],"Ami", "Mami")
b = append(a[:5],"Hibiki", "Takane", "Ami", "Mami")
スライスbにaの5番目までのデータ+"Ami", "Mami"をappendする。
配列のメモリを共有しているので、aとbは同じデータ構造となる。
しかし、aの要素数を越えてappendするとそれぞれの出力結果は異なるが、容量に変化はないため確保されている要素では共有状態にある。

// 十分な容量を確保していない場合

ex05.go
package main

import (
    "fmt"
)

func main() {
    a := make([]string, 7) // 要素数 == 容量
    a[0] = "Haruka"
    a[1] = "Chihaya"
    a[2] = "Miki"
    a[3] = "Yayoi"
    a[4] = "Iori"
    a[5] = "Hibiki"
    a[6] = "Takane"
    b := append(a[:5],"Ami", "Mami")
    fmt.Println(a)      // [Haruka Chihaya Miki Yayoi Iori Ami Mami]
    fmt.Println(len(a)) // 7
    fmt.Println(cap(a)) // 7
    fmt.Println(b)      // [Haruka Chihaya Miki Yayoi Iori Ami Mami]
    fmt.Println(len(b)) // 7
    fmt.Println(cap(b)) // 7

    b = append(a[:5],"Hibiki", "Takane", "Ami", "Mami")
    fmt.Println(a)      // [Haruka Chihaya Miki Yayoi Iori Ami Mami]
    fmt.Println(len(a)) // 7
    fmt.Println(cap(a)) // 7
    fmt.Println(b)      // [Haruka Chihaya Miki Yayoi Iori Hibiki Takane Ami Mami]
    fmt.Println(len(b)) // 9
    fmt.Println(cap(b)) // 14

    b[5] = "Ritsuko"
    b[6] = "Azusa"
    fmt.Println(a)      // [Haruka Chihaya Miki Yayoi Iori Ami Mami]
    fmt.Println(len(a)) // 7
    fmt.Println(cap(a)) // 7
    fmt.Println(b)      // [Haruka Chihaya Miki Yayoi Iori Ritsuko Azusa Ami Mami]
    fmt.Println(len(b)) // 9
    fmt.Println(cap(b)) // 14
}

十分な容量を確保せずにappendした場合、自動的に容量するので前述した通り配列のメモリアドレスが変更される。
こうなると、aとbはそれぞれ独立されたスライスとなってしまい、データの編集・追加をしてもそれらは共有されることはない。

なので、共有状態を保ったままデータの操作を行うときは、予め多めに容量をとっておく必要がありそう。

copy関数

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

ex06.go
package main

import (
  "fmt"
)

func main() {
  a := []string{"Haruka", "Chihaya", "Miki", "Yayoi", "Iori"}
  b := make([]string, 5, 10)
  cLen := copy(b, a)
  fmt.Println(a)    // [Haruka Chihaya Miki Yayoi Iori]
  fmt.Println(b)    // [Haruka Chihaya Miki Yayoi Iori]
  fmt.Println(cLen) // 5

  b[0] = "Ritsuko"
  fmt.Println(a)    // [Haruka Chihaya Miki Yayoi Iori]
  fmt.Println(b)    // [Ritsuko Chihaya Miki Yayoi Iori]
}

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

雑感

  • 配列よりも便利感ある
  • delete関数がないので、append関数であれこれしなきゃいけないのは面倒
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away