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,陣列大小還是沒變
這個時候,它底層到底是怎麼實現的呢?
- 首先看s1,它是[2,3,4,5]
- s2是在s1的基礎上切片,切出來的是[4,5]
- 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的語法支援的。