上一篇文章讲完了切片的概念,再来说一下切片的常用操作。
vector中封装了很多对vector的操作,那么Slice是怎么搞定这些操作的呢?
向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,数组大小还是没变
这个时候,它底层到底是怎么实现的呢?
上面的切片都是在原数组上进行操作,那么从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的2倍。
好了,添加元素的原理知道了,但是原数组怎么办呢?
GO语言是有垃圾回收机制的,如果原数组在后面还会用到,就还是存在(像这边我们就用到了,因为我们打印了这个原数组);如果用不到,它的空间就会被回收。
之前说过,切片底层是对数组的一层映射,那么切片既然是一个类型,它有没有我们所说的默认零值呢?
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]
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没变,没有发生扩容
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的语法支持的。