go語言之數組與切片

數組和切片的相同點及不一樣點

共同點:

都屬於集合類的類型,它們的值用來存儲某一類型的值。數組

不一樣點:

  • 數組類型的長度是固定的,它必須在聲明它的時候就必須給定,而且以後不會再改變。能夠說數組的長度是數組類型的一部分,例如[1]string和[2]string就是兩個不一樣的數組類型。bash

  • 切片類型的長度是可變長的,它的切片類型字面量中只有元素的類型而沒有元素的長度。切片的長度能夠自動隨着其中元素數量的增加而增加,但不會隨着元素數量的減小而減小。數據結構

  • 數組和切片都有長度和容量的概念,分別能夠經過內建函數len()cap()獲取,其中,數組的容量永遠 永遠等於長度,都是不可變的。而切片的容量雖然相對複雜些但也是有規律可尋,後文後講解如何估算一個切片的長度和容量。app

image

本質上來講,咱們能夠把切片看作是對數組的一層簡單的封裝,每一個切片的底層數據結構都是數組,它能夠看做是對數組某個連續片斷的引用,這裏須要注意的幾點是:函數

  • Go語言中的切片類型屬於引用類型,它是對數組某個連續片斷的引用。同屬引用類型的還有字典類型、通道類型和函數類型。而數組類型則屬於值類型,同屬於值類型的有基礎數據類型和結構體類型
  • 在Go語言中,判斷函數所謂的傳值仍是傳引用問題只須要看被傳遞的值的類型便可,若傳遞的值是值類型的,那麼就是傳值,反之若傳遞的值是引用類型的,那麼就是引用傳遞。從傳遞成本的角度而言,引用類型的值要比值類型低得多
  • 在數組和切片之上可使用索引表達式訪問某個具體的元素,也可使用切片表達式獲得一個新的切片

如何初始化一個切片

咱們能夠經過切片字面量表達式[]int{1,2,3}和內建make函數make([]int,5,6)初始化一個切片,也能夠經過切片表達式基於某個數組或切片生成新切片,接下來分別描述下這幾種場景:ui

經過切片表達式初始化切片

func main() {
	s := []int{1, 2, 3, 4, 5, 6}
	fmt.Printf("slice length: %d\n", len(s))
	fmt.Printf("slice cap: %d\n", cap(s))
}

// 輸出
// slice length: 6
// slice cap: 6

複製代碼

經過這種方式初始化的切片其長度和容量都等於初始化時傳入的元素數量。spa

經過make函數初始化切片

func main() {
	s1 := make([]int, 5)
	s2 := make([]int, 5, 6)
	fmt.Printf("s1 length: %d\n", len(s1))
	fmt.Printf("s1 cap: %d\n", cap(s1))
	fmt.Printf("s2 length: %d\n", len(s2))
	fmt.Printf("s2 cap: %d\n", cap(s2))
}

// 輸出
// s1 length: 5
// s1 cap: 5
// s1 length: 5
// s1 cap: 6
複製代碼

內建函數make接收三個參數,第一個參數爲切片的類型字面量,第二個變量爲切片的長度,第三個變量爲切片的容量。當不指明切片容量的時,切片的容量就會和長度一致。3d

這裏能夠把切片當作一個窗口,經過這個窗口能夠看到底層的數組,窗口被劃分紅一個一個的小格子,每一個格子表明一個數組元素,但由於窗口大小有限所以不能看到數組中全部的元素,大部分時候只能看到數組連續的一部分元素。code

當咱們經過make函數或切片值字面量初始化的切片,它的第一個元素老是會對應其底層數組的第一個元素,在這種狀況下,切片的容量就等於其底層數組的長度。拿s2爲例,窗口最左邊的格子對應的正好是其底層數組索引爲0的第一個元素,所以,s2中索引從0到4的元素爲其底層數組中索引從0到4表明的那5個元素。cdn

基於某個數組或切片生成新切片

func main() {
	s3 := []int{1, 2, 3, 4, 5, 6, 7, 8}
	s4 := s3[3:6]
	fmt.Printf("The length of s4:%d\n", len(s4))
	fmt.Printf("The capacity of s4:%d\n", cap(s4))
	fmt.Printf("The value of s4:%d\n", s4)
}

// 輸出
// The length of s4:3
// The capacity of s4:5
// The value of s4:[4 5 6]
複製代碼

在這裏須要明白s3[3:6]切片表達式中方括號兩個整數其實就是數學中的區間表示法,其中3爲起始索引,6爲終止索引,表明s4從s3中索引爲3的元素開始到索引爲5的元素結束(不包含6),s4的長度就是6-3=3。所以咱們能夠說s4中的索引從0到2指向的元素對應的是s3中索引從3到5的那3個元素。

再來看看容量,一個切片的容量能夠看作爲經過這個窗口最多能夠看到的底層數組重元素的個數。因爲s4是在s3的基礎上經過切片操做得來的,因此s4的底層數組就是s3的底層數組,所以s4能夠向右擴展直至數組末尾,它的容量就是其底層數組的長度8減去s3的起始索引3,即5。

注意這裏切片是沒法向左擴展的,所以是永遠沒法透過s4的窗口看到s3的前三個元素的。

若要將s4的窗口向右擴展到最大,能夠經過切片表達式s4[0,cap(s4)]作到,它的結果值爲[]int{4,5,6,7,8}

切片容量的增加規律

func main() {
	s5 := []int{1, 2, 3, 4, 5, 6}
	s6 := s5[0:2]
	s6 = append(s6, 66)
	fmt.Printf("The value of s6:%d\n", s6)
	fmt.Printf("The value of s5:%d\n", s5)
}

// 輸出
// The value of s6:[1 2 66]
// The value of s5:[1 2 66 4 5 6]
複製代碼

能夠經過append函數對切片進行擴展(這裏append函數返回的是一個新切片,所以須要用切片類型的變量去接收它的返回值),只要新長度不會超過切片的原容量時,那麼使用append函數對其追加元素時就不會引發擴容,只會使得緊鄰切片窗口右邊的(底層數組中的)元素被新的元素替換掉,生成一個指向原先底層數組的新切片。

當新長度超過切片的容量時,append函數返回的是指向新底層數組的新切片,它會生成一個新的底層數組,而後將原有的元素和新的元素拷貝到新數組中,而後再生成一個新切片指向這個新的底層數組,在通常狀況下,能夠簡單地認爲新切片的容量時原切片容量的兩倍。

相關文章
相關標籤/搜索