非懂不可的Slice(一)-- 就要學習Go語言

前言

切片是一種複合數據類型,與數組相似,存放相同數據類型的元素,但數組的大小是固定的,而切片的大小可變,能夠按需自動改變大小。切片是基於底層數組實現的,是對數組的抽象。切片很小,只有三個字段的數據結構:指向底層數組的指針、能訪問的元素個數(即切片長度)和容許增加到的元素個數(即切片容量)。golang

如上圖所示,一個長度爲三、容量爲5的整型切片的底層結構。

聲明與初始化

make()建立

使用內置函數建立空切片,形如:算法

s := make([]type, len, cap)  // len 長度,cap 容量
複製代碼

也能夠只指定len,那麼切片的容量和長度是同樣的。Go語言提供了內置函數lencap分別返回切片的長度和容量。數組

// 聲明一個長度爲三、容量爲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索引的值。ij都是可選的,i若是省略,默認是0,j若是省略,默認是原切片或數組的長度。ij都不能超過這個長度值。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])
複製代碼

輸出
你可能會有個疑問:截取得到的新切片的長度和容量怎麼計算呢?咱們固然可使用內置函數lencap直接得到,若是明白了怎麼計算的,咱們處理問題就能夠更駕輕就熟。
對底層數組大小爲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的長度和容量分別是26注意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以前:

追加元素10以前
追加元素50以後:
追加元素10以後
經過輸出結果能夠得出,新切片 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來啦」給你準備了一份神祕學習大禮包,後臺回覆【電子書】領取!

公衆號二維碼
相關文章
相關標籤/搜索