Go言語のスライス

8386 ワード

スライスもデータ構造であり、配列と非常に似ている.動的配列の概念をめぐって設計されているため、必要に応じて自動的にサイズを変更することができ、この構造を使用すると、データセットの管理と使用をより容易にすることができる.
内部実装
スライスは配列に基づいて実現され,その下層は配列であり,それ自体は非常に小さく,下層配列の抽象と理解できる.チャンス配列が実現されるため,その下位層のメモリは連続的に非整合であり,非常に効率的である.また、インデックスを使用してデータを取得したり、反復したり、ゴミ回収を最適化したりすることもできます.
スライスオブジェクトが非常に小さいのは、3つのフィールドのみのデータ構造であるためです.1つは最下位の配列を指すポインタであり、1つはスライスの長さであり、1つはスライスの容量です.この3つのフィールドは,Go言語が下位配列を操作するメタデータであり,それらがあればスライスを任意に操作できる.
宣言と初期化
スライスの作成方法はいくつかありますが、まず最も簡潔なmakeの方法を見てみましょう.
slice:=make([]int,5)

内蔵のmake関数を使用する場合は、パラメータを入力してスライスの長さを指定する必要があります.例では、スライスの容量も5です.もちろん、スライスの容量を個別に指定することもできます.
slice:=make([]int,5,10)

このとき,我々が作成したスライス長は5であり,容量は10である.なお、この容量10は、実際にはスライス下位配列に対応している.
スライスの下部が配列であるため、スライスを作成する際に、フォント値を指定しないと、デフォルト値は配列の要素のゼロ値になります.ここでは容量が10であることを指定しましたが、5つの要素にしかアクセスできません.スライスの長さは5で、残りの5つの要素は、スライスが拡張されてからアクセスできる必要があります.
容量は>=長さでなければなりません.容量よりも長いスライスを作成することはできません.
もう1つのスライスを作成する方法は、字面量を使用して、初期化の値を指定することです.
slice:=[]int{1,2,3,4,5}

配列の作成によく似ていますが、[]の値を設定する必要はありません.このときスライスの長さと容量は等しく,我々が指定した字面量に基づいて導出される.もちろん、配列のようにインデックスの値だけを初期化することもできます.
slice:=[]int{4:1}

これは、5番目の要素が1であることを指定し、他の要素はデフォルト値0です.このときスライスの長さや容量も同じです.ここではスライスと配列のわずかな違いを改めて強調する.
//  
array:=[5]int{4:1}
//  
slice:=[]int{4:1}

スライスにはnilスライスと空スライスもあり、それらの長さと容量は0です.しかし、それらが下位配列を指すポインタは異なり、nilスライスは、下位配列を指すポインタがnilであり、空のスライスに対応するポインタがアドレスであることを意味する.
//nil  
var nilSlice []int
//   
slice:=[]int{}

nilスライスは存在しないスライスを表し、空スライスはそれぞれ役に立つ空の集合を表す.
スライスのもう一つの用途が多い作成は、既存の配列またはスライスに基づいて作成されます.
slice := []int{1, 2, 3, 4, 5}
slice1 := slice[:]
slice2 := slice[0:]
slice3 := slice[:5]

fmt.Println(slice1)
fmt.Println(slice2)
fmt.Println(slice3)

既存のスライスまたは配列に基づいて作成され、[i:j]のようなオペレータを使用すればよい.iインデックスで始まり、jインデックスで終わり、元の配列またはスライスを切り取って作成された新しいスライスを表します.新しいスライスの値には、元のスライスのiインデックスが含まれますが、jインデックスは含まれません.Javaと比べると、StringのsubStringの方法に似ていることがわかります.i省略すると、デフォルトは0です.j省略すると、デフォルトは元の配列またはスライスの長さです.したがって、例の3つの新しいスライスの値は同じである.ここで、iおよびjは、いずれも元のスライスまたは配列のインデックスを超えてはならないことに注意する.
slice := []int{1, 2, 3, 4, 5}
newSlice := slice[1:3]

newSlice[0] = 10

fmt.Println(slice)
fmt.Println(newSlice)

この例は,新しいスライスと元のスライスが共通して最下位配列であることを実証した.したがって,修正すると下位配列の値が変化するので,元のスライスの値も変化する.もちろん配列ベースのスライスについても同様である.
元の配列またはスライスに基づいて新しいスライスを作成した後、新しいスライスのサイズと容量はいくらですか?ここに式があります.
         k   slice[i:j]  
  :j-i
  :k-i

例えば、上記の例slice[1:3]では、長さは3-1=2であり、容量は5-1=4である.しかし、コードでは、Go言語がスライスの長さと容量を計算するために内蔵されたlenおよびcap関数を提供しているため、計算はそんなに面倒ではありません.
slice := []int{1, 2, 3, 4, 5}
newSlice := slice[1:3]

fmt.Printf("newSlice  :%d,  :%d",len(newSlice),cap(newSlice))

以上は、1つの配列またはスライスに基づいて2つのインデックスを使用して新しいスライスを作成する方法です.さらに、slice[i:j:k]で使用される新しいスライスの容量を限定するための3つのインデックスの方法もある.
slice := []int{1, 2, 3, 4, 5}
newSlice := slice[1:2:3]

これにより、2-1=1の長さ、3-1=2の容量の新しいスライスを作成しましたが、3番目のインデックスは、元のスライスの最大インデックス値5を超えることはできません.
スライスの使用
スライスを使用すると、配列を使用するのと同様に、インデックスを使用してスライス対応要素の値を取得できます.また、対応要素の値を変更することもできます.
slice := []int{1, 2, 3, 4, 5}
fmt.Println(slice[2]) //   
slice[2] = 10 //   
fmt.Println(slice[2]) //  10

スライスはその長さ内の要素のみにアクセスでき、長さ外の要素にアクセスすると、実行時に異常が発生し、スライス容量に関連付けられた要素はスライス成長にのみ使用できます.
前述したように、スライスは動的配列であるため、必要に応じて成長することができ、append関数を内蔵すればよい.append関数は、1つのスライスに要素を追加することができ、元のスライスをどのように追加するか、元のスライスを返すか、新しいスライスを返すか、長さと容量がどのようにこれらの詳細を変更するかについては、append関数が自動的に処理します.
slice := []int{1, 2, 3, 4, 5}
newSlice := slice[1:3]

newSlice=append(newSlice,10)
fmt.Println(newSlice)
fmt.Println(slice)

//Output
[2 3 10]
[1 2 3 10 5]

例では、append関数により新たに作成されたスライスnewSliceに要素10が追加される.印刷された出力は、元のスライスsliceの4番目の値も変更され、10になったことが分かった.この結果は、newSliceが利用可能な容量を有し、追加を満たすために新しいスライスが作成されないため、newSliceの後に要素10が直接追加されたためである.newSliceおよびsliceスライスは、1つの下位配列を共有するので、スライスsliceの対応する要素値も変更される.
ここでnewSliceに新しく追加された3番目の要素は、実はsliceの4番目の要素に対応しているので、ここでの追加は実は下位配列の4番目の要素を10に修正し、newSliceの長さを3に調整します.
スライスの下位配列に十分な容量がない場合、新しい下位配列が作成され、元の配列の値を新しい下位配列にコピーし、新しい値を追加すると、元の下位配列に影響しません.
したがって、一般的には、新しいスライスを作成するときは、新しいスライスの長さと容量を同じにすることが望ましい.このように、追加操作時に新しい下位配列を生成し、既存の配列と分離し、下位配列を共有することで奇妙な問題を引き起こすことはない.配列を共有するときに内容を変更すると、複数のスライスに影響を与えるからだ.append関数は、下位配列の容量をインテリジェントに増加させ、現在のアルゴリズムは、容量が1000個未満の場合、常に倍増する.容量が1000個を超えると、成長因子は1.25、すなわち毎回25%増加する.
内蔵のappendも可変パラメータの関数なので、いくつかの値を同時に追加することができます.
newSlice=append(newSlice,10,20,30)

また、...オペレータで、1つのスライスを別のスライスに追加することもできます.
slice := []int{1, 2, 3, 4, 5}
newSlice := slice[1:2:3]

newSlice=append(newSlice,slice...)
fmt.Println(newSlice)
fmt.Println(slice)

反復スライス
スライスはセットであり、for rangeループを使用して反復し、各要素と対応するインデックスを印刷することができます.
    slice := []int{1, 2, 3, 4, 5}
    for i,v:=range slice{
        fmt.Printf("  :%d, :%d
",i,v)     }

インデックスが欲しくない場合は、_を使用して無視できます.これはGo言語の使い方で、不要な関数などの戻り値が多く無視できます.
    slice := []int{1, 2, 3, 4, 5}
    for _,v:=range slice{
        fmt.Printf(" :%d
",v)     }

ここで、rangeは、要素の参照ではなく、スライス要素のコピーを返すことを説明する必要がある.
for rangeループに加えて,従来のforループを用いて内蔵len関数と組み合わせて反復することもできる.
 
  

    slice := []int{1, 2, 3, 4, 5}
    for i := 0; i         fmt.Printf("值:%d
", slice[i])
    }

在函数间传递切片


我们知道切片是 3 个字段构成的结构类型,所以在函数间以值的方式传递的时候,占用的内存非常小,成本很低。在传递复制切片的时候,其底层数组不会被复制,也不会受影响,复制只是复制的切片本身,不涉及底层数组。


func main() {
    slice := []int{1, 2, 3, 4, 5}
    fmt.Printf("%p
", &slice)     modify(slice)     fmt.Println(slice) } func modify(slice []int) {     fmt.Printf("%p
", &slice)     slice[1] = 10 }

印刷の出力は次のとおりです.
0xc420082060
0xc420082080
[1 10 3 4 5]

よく見ると,この2つのスライスのアドレスが異なるので,スライスが関数間で伝達されて複製されていることが確認できる.インデックスの値を変更すると、元のスライスの値も変更され、下位配列を共有していることがわかります.
関数間でスライスを転送するのは非常に効率的で、ポインタを転送したり複雑な構文を処理したりする必要はありません.スライスをコピーして、自分のビジネスに基づいて修正し、最後に新しいスライスのコピーを転送すればいいだけです.これも、関数間で配列ではなくスライス伝達パラメータが使用される理由です.
多次元スライスについては紹介しませんが、多次元配列もあります.一般的なスライス配列と同じように、複数の一次元からなる多次元にすぎません.二つ目は、多次元スライスと配列を使うことをお勧めしません.可読性が悪く、構造がはっきりしていないので、問題が発生しやすいです.