序章

Goでは、配列スライスは、要素の順序付けられたシーケンスで構成されるデータ構造です。 これらのデータコレクションは、関連する多くの値を処理する場合に使用すると便利です。 これらを使用すると、一緒に属するデータをまとめ、コードを凝縮し、複数の値に対して同じメソッドと操作を一度に実行できます。

Goの配列とスライスはどちらも要素の順序付けられたシーケンスですが、2つの間には大きな違いがあります。 Goのarrayは、データ構造であり、作成時に容量が定義された要素の順序付けられたシーケンスで構成されます。 配列がそのサイズを割り当てると、サイズは変更できなくなります。 一方、スライスは、配列の可変長バージョンであり、これらのデータ構造を使用する開発者により多くの柔軟性を提供します。 スライスは、他の言語の配列と考えるものを構成します。

これらの違いを考えると、一方を他方の上に使用する特定の状況があります。 Goを初めて使用する場合、いつ使用するかを決定するのは混乱を招く可能性があります。スライスの多様性により、ほとんどの状況でより適切な選択になりますが、配列によってプログラムのパフォーマンスを最適化できる特定のインスタンスがあります。

この記事では、配列とスライスについて詳しく説明し、これらのデータ型から選択するときに適切な選択を行うために必要な情報を提供します。 さらに、配列とスライスの両方を宣言して操作するための最も一般的な方法を確認します。 チュートリアルでは、最初に配列とその操作方法について説明し、次にスライスとその違いについて説明します。

配列

配列は、設定された数の要素を持つコレクションデータ構造です。 配列のサイズは静的であるため、データ構造はメモリを1回割り当てるだけで済みます。これに対して、可変長のデータ構造では、将来メモリを大きくしたり小さくしたりできるように動的にメモリを割り当てる必要があります。 配列の長さが固定されていると、配列の操作が多少難しくなる可能性がありますが、1回限りのメモリ割り当てにより、プログラムの速度とパフォーマンスが向上する可能性があります。 このため、開発者は通常、データ構造が可変量の要素を必要としない場合にプログラムを最適化するときに配列を使用します。

配列の定義

配列は、括弧[ ]で配列のサイズを宣言し、その後に要素のデータ型を指定することで定義されます。 Goの配列は、すべての要素が同じデータ型である必要があります。 データ型の後に、中括弧{ }で配列要素の個々の値を宣言できます。

配列を宣言するための一般的なスキーマは次のとおりです。

[capacity]data_type{element_values}

注:新しい配列を宣言するたびに、異なる型が作成されることを覚えておくことが重要です。 したがって、[2]int[3]intはどちらも整数要素を持っていますが、長さが異なるため、データ型に互換性がありません。

配列の要素の値を宣言しない場合、デフォルトはゼロ値です。これは、配列の要素が空になることを意味します。 整数の場合、これは0で表され、文字列の場合、これは空の文字列で表されます。

たとえば、次の配列numbersには、まだ値がない3つの整数要素があります。

var numbers [3]int

numbersを印刷すると、次の出力が表示されます。

Output
[0 0 0]

配列を作成するときに要素の値を割り当てる場合は、値を中括弧で囲みます。 設定値を持つ文字列の配列は次のようになります。

[4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}

配列を変数に格納して出力できます。

coral := [4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}
fmt.Println(coral)

前の行でプログラムを実行すると、次の出力が得られます。

Output
[blue coral staghorn coral pillar coral elkhorn coral]

印刷時に配列内の要素間に線引きがないため、ある要素がどこで終わり、別の要素がどこで始まるかを判断するのが難しいことに注意してください。 このため、代わりにfmt.Printf関数を使用すると便利な場合があります。この関数を使用すると、文字列を画面に出力する前にフォーマットできます。 %q動詞にこのコマンドを指定して、値を引用符で囲むように関数に指示します。

fmt.Printf("%q\n", coral)

これにより、次のようになります。

Output
["blue coral" "staghorn coral" "pillar coral" "elkhorn coral"]

これで、各アイテムが引用されます。 \n動詞は、最後に改行を追加するようにフォーマッターに指示します。

