我正在参加「掘金·启航计划」
1、切片底层实现
1.1、为什么使用切片
- Go数组是值类型,赋值和函数传参操作都会复制整个数组,会消耗大量的内存。切片传数组参数,是引用传递,可以共享内存,节约内存。
- 数组长度是数组类型的一部分,所以数组有很多局限性
1.2、切片的数据结构
切片本身并不是动态数组或者数组指针,它内部实现的数据结构通过指针引用底层数组,设定相关属性将数据读写操作限定在指定的区域内,工作机制类似数组指针的一种封装
切片是对数组一个连续片段的引用, 所以切片是一个引用类型,这个片段可以是整个数组,或者是由起始和终止标识的一些项的子集,终止索引不包含在切片内(左包含右不包含);切片提供了一个与指向数组的动态窗口。
Slice的数据结构定义如下:
type slice struct{
array unsafe.Pointer //指向一个数组的指针
len int //当前切片的长度
cap int //当前切片的容量 cap总是大于len
}
切片的结构体由3部分组成,Pointer是指向一个数组的指针,len代表当前切片的长度,cap是当前切片的容量,cap总是大于len的
1.3创建切片
make函数允许在运行期动态指定数组长度,绕开了数组类型必须使用编译期常量的限制,
创建切片有两种形式,make创建切片,空切片。
1.3.1make和切片字面量
func makeslice(et *_type, len, cap int ) slice {
//根据切片的数据类型,获取切片的最大容量
maxElement := maxSliceCap(et.size)
//比较切片的长度,长度值域应该在[0, maxElement]之间
if len < 0 || uintptr(len) > maxElement {
panic(errorString("makeslice: len out of range"))
}
//比较切片的容量,容量值应该在[len,maxElement]之间
if cap < len || uintptr(cap) > maxElement {
panic(errorString("makeslice: cap out of range"))
}
//根据切片的容量申请内存
p := mallocgc(et.size*uintptr(cap), et, true)
//返回申请内存的切片的首地址
return slice{p, len, cap}
}
1.3.2 nil和空切片
var slice []int //nil切片
nil切片描述一个不存在的切片,nil切片的指针指向nil,比如函数在发生异常的时候,返回的切片就是nil切片。
空切片一般会用来表示一个空的集合。比如数据库查询,一条结果已没有查到,就返回一个空的切片
slice := make([]int, 0)
slice := []int{}
空切片和nil切片的区别在于,空切片指向的地址不是nil,指向的是一个内存地址,但是他没有分配任何内存空间,既底层元素包含0个元素。
不管是使用nil切片还是空切片,调用内置函数append、len和cap的效果都是一样的。