Golang中的array與slice(2)

    這文章分爲三部分,第一、二部分分別詳細講述golang中的array與slice,第三部分則討論slice的使用與技巧。這文章不是教程,需要基本的golang知識。

    關於第一、二部分,這裏有一篇文章比我敘述得更好(自備爬梯):http://blog.golang.org/go-slices-usage-and-internals

    看過這文章的朋友可以直接忽略此文一二部分。

    該篇是第一部分,另外兩篇的鏈接:

    Golang中的array與slice(1)

    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的細節

make([]int, 1, 3)究竟做了些什麼?
首先,它在內存裏分配了一段連續空間,這段空間的大小等於擁有3(cap)個元素的int數組(即[3]int{}),注意,只是空間大小相等;
然後,它在這段連續空間的開始位置,創建一個只有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的一些操作技巧
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章