切片是一種複合數據類型,與數組相似,存放相同數據類型的元素,但數組的大小是固定的,而切片的大小可變,能夠按需自動改變大小。切片是基於底層數組實現的,是對數組的抽象。切片很小,只有三個字段的數據結構:指向底層數組的指針、能訪問的元素個數(即切片長度)和容許增加到的元素個數(即切片容量)。golang
使用內置函數建立空切片,形如:算法
s := make([]type, len, cap) // len 長度,cap 容量
複製代碼
也能夠只指定len
,那麼切片的容量和長度是同樣的。Go語言提供了內置函數len
、cap
分別返回切片的長度和容量。數組
// 聲明一個長度爲三、容量爲5的整型切片
s1 := make([]int,3,5)
fmt.Println(len(s1),cap(s1)) // 輸出:3 5
// 聲明一個長度和容量都是5的字符串切片
s2 := make([]string,5)
fmt.Println(len(s2),cap(s2)) // 輸出:5 5
複製代碼
切片建立完成,若是不指定字面量的話,默認值就是數組的元素的零值。
切片的容量就是切片底層數組的大小,咱們只能訪問切片長度範圍內的元素,如第一節的圖所示,長度爲3的整型切片存入3個值後的結構,咱們只能訪問到第3個元素,剩餘的2個元素須要切片擴充之後才能夠訪問。因此,很明顯的:容量>=長度,咱們不能建立長度大於容量的切片。數據結構
s1 := make([]int,5,3)
// 報錯:len larger than cap in make([]int)
複製代碼
使用字面量建立,就是指定了初始化的值app
s := []int{1,2,3,4,5} // 長度和容量都是5的整型切片
複製代碼
有沒有發現,這種建立方式與建立數組相似,只不過不用指定[]
的值,這時候切片的長度和容量是相等的,而且會根據指定的字面量推導出來。
區別:函數
// 建立大小爲10的數組
s := [10]int{1,2,3,4,5}
// 建立切片
s := []int{1,2,3,4,5}
複製代碼
咱們也能夠只初始化某一個索引的值:學習
s := []int{4:1}
fmt.Println(len(s),cap(s)) // 輸出:5 5
fmt.Println(s) // 輸出:[0 0 0 0 1]
複製代碼
指定了第5個元素爲1
,其餘元素初始化爲0。ui
使用操做符[start:end]
,簡寫成[i:j]
,表示從索引i
,到索引j
結束,截取已有數組或者切片的任意部分,返回一個新的切片,新切片的值包含原切片的i
索引的值,可是不包含j
索引的值。i
、j
都是可選的,i
若是省略,默認是0,j
若是省略,默認是原切片或數組的長度。i
、j
都不能超過這個長度值。spa
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println("s[:]", s[:])
fmt.Println("s[2:]", s[2:])
fmt.Println("s[:4]", s[:4])
fmt.Println("s[2:4]", s[2:4])
複製代碼
輸出
你可能會有個疑問:截取得到的新切片的長度和容量怎麼計算呢?咱們固然可使用內置函數len
、cap
直接得到,若是明白了怎麼計算的,咱們處理問題就能夠更駕輕就熟。
對底層數組大小爲k
的切片執行[i,j]
操做以後得到的新切片的長度和容量是: 長度:j-i
容量:k-i
就拿上一個例子的s[2:4]
來講,原切片底層數組大小是10,因此新切片的長度是4-2=2
,容量是10-2=8
。 可使用內置函數驗證下:.net
s1 := s[2:4]
fmt.Println(len(s1),cap(s1)) // 輸出:2 8
複製代碼
上面是使用2個索引的方法建立切片,還可使用3個索引的方法,第3個用來限定新切片的容量,用法爲slice[i:j:k]
。
s2 := s[2:4:8]
fmt.Println(s2) // 輸出:[2 3]
複製代碼
長度和容量如何計算:長度j-i
,容量k-i
。因此切片s2
的長度和容量分別是2
、6
。注意:k
不能超過原切片(數組)的長度,不然報錯panic: runtime error: slice bounds out of range
。
例如上面的例子中,第三個索引值不能超過10。
咱們來看個例子:
s := []int{0, 1, 2, 3, 4, 5}
fmt.Println("before,s:",s)
s1 := s[1:4]
fmt.Println("before,s1:",s1)
s1[1] = 10
fmt.Println("after,s1:",s1)
fmt.Println("after,s:",s)
複製代碼
輸出:
before,s: [0 1 2 3 4 5]
before,s1: [1 2 3]
after,s1: [1 10 3]
after,s: [0 1 10 3 4 5]
複製代碼
這個例子說明,原切片和新切片是基於同一個底層數組的,因此當修改的時候,底層數組的值就會被改變,原切片的值也隨之改變了。對於基於數組的切片也同樣的。
s
可以看到底層數組所有6個元素,而切片
s1
只能看到索引
1
及以後的所有元素,對於
s1
來講,索引
1
以前的部分是不存在。
切片的使用方法與數組的使用方法相似,直接經過索引就能夠獲取、修改元素的值。
s := []int{1, 2, 3, 4, 5}
fmt.Println(s[1]) // 獲取值 輸出:2
s[1] = 10 // 修改值
fmt.Println(s) //輸出:[1 10 3 4 5]
複製代碼
只能訪問切片長度範圍內的元素,不然報錯
s := []int{1, 2, 3, 4, 5}
s1 := s[2:3]
fmt.Println(s1[1])
複製代碼
上面這個例子中,s1
的容量爲3,長度爲1,因此只能訪問s1
第一個元素s1[0]
,訪問s1[1]
就會報錯:panic: runtime error: index out of range
與切片的容量相關聯的元素只能用於增加切片,在使用這部分元素前,必須將其合併到切片的長度裏。
相較於數組,使用切片的好處在於,能夠按需增加,相似於動態數組。Go提供了內置append
函數,可以幫咱們處理切片增加的一些列細節,咱們只管使用就能夠了。
函數原型:
func append(slice []Type, elems ...Type) []Type 複製代碼
使用append
函數,須要一個被操做的切片和一個(多個)追加值,返回一個相同數據類型的新切片。
s := []int{1, 2, 3, 4, 5}
newS := s[2:4]
newS = append(newS, 50)
fmt.Println(s, newS)
fmt.Println(&s[2] == &newS[0])
複製代碼
輸出:
[1 2 3 4 50] [3 4 50]
true
複製代碼
上面的例子中,截取得到一個長度爲2,容量爲3(可用容量爲1)的新切片newS
,經過append
函數向切片newS
追加一個元素50。 追加元素50以前:
newS
與原切片
s
是共享底層數組的,當切片可用容量可以存下追加元素時,不會建立新的切片。
s := []int{1, 2, 3, 4, 5, 6, 7, 8}
s1 := s[2:4]
fmt.Printf("before -> s=%v\n", s)
fmt.Printf("before -> s1=%v\n", s1)
fmt.Printf("before -> len=%d, cap=%d\n", len(s1), cap(s1))
fmt.Println("&s[2] == &s1[0] is", &s[2] == &s1[0])
s1 = append(s1, 60, 70, 80, 90, 100, 110)
fmt.Printf("after -> s=%v\n", s)
fmt.Printf("after -> s1=%v\n", s1)
fmt.Printf("after -> len=%d, cap=%d\n", len(s1), cap(s1))
fmt.Println("&s[2] == &s1[0] is", &s[2] == &s1[0])
複製代碼
輸出:
before -> s=[1 2 3 4 5 6 7 8]
before -> s1=[3 4]
before -> len=2, cap=6
&s[2] == &s1[0] is true
after -> s=[1 2 3 4 5 6 7 8]
after -> s1=[3 4 60 70 80 90 100 110]
after -> len=8, cap=12
&s[2] == &s1[0] is false
複製代碼
追加元素60、70、80、90、100和110以前:
append
函數會建立一個新的底層數組,將原數組的值複製到新數組裏,再追加新的值,就不會影響原來的底層數組。
通常咱們在建立新切片的時候,最好要讓新切片的長度和容量同樣,這樣咱們在追加操做的時候就會生成新的底層數組,和原有數組分離,就不會由於共用底層數組而引發奇怪問題,由於共用數組的時候修改內容,會影響多個切片。
append
函數會智能地增長底層數組的容量,目前的算法是:當數組容量<=1024
時,會成倍地增長;當超過1024
,增加因子變爲1.25,也就是說每次會增長25%的容量。
Go提供了...
操做符,容許將一個切片追加到另外一個切片上:
s := []int{1, 2,3,4,5}
s1 := []int{6,7,8}
s = append(s,s1...)
fmt.Println(s,s1)
複製代碼
輸出:
[1 2 3 4 5 6 7 8] [6 7 8]
複製代碼
使用for
循環迭代切片,配合len
函數使用:
s := []int{1, 2, 3, 4, 5}
for i:=0;i<len(s) ;i++ {
fmt.Printf("Index:%d,Value:%d\n",i,s[i])
}
複製代碼
使用for range
迭代切片:
s := []int{1, 2, 3, 4, 5}
for i,v := range s {
fmt.Printf("Index:%d,Value:%d\n",i,v)
}
// 使用‘_’能夠忽略返回值
s := []int{1, 2, 3, 4, 5}
for _,v := range s {
fmt.Printf("Value:%d\n",v)
}
複製代碼
須要注意的是,range
返回的是切片元素的複製,而不是元素的引用。若是使用該值變量的地址做爲指向每一個元素的指針,就會形成錯誤。
s := []int{1, 2, 3, 4, 5}
for i,v := range s {
fmt.Printf("v:%d,v_addr:%p,elem_addr:%p\n",v,&v,&s[i])
}
複製代碼
輸出:
v:1,v_addr:0xc000018058,elem_addr:0xc000016120
v:2,v_addr:0xc000018058,elem_addr:0xc000016128
v:3,v_addr:0xc000018058,elem_addr:0xc000016130
v:4,v_addr:0xc000018058,elem_addr:0xc000016138
v:5,v_addr:0xc000018058,elem_addr:0xc000016140
複製代碼
能夠看到,v
的地址老是相同的,由於迭代返回的變量在迭代過程當中根據切片依次賦值的新變量。 好了,今天先講到這裏,下一節,咱們再來討論關於Slice
更多的用法!
原創文章,若需轉載請註明出處!
歡迎掃碼關注公衆號「Golang來啦」或者移步 seekload.net ,查看更多精彩文章。
公衆號「Golang來啦」給你準備了一份神祕學習大禮包,後臺回覆【電子書】領取!