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

【Golang】スライスと基底配列

はじめに

まだGo言語初心者で学習中の身ですので、間違いや不適切な表現などがありましたら、ご指摘頂けると助かりますm(_ _)m

スライスとは

  • すべての要素が同じ型の可変長列
  • 配列の要素の部分列(もしくは全部)を参照している。この配列のことを基底配列という

スライスの構成要素

スライスは次の3つの要素を持つ。

  • ポインタ
    • スライスを通して到達できる配列の最初の要素を指している
  • 長さ
    • スライスの要素数
  • 容量
    • 長さはこの容量を超えることは出来ない。容量を拡張しなければ、スライスの開始から基底配列の最後までの要素数が容量となる。

スライスの更新と基底配列

スライスは、基底配列を参照しているため、スライスの要素を変更すると基底配列も変更される。

例)配列aからスライスsを作成し、スライスsの要素を変更してみると、基底配列aも変更されている。

a := [3]string{"ruby", "php", "python"}
s := a[:]
s[0] = "b"
fmt.Println(s) // [b php python]
fmt.Println(a) // [b php python]

これは、関数の引数としてスライスを渡したときも同じことが起こる。golangの引数は値渡しだが、参照している基底配列が同じため。

func main() {
    a := [3]int{1, 5, 10}
    s1 := a[:]
    s2 := twice(s1)
    fmt.Println(a)  // [2 10 20]
    fmt.Println(s1) // [2 10 20]
    fmt.Println(s2) // [2 10 20]
}

func twice(slice []int) []int {
    for i, _ := range slice {
        slice[i] *= 2
    }
    return slice
}

直接スライスを生成した場合は?

ある配列からスライス演算子(s[i:j])を使ってスライスを作成した場合は、元となった配列が基底配列となるが、make関数やスライスリテラル([]int{1, 2, 3})を使って作成した場合はどうなるのか?

=> 暗黙的に無名配列を生成し、そのスライスを返している。この無名配列が基底配列であり、スライスはこの無名配列を参照している。

スライスの拡張と基底配列

スライスは容量を超える要素数を含むことができない。panicになる。

a := [3]int{1, 5, 10}
s := a[0:3]                     // OK
fmt.Printf("cap: %d\n", cap(s)) // cap: 3
s = s[0:5]                      // panic: runtime error: slice bounds out of range [:5] with capacity 3

そのため、容量を超える数の要素をスライスに入れたい場合、容量を増やす必要がある。しかし、参照している配列の長さを変更することはできないので、容量を増やすには新しい配列を用意し、それを参照するスライスを作成する必要がある。
=> つまり、基底配列が変わる

実際、スライスに要素を追加するappend関数は、次のような処理をしている。
容量が足りる場合

・新しい要素をコピーする
・長さを更新する

容量が足りない場合

・元のおよそ2倍の容量を確保しなおす
・配列へのポインタを貼り直す
・元の配列から要素をコピーする
・新しい要素をコピーする
・長さと容量を更新する

【参考】: 実装して理解するスライス #golang

実際に試してみる。

a := [3]int{1, 2, 3}
s := a[:]
fmt.Printf("len=%d, cap=%d\n", len(s), cap(s)) // len=3, cap=3
// 容量3なので、要素を追加するためにはスライスを拡張する必要がある。
s = append(s, 4)
fmt.Println(s) // [1 2 3 4]
fmt.Println(a) // [1 2 3] 基底配列aが変わってないのは、スライスsに新たな配列が割り当てられたから

append関数を呼び出したときに、配列の再割り当てされる(基底配列が変わる)可能性があるため、一般的にappend関数の返り値はもとのスライス変数に代入すべき

※ append関数に限らず、スライスに対して何かしらの操作をする関数の結果はもとのスライスに代入した方が良い。そうしないと、もとのスライス変数が予期せぬ値になってしまうといったことが起こる。

nomuyoshi
フリーランス。 Rails/React/Vue/golang
Why not register and get more from Qiita?
  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
No 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
ユーザーは見つかりませんでした