Go 語言的數(shù)組與切片
數(shù)組
數(shù)組是一組類型相同的,長度固定的,按數(shù)字編號排列的數(shù)據(jù)序列。由于 go 語言中,數(shù)組的類型相同且長度固定,所以在聲明數(shù)組的時候,就會體現(xiàn)這兩個特點(diǎn)。
var?array?[5]int?//?[0?0?0?0?0]
數(shù)組通過 [SIZE](方括號內(nèi)為數(shù)組長度) 加上 TYPE(類型) 的形式聲明,上面的代碼就表示 array 變量為一個長度為 5,且五個數(shù)據(jù)的類型都為 int。
在之前介紹變量的時候,介紹過 int 類型的默認(rèn)值為 0,所以 array 的值為 [0 0 0 0 0]。
數(shù)組初始化
數(shù)組在初始化階段,需要通過 {} 的方式,指定數(shù)組每個位置的具體值。
var?array?[3]int?=?[3]int{1,?2,?3}?//?[1?2?3]
可以看到 {} 的前面也要帶上數(shù)組的長度與類型,由于 go 能夠進(jìn)行類型推導(dǎo),變量后聲明的類型顯得有點(diǎn)多余,是可以省略的。
var?array?=?[3]int{1,?2,?3}?//?[1?2?3]
?? 指定索引賦值
初始化的過程中,我們還可以指定索引進(jìn)行賦值,也就是不必給數(shù)組的每個位置都安排上具體的值。
var?array?=?[5]int{1:?77,?3:?77}?//?[0?77?0?77?0]
上面的數(shù)組輸出的結(jié)果為:[0 77 0 77 0]。和其他語言一樣,數(shù)組的索引是從 0 開始的,我們給索引為 1 和 3 位置都指定了值為 77 ,其他位置由于沒有指定具體值,就是其類型的默認(rèn)值。
?? 自動推導(dǎo)數(shù)組長度
前面的案例都是指定了數(shù)組的長度,其實(shí)我們可以通過 [...] 的方式,告訴 go 編譯器,數(shù)組長度尚未確定,在初始化之后才能確定其長度,然后 go 在編譯階段就會自動進(jìn)行推導(dǎo)。
var?array?=?[...]int{1,?2,?3,?4,?5}?//?[1?2?3?4?5]
fmt.Println("array?length?is",?len(array))
我們可以通過 len 方法獲取數(shù)組的長度,上面代碼的運(yùn)行結(jié)果如下:

如果我們在指定索引的位置賦值了,最終長度取決于最末尾的索引,下面的代碼中,指定了索引 5 的值為 77,則數(shù)組的長度為 6。
var?array?=?[...]int{1:?77,?5:?77}?//?[0?77?0?0?0?77]
fmt.Println("array?length?is",?len(array))

賦值與訪問
與其他語言一樣,數(shù)組的賦值和訪問都是通過 [Index] 操作的。
var?array?=?[...]int{1,?2,?3}
array[0]?=?100?//?索引?0?的位置重新賦值為?100
fmt.Println("array?is",?array)

取值也是同樣的操作,我們現(xiàn)在實(shí)現(xiàn)一個求數(shù)組平均數(shù)的函數(shù):
func?getAverage(array?[5]int)?float32?{
?var?sum?int
?var?avg?float32
?for?i?:=?0;?i?<?5;?i++?{
??sum?+=?array[i]
?}
?avg?=?float32(sum)?/?5
?return?avg
}
var?array?=?[5]int{1,?2,?3,?4,?5}
fmt.Println("average?is",?getAverage(array))

多維數(shù)組
多維數(shù)組的聲明,相對于一維數(shù)組,就是看前面有幾個 [SIZE]。
var?a1?[2][3]int?//?二維數(shù)組
var?a1?[2][3][4]int?//?三維數(shù)組
我們拿三維數(shù)組舉例,第一個 [] ?內(nèi)的數(shù)字表示最外層數(shù)組的長度,往后以此類推。[2][3][4]int 表示最外層數(shù)組長度為 2,第二層數(shù)組長度為 3,最內(nèi)層數(shù)組長度為 4。其賦值方式也和一維數(shù)組一樣,只是多維數(shù)組需要將多個 {} 進(jìn)行嵌套。
var?a1?=?[2][3][4]int{
??{
????{1,?2,?3,?4},
????{1,?2,?3,?4},
????{1,?2,?3,?4},
??},
??{
????{1,?2,?3,?4},
????{1,?2,?3,?4},
????{1,?2,?3,?4},
??},
}
fmt.Println(a1)
打印結(jié)果:

多維數(shù)組的訪問和一維數(shù)組一樣,也是通過 [] + 數(shù)組索引,只是多維數(shù)組要訪問某個值需要多個 []。
如果我們要拿到下圖的 2,訪問方式為:array[0][1][1]

fmt.Println("array[0][1][1]?=?",?array[0][1][1])

