Go 言語におけるスライスの内部構造には、アドレス、サイズ、容量が含まれます。スライスは通常、データ収集の一部を迅速に操作するために使用されます。データ収集をケーキの切り分けに例えると、スライスは必要な「部分」です。カット工程には、どこから切り出すか(スライスの開始位置)、どこまでカットするか(スライスのサイズ)が含まれます。容量は、下図に示すように、スライスのポケットのサイズとして理解できます。
配列またはスライスから新しいスライスを生成する
デフォルトでは、スライスは連続したメモリ領域を指します。これは配列またはスライス自体の場合があります。
連続したメモリ領域からスライスを生成するのは一般的な操作であり、その形式は次のとおりです。
slice [開始位置 : 終了位置]
構文は次のように説明されます。
- スライス: ターゲットのスライス オブジェクトを示します。
- 開始位置: ターゲット スライス オブジェクトに対応するインデックス。
- 終了位置: ターゲット スライスの終了インデックスに対応します。
配列からスライスを生成するコードは次のとおりです。
var a[3]int{1, 2, 3}
fmt.Println(a, a[1:2])
このうち、a は 3 つの整数要素を持つ配列で、1 ~ 3 の値に初期化されており、a[1:2] を使用して新しいスライスを生成できます。コード操作の結果は次のとおりです。
[1 2 3] [2]
ここで、[2] は [1:2] スライス操作の結果です。
配列またはスライスからの新しいスライスの生成には、次の特性があります。
- 取り出す要素数は、終了位置-開始位置、となります。
- 取得した要素には終了位置に対応するインデックスが含まれておらず、スライスの最後の要素はslice[len(slice)];を使用して取得されます。
- デフォルトの開始位置とは、連続領域の先頭から終了位置までを指します。
- デフォルトの終了位置を使用する場合、開始位置から連続領域全体の終了までを意味します。
- 両方が同時にデフォルトに設定されると、それはスライス自体と同等になります。
- 両方が同時に 0 の場合、それは空のスライスと同等であり、一般にスライスのリセットに使用されます。
インデックス位置に応じてスライス要素の値を取得する場合、値の範囲は (0~len(slice)-1) であり、制限を超えると実行時エラーが報告されます。スライスを生成するときに、終了位置を指定できます。 len(slice) で埋められますが、エラーは報告されません。
次の例は、スライスの特性を理解するために使用されます。
1) 指定範囲からスライスを生成
スライスとアレイは切り離せないものです。アレイをオフィスビルとして理解すると、スライスとは、連続する異なるフロアをユーザーに貸し出すことです。賃貸のプロセスでは、開始フロアと終了フロアを選択する必要があります。このプロセスによりスライスが生成されます。サンプルコードは次のとおりです。
var highRiseBuilding [30]int
for i := 0; i < 30; i++ {
highRiseBuilding[i] = i + 1
}
// 範囲
fmt.Println(highRiseBuilding[10:15])
// 中央から末尾までのすべての要素
fmt.Println(highRiseBuilding[20:])
// 先頭から指定した位置までのすべての要素
fmt.Println(highRiseBuilding[:2])
コード出力は次のとおりです。
[11 12 13 14 15]
[21 22 23 24 25 26 27 28 29 30]
[1 2]
コードでは 30 階建ての高層ビルが建設されており、配列の要素値は 1 から 30 の範囲で、異なる独立したフロアを表し、出力結果は異なる賃貸計画と販売計画になります。
コードの説明は次のとおりです。
- 8 行目で、セクション フロアを賃貸しようとしています。
- 11号線、20階以上の賃貸物件。
- 14 号線、2 階以下の賃貸用、通常は商業用途。
スライスは C 言語のポインタに似ています。ポインタは計算に使用できますが、メモリ操作が範囲外になるという代償が伴います。スライスはポインタに基づいてサイズを増加させ、スライスに対応するメモリ領域を制限します。スライスを使用する場合、スライス内のアドレスとサイズは変更できません。手動で調整されるため、スライスはポインタよりも安全で堅牢です。
2) 元のスライスを表します
生成されたスライスの形式では、開始位置と終了位置が無視されると、生成されたスライスは元のスライスと同じスライスを表し、生成されたスライスはデータの内容に関しても元のスライスと一致します。次のように:
a := []int{1, 2, 3}
fmt.Println(a[:])
a は 3 つの要素を持つスライスです。スライス a を a[:] で操作した後、結果のスライスはスライス a と一致します。コード出力は次のとおりです。
[1 2 3]
3) スライスをリセットし、所有要素をクリアします
スライスの開始位置と終了位置の両方が 0 に設定されている場合、生成されたスライスは空になります。コードは次のとおりです。
a := []int{1, 2, 3}
fmt.Println(a[0:0])
a := []int{1, 2, 3}
fmt.Println(a[0:0])
コード出力は次のとおりです。
[]
新しいスライスを直接宣言する
元の配列またはスライスからスライスを生成することに加えて、新しいスライスを宣言することもできます。各型は、同じ型の複数の要素の連続コレクションを表すスライス タイプを持つことができるため、スライス タイプを宣言することもできます。スライスタイプの宣言形式は次のとおりです。
var name []Type
このうち、nameはスライスの変数名を表し、Typeはスライスに対応する要素の型を表します。
次のコードは、スライス宣言の使用法を示しています。
// 文字列スライスを宣言する
var strList []string
// 整数スライスを宣言する
var numList []int
// 空スライスを宣言する
var numListEmpty = []int{}
// 3つのスライスを出力する
fmt.Println(strList、numList、numListEmpty)
// 3つのスライスのサイズを出力する
fmt.Println(len(strList)、len(numList)、len(numListEmpty))
// スライスの空の判定結果を出力する
fmt.Println(strList == nil)
fmt.Println(numList == nil)
fmt.Println(numListEmpty == nil)
コード出力結果:
[] [] []
0 0 0
true
true
false
コードの説明は次のとおりです。
- 行 2 では、複数の文字列を含む文字列スライスを宣言しています。
- 5 行目では、複数の整数値を含む整数スライスを宣言しています。
- 8 行目では、 numListEmpty を整数スライスとして宣言しています。これにより、
{}
内のスライスの初期化要素が埋められますが、ここには埋め込みがないため、スライスは空になります。 ただし、この時点では、numListEmpty にはメモリが割り当てられていますが、メモリはありません。要素はまだあります。 - 11 行目では、どのスライスにも要素がなく、3 つのスライスの出力要素の内容はすべて空です。
- 14 行目では、スライスに対して操作は実行されず、strList と numList は配列や他のスライスを指しません。
- 17行目と18行目では、宣言されているが使用されていないスライスのデフォルト値はnilであり、strListとnumListもnilなので、nilとの比較結果はtrueとなります。
- 19 行目では、numListEmpty が割り当てられていますが、要素がないため、nil と比較すると false になります。
スライスは動的構造であり、スライス同士ではなく、nil に等しいとのみ判断できます。新しいスライスを宣言した後、 append()関数を使用して要素をスライスに追加できます。
スライスを動的に作成する必要がある場合は、組み込み関数 make() を使用できます。形式は次のとおりです。
make( []Type, size, cap )
このうち、Type はスライスの要素タイプを指し、size はこのタイプに割り当てられる要素の数を指し、cap は事前に割り当てられる要素の数です。この値を設定した後は、サイズには影響しませんが、複数の割り当てによるパフォーマンスの問題のリスクを軽減するために、事前にスペースを割り当てることができます。
例は次のとおりです。
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
コード出力は次のとおりです。
[0 0] [0 0]
2 2
a と b はどちらも 2 つの要素を持つ事前に割り当てられたスライスですが、b の内部ストレージ領域には 10 個のスライスが割り当てられていますが、実際には 2 つの要素が使用されます。
容量は現在の要素数には影響しないため、a と b の len は両方とも 2 になります。
親切なヒント
make() 関数を使用して生成されたスライスにはメモリ割り当て操作が必要ですが、指定された開始位置と終了位置を持つスライス (スライスのリセットを含む) は、新しいスライス構造が割り当てられたメモリ領域を指し、開始位置と終了位置を設定するだけです。メモリ割り当て操作は行われません。