Go 數(shù)組和切片的介紹
了解使用數(shù)組和切片在 Go 中存儲(chǔ)數(shù)據(jù)的優(yōu)缺點(diǎn),以及為什么其中一個(gè)更好。
在本文中,我將解釋 Go 數(shù)組和切片,包括如何使用它們,以及為什么你通常要選擇其中一個(gè)而不是另一個(gè)。
數(shù)組
數(shù)組是編程語言中最流行的數(shù)據(jù)結(jié)構(gòu)之一,主要原因有兩個(gè):一是簡單易懂,二是可以存儲(chǔ)許多不同類型的數(shù)據(jù)。
你可以聲明一個(gè)名為 anArray 的 Go 數(shù)組,該數(shù)組存儲(chǔ)四個(gè)整數(shù),如下所示:
anArray := [4]int{-1, 2, 0, -4}
數(shù)組的大小應(yīng)該在它的類型之前聲明,而類型應(yīng)該在聲明元素之前定義。len() 函數(shù)可以幫助你得到任何數(shù)組的長度。上面數(shù)組的大小是 4。
如果你熟悉其他編程語言,你可能會(huì)嘗試使用 for 循環(huán)來遍歷數(shù)組。Go 當(dāng)然也支持 for 循環(huán),不過,正如你將在下面看到的,Go 的 range 關(guān)鍵字可以讓你更優(yōu)雅地遍歷數(shù)組或切片。
最后,你也可以定義一個(gè)二維數(shù)組,如下:
twoD := [3][3]int{
{1, 2, 3},
{6, 7, 8},
{10, 11, 12}}
arrays.go 源文件中包含了 Go 數(shù)組的示例代碼。其中最重要的部分是:
for i := 0; i < len(twoD); i++ {
k := twoD[i]
for j := 0; j < len(k); j++ {
fmt.Print(k[j], " ")
}
fmt.Println()
}
for _, a := range twoD {
for _, j := range a {
fmt.Print(j, " ")
}
fmt.Println()
}
通過上述代碼,我們知道了如何使用 for 循環(huán)和 range 關(guān)鍵字迭代數(shù)組的元素。arrays.go 的其余代碼則展示了如何將數(shù)組作為參數(shù)傳遞給函數(shù)。
以下是 arrays.go 的輸出:
$ go run arrays.go
Before change(): [-1 2 0 -4]
After change(): [-1 2 0 -4]
1 2 3
6 7 8
10 11 12
1 2 3
6 7 8
10 11 12
這個(gè)輸出告訴我們:對(duì)函數(shù)內(nèi)的數(shù)組所做的更改,會(huì)在函數(shù)退出后丟失。
數(shù)組的缺點(diǎn)
Go 數(shù)組有很多缺點(diǎn),你應(yīng)該重新考慮是否要在 Go 項(xiàng)目中使用它們。
首先,數(shù)組定義之后,大小就無法改變,這意味著 Go 數(shù)組不是動(dòng)態(tài)的。簡而言之,如果你需要將一個(gè)元素添加到一個(gè)沒有剩余空間的數(shù)組中,你將需要?jiǎng)?chuàng)建一個(gè)更大的數(shù)組,并將舊數(shù)組的所有元素復(fù)制到新數(shù)組中。
其次,當(dāng)你將數(shù)組作為參數(shù)傳遞給函數(shù)時(shí),實(shí)際上是傳遞了數(shù)組的副本,這意味著你對(duì)函數(shù)內(nèi)部的數(shù)組所做的任何更改,都將在函數(shù)退出后丟失。
最后,將大數(shù)組傳遞給函數(shù)可能會(huì)很慢,主要是因?yàn)?Go 必須創(chuàng)建數(shù)組的副本。
以上這些問題的解決方案,就是使用 Go 切片。
切片
Go 切片與 Go 數(shù)組類似,但是它沒有后者的缺點(diǎn)。
首先,你可以使用 append() 函數(shù)將元素添加到現(xiàn)有切片中。此外,Go 切片在內(nèi)部使用數(shù)組實(shí)現(xiàn),這意味著 Go 中每個(gè)切片都有一個(gè)底層數(shù)組。
切片具有 capacity 屬性和 length 屬性,它們并不總是相同的。切片的長度與元素個(gè)數(shù)相同的數(shù)組的長度相同,可以使用 len() 函數(shù)得到。切片的容量是當(dāng)前為切片分配的空間,可以使用 cap() 函數(shù)得到。
由于切片的大小是動(dòng)態(tài)的,如果切片空間不足(也就是說,當(dāng)你嘗試再向切片中添加一個(gè)元素時(shí),底層數(shù)組的長度恰好與容量相等),Go 會(huì)自動(dòng)將它的當(dāng)前容量加倍,使其空間能夠容納更多元素,然后將請(qǐng)求的元素添加到底層數(shù)組中。
此外,切片是通過引用傳遞給函數(shù)的,這意味著實(shí)際傳遞給函數(shù)的是切片變量的內(nèi)存地址,這樣一來,你對(duì)函數(shù)內(nèi)部的切片所做的任何修改,都不會(huì)在函數(shù)退出后丟失。因此,將大切片傳遞給函數(shù),要比將具有相同數(shù)量元素的數(shù)組傳遞給同一函數(shù)快得多。這是因?yàn)?Go 不必拷貝切片 —— 它只需傳遞切片變量的內(nèi)存地址。
slice.go 源文件中有 Go 切片的代碼示例,其中包含以下代碼:
package main
import (
"fmt"
)
func negative(x []int) {
for i, k := range x {
x[i] = -k
}
}
func printSlice(x []int) {
for _, number := range x {
fmt.Printf("%d ", number)
}
fmt.Println()
}
func main() {
s := []int{0, 14, 5, 0, 7, 19}
printSlice(s)
negative(s)
printSlice(s)
fmt.Printf("Before. Cap: %d, length: %d\n", cap(s), len(s))
s = append(s, -100)
fmt.Printf("After. Cap: %d, length: %d\n", cap(s), len(s))
printSlice(s)
anotherSlice := make([]int, 4)
fmt.Printf("A new slice with 4 elements: ")
printSlice(anotherSlice)
}
切片和數(shù)組在定義方式上的最大區(qū)別就在于:你不需要指定切片的大小。實(shí)際上,切片的大小取決于你要放入其中的元素?cái)?shù)量。此外,append() 函數(shù)允許你將元素添加到現(xiàn)有切片 —— 請(qǐng)注意,即使切片的容量允許你將元素添加到該切片,它的長度也不會(huì)被修改,除非你調(diào)用 append()。上述代碼中的 printSlice() 函數(shù)是一個(gè)輔助函數(shù),用于打印切片中的所有元素,而 negative() 函數(shù)將切片中的每個(gè)元素都變?yōu)楦髯缘南喾磾?shù)。
運(yùn)行 slice.go 將得到以下輸出:
$ go run slice.go
0 14 5 0 7 19
0 -14 -5 0 -7 -19
Before. Cap: 6, length: 6
After. Cap: 12, length: 7
0 -14 -5 0 -7 -19 -100
A new slice with 4 elements: 0 0 0 0
請(qǐng)注意,當(dāng)你創(chuàng)建一個(gè)新切片,并為給定數(shù)量的元素分配內(nèi)存空間時(shí),Go 會(huì)自動(dòng)地將所有元素都初始化為其類型的零值,在本例中為 0(int 類型的零值)。
使用切片來引用數(shù)組
Go 允許你使用 [:] 語法,使用切片來引用現(xiàn)有的數(shù)組。在這種情況下,你對(duì)切片所做的任何更改都將傳播到數(shù)組中 —— 詳見 refArray.go。請(qǐng)記住,使用 [:] 不會(huì)創(chuàng)建數(shù)組的副本,它只是對(duì)數(shù)組的引用。
refArray.go 中最有趣的部分是:
func main() {
anArray := [5]int{-1, 2, -3, 4, -5}
refAnArray := anArray[:]
fmt.Println("Array:", anArray)
printSlice(refAnArray)
negative(refAnArray)
fmt.Println("Array:", anArray)
}
運(yùn)行 refArray.go,輸出如下:
$ go run refArray.go
Array: [-1 2 -3 4 -5]
-1 2 -3 4 -5
Array: [1 -2 3 -4 5]
我們可以發(fā)現(xiàn):對(duì) anArray 數(shù)組的切片引用進(jìn)行了操作后,它本身也被改變了。
總結(jié)
盡管 Go 提供了數(shù)組和切片兩種類型,你很可能還是會(huì)使用切片,因?yàn)樗鼈儽?Go 數(shù)組更加通用、強(qiáng)大。只有少數(shù)情況需要使用數(shù)組而不是切片,特別是當(dāng)你完全確定元素的數(shù)量固定不變時(shí)。
你可以在 GitHub 上找到 arrays.go、slice.go 和 refArray.go 的源代碼。