【Go入門學習】理解區分數組和切片

1、前言

  學過 Go 的都知道在 Go 語言中有四種複合數據類型:數組、切片(Slice)、哈希表(Map)和結構體(Struct),而不少 Go 初學者也很容易把數組和切片弄混淆,因此要怎麼把這兩個數據類型分清楚呢?數組

   

 

2、數組

1.簡介

  數組是聚合類型,是一組同類型數據的集合,經過從0開始的下標索引訪問元素值。在 Go 語言中,數組是值類型,這就意味着當你將一個數組賦值給另外一個數組的時候,其實是將這個數組拷貝了一份。app

  數組的聲明語法爲:函數

var 數組變量名 [元素數量]Typespa

  語法說明以下所示:code

  • 數組變量名:數組聲明及使用時的變量名;
  • 元素數量:數組的元素數量,能夠是一個表達式,但最終計算的結果必須是整型數值;
  • Type:能夠是任意基本類型,包括數組自己,類型爲數組自己時,能夠實現多維數組。

2.初始化

  默認狀況下,數組的每一個元素都被初始化爲元素類型對應的零值,對於數字類型來講就是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]

3.遍歷數組

  數組經過下標訪問元素,可修改其元素值。數組的遍歷經過 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 

 

3、切片

1.簡介

  數組的長度不可改變,在必定場合下就不太適用了,Go 語言則提供了一種能夠動態擴容的數據類型--切片(Slice)。一個切片類型一般會寫做 []T,其中 T 表明切片中元素的數據類型,切片的語法和數組相似,只是沒有固定長度。  內存

2.區別

  切片和數組有以下區別:源碼

  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 產生影響了。

3.初始化

  切片的初始化能夠經過數組來實現,也能夠經過內置函數 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]

4.追加元素

  在 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 將元素添加至切片時,若是超出了容量,將會返回一個容量二倍與當前切片的切片

5.切片拷貝

  在 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 中了。

相關文章
相關標籤/搜索