Go切片:二級跳實現優雅的“數組”

切片是一種數據結構,便于使用和管理數據集合,切片是基于數組的概念構成,可以按需自動增長和縮小,切片底層也會是在連續塊中分配的,所以切片還能獲得索引,迭代。

內部實現

// 切片是一個很小的對象,對底層數組進行了抽象,切片有三個字段組成如圖所示
// runtime/slice.go
type slice struct {
    array unsafe.Pointer `description:"元素指針"`
    len   int            `description:"長度"`
    cap   int            `description:"容量"`
}
image.png

切片的創建于初始化

  • 創建切片
    // TODO 使用var關鍵字創建
    // var是關鍵字表示聲明變量
    // slice 是變量的名稱自定義(字母,數組,下劃線,不能用數字打頭)
    // []int 表示切片的類型,與數組類似,只是中括號中不用寫數字,寫了就是數組,不寫就是切片
    var slice []int

    // TODO  使用內置函數make創建一個切片
    // 第一個參數( []int)表示切片的類型,此示例是整形
    // 第二個參數 (3) 表示切片的長度,此示例是3
    // 第三個參數 (5) 表示切片的容量,容量必須大于等于長度,這個數值可以省略,當省略的時候容量與長度相等,此示例是5
    slice := make([]int,3,5)

    // TODO 通過切片字面量創建
    // 創建方式與數組一致,可指定下標,也可不指定
    // 指定下標如  []int{2:20,30},表示下標為2的值是20,下標為3的值是30,索引從0開始,未指定的使用類型的零值初始化
    slice :=  []int{}
  • nil與空切片的對比與區別


    image.png

    image.png

nil切片與空切片的區別
1:nil切片的值與nil比較是true ,空切片與nil比較是false
2:建議使用nil切片效率高

  • 幾種聲明方式對比
優勢 var make 字面量
是否可聲明
初始化值 nil 類型零值 類型零值+指定下標值
指定下標賦值
指定長度 0 聲明的長度 根據下標與實際值決定
指定容量 0 同長度或指定值 同長度

使用切片

// 切片的截取如   slice[1:3:5] ,左閉右開法則[1,3)
// 第一個參數 表示從哪一個下標開始截取
// 第二個參數 表示截取到哪一位(切記不包含此下標)
// 第三個參數 表示容量截取到哪一位
// 新切片與原始的切片之間共享底層數組,除非兩個切片發生擴容(當切片的長度大于容量的時候,必然擴容,因為切片是動態數組)
// 創建一個切片
slice := []int{10,20,30,40,50}
// 從其他切片中重新生成一個切片
// 長度是2,容量是3
newSlice := slice[1:3:5]
image.png
func main() {
    s := []int{1, 2, 3, 4}
    fmt.Printf("%p\n", s)
    fmt.Printf("%p\n", &s)
    sliceFirstAddr(s)
}
// @title  輸出切片的第一個值的地址
// @description   2019/6/27   14:33  mick  
// @param   id       int      "id"
// @return  err      error    "錯誤"
// TODO   直接切片值的地址
// TODO   判斷切片的長度是否小于1
// TODO   輸出切片第一個位置的元素地址
func sliceFirstAddr(s []int){
    fmt.Printf("%p\n", s)
    if len(s) <1 {
        return
    }
    fmt.Printf("%p\n", &s[0])
}

切片擴容(重點)

切片是一種動態數組,既然是動態的,那么就可以實現數據集合的增加與刪除,切片使用append函數進行數據的追加,使用切割實現數據的切分,當切片的長度大于容量的時候,切片會自動擴容

  • 第一種擴容策略-雙倍擴容
    1.切片每次新增個數不超過原來的1倍,
    1.且每次增加數不超過1024個,
    2.且增加后總長度小于1024個,
    3.這種情況下擴容后為原來的2倍
// @title   切片的雙倍擴容
// @description   2019/6/27   15:07  mick  
// @param   void
// @return  void
// TODO   使用make函數創建一個空切片(不是nil切片)
// TODO   打印當前的容量
// TODO   循環追加數據
// TODO   每次循環打印切片的長度與容量
func doubleIncrease() {
    s1 := make([]int, 0)
    fmt.Printf("The capacity of s1: %d\n", cap(s1))
    for i := 1; i <= 17; i++ {
        s1 = append(s1, i)
        fmt.Printf("s1(%d): len: %d, cap: %d\n", i, len(s1), cap(s1))
    }
}

  • 第二種擴容策略-微擴容
    1.切片一次新增個數超過原來1倍
    2.但不超過1024個
    3.且增加后總長度小于1024個
    4.這種情況下擴容后比實際具有的總長度還要大一些。
// @title  容量微擴增
// @description   2019/6/27   15:12  mick  
// @param   void
// @return  void
// TODO   使用make函數創建一個切片
// TODO   打印此切片的容量
// TODO   使用append函數追加5個元素,并賦值給新切片
// TODO   打印此切片的長度與容量
// TODO   使用append函數追加11個元素,并賦值給新切片
// TODO   打印此切片的長度與容量
func littleIncrease(){
    s2 := make([]int, 10)
    fmt.Printf("The capacity of s2: %d\n", cap(s2))
    r1 := append(s2, make([]int, 5)...)
    fmt.Printf("r1: len: %d, cap: %d\n", len(r1), cap(r1))
    r2 := append(s2, make([]int, 11)...)
    fmt.Printf("r2: len: %d, cap: %d\n", len(r2), cap(r2))
    fmt.Printf("注意:像r2 一次增加個數超過原容量的1倍,增加后結果比實際總長度預想的稍大一點 \n")
}
  • 第三種擴容策略-0.25倍增長
    1.原切片長度超過1024時,
    2.一次增加容量不是2倍而是0.25倍
    3.每次超過預定的都是0.25累乘
// @title  0.25倍的固定增長
// @description   2019/6/27   15:16  mick
// @param   void
// @return  void
// TODO  使用make函數創建一個1024個長度的切片
// TODO  打印此切片的容量
// TODO  使用append函數追加200個元素,并賦值給新切片
// TODO  打印此切片的長度與容量
// TODO  打印驗證數值的正確性
func fixedIncrease(){
    s3 := make([]int, 1024)
    fmt.Printf("The capacity of s3: %d\n", cap(s3))
    r4 := append(s3, make([]int, 200)...)
    fmt.Printf("r4: len: %d, cap: %d\n", len(r4), cap(r4))
    fmt.Println(1024+1024*0.25)
}
image.png

課后練習

  • 第一題:為什么切片的值是一樣的呢?
    var s []*int
    source := []int{1,2,3,4,5}
    for _,v := range source{
        s = append(s,&v)
    }
    fmt.Println(s)

高級講解友情連接

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容