The Way To Go --- 切片

書旅發表於2020-09-21

初識切片

基本使用

在go語言中一般不使用陣列,大多都是使用切片(slice),看一個切片的示例


arr := [...]int{0,1,2,3,4,5,6,7,8}

s := arr[2:6]

這裡定的s就是切片,上邊的arr是一個陣列,這裡s的結果是[2,3,4,5](取arr中3到5這個區間的資料,是左閉右開的)

切片還有很多的用法,如下:


arr := [...]int{0,1,2,3,4,5,6,7,8}

fmt.Println("arr[2:6] = ", arr[2:6])

fmt.Println("arr[:6] = ", arr[:6])

fmt.Println("arr[2:] = ", arr[2:6])

fmt.Println("arr[:] = ", arr[:])

輸出結果:


arr[2:6] = [2 3 4 5]

arr[:6] = [0 1 2 3 4 5]

arr[2:] = [2 3 4 5]

arr[:] = [0 1 2 3 4 5 6 7 8]

像這種方括號中([])加個冒號(:)的資料結構,都可以看做是切片(slice)。當然還有很多其它的宣告方法

切片是對陣列的view

我們知道在go中,陣列是一個值型別,而切片是一個引用型別,切片是對陣列的檢視(view),可能不太好理解,看下邊的程式碼


func updateSlice(s []int) {

s[0] = 520

}

func main() {

arr := [...]int{0,1,2,3,4,5,6,7,8}

s1 := arr[2:]

fmt.Println("s1更新之前")

fmt.Println("s1 = ", s1)

fmt.Println("arr = ", arr)

fmt.Println("更新切片s1")

updateSlice(s1)

fmt.Println("更新之後,s1和arr的值")

fmt.Println(s1)

fmt.Println(arr)

}

輸出結果:


s1更新之前

s1 = [2 3 4 5 6 7 8]

arr = [0 1 2 3 4 5 6 7 8]

更新切片s1

更新之後,s1和arr的值

[520 3 4 5 6 7 8]

[0 1 520 3 4 5 6 7 8]

因為s1(切片)是對arr(陣列)的檢視(view),所以當s1中的值被改變之後,陣列中的值也發生了改變(改變的是s1中下標為0的元素,對應的是陣列arr中下標為2的元素)

對切片進行切片操作


func learnSlice1() {

arr := [...]int{0,1,2,3,4,5,6,7,8}

s1 := arr[2:6]

s2 := s1[1:]

fmt.Println("s1 = ", s1)

fmt.Println("s2 = ", s2)

}

輸出結果:


s1 = [2 3 4 5]

s2 = [3 4 5]

s2是對切片s1又進行了一次切片

深度理解切片

直接看一個示例


func learnSlice2() {

arr := [...]int{0,1,2,3,4,5,6,7,8}

s1 := arr[2:6]

s2 := s1[3:5]

fmt.Println("s1 = ", s1)

fmt.Println("s2 = ", s2)

}

s1的列印結果,我們肯定知道,但是這裡的s2列印結果是什麼?

我們知道s1 = [2,3,4,5],s2是對s1取下標是3到5這個區間的資料,也就是s1[3]、s1[4]。但是可以看到s1的下標最大是s[3],那這裡s1[3:5]會報錯嗎?

答案是不會報錯,可以看一下列印的結果,然後再解釋為什麼


s1 = [2 3 4 5]

s2 = [5 6] //s1[3]、s1[4]

可以看到s2中的那個6就不在s1裡邊,如果我們嘗試直接列印s1[4]會報錯。但是列印s2這個切片,就會正常列印出結果,那到底這個6是如何取出來的?

上邊有說切片(slice)是對陣列(array)的檢視(view),那這個底層是什麼樣的,如何view的?

上邊給出的基礎陣列arr是[0,1,2,3,4,5,6,7,8],然後s1 := arr[2:6],取到的就是[2,3,4,5],這四個數字對映到s1的下標就是0、1、2、3,s1裡邊因為是對底層陣列arr的view,它的下標不是到3就結束了,它還有4、5的,但是直接透過s1[4]或s1[5]是取不到的,但是s1還是知道它裡邊有4和5這兩個下標的,因為s1是知道底層的陣列的

然後,s2 := s1[3:5],取到的就是s1中3,4這兩個下標的值。s2的下標就是0、1、2(2其實是看不見的)。從底層陣列的角度s1[3]、s1[4]的值就是5和6

切片內部實現

切片中首先有一個ptr指標,它指向slice開始的那個元素。有一個len,標記的是切片的長度,我們使用下標的方式對切片取值的時候,最大隻能取到下標為len-1的值,如果大於或等於len,就會報錯。然後它裡邊還有一個值cap,它代表的是整個陣列從ptr指向的位置開始到結束的長度

切片它可以向後擴充套件,但是不可以向後擴充套件。比如s1:=arr[2:6],s1最前邊只能看到2,前邊的0、1下標對應的值是看不到的

切片的下標不可以超過len(s),向後擴充套件不可以超過底層陣列cap(s)

看一波示例


func learnSlice2() {

arr := [...]int{0,1,2,3,4,5,6,7,8}

s1 := arr[2:6]

s2 := s1[3:5]

fmt.Printf("s1 = %v, len(s1) = %d, cap(s1) = %d", s1, len(s1), cap(s1))

fmt.Println()

fmt.Printf("s2 = %v, len(s2) = %d, cap(s2) = %d", s2, len(s2), cap(s2))

}

輸出結果


s1 = [2 3 4 5], len(s1) = 4, cap(s1) = 7

s2 = [5 6], len(s2) = 2, cap(s2) = 4

相關文章