這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
這文章分為三部分,第一、二部分分別詳細講述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 := intSlicefmt.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] = 1fmt.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: 6the 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的一些操作技巧