切片
前面介紹過,數(shù)組是一組類型相同且長度固定的數(shù)據(jù)集合,而切片就是一種比較抽象的數(shù)組,其長度不固定,聲明方式與數(shù)組類似([] 中不顯示注明數(shù)組長度,也不使用 [...] 的方式進(jìn)行長度的推導(dǎo)):
var?slice?[]int
切片初始化
切片的初始化與數(shù)組類似,只要省略掉 [] 內(nèi)注明的數(shù)組長度即可:
var?s1?=?[]int{1,?2,?3}
s2?:=?[]int{1,?2,?3}?//?簡寫
除了這種字面量的聲明方式,還可以通過 go 的內(nèi)置方法:make,來進(jìn)行切片的初始化:
var?s1?=?make([]int,?3)
s2?:=?make([]int,?3)?//?簡寫
make 方法的第二個參數(shù)表示切片的長度,雖然切片的長度可變,但是通過 make 方法創(chuàng)建切片時,需要指定一個長度。除了指定切片的長度,make 方法還支持傳入第三個參數(shù),用來指定切片的『容量』,如果沒有指定切片的容量,那初始狀態(tài)切片的容量與長度一致。
func?make([]T,?len,?cap)
長度與容量
長度指的是,切片內(nèi)有多少個元素,而容量可以理解為,當(dāng)前切片在內(nèi)存中開辟了多大的空間。前面介紹過,可以通過 len 方法獲取到數(shù)組的長度,獲取切片的長度也可以使用該方法。要獲取切片的容量,可以使用 cap 方法。
s1?:=?make([]int,?5)
fmt.Printf("The?length?of?s1?is?%d\n",?len(s1))
fmt.Printf("The?capacity?of?s1?is?%d\n",?cap(s1))

可以看到初始狀態(tài)下,切片的長度與容量一致。如果要修改切片的長度,可以通過 append 方法,在切片尾部追加一個新的值。
s1?:=?make([]int,?3,?5)?//?聲明一個長度為?3,容量為?5?的切面
s1?=?append(s1,?1)?//?在尾部追加一個值,長度會變成?4
fmt.Printf("The?length?of?s1?is?%d\n",?len(s1))
fmt.Printf("The?capacity?of?s1?is?%d\n",?cap(s1))

append 方法是可以接受多個參數(shù),我們在追加一個值之后,繼續(xù)調(diào)用 append 方法,往切片后再追加兩個值:
s1?:=?make([]int,?3,?5)
s1?=?append(s1,?1)
s1?=?append(s1,?2,?3)
fmt.Println(s1)?//?[0?0?0?1?2?3]
fmt.Printf("The?length?of?s1?is?%d\n",?len(s1))
fmt.Printf("The?capacity?of?s1?is?%d\n",?cap(s1))
此時的切片的長度已經(jīng)變成了 6,超過了切片的容量,那這個時候切換的容量會不會也變成 6?

根據(jù)輸出的結(jié)果,此時切片的容量變成了 10,這意味著切片的容量的擴(kuò)充是在之前的基礎(chǔ)上進(jìn)行翻倍操作的。為了驗(yàn)證這個結(jié)論,我們在切片后繼續(xù)追加 5 個值,讓切片的長度變成 11,超出當(dāng)前的容量,看看容量會變成多少。
s1?:=?make([]int,?3,?5)
s1?=?append(s1,?1)
s1?=?append(s1,?2,?3)
s1?=?append(s1,?4,?5,?6,?7,?8)
fmt.Printf("The?length?of?s1?is?%d\n",?len(s1))
fmt.Printf("The?capacity?of?s1?is?%d\n",?cap(s1))

可以看到切片的容量變成了 20,這也驗(yàn)證了我們之前的結(jié)論,當(dāng)切片長度超過了其容量,容量會在原來的基礎(chǔ)上翻倍。那如果切片容量達(dá)到了 2000,長度超過 2000,容量也會變成 4000 嗎?
s1?:=?make([]int,?1024)
s1?=?append(s1,?1)
fmt.Printf("\nThe?length?of?s1?is?%d\n",?len(s1))
fmt.Printf("The?capacity?of?s1?is?%d\n",?cap(s1))

可以看到,我們新定義的切片長度為 1024,在長度變成 1025 的時候,容量并沒有翻倍。為了避免切片容量無休止的擴(kuò)展,go 規(guī)定如果當(dāng)前切片的長度大于 1024 ,在長度超過其容量時,只會增加 25% 的容量。
切片截取
切片之所以叫切片,是因?yàn)樗梢酝ㄟ^切出數(shù)組中的某一塊來創(chuàng)建。語法規(guī)則也很簡單:Array[start:end]。
arr?:=?[5]int{1,?2,?3,?4,?5}
slice?:=?arr[1:3]
fmt.Println(slice)?//?[2?3]

arr[1:3] 表示將數(shù)組的從索引為 1 的位置一直到索引為 3 的位置(不包括 3)截取出來,形成一個切片。當(dāng)然這個開頭結(jié)尾的數(shù)字也是可以省略的,如果我們?nèi)绻覀兪÷蚤_頭就表示截取開始的位置為 0,省略結(jié)尾就表示截取結(jié)束的位置一直到數(shù)組的最后一位。
arr?:=?[5]int{1,?2,?3,?4,?5}
slice?:=?arr[1:]
fmt.Println(slice)?//?[2?3?4?5]

通過省略截取的開頭和結(jié)尾,我們就能將一個數(shù)組進(jìn)行一次拷貝操作,然后形成一個切片。(PS. 截取操作形成的新數(shù)據(jù)是一個切片)
arr?:=?[5]int{1,?2,?3,?4,?5}
slice?:=?arr[:]
fmt.Printf("slice?=?%v,?slice?type?is?%T",?slice,?slice)

