初めに
大前提として、Go言語では、スライスからスライスを切り出すことができる(配列からもスライスを切り出せる)。 スライスの基本的な切り出し方はx[i:j]であり、これにはi番目からj-1番目の要素が含まれている。
x := []int{1, 2, 3, 4} y := x[:2] z := x[1:] d := x[1:3] e := x[:] fmt.Println("x:", x) // x: [1 2 3 4] fmt.Println("y:", y) // y: [1 2] fmt.Println("z:", z) // z: [2 3 4] fmt.Println("d:", d) // d: [2 3] fmt.Println("e:", e) // e: [1 2 3 4]
スライスを使う上での注意点
スライスからスライスを切り出す際にはデータのコピーを作っているわけでは無いので、要素を変更すると共有している全てのスライスが影響を受ける。
x := []int{1, 2, 3, 4} y := x[:2] z := x[1:] x[1] = 20 y[0] = 10 z[1] = 30 fmt.Println("x:", x) // x: [10 20 30 4] fmt.Println("y:", y) // y: [10 20] fmt.Println("z:", z) // z: [20 30 4]
Append干渉問題
また、スライスのスライスではキャパシティ部分も共有されるため、appendを使うとややこしいことになる。以下のコードでは、yはサイズ2, キャパシティ4に設定されるので、30をappendするとxの3が入っていた場所が上書きされてしまう。
x := []int{1, 2, 3, 4} y := x[:2] fmt.Println(cap(x), cap(y)) // 4 4 y = append(y, 30) fmt.Println("x:", x) // x: [1 2 30 4] fmt.Println("y:", y) // y: [1 2 30]
解決法
appendの問題についてはフルスライス式という解決策がある。具体的には、キャパシティをスライスのスライスの長さに明示的に指定してあげることで、appendした際に新しくメモリを確保してくれて干渉を防ぐことができる。(とはいえ、フルスライス式はそんなに頻繁に出くわすものではなさそう。)
x := []int{1, 2, 3, 4} y := x[:2:2] fmt.Println(cap(x), cap(y)) // 4 4 y = append(y, 30) fmt.Println("x:", x) // x: [1 2 3 4] fmt.Println("y:", y) // y: [1 2 30]
参考文献: