ホームページ >バックエンド開発 >Golang >Go言語の配列とスライスの違いは何ですか?

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

青灯夜游
青灯夜游オリジナル
2023-01-13 17:26:468275ブラウズ

配列とスライスの違い: 1. スライスはポインタ型、配列は値型; 2. 配列の代入形式は値転送、スライスの代入形式は参照転送; 3. 長さ配列の長さは固定されており、スライスの長さは任意に調整できます (スライスは動的配列です); 4. 配列の長さは固定ですが、スライスの長さは任意に調整できます (スライスは動的配列です)。

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

このチュートリアルの動作環境: Windows 7 システム、GO バージョン 1.18、Dell G3 コンピューター。

Go 言語の配列は C/C の配列とほぼ同等で、サイズが固定されており、動的にサイズを拡張することはできませんが、スライスは C/C のベクトルとほぼ同等です。 C は動的にサイズを拡張でき、サイズが容量を超えた場合にメモリを再割り当てし、新しいメモリ領域にデータをコピーします。 golang の配列とスライスをより深く理解するために、いくつかの質問に答えてみましょう。

1. 配列

Go のスライスは配列の上にある抽象データ型であるため、配列を理解する前にまず配列を理解する必要があります。スライス 。

1. 配列を宣言する 3 つの方法

  • var identifier [len]type
  • var identifier = [len]type{value1 , value2, … , valueN}
  • var identifier = […]type{value1, value2, … , valueN}

対応する:

  • identifier : = [len]type{}
  • identifier := [len]type{value1, value2, … , valueN}
  • identifier := […]type{value1, value2, …, valueN }

例:

var iarray1 [5]int32
var iarray2 [5]int32 = [5]int32{1, 2, 3, 4, 5}
iarray3 := [5]int32{1, 2, 3, 4, 5}
iarray4 := [5]int32{6, 7, 8, 9, 10}
iarray5 := [...]int32{11, 12, 13, 14, 15}
iarray6 := [4][4]int32{{1}, {1, 2}, {1, 2, 3}}  

fmt.Println(iarray1)
fmt.Println(iarray2)
fmt.Println(iarray3)
fmt.Println(iarray4)
fmt.Println(iarray5)
fmt.Println(iarray6)

結果:

[0 0 0 0 0]
[1 2 3 4 5]
[1 2 3 4 5]
[6 7 8 9 10]
[11 12 13 14 15]
[[1 0 0 0] [1 2 0 0] [1 2 3 0] [0 0 0 0]]

配列 iarray1 を調べますが、これは宣言されただけで割り当てられていません。Go 言語を使用すると、自動的に割り当てることができます。値は 0 です。 iarray2 と iarray3 をもう一度見てみると、Go 言語の宣言で型を示すかどうかを指定できることがわかります。var iarray3 = [5]int32{1, 2, 3, 4, 5} もまったく問題ありません。

2. 配列の容量と長さ

配列の容量と長さは同じです。 cap() 関数と len() 関数はどちらも、配列の容量 (つまり長さ) を出力します

3. Type

配列は値型です。ある配列を別の配列に代入するとき、渡された コピーです。スライスは参照型ですが、スライスによってラップされた配列は、スライスの基になる配列と呼ばれます。次の例を見てください:

//a是一个数组,注意数组是一个固定长度的,初始化时候必须要指定长度,不指定长度的话就是切片了
a := [3]int{1, 2, 3}
//b是数组,是a的一份拷贝
b := a
//c是切片,是引用类型,底层数组是a
c := a[:]
for i := 0; i < len(a); i++ {
 a[i] = a[i] + 1
}
//改变a的值后,b是a的拷贝,b不变,c是引用,c的值改变
fmt.Println(a) //[2,3,4]
fmt.Println(b) //[1 2 3]
fmt.Println(c) //[2,3,4]

2. スライス

Go 言語では、スライスは可変長で固定容量の同一の要素シーケンスです。 Go 言語のスライスの本質は配列です。配列の長さが固定されており、スライスの容量が隠し配列の長さになるため、容量も固定されます。可変長とは、配列の長さの範囲内で可変であることを指します。

1. スライスを作成する 4 つの方法

  • var slide1 = make([]int,5,10)
  • var slide2 = make([]int,5)
  • var スライス 3 = []int{}
  • var スライス 4 = []int{1,2,3,4,5}

同様に:

  • slice1 := make([]int,5,10)
  • slice2 := make([]int,5)
  • slice3 := []int{}
  • slice4 := []int{1,2,3,4,5}

上記の対応する出力

[0 0 0 0 0]
[0 0 0 0 0]
[]
[1 2 3 4 5]

3) と 4) からわかるように、スライスの作成と配列の作成の唯一の違いは、Type の前の "[]" に数字があるかどうかです。空の場合、スライスを表します。 、それ以外の場合は配列を表します。スライスは可変長であるため、

2. 隠し配列

Go のスライスは配列の上にある抽象データ型であるため、作成されたスライスには常に配列が含まれます。存在します。

例:

slice0 := []string{"a", "b", "c", "d", "e"}
slice1 := slice0[2 : len(slice0)-1]
slice2 := slice0[:3]
fmt.Println(slice0, slice1, slice2)
slice2[2] = "8"
fmt.Println(slice0, slice1, slice2)

出力:

[a b c d e] [c d] [a b c]
[a b 8 d e] [8 d] [a b 8]

スライスslice0、slice1、slice2が同じ基になる配列への参照であるため、slice2が変更され、その他のスライスが変更されたことも示しています。 2 つすべてが変わります

3. append はスライスを追加します

組み込み関数 append は、同じ型の 1 つ以上の他の値をスライスに追加できます。スライス。追加された要素の数が元のスライスの容量を超える場合、最終的に返されるのは、新しい配列内の新しいスライスです。それを超えない場合、最終的に返されるのは、元の配列内のまったく新しいスライスです。いずれの場合も、追加は元のスライスには影響しません。

例:

slice1 := make([]int, 2, 5)
fmt.Println(len(slice1), cap(slice1))
for k := range slice1{
    fmt.Println(&slice1[k])
}
slice1 = append(slice1,4)
fmt.Println(len(slice1), cap(slice1))
for k := range slice1{
    fmt.Println(&slice1[k])
}
slice1 = append(slice1,5,6,7)
fmt.Println(len(slice1), cap(slice1))
for k := range slice1{
    fmt.Println(&slice1[k])
}

出力:

2 5 //长度和容量
0xc420012150
0xc420012158

3 5 //第一次追加,未超出容量,所以内存地址未发生改变
0xc420012150
0xc420012158
0xc420012160

6 10 //第二次追加,超过容量,内存地址都发生了改变,且容量也发生了改变,且是原来的2倍
0xc4200100f0
0xc4200100f8
0xc420010100
0xc420010108
0xc420010110
0xc420010118

別の例を見てください:

slice1 := make([]int, 2, 5)
slice2 := slice1[:1]
fmt.Println(len(slice1), cap(slice1))
for k := range slice1{
    fmt.Println(&slice1[k])
}
fmt.Println(len(slice2), cap(slice2))
for k := range slice2{
    fmt.Println(&slice2[k])
}

slice2 = append(slice2,4,5,6,7,8)

fmt.Println(len(slice1), cap(slice1))
for k := range slice1{
    fmt.Println(&slice1[k])
}
fmt.Println(len(slice2), cap(slice2))
for k := range slice2{
    fmt.Println(&slice2[k])
}

上記の出力:

2 5 //slice1的长度和容量
0xc4200700c0
0xc4200700c8

1 5  //slice2的长度和容量
0xc4200700c0

2 5 //slice2追加后,slice1的长度和容量、内存都未发生改变
0xc4200700c0
0xc4200700c8

6 10 //slice2追加后,超过了容量,所以slice2的长度和容量、内存地址都发生改变。
0xc42007e000
0xc42007e008
0xc42007e010
0xc42007e018
0xc42007e020
0xc42007e028

3. GO配列とスライスの違い

以下では主に、配列とスライスの違いを説明するためにいくつかの実際的な例を見ていきます。

1. 配列とスライスの代入形式

例 1

arr1 := [3] int {1,2,3}
arr2 := arr1
for k := range arr1 {
    fmt.Printf("%v ",&arr1[k]);
}

fmt.Println("");

for k := range arr2 {
    fmt.Printf("%v ",&arr2[k]);
}

fmt.Println("\n=================");

slice1 := [] int{1,2,3}
slice2 := slice1
for k := range slice1 {
    fmt.Printf("%v ",&slice1[k]);
}
fmt.Println("");
for k := range slice2 {
    fmt.Printf("%v ",&slice2[k]);
}

输出结果:
0xc420014140 0xc420014148 0xc420014150 
0xc420014160 0xc420014168 0xc420014170 
=================
0xc4200141a0 0xc4200141a8 0xc4200141b0 
0xc4200141a0 0xc4200141a8 0xc4200141b0

この例からわかるように、array割り当ては値のコピーであり、まったく新しい配列です。スライスの割り当ては参照 です。別の例を見てみましょう。

例 2:

arr1 := [3] int {1,2,3}
arr2 := arr1
fmt.Printf("%v %v ",arr1,arr2);

arr1[0] = 11
arr2[1] = 22
fmt.Printf("\n%v %v ",arr1,arr2);

fmt.Println("\n================");

slice1 := [] int{1,2,3}
slice2 := slice1
fmt.Printf("%v %v ",slice1,slice2);

slice1[0] = 11
slice2[1] = 22
fmt.Printf("\n%v %v ",slice1,slice2);

输出结果:
[1 2 3] [1 2 3] 
[11 2 3] [1 22 3] 
================
[1 2 3] [1 2 3] 
[11 22 3] [11 22 3]

