學過 Go 的都知道在 Go 語言中有四種複合數據類型:數組、切片(Slice)、哈希表(Map)和結構體(Struct),而不少 Go 初學者也很容易把數組和切片弄混淆,因此要怎麼把這兩個數據類型分清楚呢?數組
數組是聚合類型,是一組同類型數據的集合,經過從0開始的下標索引訪問元素值。在 Go 語言中,數組是值類型,這就意味着當你將一個數組賦值給另外一個數組的時候,其實是將這個數組拷貝了一份。app
數組的聲明語法爲:函數
var 數組變量名 [元素數量]Typespa
語法說明以下所示:code
默認狀況下,數組的每一個元素都被初始化爲元素類型對應的零值,對於數字類型來講就是0。在數組字面值中,若是在數組的長度位置出現的是「...」省略號,則表示數組的長度是根據初始化值的個數來計算。示例以下:blog
1 var a [3]int 2 a[0] = 1 3 b := [2]int{1, 2} 4 c := [...]int{3, 5, 7} 5 fmt.Println(a) // [1 0 0] 6 fmt.Println(b) // [1 2] 7 fmt.Println(c) // [3 5 7]
數組經過下標訪問元素,可修改其元素值。數組的遍歷經過 for 循環實現:索引
1 arr := [3]int{2, 4, 6} 2 for i := 0; i < 3; i++ { 3 fmt.Printf("%d ", arr[i]) 4 } // 2 4 6 5 fmt.Println() 6 for _, v := range arr { 7 fmt.Printf("%d ", v) 8 } // 2 4 6
數組的長度不可改變,在必定場合下就不太適用了,Go 語言則提供了一種能夠動態擴容的數據類型--切片(Slice)。一個切片類型一般會寫做 []T,其中 T 表明切片中元素的數據類型,切片的語法和數組相似,只是沒有固定長度。 內存
切片和數組有以下區別:源碼
1)和數組相比,切片除了有長度(len),還有容量(cap),容量指切片當前可容納元素的最大數量。class
2)數組是值類型,切片是引用類型。
值類型和引用類型有什麼區別呢?在傳遞參數時,若是是值類型,對參數修改不會對原來的變量產生影響,但如果引用傳遞,對參數的修改也會影響到原始數據。示例以下:
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func change(a [3]int, s []int) { 8 a[0] += 1 9 s[0] += 1 10 s = append(s, 9) 11 } 12 13 func main() { 14 arr := [3]int{2, 4, 6} 15 sli := []int{3, 5, 7} 16 change(arr, sli) 17 fmt.Println(arr) // [2 4 6] 18 fmt.Println(sli) // [4 5 7] 19 }
在示例中,分別對數組 arr 和切片 sli 的第一個元素進行了+1操做,但從打印結果能夠看出來只有切片的數據被修改了,而對數組的修改並無改變原始數據。那爲何最後 sli 的結果不是 [4 5 7 9]呢?這是由於 append() 其實是將切片 sli 複製了一份而後賦值給了 s,已是一份新的數據了,也就不會對 sli 產生影響了。
切片的初始化能夠經過數組來實現,也能夠經過內置函數 make() 來實現,在使用 make() 方法時還能夠設置切片的容量,在追加元素時,若切片的容量不足,則會按切片的長度的二倍進行擴容。示例以下:
1 arr := [5]int{1, 2, 3, 4, 5} 2 s1 := arr[2:] 3 fmt.Println(s1) // [3 4 5] 4 s2 := arr[:] 5 fmt.Println(s2) // [1 2 3 4 5] 6 s3 := make([]int, 3) 7 s3[0], s3[1], s3[2] = 2, 4, 6 8 fmt.Println(s3) // [2 4 6]
在 Go 語言中有一個內置函數 append(),查看源碼發現它是這麼定義的:
func append(slice []Type, elems ...Type) []Type
內置的 append() 函數用於向 slice 追加元素,示例爲:
1 arr := [5]int{1, 2, 3, 4, 5} 2 var sli []int 3 for _, v := range arr { 4 sli = append(sli, v) 5 } 6 fmt.Println(sli) // [1 2 3 4 5]
細心的人會發現源碼中寫的是 elems,這是否是就意味着能夠一次添加多個元素呢?試一試:
1 var sli []int 2 sli = append(sli, 1, 2, 3) 3 fmt.Println(sli) // [1 2 3]
例子很簡單,append() 使用起來也很方便,但問題是若是要添加的元素數量超過了切片的容量,又會發生什麼狀況呢?看下面的例子:
1 var y []int 2 for i := 0; i < 10; i++ { 3 y = append(y, i) 4 fmt.Printf("%d cap=%d %v\n", i, cap(y), y) 5 }
這幾行代碼的運行結果爲:
0 cap=1 [0]
1 cap=2 [0 1]
2 cap=4 [0 1 2]
3 cap=4 [0 1 2 3]
4 cap=8 [0 1 2 3 4]
5 cap=8 [0 1 2 3 4 5]
6 cap=8 [0 1 2 3 4 5 6]
7 cap=8 [0 1 2 3 4 5 6 7]
8 cap=16 [0 1 2 3 4 5 6 7 8]
9 cap=16 [0 1 2 3 4 5 6 7 8 9]
能夠發現切片的容量從1慢慢增長爲二、四、八、16,也就是說在使用 append 將元素添加至切片時,若是超出了容量,將會返回一個容量二倍與當前切片的切片。
在 Go 語言中,切片的拷貝使用內置函數 copy() 來實現,能夠放心的是,切片拷貝是深拷貝,不用像 Python 中糾結深淺拷貝真的很幸福呢!只不過拷貝的時候須要確保目的切片有足夠的容量,不然會拷貝。示例以下:
1 sli := []int{3, 5, 7} 2 res := make([]int, 5) 3 copy(res, sli) 4 fmt.Println(res) // [3 5 7 0 0] 5 fmt.Println(&sli[0], &res[0]) // 0xc000012340 0xc00000c3c0 6 var s []int 7 copy(s, sli) 8 fmt.Println(s) // []
這裏 s 打印出來是空的,是因爲 s 在初始化的時候沒有分配內存空間,copy() 也不會爲 s 分配空間,因此 sli 中的元素也就沒法拷貝到 s 中了。