配列を宣言する方法とその構成についての一般的な考え方を理解したら、次に、配列内の要素をインデックス番号で指定する方法の学習に進むことができます。

配列(およびスライス)のインデックス作成

配列(およびスライス)の各要素は、インデックス付けによって個別に呼び出すことができます。 各要素はインデックス番号に対応します。これは、インデックス番号0から始まり、カウントアップするint値です。

次の例では配列を使用しますが、両方のインデックスを作成する方法が同じであるため、スライスを使用することもできます。

配列coralの場合、インデックスの内訳は次のようになります。

「ブルーコーラル」 「シカツノサンゴ」 「柱珊瑚」 「エルクホーンコーラル」
0 1 2 3

最初の要素である文字列"blue coral"は、インデックス0で始まり、スライスは、要素"elkhorn coral"でインデックス3で終わります。

スライスまたは配列の各要素には対応するインデックス番号があるため、他のシーケンシャルデータ型と同じ方法でそれらにアクセスして操作できます。

これで、インデックス番号を参照して、スライスの個別要素を呼び出すことができます。

fmt.Println(coral[1])
Output
staghorn coral

このスライスのインデックス番号は、前の表に示すように、0-3の範囲です。 したがって、要素のいずれかを個別に呼び出すには、次のようなインデックス番号を参照します。

coral[0] = "blue coral"
coral[1] = "staghorn coral"
coral[2] = "pillar coral"
coral[3] = "elkhorn coral"

インデックス番号が3より大きい配列coralを呼び出すと、有効ではなくなるため、範囲外になります。

fmt.Println(coral[18])
Output
panic: runtime error: index out of range

配列またはスライスにインデックスを付けるときは、常に正の数を使用する必要があります。 負の数で逆方向にインデックスを付けることができる一部の言語とは異なり、Goでそれを行うとエラーが発生します。

fmt.Println(coral[-1])
Output
invalid array index -1 (index must be non-negative)

+演算子を使用して、配列またはスライス内の文字列要素を他の文字列と連結できます。

fmt.Println("Sammy loves " + coral[0])
Output
Sammy loves blue coral

インデックス番号0の文字列要素を文字列"Sammy loves "と連結することができました。

配列またはスライス内の要素に対応するインデックス番号を使用して、各要素に個別にアクセスし、それらの要素を操作できます。 これを実証するために、次に、特定のインデックスで要素を変更する方法を見ていきます。

要素の変更

インデックス番号を付けた要素を別の値に設定することで、インデックスを使用して配列またはスライス内の要素を変更できます。 これにより、スライスと配列のデータをより細かく制御でき、個々の要素をプログラムで操作できるようになります。

配列coralのインデックス1にある要素の文字列値を"staghorn coral"から"foliose coral"に変更する場合は、次のように変更できます。

coral[1] = "foliose coral"

coralを出力すると、配列は異なります。

fmt.Printf("%q\n", coral)
Output
["blue coral" "foliose coral" "pillar coral" "elkhorn coral"]

配列またはスライスの個々の要素を操作する方法がわかったので、コレクションのデータ型を操作するときに柔軟性を高めるいくつかの関数を見てみましょう。

len()で要素を数える

Goでは、len()は、配列とスライスの操作を支援するために作成された組み込み関数です。 文字列と同様に、len()を使用し、配列またはスライスをパラメーターとして渡すことで、配列またはスライスの長さを計算できます。

たとえば、coral配列に含まれる要素の数を見つけるには、次を使用します。

len(coral)

配列coralの長さを出力すると、次の出力が返されます。

Output
4

これにより、intデータ型の配列4の長さがわかります。これは、配列coralに4つの項目があるため正しいです。

coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}

より多くの要素を含む整数の配列を作成する場合は、これにもlen()関数を使用できます。

numbers := [13]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
fmt.Println(len(numbers))

これにより、次の出力が得られます。

Output
13

これらの例の配列には比較的少ない項目がありますが、len()関数は、非常に大きな配列に含まれる要素の数を判別する場合に特に役立ちます。

次に、コレクションデータ型に要素を追加する方法について説明し、配列の長さが固定されているため、これらの静的データ型を追加するとエラーが発生する方法を示します。

append()で要素を追加する

append()は、コレクションデータ型に要素を追加するGoの組み込みメソッドです。 ただし、このメソッドは、アレイで使用すると機能しません。 前述のように、配列がスライスと異なる主な方法は、配列のサイズを変更できないことです。 つまり、配列内の要素の値を変更することはできますが、定義後に配列を大きくしたり小さくしたりすることはできません。

coralアレイについて考えてみましょう。

coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}

この配列にアイテム"black coral"を追加するとします。 次のように入力して、配列でappend()関数を使用しようとした場合。

coral = append(coral, "black coral")

出力としてエラーが表示されます。

Output
first argument to append must be slice; have [4]string

これを修正するために、スライスデータ型、スライスを定義する方法、および配列からスライスに変換する方法について詳しく学びましょう。

スライス

スライスは、Goのデータ型であり、可変、または変更可能な順序付けられた要素のシーケンスです。 スライスのサイズは可変であるため、それらを使用する際の柔軟性が大幅に向上します。 将来拡張または縮小する必要がある可能性のあるデータコレクションを操作する場合、スライスを使用すると、コレクションの長さを操作しようとしたときにコードでエラーが発生しないようになります。 ほとんどの場合、この可変性は、アレイと比較した場合にスライスで必要になる可能性のあるメモリの再割り当てに見合う価値があります。 多くの要素を格納したり、要素を反復処理したりする必要があり、それらの要素を簡単に変更できるようにしたい場合は、スライスデータ型を使用することをお勧めします。

スライスの定義

スライスは、空の角括弧のセット([])と中括弧の間の要素のリスト({})で始まるデータ型を宣言することによって定義されます。 特定の長さを宣言するために括弧の間にintが必要な配列とは対照的に、スライスは括弧の間に何も持たず、可変長を表します。

文字列データ型の要素を含むスライスを作成しましょう。

seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp", "anemone"}

スライスを印刷すると、スライス内の要素を確認できます。

fmt.Printf("%q\n", seaCreatures)

これにより、次のようになります。

Output
["shark" "cuttlefish" "squid" "mantis shrimp" "anemone"]

コレクションの要素をまだ入力せずに特定の長さのスライスを作成する場合は、組み込みのmake()関数を使用できます。

oceans := make([]string, 3)

このスライスを印刷すると、次のようになります。

Output
["" "" ""]

特定の容量にメモリを事前に割り当てたい場合は、3番目の引数をmake()に渡すことができます。

oceans := make([]string, 3, 5)

これにより、長さが3で、事前に割り当てられた容量が5要素のゼロスライスが作成されます。

これで、スライスを宣言する方法がわかりました。 ただし、これでは、以前のcoralアレイで発生したエラーはまだ解決されていません。 append()関数をcoralで使用するには、最初に配列のセクションをスライスする方法を学ぶ必要があります。

アレイをスライスにスライスする

インデックス番号を使用して開始点と終了点を決定することにより、配列内の値のサブセクションを呼び出すことができます。 これは、配列のスライスと呼ばれ、[first_index:second_index]の形式で、コロンで区切られたインデックス番号の範囲を作成することでこれを行うことができます。 ただし、配列をスライスすると、結果は配列ではなくスライスになることに注意してください。

coral配列の真ん中の項目だけを、最初と最後の要素なしで印刷したいとします。 これを行うには、インデックス1で始まり、インデックス3の直前で終わるスライスを作成します。

fmt.Println(coral[1:3])

この行を使用してプログラムを実行すると、次のようになります。

Output
[foliose coral pillar coral]

[1:3]のようにスライスを作成する場合、最初の数値はスライスの開始位置(両端を含む)であり、2番目の数値は最初の数値と取得する要素の総数の合計です。

array[starting_index : (starting_index + length_of_slice)]

この例では、開始点として2番目の要素(またはインデックス1)を呼び出し、合計2つの要素を呼び出しました。 計算は次のようになります。

array[1 : (1 + 2)]

これが、この表記法にたどり着いた方法です。

coral[1:3]

配列の開始点または終了点をスライスの開始点または終了点として設定する場合は、array[first_index:second_index]構文の数値の1つを省略できます。 たとえば、配列coralの最初の3つの項目("blue coral""foliose coral"、および"pillar coral")を印刷する場合は、次のように実行できます。したがって、次のように入力します。

fmt.Println(coral[:3])

これは印刷されます:

Output
[blue coral foliose coral pillar coral]

これにより、配列の先頭が出力され、インデックス3の直前で停止しました。

配列の最後にすべての項目を含めるには、構文を逆にします。

fmt.Println(coral[1:])

これにより、次のスライスが得られます。

Output
[foliose coral pillar coral elkhorn coral]

このセクションでは、サブセクションをスライスして配列の個々の部分を呼び出す方法について説明しました。 次に、スライスを使用して配列全体をスライスに変換する方法を学習します。

配列からスライスへの変換

配列を作成し、可変長にする必要があると判断した場合は、配列をスライスに変換できます。 配列をスライスに変換するには、このチュートリアルの配列をスライスにスライスするステップで学習したスライスプロセスを使用します。ただし、今回は、エンドポイントを決定する両方のインデックス番号を省略してスライス全体を選択します。 :

coral[:]

変数coralをスライス自体に変換することはできないことに注意してください。これは、変数がGoで定義されると、その型を変更できないためです。 これを回避するには、配列の内容全体をスライスとして新しい変数にコピーします。

coralSlice := coral[:]

coralSliceを印刷すると、次の出力が表示されます。

Output
[blue coral foliose coral pillar coral elkhorn coral]

次に、新しく変換されたスライスでappend()を使用して、配列セクションのようにblack coral要素を追加してみます。

coralSlice = append(coralSlice, "black coral")
fmt.Printf("%q\n", coralSlice)

これにより、要素が追加されたスライスが出力されます。

Output
["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral"]

1つのappend()ステートメントに複数の要素を追加することもできます。

coralSlice = append(coralSlice, "antipathes", "leptopsammia")
Output
["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia"]

2つのスライスを組み合わせるには、append()を使用できますが、...展開構文を使用して、2番目の引数を展開して追加する必要があります。

moreCoral := []string{"massive coral", "soft coral"}
coralSlice = append(coralSlice, moreCoral...)
Output
["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

スライスに要素を追加する方法を学習したので、要素を削除する方法を見ていきます。

スライスからの要素の削除

他の言語とは異なり、Goには、スライスから要素を削除するための組み込み関数はありません。 アイテムは、スライスしてスライスから削除する必要があります。

要素を削除するには、その要素の前にあるアイテムをスライスし、その要素の後にあるアイテムをスライスしてから、削除する要素なしでこれら2つの新しいスライスを一緒に追加する必要があります。

iが削除する要素のインデックスである場合、このプロセスの形式は次のようになります。

slice = append(slice[:i], slice[i+1:]...)

coralSliceから、アイテム"elkhorn coral"を削除しましょう。 このアイテムは3のインデックス位置にあります。

coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}

coralSlice = append(coralSlice[:3], coralSlice[4:]...)

fmt.Printf("%q\n", coralSlice)
Output
["blue coral" "foliose coral" "pillar coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

これで、インデックス位置3の要素、文字列"elkhorn coral"は、スライスcoralSliceに含まれなくなりました。

同じ方法で範囲を削除することもできます。 アイテム"elkhorn coral"だけでなく、"black coral""antipathes"も削除したいとします。 これを実現するために、式で範囲を使用できます。

coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}

coralSlice = append(coralSlice[:3], coralSlice[6:]...)

fmt.Printf("%q\n", coralSlice)

このコードは、スライスからインデックス34、および5を取り出します。

Output
["blue coral" "foliose coral" "pillar coral" "leptopsammia" "massive coral" "soft coral"]

スライスに要素を追加および削除する方法がわかったので、スライスが任意の時点で保持できるデータの量を測定する方法を見てみましょう。

cap()を使用したスライスの容量の測定

スライスの長さは可変であるため、len()メソッドはこのデータ型のサイズを決定するための最良のオプションではありません。 代わりに、cap()関数を使用して、スライスの容量を学習できます。 これにより、スライスが保持できる要素の数が表示されます。これは、スライスにすでに割り当てられているメモリの量によって決まります。

注:アレイの長さと容量は常に同じであるため、cap()関数はアレイでは機能しません。

cap()の一般的な使用法は、事前設定された数の要素でスライスを作成し、それらの要素をプログラムで入力することです。 これにより、append()を使用して、現在割り当てられている容量を超える要素を追加することで発生する可能性のある不要な割り当てを回避できます。

0から3までの数字のリストを作成するシナリオを考えてみましょう。 ループでappend()を使用してこれを行うか、最初にスライスを事前に割り当て、cap()を使用してループして値を入力することができます。

まず、append()の使用を見てみましょう。

numbers := []int{}
for i := 0; i < 4; i++ {
	numbers = append(numbers, i)
}
fmt.Println(numbers)
Output
[0 1 2 3]

この例では、スライスを作成してから、4回繰り返すforループを作成しました。 各反復は、ループ変数iの現在の値をnumbersスライスのインデックスに追加しました。 ただし、これにより、プログラムの速度が低下する可能性のある不要なメモリ割り当てが発生する可能性があります。 空のスライスに追加する場合、appendを呼び出すたびに、プログラムはスライスの容量をチェックします。 追加された要素によってスライスがこの容量を超えた場合、プログラムはそれを考慮して追加のメモリを割り当てます。 これにより、プログラムに追加のオーバーヘッドが発生し、実行速度が低下する可能性があります。

次に、append()を使用せずに、特定の長さ/容量を事前に割り当てて、スライスにデータを入力します。

numbers := make([]int, 4)
for i := 0; i < cap(numbers); i++ {
	numbers[i] = i
}

fmt.Println(numbers)

Output
[0 1 2 3]

この例では、make()を使用してスライスを作成し、4要素を事前に割り当てました。 次に、ループ内のcap()関数を使用して、ゼロ化された各要素を反復処理し、事前に割り当てられた容量に達するまで各要素を埋めました。 各ループで、ループ変数iの現在の値をnumbersスライスのインデックスに配置しました。

append()戦略とcap()戦略はどちらも機能的に同等ですが、cap()の例では、append()関数を使用して必要となる追加のメモリ割り当てを回避しています。

多次元スライスの構築

他のスライスで構成されるスライスを要素として定義することもできます。各括弧で囲まれたリストは、親スライスの大きい括弧で囲まれています。 このようなスライスのコレクションは、多次元スライスと呼ばれます。 これらは、多次元座標を表すものと考えることができます。 たとえば、それぞれ6要素の長さの5つのスライスのコレクションは、水平方向の長さが5、垂直方向の高さが6の2次元グリッドを表すことができます。

次の多次元スライスを調べてみましょう。

seaNames := [][]string{{"shark", "octopus", "squid", "mantis shrimp"}, {"Sammy", "Jesse", "Drew", "Jamie"}}

このスライス内の要素にアクセスするには、構成の各次元に1つずつ、複数のインデックスを使用する必要があります。

fmt.Println(seaNames[1][0])
fmt.Println(seaNames[0][0])

上記のコードでは、最初にインデックス1のスライスのインデックス0にある要素を識別し、次にインデックス0。 これにより、次のようになります。

Output
Sammy shark

以下は、残りの個々の要素のインデックス値です。

seaNames[0][0] = "shark"
seaNames[0][1] = "octopus"
seaNames[0][2] = "squid"
seaNames[0][3] = "mantis shrimp"

seaNames[1][0] = "Sammy"
seaNames[1][1] = "Jesse"
seaNames[1][2] = "Drew"
seaNames[1][3] = "Jamie"

多次元スライスを操作する場合、関連するネストされたスライス内の特定の要素にアクセスするには、複数のインデックス番号を参照する必要があることに注意してください。

結論

このチュートリアルでは、Goで配列とスライスを操作するための基礎を学びました。 配列の長さが固定されているのに対し、スライスの長さは可変である方法を示すために複数の演習を行い、この違いがこれらのデータ構造の状況に応じた使用にどのように影響するかを発見しました。

Goでのデータ構造の学習を続けるには、 Goでのマップの理解に関する記事を確認するか、Goでのコーディング方法シリーズ全体を調べてください。