1. 程式人生 > >GO學習筆記——切片的操作(12)

GO學習筆記——切片的操作(12)

上一篇文章講完了切片的概念,再來說一下切片的常用操作。

vector中封裝了很多對vector的操作,那麼Slice是怎麼搞定這些操作的呢?

向Slice新增元素(append)

向Slice新增元素使用內建函式append,先來看一下程式碼

func main() {
	arr := []int{0,1,2,3,4,5,6}
	s1 := arr[2:6]
	s2 := s1[2:4]
	s3 := append(s2,7)
	s4 := append(s3,8)
	s5 := append(s4,9)
	fmt.Println(s1)
	fmt.Println(s2)
	fmt.Println(s3)
	fmt.Println(s4)
	fmt.Println(s5)
	fmt.Println(arr)
}

 輸出結果

[2 3 4 5]
[4 5]        //s2是[4,5]
[4 5 7]        //在s2後面新增一個元素7是s3,但是元素7原來的位置是6所在的位置,所以覆蓋了
[4 5 7 8]      //在s3後面新增一個元素8是s4
[4 5 7 8 9]    //在s4後面新增一個元素9是s5
[0 1 2 3 4 5 7]    //在原陣列只有元素6那個位置被改成了元素7,陣列大小還是沒變

這個時候,它底層到底是怎麼實現的呢?

  1. 首先看s1,它是[2,3,4,5]
  2. s2是在s1的基礎上切片,切出來的是[4,5]
  3. s3是在s2的基礎上切片,並且在s2加上了元素7,但是在原陣列上,5這個元素後面是6,因此,新新增的元素7就覆蓋了元素6,這一點在最後一行列印的原陣列arr上也可以看出來

上面的切片都是在原陣列上進行操作,那麼從s4起,因為s3已經達到了它的原陣列的上限(它新增的元素7覆蓋了元素6,元素6就是原陣列的上限),這個時候s4是在s3的基礎上,又添加了一個元素8。那麼問題來了,現在這個s4還是原陣列的一層對映嗎?

顯然不是的,因為陣列的大小是不可以改變的。其實GO在底層重新建立了一個數組,並且該陣列的大小要比原來的大一點(可能是原陣列大小的2倍),並把原陣列的元素拷貝到新陣列中,剩下的那些元素都被設為了預設零值也就是0。

所以從s4開始,這些Slice就不是原陣列的檢視了,而是一個新陣列的檢視。這個和vector擴容的道理有點類似。

畫個圖來理解一下

s4新增元素以後執行的拷貝操作

那麼擴容,究竟擴了多少呢?我們來列印一下試試

func main() {
	arr := []int{0,1,2,3,4,5,6}
	s1 := arr[2:6]
	s2 := s1[2:4]
	s3 := append(s2,7)
	s4 := append(s3,8)
	fmt.Println(cap(s3))
	fmt.Println(cap(s4))
}

輸出結果

3
6

所以,我們可以想到,s3的cap是3,也就是(4,5,7)3個元素,且7這個元素是原陣列的上限;而s4是新陣列,s4的cap是6,也就(4,5,7,8,0,0)6個元素,其中(4,5,7)是從原陣列拷貝過來的,而(8,0,0)是新陣列多開闢的,也就是多了原切片的cap。

為了驗證,我們換一個切片模型再來看一下

func main() {
	arr := []int{0,1,2,3,4,5,6}
	s1 := arr[2:6]
	//s2 := s1[2:4]
	s3 := append(s1,7)	//這裡s3是在s1的切片基礎上
	s4 := append(s3,8)	
	fmt.Println(cap(s3))
	fmt.Println(cap(s4))
}

輸出結果

5
10

s3的cap是5,也就是(2,3,4,5,7)這5個元素,且7這個元素是原陣列的上限;而s4是新陣列,s4的cap是10,也就(2,3,4,5,7,8,0,0,0,0)10個元素,其中(2,34,5,7)是從原陣列拷貝過來的,而(8,0,0,0,0)是新陣列多開闢的,也就是多了原切片的cap。

所以可以得出結論:新陣列擴的容是根據原切片的長度決定的,在需要傳送擴容的那一條語句中,原陣列上切片的cap是多少,那麼新陣列上擴容的大小就是原陣列的cap。

好了,新增元素的原理知道了,但是原陣列怎麼辦呢?

GO語言是有垃圾回收機制的,如果原陣列在後面還會用到,就還是存在(像這邊我們就用到了,因為我們列印了這個原陣列);如果用不到,它的空間就會被回收。

建立Slice(make)

之前說過,切片底層是對陣列的一層對映,那麼切片既然是一個型別,它有沒有我們所說的預設零值呢?

func main() {
	var s1 []int
	var s2 []int = nil
	fmt.Println(s1,s2)
}

這邊我們定義了一個Slice,但是它不是任何陣列的對映,但是它既然作為一個變數,一個型別,就肯定是有預設零值的。GO中定義Slice的預設零值為nil,也就是什麼都沒有的意思。看一下列印結果

[] []

這兩個切片中就沒有任何元素,它們也不是任意一個數組的對映,它們就是兩個空的切片。

說到這裡,我們就可以對一個空的切片進行append操作了。

func main() {
	var s []int
	for i := 0; i < 10 ; i++{
		s = append(s,i)
	}
	fmt.Println(s)
}

上述就是對一個空的Slice進行append操作,來不斷地插入元素。

另外也可以在建立切片的時候直接賦值

func main() {
	s1 := []int{0,1,2,3,4,5,6}    
	fmt.Println(s1)
}

這相當於下面的建立方式,建立了一個數組

func main() {
	arr := []int{0,1,2,3,4,5,6}
	s1 := arr[:]
	fmt.Println(s1)
}

使用make建立Slice

func main() {
	s1 := make([]int,5)		//表示建立一個len為5的Slice,那麼它的cap也被預設為5
	s2 := make([]int,6,10)	//表示建立一個len為6的Slice,且它的cap為10
	fmt.Println(len(s1),cap(s1),s1)
	fmt.Println(len(s2),cap(s2),s2)
}

輸出結果

5 5 [0 0 0 0 0]
6 10 [0 0 0 0 0 0]

拷貝Slice(copy)

func main() {
	s1 := []int{1,2,3,4}
	s2 := make([]int,5,10)
	fmt.Println(len(s2),cap(s2),s2)
	copy(s2,s1)    //將s1拷貝到s2中
	fmt.Println(len(s2),cap(s2),s2)
}

輸出結果

5 10 [0 0 0 0 0]
5 10 [1 2 3 4 0]

注意,拷貝的時候不會發生擴容,如果目標切片的cap不能夠裝下源切片的元素,那麼多餘的就會自動捨棄

func main() {
	s1 := []int{1,2,3,4}
	s2 := make([]int,3)
	fmt.Println(len(s2),cap(s2),s2)
	copy(s2,s1)
	fmt.Println(len(s2),cap(s2),s2)
}

輸出結果

3 3 [0 0 0]
3 3 [1 2 3]    //多餘的4被自動捨棄了,cap沒變,沒有發生擴容

刪除Slice中的元素

GO中沒有內建像erase這樣的刪除元素的函式,所以我們想要刪除Slice中的元素,需要通過append來搞定刪除操作。

func main() {

	s1 := []int{0,1,2,3,4,5,6}

	//刪除下標為index的元素
	//只要將index之前的部分和index之後的部分連線起來就可以了
	fmt.Println(len(s1),cap(s1),s1)

	index := 3	//刪除下標為3的元素
	s1 = append(s1[:index],s1[(index + 1):]...)
	fmt.Println(len(s1),cap(s1),s1)

	index = 0	//頭刪
	s1 = append(s1[:index],s1[(index + 1):]...)
	fmt.Println(len(s1),cap(s1),s1)

	index = len(s1) - 1	//尾刪
	s1 = append(s1[:index],s1[(index + 1):]...)
	fmt.Println(len(s1),cap(s1),s1)

}

我們要刪除切片中的指定index的元素,只需要將index之前的部分和index之後的部分連線起來就可以了

輸出結果

7 7 [0 1 2 3 4 5 6]    //原始切片
6 7 [0 1 2 4 5 6]    //刪除下標為3的元素,即3
5 7 [1 2 4 5 6]    //頭刪,即刪除0
4 7 [1 2 4 5]    //尾刪,即刪除6

上述語法中的 s1[(index + 1):]... 表示從index+1位置開始的後續所有元素,這是GO的語法支援的。