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扩容的道理有点类似。

画个图来理解一下

GO学习笔记——切片的操作(12)_第1张图片

s4添加元素以后执行的拷贝操作

GO学习笔记——切片的操作(12)_第2张图片

那么扩容,究竟扩了多少呢?我们来打印一下试试

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语言是有垃圾回收机制的,如果原数组在后面还会用到,就还是存在(像这边我们就用到了,因为我们打印了这个原数组);如果用不到,它的空间就会被回收。

 

创建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的语法支持的。

 

你可能感兴趣的:(GO语言学习笔记)