Slice

Slice 是可變長度的 Array ,其元素的型別皆相同。slice 的型別寫作 []T 。

array 與 slice 緊密相關。slice 可存取底層陣列的部分或全部元素。

slice 有三個元件:

  • 指標(指向底層陣列,代表 slice 的第一個元素。但不一定是陣列的第一個元素)
  • 長度(該 slice 長度,但長度不會超過 slice 開頭到底層陣列最尾端的長度)
  • 容量(slice 開頭的元素到底層陣列最尾端的長度)

舉例來說

Slice 與底層陣列

  months := [...]string{1: "January", 2: "February", 3: "March", 4: "April", 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", 10: "October", 11: "November", 12: "December"}
  Q2 := months[4:7]
  summer := months[6:9]
  fmt.Println(Q2)        // ["April", "May", "June"]
  fmt.Println(summer)    // ["June", "July", "August"]

Slice 的運算子 s[i:j]的意思是從底層陣列的第 i 項取到 j-1 項 依上面的例子來說,Q2 就是從 months 第 4 項取到第 6 項 也有些特殊的寫法 s[i:] 意思是從第 i 項取到最後一項 s[:j] 意思是從頭取到第 j - 1 項 s[:] 意思是取全部元素 所以 months[3:] 就會從三月取到十二月

要注意的是如果 slice 超過底層陣列的容量(cap)會造成 panic 承上例如果 summer slice 的範圍超過 months 就會出現 panic

  summer := months[:20]  // panic

然後,跟 array 不同的地方。因為 slice 的元素中帶有指標。所以我們把 slice 傳給函式(function)的時候。他是會修改到底層陣列的實例的。

  func reverse(s []int){
    for i, j := 0, len(s)-1; i < j; i, j = i + 1, j - 1 {
      s[i], s[j] = s[j], s[i]
    }
  }

  a := [...]int{0, 1, 2, 3, 4, 5}
  reverse(a[:])
  fmt.Println(a) // [5, 4, 3, 2, 1]

還有一點需要特別注意兩個 Slice 之間是不能使用 == 比較的 標準函式庫中,有 bytes.Equal 來比較兩個 []byte 之間的差別,但其他的型態我們需要自己實作。

唯一合法的 slice 比較是與 nil 互相比較。 如果你要判斷 slice 是否為空請使用 len(s) == 0 來判斷

  var s []int        // len(s) == 0, s == nil
  s = nil            // len(s) == 0, s == nil
  s = []int(nil)     // len(s) == 0, s == nil
  s = []int{}        // len(s) == 0, s != nil

append function

我們來看看 slice append 的簡單概念

  func appendInt(x []int, y int) []int{
      var z []int
      zlen := len(x) + 1
      if zlen <= cap(x){
        z = x[:zlen]
      } else {
        zcap := zlen
        if zcap < 2*len(x){
          zcap = 2 * len(x)
        }
        z = make([]int, zlen, zcap)
        copy(z, x)
      }
      z[len(x)] = y
      return z
  }

實際上的 append function 用的是更複雜的策略,但這個簡單的 appendInt 是一個很好的概念。 由上面的 function 你可以清楚的知道 當一個 slice 要加入新的元素時,他會先 check 底層陣列的容量夠不夠。如果夠就擴大 slice 的長度,再把元素加到最後面。 如果不夠,那就建立一個兩倍容量的底層陣列。然後把現有的複製過去,再放入元素。