Goでの配列とスライスについて

前書き

Goでは、arraysslicesdata structuresであり、要素の順序付けられたシーケンスで構成されます。 これらのデータコレクションは、多くの関連する値を操作する場合に便利です。 これらは、一緒に属するデータをまとめ、コードを圧縮し、複数の値に対して同じメソッドと操作を一度に実行できるようにします。

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

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

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

配列

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

配列の定義

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

以下は、配列を宣言するための一般的なスキーマです。

[capacity]data_type{element_values}

[.note]#Note:新しい配​​列を宣言するたびに、異なる型が作成されることを覚えておくことが重要です。 したがって、[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]

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

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

これにより、次の結果が得られます。

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

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

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

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

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

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

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

「ブルーコーラル」 「シカツノサンゴ」 「ピラーコーラル」 「エルクホーンコーラル」

0

1

2

3

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

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

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

fmt.Println(coral[1])
Outputstaghorn 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])
Outputpanic: runtime error: index out of range

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

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

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

fmt.Println("Sammy loves " + coral[0])
OutputSammy 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の長さを出力すると、次の出力が返されます。

Output4

これにより、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))

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

Output13

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

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

append()で要素を追加する

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

coral配列について考えてみましょう。

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

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

coral = append(coral, "black coral")

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

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

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

スライス数

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

スライスの定義

スライスは、空の角括弧のセット([])と中括弧({})の間の要素のリストが前に付いたデータ型を宣言することによって定義されます。 特定の長さを宣言するために角かっこの間に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で使用するには、最初に配列のセクションをスライスする方法を学習する必要があります。

配列をスライスにスライスする

インデックス番号を使用して開始点と終了点を決定することにより、配列内の値のサブセクションを呼び出すことができます。 これはslicing配列と呼ばれ、コロンで区切られたインデックス番号の範囲を[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]

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

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

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

coral[:]

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

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()+`method is not the best option to determine the size of this data type. Instead, you can use the `+cap()はスライスの容量を学習するために機能します。 これにより、スライスが保持できる要素の数が表示されます。これは、スライスに既に割り当てられているメモリの量によって決まります。

[.note]#Note:配列の長さと容量は常に同じであるため、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()関数を使用して必要となる追加のメモリ割り当てを回避しています。

多次元スライスの構築

また、他のスライスで構成されるスライスを要素として定義することもできます。各スライスのリストは、親スライスの大きな括弧で囲まれます。 このようなスライスのコレクションはmultidimensional slicesと呼ばれます。 これらは、多次元座標を描いていると考えることができます。たとえば、長さがそれぞれ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のスライスのインデックス0にある要素を示します。 。 これにより、次の結果が得られます。

OutputSammy
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でデータ構造の調査を続けるには、Understanding Maps in Goに関する記事を確認するか、How To Code in Goシリーズ全体を調べてください。