フラミナル

考え方や調べたことを書き殴ります。IT技術系記事多め

Go のスライスの挙動(Go言語100Tips No.20 / No.25)

Go のスライスは動的配列のように使えるがその実態はただの配列のビューになっている。

スライスからスライスを生成する時においては、同じデータを参照している。

s1 := make([]int, 0, 6)
s1 = append(s1, 1, 2, 3)
s2 := s1[1:3] // 2, 3

// ここに s1 = append(s1, 4) とすると
// 裏側の配列(back array) には 1,2,3,4 となるが
// s1 は len(4) なので全て表示 [1,2,3,4]
// s2 は len(2) なので [2, 3] となる

この状態で

s2 = append(s2, 10)

とすると s2 は [2, 3 ,10] となるが s1 は [1, 2, 3, 10] となる。

※4がきえてしまう

これは back array を共有しているから起きる。 つまり、スライスへの append は実際には backArray[i] = xxx をやっていることになるので、同じものを見ていれば更新されるわけ。

これに関しては slice が len の範囲しかこっちに見せないとしているのでわからないだけ。

// ここに s1 = append(s1, 4) とすると
// 裏側の配列(back array) には 1,2,3,4 となるが
// s1 は len(4) なので全て表示 [1,2,3,4]
// s2 は len(2) なので [2, 3] となる

full slice expression

a[low : high : max]のこと

low~high 未満の範囲の値を取得する。このとき max を指定すると cap を制限できる。

s3 := []int{1, 2, 3, 4, 5}
s4 := s3[0:3:3] // 1, 2, 3
s5 := s3[0:3:4] // 1, 2, 3
s3: value([1 2 3 4 5]) / len(5) / cap(5)
s4: value([1 2 3]) / len(3) / cap(3)
s5: value([1 2 3]) / len(3) / cap(4)

応用

slice に要素を足す。

s3 := []int{1, 2, 3, 4, 5}
s4 := s3[0:3:3] // 1, 2, 3 (cap 3)
s5 := s3[0:3:4] // 1, 2, 3 (cap 4)

addVal := func(s []int) {
        s = append(s, 10)
    }

addVal(s4)
print("s3", s3)
print("s4", s4)
print("s5", s5)

この時 cap が 3 の s4 に append すると、cap が溢れてるので配列のコピーが作成されるため s4 自体が更新されない。

s3: value([1 2 3 4 5]) / len(5) / cap(5)
s4: value([1 2 3]) / len(3) / cap(3)
s5: value([1 2 3]) / len(3) / cap(4)

一方で cap が 4 の s5 に append すると余裕があるのでそのまま追加され、s3 の index 4 も変わる。

addVal(s5)
print("s3", s3)
print("s4", s4)
print("s5", s5)
s3: value([1 2 3 10 5]) / len(5) / cap(5)
s4: value([1 2 3]) / len(3) / cap(3)
s5: value([1 2 3]) / len(3) / cap(4)

あれ s5 の len が増えない。

↓ chatgpt に聞いた

Goのスライスは、実際には参照型ですが、関数にスライスを渡すとき、そのスライス自体の構造体(ポインタ、長さ、容量を含む)は値としてコピーされます。しかし、このコピーされたスライスは元のスライスと同じバックアレイ(配列)を指しています。

実際はこのような構造体を値渡ししているので、Len / Cap は更新されないとのこと。

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

つまりスライスのポインタを渡せば

addValPtr := func(s *[]int) {
        *s = append(*s, 10)
}

addValPtr(&s5)
print("s3", s3)
print("s4", s4)
print("s5", s5)

len も更新され 10 がでてくる。

s3: value([1 2 3 10 5]) / len(5) / cap(5)
s4: value([1 2 3]) / len(3) / cap(3)
s5: value([1 2 3 10]) / len(4) / cap(4)