這文章分爲三部分,第一、二部分分別詳細講述golang中的array與slice,第三部分則討論slice的使用與技巧。這文章不是教程,需要基本的golang知識。
關於第一、二部分,這裏有一篇文章比我敘述得更好(自備爬梯):http://blog.golang.org/go-slices-usage-and-internals
看過這文章的朋友可以直接忽略此文一二部分。
該篇是第一部分,另外兩篇的鏈接:
Golang中的array與slice(3)
-------
Golang中的slice
1)基礎
Slice更類似於"其他語言中的array",簡單來說,它是一個指向一段數組的指針。
首先看看其聲明:var intSlice []int
上面聲明瞭intSlice是一個指向int數組的slice,注意中括號裏爲空,這區別於array的聲明;另外這只是一個聲明,所以intSlice會得到一個slice的默認值,即爲nil:
fmt.Printf("intSlice == nil? %v\n", intSlice == nil)
輸出:
intSlice == nil? true
注意slice只能跟nil比較,如果你想嘗試下面這代碼:letsTry := intSlice
fmt.Printf("intSlice == letsTry? %v\n", intSlice == letsTry)
你會得到下面這個錯誤信息:invalid operation: intSlice == letsTry (slice can only be compared to nil)
接下來我們嘗試創建一個數組並賦給intSlice:intSlice = make([]int, 1, 3)
make()是builtin的方法,可以創建slice, map, chan,當然這裏只討論slice。第一個參數是你要創建的東西的類型,這裏要創建一個指向int數組的slice,即[]int;
第二個參數是該slice的長度,第三個參數是該slice的容量,這裏分別是1和3;長度和容量分別代表什麼,接下來我們會慢慢講解。
我們先看一下我們剛纔究竟創建了什麼:
fmt.Printf("the intSlice is: %v, len: %d, cap: %d \n", intSlice, len(intSlice), cap(intSlice))
輸出:
the intSlice is: [0], len: 1, cap: 3
通過結果我們知道,剛纔我們創建了一個長度爲1(擁有一個元素)的數組,其元素的值爲0(默認值);所以,當我們嘗試得到第二個元素,即intSlice[1]時:
fmt.Printf("The intSlice[1] is: %d\n", intSlice[1])
報錯:
panic: runtime error: index out of range
那當我們想向數組增加一個元素時怎麼辦?這就是cap的作用了。只要在cap的範圍類,我們可以增加數組長度:intSlice = intSlice[:len(intSlice)+1]
等號右面的代碼返回一個新的slice,新的slice與intSlice指向同一個內存地址,但對內存裏的array添加了一個默認元素,且長度+1;將新的slice賦給intSlice,現在我們看看其內容:
fmt.Printf("the intSlice is: %v, len: %d, cap: %d \n", intSlice, len(intSlice), cap(intSlice))
輸出
the intSlice is: [0 0], len: 2, cap: 3
當然上面的操作看起來有點複雜,其實有更簡單的表達,使用自帶的append方法:intSlice = append(intSlice, 0)
fmt.Printf("the intSlice is: %v, len: %d, cap: %d \n", intSlice, len(intSlice), cap(intSlice))
輸出:
the intSlice is: [0 0 0], len: 3, cap: 3
暫時還沒問題;append()的用法在下一篇文章會具體說明。那這數組長度可以無限增加嗎?我們先捨棄append,繼續使用一開始的擴展方式:
intSlice = intSlice[:len(intSlice)+1]
fmt.Printf("the intSlice is: %v, len: %d, cap: %d \n", intSlice, len(intSlice), cap(intSlice))
報錯:
panic: runtime error: slice bounds out of range
這裏報錯了,slice的len是不能超過其cap的。在解決上面這個問題之前,先詳細說明一下make([]int, 1, 3)究竟做了些什麼。
2)make的細節
然後,它在這段連續空間的開始位置,創建一個只有1(len)個元素的int數組,即[1]int{};
最後,它創建並返回一個slice,這個slice包含3個信息,指向的元素類型及內存位置(這裏是剛纔[1]int{}的第一個元素)、len(長度,這裏爲1),cap(容量,這裏爲3)
3)append的細節
好的,我們回到1)中最後的問題:如果一個slice的長度已達到其容量,而我想繼續擴展,該怎麼辦呢?很簡單,建立一個更大連續空間,並把原本slice的內容複製進去。
而其實builtin裏已存在方法能智能幫我們完成這動作:就是剛纔的append()。newIntSlice := append(intSlice, 0)
fmt.Printf("the newIntSlice is: %v, len: %d, cap: %d \n", newIntSlice, len(newIntSlice), cap(newIntSlice))
輸出:
the newIntSlice is: [0 0 0 0], len: 4, cap: 6
我們可以看到newIntSlice的cap是原來的兩倍。當len要超過cap時,append會幫我們創建一個容量是之前兩倍的連續空間來存放元素那newIntSlice中的前三個元素是從intSlice複製過來的嗎(而不是用slice指向)?我們驗證一下:
newIntSlice[0] = 1
fmt.Printf("the newIntSlice is: %v, len: %d, cap: %d \n", newIntSlice, len(newIntSlice), cap(newIntSlice))
fmt.Printf("the intSlice is: %v, len: %d, cap: %d \n", intSlice, len(intSlice), cap(intSlice))
輸出:
the newIntSlice is: [1 0 0 0], len: 4, cap: 6
the intSlice is: [0 0 0], len: 3, cap: 3
上面測試可以看出,修改newIntSlice不會影響intSlice。當然intSlice與newIntSlice是相同類型的,可以直接用newIntSlice覆蓋intSlice:
最後補充一下,創建slice的make方法可以只用兩個參數,如make([]int, 3),這樣得到的slice的len與cap都爲3。
一下篇將會討論slice的一些操作技巧