【Go言語学習#2】~配列とスライス~
はじめに
最近Go言語を学習し始めたので、基本文法の定着を図るために記事にまとめていきます。今回は配列
とスライス
に焦点を当てて学習していきます。
参考書
配列
Go言語の配列は固定長なので後から配列の大きさを変更することはできません。宣言方法はvar
と:=
を使う二つの方法があります。値を指定しない場合は、指定した型のゼロ値が各要素に入ります。
var arr1 [5]int // ゼロ値
var arr2 = [5]int{1, 2, 3, 4, 5}
arr3 := [5]int{1, 2, 3, 4, 5}
fmt.Printf("arr1 --> %d\n", arr1)
fmt.Printf("arr2 --> %d\n", arr2)
fmt.Printf("arr3 --> %d\n", arr3)
arr1 --> [0 0 0 0 0]
arr2 --> [1 2 3 4 5]
arr3 --> [1 2 3 4 5]
配列の型
Go言語では配列の型は、配列の大きさと指定した型によって決まります。以下の二つの配列はint
で宣言されていますが、、配列の大きさが異なるので型は異なります。
arr1 := [5]int{1, 2, 3, 4, 5}
arr2 := [2]int{100, 200}
fmt.Printf("arr1 --> Type: %T, Value: %d\n",arr1, arr1)
fmt.Printf("arr2 --> Type: %T, Value: %d\n",arr2, arr2)
arr1 --> Type: [5]int, Value: [1 2 3 4 5]
arr2 --> Type: [2]int, Value: [100 200]
要素数の省略
要素数を省略したい場合には、要素数ではなく...
を使用することで指定した要素数で配列を宣言することができます。
arr1 := [...]int{1, 2, 3, 4, 5}
arr2 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Printf("arr1 --> Type: %T, Value: %d\n", arr1, arr1)
fmt.Printf("arr2 --> Type: %T, Value: %d\n", arr2, arr2)
arr1 --> Type: [5]int, Value: [1 2 3 4 5]
arr2 --> Type: [9]int, Value: [1 2 3 4 5 6 7 8 9]
配列の代入
配列を変数に代入する際には配列のコピーが変数に代入されます。Go Slices: usage and internalsでは以下のような記述があります。
Go’s arrays are values. An array variable denotes the entire array; it is not a pointer to the first array element (as would be the case in C). This means that when you assign or pass around an array value you will make a copy of its contents.
Goの配列は値です。配列変数は配列全体を表します。(C言語の場合のように)配列の最初の要素へのポインタではありません。つまり、配列の値を代入したり渡したりすると、その内容のコピーが作成されます。
arr1 := [5]int{1, 2, 3, 4, 5}
arr2 := arr1
// arr1への変更はarr2に影響しない
arr1[0] = 100
fmt.Printf("arr1 --> Pointer: %p, Value: %d\n", &arr1, arr1)
fmt.Printf("arr2 --> Pointer: %p, Value: %d\n", &arr2, arr2)
arr1 --> Pointer: 0x140000162a0, Value: [100 2 3 4 5]
arr2 --> Pointer: 0x140000162d0, Value: [1 2 3 4 5]
スライス
Go言語の「可変長の配列」がスライスです。スライスは以下のように宣言します。配列の宣言と違い、[]
内の大きさの指定をしません。
var slice1 []int // ゼロ値
var slice2 = []int{1, 2, 3, 4, 5}
slice3 := []int{1, 2, 3, 4, 5}
fmt.Printf("slice1 --> %d\n", slice1)
fmt.Printf("slice2 --> %d\n", slice2)
fmt.Printf("slice3 --> %d\n", slice3)
slice1 --> []
slice2 --> [1 2 3 4 5]
slice3 --> [1 2 3 4 5]
スライスのゼロ値
スライスのゼロ値はnil
という値になります。
var slice1 []int
if slice1 == nil {
fmt.Printf("slice1 is nil\n")
}
slice1 is nil
要素の追加
スライスの要素を追加するにはappend
を使います。append
関数の第一引数に対象のスライスを、第二引数以降に対象のスライスに追加したい要素を指定します。
slice := []int{1}
slice = append(slice, 2, 3, 4, 5)
fmt.Printf("slice --> %d\n", slice)
slice --> [1 2 3 4 5]
...
を使用することで対象のスライスの最後に別スライスの全要素を追加できるようになります。
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{6, 7, 8, 9}
slice1 = append(slice1, ...slice2)
fmt.Printf("slice1 --> %d\n", slice1)
slice1 --> [1 2 3 4 5 6 7 8 9]
長さ(len)と容量(cap)
スライスは長さと容量を持っています。(長さは配列も持っている)スライスの長さと容量が同じ大きさで、append
によってさらに要素を追加しようとすると、より大きな容量を持つスライスの領域を確保し、そこに新しいスライスのコピーとappendに指定された要素が最後に追加されたスライスが返されます。
容量の変化を追ってみる
var slice []int
slice = append(slice, 1)
fmt.Printf("Length: %d, Capacity: %d, Value: %d\n", len(slice), cap(slice), slice)
slice = append(slice, 2)
fmt.Printf("Length: %d, Capacity: %d, Value: %d\n", len(slice), cap(slice), slice)
slice = append(slice, 3)
fmt.Printf("Length: %d, Capacity: %d, Value: %d\n", len(slice), cap(slice), slice)
slice = append(slice, 4)
fmt.Printf("Length: %d, Capacity: %d, Value: %d\n", len(slice), cap(slice), slice)
slice = append(slice, 5)
fmt.Printf("Length: %d, Capacity: %d, Value: %d\n", len(slice), cap(slice), slice)
Length: 1, Capacity: 1, Value: [1]
Length: 2, Capacity: 2, Value: [1 2]
Length: 3, Capacity: 4, Value: [1 2 3]
Length: 4, Capacity: 4, Value: [1 2 3 4]
Length: 5, Capacity: 8, Value: [1 2 3 4 5]
配列からスライスを生成する
スライス式
を使用して配列からスライスを生成することができます。
arr := [5]int{1, 2, 3, 4, 5}
slice1 := arr[:] // 最初から最後まで
slice2 := arr[:3] // 最初から指定した位置
slice3 := arr[1:3] // 指定した範囲(開始 - 終了)
slice4 := arr[2:] // 指定した位置から最後まで
fmt.Printf("arr -----> Pointer: %p, Refer: %p, Value: %d\n", &arr, &arr[0], arr)
fmt.Printf("slice1 --> Pointer: %p, Refer: %p, Value: %d\n", &slice1, &slice1[0], slice1)
fmt.Printf("slice2 --> Pointer: %p, Refer: %p, Value: %d\n", &slice2, &slice2[0], slice2)
fmt.Printf("slice3 --> Pointer: %p, Refer: %p, Value: %d\n", &slice3, &slice3[0], slice3)
fmt.Printf("slice4 --> Pointer: %p, Refer: %p, Value: %d\n", &slice4, &slice4[0], slice4)
arr -----> Pointer: 0x140000162a0, Refer: 0x140000162a0, Value: [1 2 3 4 5]
slice1 --> Pointer: 0x1400000c2b8, Refer: 0x140000162a0, Value: [1 2 3 4 5]
slice2 --> Pointer: 0x1400000c2d0, Refer: 0x140000162a0, Value: [1 2 3]
slice3 --> Pointer: 0x1400000c2e8, Refer: 0x140000162a8, Value: [2 3]
slice4 --> Pointer: 0x1400000c300, Refer: 0x140000162b0, Value: [3 4 5]
上記のコードを図示しました。配列からスライスを生成すると配列がコピーされるのではなく、スライスは元の配列を指していることがわかります。スライスは元の配列を指しているため、配列の要素を変更すれば当然slice1~4にも影響します。
スライスからスライスを生成する
スライス式
を使用して配列からスライスを生成できましたが、スライスからもスライスを生成することができます。
slice := [5]int{1, 2, 3, 4, 5}
slice1 := slice[:]
fmt.Printf("slice ---> Pointer: %p, Refer: %p, Value: %d\n", &slice, &slice[0], slice)
fmt.Printf("slice1 --> Pointer: %p, Refer: %p, Value: %d\n", &slice1, &slice[0], slice1)
slice ---> Pointer: 0x140000162a0, Refer: 0x140000162a0, Value: [1 2 3 4 5]
slice1 --> Pointer: 0x1400000c2b8, Refer: 0x140000162a0, Value: [1 2 3 4 5]
Refer
が同一なので、一方のスライスを変更するともう一方のスライスにも影響します。
slice := [5]int{1, 2, 3, 4, 5}
slice1 := slice[:]
slice[0] = 100
slice1[3] = 400
fmt.Printf("slice ---> Pointer: %p, Refer: %p, Value: %d\n", &slice, &slice[0], slice)
fmt.Printf("slice1 --> Pointer: %p, Refer: %p, Value: %d\n", &slice1, &slice[0], slice1)
slice ---> Pointer: 0x140000b21e0, Refer: 0x140000b21e0, Value: [100 2 3 400 5]
slice1 --> Pointer: 0x140000b02a0, Refer: 0x140000b21e0, Value: [100 2 3 400 5]
スライスを複製する
スライスを代入するとメモリを共有するため、変更が互いに影響し合います。メモリを共有せずスライスを複製したい場合は、copy
関数を使用します。
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, 5)
copy(slice2, slice1)
slice1[0] = 100
slice2[1] = 200
fmt.Printf("slice1 --> Pointer: %p, Refer: %p, Value: %d\n", &slice1, &slice1[0], slice1)
fmt.Printf("slice2 --> Pointer: %p, Refer: %p, Value: %d\n", &slice2, &slice2[0], slice2)
slice1 --> Pointer: 0x1400000c2b8, Refer: 0x140000162a0, Value: [100 2 3 4 5]
slice2 --> Pointer: 0x1400000c2d0, Refer: 0x140000162d0, Value: [1 200 3 4 5]
まとめ
普段触っているTypeScriptとは宣言方法や代入時のメモリ共有などが異なる部分なので、手を動かして言語使用を確認することでよりGo言語の配列とスライスについて理解が進みました。
一人前のGopher目指して頑張ります💪
参考
Discussion