この例は、配列が代入およびコピーであるのに対し、スライスは単なる参照であることをもう一度示します。例 1 と 2 のスライス宣言では、隠し配列が使用されています。非表示でない配列を使用した例 3 をもう一度見てみましょう。

例 3:

arr1 := [5] int {1,2,3}
slice1 := arr1[0:3]
slice2 := slice1[0:4]

fmt.Printf("len: %d cap: %d %v\n",len(arr1),cap(arr1),arr1); //打印出非隐藏数组
for k := range arr1 {
    fmt.Printf("%v ",&arr1[k]);
}

fmt.Println("");

fmt.Printf("len: %d cap: %d %v\n",len(slice1),cap(slice1),slice1); //打印切片1
for k := range slice1 {
    fmt.Printf("%v ",&slice1[k]);
}

fmt.Println("");

fmt.Printf("len: %d cap: %d %v\n",len(slice2),cap(slice2),slice2); //打印切片2
for k := range slice2 {
    fmt.Printf("%v ",&slice2[k]);
}
fmt.Println("\n=================");

arr1[0] = 11   //非隐藏数组、切片1、切片2各自发生更改
slice1[1] = 22
slice2[2] = 33

fmt.Printf("len: %d cap: %d %v\n",len(arr1),cap(arr1),arr1); //再次打印非隐藏数组
for k := range arr1 {
    fmt.Printf("%v ",&arr1[k]);
}

fmt.Println("");

fmt.Printf("len: %d cap: %d %v\n",len(slice1),cap(slice1),slice1); //再此打印切片1
for k := range slice1 {
    fmt.Printf("%v ",&slice1[k]);
}
fmt.Println("");

fmt.Printf("len: %d cap: %d %v\n",len(slice2),cap(slice2),slice2); //再次打印切片2
for k := range slice2 {
    fmt.Printf("%v ",&slice2[k]);
}

输出结果:
len: 5 cap: 5 [1 2 3 0 0]0xc420012150 0xc420012158 0xc420012160 0xc420012168 0xc420012170 
len: 3 cap: 5 [1 2 3]0xc420012150 0xc420012158 0xc420012160 len: 4 cap: 5 [1 2 3 0]0xc420012150 0xc420012158 0xc420012160 0xc420012168 
=================
len: 5 cap: 5 [11 22 33 0 0]
0xc420012150 0xc420012158 0xc420012160 0xc420012168 0xc420012170 
len: 3 cap: 5 [11 22 33]
0xc420012150 0xc420012158 0xc420012160 
len: 4 cap: 5 [11 22 33 0]
0xc420012150 0xc420012158 0xc420012160 0xc42001216

上記の 3 つの例を要約すると、スライスは、非表示の配列および非表示の配列への参照を含む、配列への参照であることがわかります。

2. 配列はパラメータとして使用され、関数

によって呼び出されます。
func Connect() {
    arr1 := [5] int {1,2,3}
    fmt.Printf("%v ",arr1);
    for k := range arr1 {
        fmt.Printf("%v ",&arr1[k]);
    }
    fmt.Println("");
    f1(arr1)
    fmt.Println("");
    f2(&arr1)
}
func f1(arr [5]int) {
    fmt.Printf("%v ",arr);
    for k := range arr {
        fmt.Printf("%v ",&arr[k]);
    }
}
func f2(arr *[5]int) {
    fmt.Printf("%v ",arr);
    for k := range arr {
        fmt.Printf("%v ",&arr[k]);
    }
}

输出结果:
[1 2 3 0 0]  0xc420012150 0xc420012158 0xc420012160 0xc420012168 0xc420012170 
[1 2 3 0 0]  0xc4200121b0 0xc4200121b8 0xc4200121c0 0xc4200121c8 0xc4200121d0 
&[1 2 3 0 0] 0xc420012150 0xc420012158 0xc420012160 0xc420012168 0xc420012170

从上面的例子可以看出,数组在参数中,可以使用值传递和引用传递。值传递会拷贝新数组,引用传递则使用原数组。

func Connect() {
    slice1 := [] int {1,2,3}
    fmt.Printf("%v ",slice1);
    for k := range slice1 {
        fmt.Printf("%v ",&slice1[k]);
    }
    fmt.Println("");
    f1(slice1)
}
func f1(slice []int) {
    fmt.Printf("%v ",slice);
    for k := range slice {
        fmt.Printf("%v ",&slice[k]);
    }
}

输出结果:
[1 2 3] 0xc420014140 0xc420014148 0xc420014150 
[1 2 3] 0xc420014140 0xc420014148 0xc42001415

从这个例子中可以看出,切片在参数中传递本身就引用。

总结:

  • 切片是指针类型,数组是值类型

  • 数组的赋值形式为值传递,切片的赋值形式为引用传递

  • 数组的长度是固定的,而切片长度可以任意调整(切片是动态的数组)

  • 数组只有长度一个属性,而切片比数组多了一个容量(cap)属性

【相关推荐:Go视频教程编程教学

以上がGo言語の配列とスライスの違いは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。