Go語言中的切片是圍繞動態數組的概念構建的,能夠按需自動增加和縮小。切片的動態增加是經過內置函數append來實現的,還能夠經過對切片再次切片來縮小一個切片的大小。由於切片在內存中是連續的,因此切片還能得到索引、迭代以及垃圾回收優化的好處。數組
切片的底層實現包含3個字段:指向底層數組的指針、切片訪問的元素的個數(長度)、切片容許增加到的元素的個數(容量),以下圖所示。切片能夠理解爲對底層數組進行了抽象,並提供了相關的操做方法。
數據結構
能夠經過make、切片字面量來建立和初始化切片,也能夠利用現有數組或切片直接建立切片(Go語言中的引用類型(slice、map、chan)不能使用new進行初始化)。閉包
// make只傳入一個參數指定長度,則容量和長度相等。如下輸出:"len: 5, cap: 5" s := make([]int, 5) fmt.Printf("len: %d, cap: %d\n", len(s), cap(s)) // make 傳入長度和容量。如下輸出:"len: 5, cap: 10" s := make([]int, 5, 10) fmt.Printf("len: %d, cap: %d\n", len(s), cap(s)) // 不容許建立容量小於長度的切片。下面語句編譯會報錯:"len larger than cap in make([]int)" s := make([]int, 10, 5)
// 經過字面量聲明切片,其長度和容量都爲5。如下輸出:「len: 5, cap: 5」 s := []int{1, 2, 3, 4, 5} fmt.Printf("len: %d, cap: %d\n", len(s), cap(s)) // 能夠在聲明切片時利用索引來給出所需的長度和容量。 // 經過指定索引爲99的元素,來建立一個長度和容量爲100的切片 s := []int{99: 0}
s := baseStr[low:high:max]
,low指定開始元素下標,high指定結束元素下標,max指定切片能增加到的元素下標。這三個參數均可以省略,low省略默認從下標0開始,high省略默認爲最後一個元素下標,max省略默認是底層數組或切片的容量(這裏也要注意max不能小於high)。這種方式下,切片的長度和容量的計算方式爲:len = hith - low cap = max - low
s1 := baseStr[1:3:10] fmt.Printf("len: %d, cap: %d\n", len(s1), cap(s1)) // len: 2, cap: 9 s2 := baseStr[1:3] fmt.Printf("len: %d, cap: %d\n", len(s2), cap(s2)) // len: 2, cap: 9 s3 := baseStr[:3] fmt.Printf("len: %d, cap: %d\n", len(s3), cap(s3)) // len: 3, cap: 10 ss1 := s1[2:5] ss2 := s1[3:8] fmt.Printf("len: %d, cap: %d\n", len(ss1), cap(ss1)) // len: 3, cap: 7 fmt.Printf("len: %d, cap: %d\n", len(ss2), cap(ss2)) // len: 5, cap: 6
基於同一個數組或切片建立的不一樣切片都共享同一個底層數組。若是一個切片修改了該底層數組的共享部分,其餘切片和原始數組或切片都能感知到。其底層數據結構以下面兩個圖所示:
共享同一底層數組:
app
改變互相感知:
函數
baseSlice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} s1 := baseSlice[1:5:10] s2 := baseSlice[2:7] // 這裏 baseSlice、s一、s2 都共享同一個底層數組 /* s1 起始指針指向 baseSlice 下標爲1的元素,能夠訪問到baseSlice下標爲4的元素, 能夠經過append增長容量到baseSlice最後一個元素。 s1 起始指針指向 baseSlice 下標爲1的元素,能夠訪問到baseSlice下標爲4的元素, 能夠經過append增長容量到baseSlice最後一個元素。 */ // 下面的例子能夠看到,無論修改 baseSlice、s一、s2 中的哪一個,這幾個切片能訪問到的數據都會跟着改變 // 修改 baseSlice 下標爲3元素 /* baseSlice: [1 2 3 999 5 6 7 8 9 10] s1: [2 3 999 5] s2: [3 999 5 6 7] */ baseSlice[3] = 999 fmt.Printf("baseSlice: %v\n", baseSlice) fmt.Printf("s1: %v\n", s1) fmt.Printf("s2: %v\n", s2) // 修改 s1 下標爲1元素 /* baseSlice: [1 2 888 999 5 6 7 8 9 10] s1: [2 888 999 5] s2: [888 999 5 6 7] */ s1[1] = 888 fmt.Printf("baseSlice: %v\n", baseSlice) fmt.Printf("s1: %v\n", s1) fmt.Printf("s2: %v\n", s2) // 修改 s2 下標爲2元素 /* baseSlice: [1 2 888 999 222 6 7 8 9 10] s1: [2 888 999 222] s2: [888 999 222 6 7] */ s2[2] = 222 fmt.Printf("baseSlice: %v\n", baseSlice) fmt.Printf("s1: %v\n", s1) fmt.Printf("s2: %v\n", s2)
用var s []int
聲明的切片若是未經初始化,就是nil切片。空切片是用make或字面量建立的切片,s := make([]int, 0)或者s := []int{}
。空切片在底層數組包含0個元素,也沒有分配任何存儲空間。無論是空切片仍是nil切片,對其調用函數append、len和cap的效果都是同樣的。nil切片和空切片底層結構以下:
學習
切片的增加是經過調用append函數完成的。函數append老是會增長新切片的長度,而容量可能會改變,也可能不會改變,這取決於被操做切片的可用用量(注意:append不會修改傳入的切片,而是會返回一個新的切片)。優化
// 建立一個整型切片 // 其長度和容量都是5個元素 slice := []int{10, 20, 30, 40, 50} // 建立一個新切片 // 其長度爲2 個元素,容量爲4個元素 newSlice := slice[1:3] // 使用原有的容量來分配一個新元素 // 將新元素賦值爲 60 newSlice = append(newSlice, 60) fmt.Printf("slice: %v\n", slice) // slice: [10 20 30 60 50] fmt.Printf("newSlice: %v\n", newSlice) // newSlice: [20 30 60]
以上代碼運行的底層結構以下圖:
3d
由於newSlice在底層數組裏還有額外的容量可用,append操做將可用的元素合併到切片的長度,並對其進行賦值。因爲和原始的slice共享同一個底層數組,因此slice中索引爲3的元素的值也被改動 。若是切片的底層數組沒有足夠的容量可用,append函數會建立一個新的底層數組,將被引用的現有的值複製到新的數組裏,再追加新的值。指針
// 建立一個整型切片 // 其長度和容量都是4個元素 slice := []int{10, 20, 30, 40} // 向切片追加一個新元素 // 將新元素賦值爲50 newSlice := append(slice, 50) // 改變newSlice中的某個值,發現原始slice的值並無變化 newSlice[2] = 999 fmt.Printf("slice: %v\n", slice) // slice: [10 20 30 40] fmt.Printf("newSlice: %v\n", newSlice) // newSlice: [10 20 999 40 50]
當這個append操做完成後,newSlice擁有一個全新的底層數組,這個數組的容量是原來的兩倍。
code
函數append會智能地處理底層數組的容量增加。在切片的容量小於1000時,老是會成倍的增加容量。一旦元素個數超過1000,容量的增加因子會設爲1.25,也就是每次增長25%的容量。
切片能夠用range迭代,可是要注意:若是隻用一個值接收range,則獲得的只是切片的下標,用兩個值接收range,則獲得的纔是下標和對應的值。
slice := []int{10, 20, 30, 40} // 若是隻用一個值接收range,則獲得的只是切片的下標 for i := range slice { fmt.Println(i) } // 若是用兩個值接收range,則獲得的是下標和對應的值 for i, v := range slice { fmt.Println(i, v) }
須要強調的是,range建立了每一個元素的副本,而不是直接返回對該元素的引用。若是使用該值變量的地址做爲指向每一個元素的指針,就會形成錯誤。
slice := []int{10, 20, 30, 40} /* 下面的打印輸出以下: Value: 10, Value-Addr: C00000C168, ElemAddr: C000012560 Value: 20, Value-Addr: C00000C168, ElemAddr: C000012568 Value: 30, Value-Addr: C00000C168, ElemAddr: C000012570 Value: 40, Value-Addr: C00000C168, ElemAddr: C000012578 Value-Addr 表示的是遍歷時用到的變量 v ElemAddr 表示的是原來的切片slice裏每一個元素的地址 能夠看出 range 在遍歷時,將slice的每一個元素都複製到了同一個變量 v 。 使用閉包的時候,尤爲要注意range的這種特性。 */ for i, v := range slice { fmt.Printf("Value: %d, Value-Addr: %X, ElemAddr: %X\n", v, &v, &slice[i]) }
Go語言中參數的傳遞都是以值的方式傳遞的,引用類型也不例外。由於類型自己包裝的是一個指針,因此傳遞引用類型是把指針複製一份,而不會複製其底層數據結構。
和多維數組相似。
《Go語言實戰》 《Go語言學習筆記》