[Golang]也許有你不知道的,Array和Slice(2)
阿新 • • 發佈:2019-01-28
Golang中的slice
1)基礎
Slice更類似於"其他語言中的array",簡單來說,它是一個指向一段陣列的指標。
首先看看其宣告:[plain] view plaincopyprint?
- var intSlice []int
var intSlice []int
上面聲明瞭intSlice是一個指向int陣列的slice,注意中括號裡為空,這區別於array的宣告;另外這只是一個宣告,所以intSlice會得到一個slice的預設值,即為nil:
[plain] view plaincopyprint?
- fmt.Printf("intSlice == nil? %v\n", intSlice == nil)
- 輸出:
- intSlice == nil? true
fmt.Printf("intSlice == nil? %v\n", intSlice == nil)
輸出:
intSlice == nil? true
注意slice只能跟nil比較,如果你想嘗試下面這程式碼:[plain] view plaincopyprint?
- letsTry := intSlice
- fmt.Printf("intSlice == letsTry? %v\n", intSlice == letsTry)
你會得到下面這個錯誤資訊:letsTry := intSlice fmt.Printf("intSlice == letsTry? %v\n", intSlice == letsTry)
[plain] view plaincopyprint?
- invalid operation: intSlice == letsTry (slice can only be compared to nil)
invalid operation: intSlice == letsTry (slice can only be compared to nil)
接下來我們嘗試建立一個數組並賦給intSlice:[plain] view plaincopyprint?
- intSlice = make([]int, 1, 3)
make()是builtin的方法,可以建立slice, map, chan,當然這裡只討論slice。intSlice = make([]int, 1, 3)
第一個引數是你要建立的東西的型別,這裡要建立一個指向int陣列的slice,即[]int;
第二個引數是該slice的長度,第三個引數是該slice的容量,這裡分別是1和3;長度和容量分別代表什麼,接下來我們會慢慢講解。
我們先看一下我們剛才究竟建立了什麼:
[plain] view plaincopyprint?
- fmt.Printf("the intSlice is: %v, len: %d, cap: %d \n", intSlice, len(intSlice), cap(intSlice))
- 輸出:
- the intSlice is: [0], len: 1, cap: 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]時:
[plain] view plaincopyprint?
- fmt.Printf("The intSlice[1] is: %d\n", intSlice[1])
- 報錯:
- panic: runtime error: index out of range
fmt.Printf("The intSlice[1] is: %d\n", intSlice[1])
報錯:
panic: runtime error: index out of range
那當我們想向陣列增加一個元素時怎麼辦?這就是cap的作用了。只要在cap的範圍類,我們可以增加陣列長度:[plain] view plaincopyprint?
- intSlice = intSlice[:len(intSlice)+1]
intSlice = intSlice[:len(intSlice)+1]
等號右面的程式碼返回一個新的slice,新的slice與intSlice指向同一個記憶體地址,但對記憶體裡的array添加了一個預設元素,且長度+1;將新的slice賦給intSlice,現在我們看看其內容:
[html] view plaincopyprint?
- 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
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方法:[plain] view plaincopyprint?- 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
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,繼續使用一開始的擴充套件方式:
[plain] view plaincopyprint?
- 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
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()。[plain] view plaincopyprint?
- 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 := 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指向)?我們驗證一下:
[plain] view plaincopyprint?
- 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[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的一些操作技巧