1. 程式人生 > >[Golang]也許有你不知道的,Array和Slice(2)

[Golang]也許有你不知道的,Array和Slice(2)

Golang中的slice

1)基礎

Slice更類似於"其他語言中的array",簡單來說,它是一個指向一段陣列的指標。

首先看看其宣告:
[plain] view plaincopyprint?
  1. var intSlice []int  
var intSlice []int
上面聲明瞭intSlice是一個指向int陣列的slice,注意中括號裡為空,這區別於array的宣告;
另外這只是一個宣告,所以intSlice會得到一個slice的預設值,即為nil:
[plain] view plaincopyprint?
  1. fmt.Printf("intSlice == nil? %v\n", intSlice == nil)  
  2. 輸出:  
  3. intSlice == nil? true  
fmt.Printf("intSlice == nil? %v\n", intSlice == nil)
輸出:
intSlice == nil? true
注意slice只能跟nil比較,如果你想嘗試下面這程式碼:
[plain] view plaincopyprint?
  1. letsTry := intSlice  
  2. fmt.Printf("intSlice == letsTry? %v\n", intSlice == letsTry)  
letsTry := intSlice
fmt.Printf("intSlice == letsTry? %v\n", intSlice == letsTry)
你會得到下面這個錯誤資訊:
[plain] view plaincopyprint?
  1. 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?
  1. intSlice = make([]int, 1, 3)  
intSlice = make([]int, 1, 3)
make()是builtin的方法,可以建立slice, map, chan,當然這裡只討論slice。
第一個引數是你要建立的東西的型別,這裡要建立一個指向int陣列的slice,即[]int;
第二個引數是該slice的長度,第三個引數是該slice的容量,這裡分別是1和3;長度和容量分別代表什麼,接下來我們會慢慢講解。
我們先看一下我們剛才究竟建立了什麼:
[plain] view plaincopyprint?
  1. fmt.Printf("the intSlice is: %v, len: %d, cap: %d \n", intSlice, len(intSlice), cap(intSlice))  
  2. 輸出:  
  3. 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?
  1. fmt.Printf("The intSlice[1] is: %d\n", intSlice[1])  
  2. 報錯:  
  3. 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?
  1. intSlice = intSlice[:len(intSlice)+1]  
intSlice = intSlice[:len(intSlice)+1]
等號右面的程式碼返回一個新的slice,新的slice與intSlice指向同一個記憶體地址,但對記憶體裡的array添加了一個預設元素,且長度+1;
將新的slice賦給intSlice,現在我們看看其內容:
[html] view plaincopyprint?
  1. fmt.Printf("the intSlice is: %v, len: %d, cap: %d \n", intSlice, len(intSlice), cap(intSlice))  
  2. 輸出  
  3. 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?
  1. intSlice = append(intSlice, 0)  
  2. fmt.Printf("the intSlice is: %v, len: %d, cap: %d \n", intSlice, len(intSlice), cap(intSlice))  
  3. 輸出:  
  4. 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?
  1. intSlice = intSlice[:len(intSlice)+1]  
  2. fmt.Printf("the intSlice is: %v, len: %d, cap: %d \n", intSlice, len(intSlice), cap(intSlice))  
  3. 報錯:  
  4. 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?
  1. newIntSlice := append(intSlice, 0)  
  2. fmt.Printf("the newIntSlice is: %v, len: %d, cap: %d \n", newIntSlice, len(newIntSlice), cap(newIntSlice))  
  3. 輸出:  
  4. 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?
  1. newIntSlice[0] = 1  
  2. fmt.Printf("the newIntSlice is: %v, len: %d, cap: %d \n", newIntSlice, len(newIntSlice), cap(newIntSlice))  
  3. fmt.Printf("the intSlice is: %v, len: %d, cap: %d \n", intSlice, len(intSlice), cap(intSlice))  
  4. 輸出:  
  5. the newIntSlice is: [1 0 0 0], len: 4, cap: 6  
  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的一些操作技巧