原文链接:slice在Golang中的实现和应用
发布时间:2021-09-05 12:36:27
文章目录
底层实现
1 | struct Slice |
我们知道slice是用来管理一个内存地址连续的内存空间,类似C++中的vector。由上面的定义可以看到,Slice在底层实现是一个结构体。
- array 是内存空间的首地址
- len 是内存中元素的个数,这个标记很重要,由它可以知道下一次插入数据时该插入到哪个位置
- cap 内存空间的长度,这个标记同样很重要,由它可以知道什么时候空间被使用完,然后申请更大的空间。
基本过程:
不断地向slice插入新的数据,len会不断增大,当len 大于 cap时,会申请更大的空间(据我观察,申请的空间是原来的2倍),然后把原来的数据复制到新的空间中。
创建slice的三种方法
1、使用make创建slice
由上面定义可知,slice底层有三个参数,所以使用make创建slice也需要指定3个参数。
1 | s1 := make([]int, 3) // 有3个元素的切片, len为3, cap缺省此时等于len |
其中s1和s2的定义方式,本质上是一致的。但是他们和s3不同,因为s1和s2已经有了3个元素,再插入一个新元素就会触发内存扩容,新的数据被插入第四个位置上。而s3内没有元素,下一个新元素被插入到第一个位置上。
2、使用数组创建slice
语法格式:
1 | s := data[low : high : max] |
同样是三个参数,但是这三个参数都是data数组上的索引位置,言外之意就是:假设data数组容量为10,那么这三个参数最大为10,超过10就是不合法,就会编译报错(out of bounds for 10-element array)。
所以:
- low 确定了slice中内存的起始位置,即确定了array的值
- high 确定了slice中len的值,len等于 high - low
- max 确定了slice中cap的值,cap等于 max - low
另外有一点需要说明,这样产生的切片底层指向data数组,修改s中数据,就会修改到data中的数据。
举例:
1 | data := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // 创建数组,容量为10,同时包含10个元素 |
刚才说了,指定的三个参数,都是原数组的索引位置,但是原数组10个元素,其索引范围为0-9,怎么能出现10呢?这个需要从指针的角度去理解,索引为10,其实就是第11个元素内存的起始指针,同时也是第10个元素内存的终止位置,这个位置是确实存在,没有越界。比如s3的定义方式,就很特殊,它的容量为0。
3、直接定义
1 | var s []int // 表示一个容量为0的切片 |
向slice插入数据
直接使用在索引位置插入
1 | data := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // 创建数组,容量为10,同时包含10个元素 |
使用append插入数据
1 | // 等价于s1[2]=100 |
append的语法格式:
1 | s1 = append(s1, 100) |
必须要接收append的返回值,否则编译报错。
为甚么一定需要一个值接收呢?
因为slice本质是一个结构体,不是指针,是值传递。当append对一个slice操作后,增加新的元素,它的len值就变化了,需要返回新的slice结构体,必须要接收。
总结
- slice是一个结构体,不是指针,map和channel是指针
- slice使用append插入数据,就是一个动态数组,会自动扩容
- 利用数组创建切片时,修改切片会修改原数组的数组(在扩容之前)