數組與切片的區別:1、切片是指標類型,數組是值類型;2、數組的賦值形式為值傳遞,切片的賦值形式為引用傳遞;3、數組的長度是固定的,而切片長度可以任意調整(切片是動態的陣列);4、陣列的長度是固定的,而切片長度可以任意調整(切片是動態的陣列)。
本教學操作環境:windows7系統、GO 1.18版本、Dell G3電腦。
Go語言中的陣列大概相當與C/C 中的數組,固定大小,不能夠動態擴展大小,而切片大概相當與C 中的Vector,可以動態擴展大小,當大小超過容量時,重新分配一塊內存,然後將資料複製到新的內存區域。下面我們透過幾個問題來更好地理解golang 的陣列和切片,一起來看看吧。
Go的切片是在陣列之上的抽象資料類型,因此在了解切片之前必須先理解數組。
#對應的:
範例:
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} 也是完全沒問題的。
陣列的容量和長度是一樣的。 cap() 函數與len() 函數皆輸出數組的容量(即長度)
陣列是值類型,將一個陣列賦值給另一個陣列時,傳遞的是一份拷貝。而切片是引用類型,切片包裝的陣列稱為該切片的底層陣列。看下面的範例:
//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]
Go語言中,切片是長度可變、容量固定的相同的元素序列。 Go語言的切片本質是一個陣列。容量固定是因為數組的長度是固定的,切片的容量就是隱藏數組的長度。長度可變指的是在陣列長度的範圍內可變。
#相對應的:
以上對應的輸出
[0 0 0 0 0] [0 0 0 0 0] [] [1 2 3 4 5]
從3)、4)可見,建立切片跟建立陣列唯一的差異在於Type 前的「 [] 」中是否有數字,為空,則代表切片,否則則代表陣列。因為切片是長度可變的
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改變了,其他兩個都會變成
內建函數append可以往一個切片後追加一個或多個同類型的其他值。如果追加的元素數量超過了原始切片容量,那麼最後回傳的是一個全新陣列中的全新切片。如果沒有超過,那麼最後回傳的是原始數組中的全新切片。無論如何,append對原始切片無任何影響。
舉例說明:
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
下面的主要看一些實際的例子來說明數組和切片的差異。
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
從這個範例可以看出,陣列的賦值是值的拷貝,是一個全新的陣列。而切片的賦值是引用。下面再來看一個例子。
範例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]
這個範例再一次說明:陣列是賦值是copy,而切片僅是引用。範例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
綜上三個例子,可以看出切片是數組的引用,這裡包括對隱藏數組、非隱藏數組引用。
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語言中數組與切片有什麼區別的詳細內容。更多資訊請關注PHP中文網其他相關文章!