Menu

Go言語の配列:固定長、値としての振る舞い、スライスとの違い

Goの配列は型の一部である固定長を持ち、代入時にコピーされます。配列の宣言、走査、比較、受け渡しの方法と、ほとんどのGoのコードが代わりにスライスを使う理由を解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

配列を宣言する

配列の型は [N]T と書きます。長さの後に要素の型が続きます。長さはコンパイル時に固定され、型の一部になります。

出力:

[90 0 0 0 75] 5
Tue
[5]int [2 3 5 7 11]
["" "b" "" "d" ""]

配列は常にすべて初期化されています。設定しなかった要素は、要素の型のゼロ値(0""falsenil)を保持します。

インデックスと範囲

インデックスは 0 から len(a)-1 までです。範囲外の定数インデックスはコンパイルエラーになります。範囲外の変数インデックスは実行時にpanicします。

var a [3]int
a[5] = 1 // compile error: invalid argument: index 5 out of bounds [0:3]

i := 5
a[i] = 1 // panic: runtime error: index out of range [5] with length 3

走査する

for i, v := range a は各要素を順に訪れます。v は要素のコピーなので、v に代入しても配列は変わりません。代わりにインデックスを通して書き込みます。

配列に特有の細かな点がもう1つあります。配列の値に対する range は、最初に配列のコピーを評価します。本体が後ろの要素を変更しても、v は古い値のままです。&aa[:] に対してrangeすればコピーを避けられます。

配列は値

これがC、Java、JavaScriptの配列との主な違いです。配列を代入するとすべての要素がコピーされます。配列を関数に渡してもコピーされます。

出力:

[1 2 3] [99 2 3]
[1 2 3]
[0 2 3]

小さな配列ならコピーは安価ですが、大きな配列では高くつきます。[1_000_000]int を値で渡すと8メガバイトがコピーされます。配列が大きいときや関数が変更する必要があるときは、ポインタ(*[N]T)かスライス(a[:])を渡します。配列へのポインタを通したインデックスには明示的な * は不要で、*[3]int に対して a[0] が使えます。

配列の比較とマップのキー

比較可能な要素の配列は ==!= をサポートします。2つの配列は、すべての要素が等しいときに等しくなります。そのため、スライスと違ってマップのキーとしても使えます。

長さの違う配列は別の型なので、そもそも比較できません。[3]int{} == [4]int{} はコンパイルできません(mismatched types [3]int and [4]int)。

多次元配列

配列の配列は固定のグリッドになります。グリッド全体が1つの連続したメモリブロックです。

row[:][3]rune の行を []rune のスライスに変え、それを string() が受け付けます。

配列とスライスの比較

配列 [N]Tスライス []T
長さ固定、型の一部append で変わる
代入と受け渡しすべての要素をコピーする小さなヘッダーをコピーし、要素を共有する
ゼロ値N個のゼロ値の要素nil、長さ0
==使える、要素ごとに比較nil との比較のみ
マップのキーなれるなれない
典型的な用途固定サイズのデータ:ハッシュ、座標、バッファほぼそれ以外すべて

実際に配列を目にするのは、いくつかの場所に限られます。sha256.Sum256 が返す [32]byte、サイズが変わらないルックアップテーブル、そしてスライスの元の配列です。a[:]a[1:3] で配列をスライスすると、メモリを共有するスライスが作られます。その共有が何を意味するかはスライスのページで扱っています。

配列とスライスの変換

スライスを配列(Go 1.20)や配列へのポインタ(Go 1.17)に変換すると、スライスが配列の長さより短い場合はpanicします:panic: runtime error: cannot convert slice with length 2 to array or pointer to array with length 3

よくある間違い

  • 関数が配列の引数を変更すると期待する。 関数はコピーを受け取ります。*[N]T かスライスを渡します。
  • スライスが必要な場所で配列を使う。 []int{1, 2} はスライス、[2]int{1, 2} は配列です。[]int を受け取る関数は [2]int を受け付けないので、a[:] を渡します。
  • rangeループでの大きな配列。 for _, v := range bigArray は最初に配列全体をコピーします。&bigArray かスライスに対してrangeします。

よくある質問

Goで配列はどう宣言しますか?

要素の型の前に、角かっこで長さを書きます:var a [5]int はゼロを5つ作ります。値を指定するなら a := [3]string{"x", "y", "z"} です。[...] を使うとコンパイラが数えてくれます:a := [...]int{1, 2, 3} の型は [3]int です。

Goの配列とスライスの違いは何ですか?

配列は型の一部である固定長を持ち([3]int[4]int は別の型です)、代入したり渡したりするとコピーされます。スライス([]int)は元の配列に対するビューで、長さは append で変わり、スライスをコピーしても要素ではなくビューだけがコピーされます。ほとんどのGoのコードはスライスを使います。

Goで配列の長さを取得するには?

組み込みの len(a) を使います。配列の場合はコンパイル時の定数なので、定数式の中でも使えます。cap(a) も同じ値を返します。

Goで配列を比較できますか?

要素の型が比較可能なら、==!= で比較できます。2つの配列は、すべての要素が等しいときに等しくなります。配列はマップのキーにもなれます。スライスは(nil との比較を除いて)== で比較できないので、slices.Equal を使います。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める