1. slice
slice又称动态数组,依托数组实现,可以方便地进行扩容和传递,实际使用中比数组更灵活。
2.基本操作
2.1 初始化
// 1.变量声明,此时为nil
var s []int
// 等价于使用new函数
s := *new([]int)
// 2.字面量,空切片是指长度为空,不是nil。
s1 := []int{}//空切片
s2 := []int{1,2,3}
// 3.内置函数make()
s1 := make([]int,12)//指定长度
s2 := make([]int,10,100)//指定长度和空间
// 4.切取
array := [5]int{1,2,3,4,5}
s1 := array[0:2] //从数组切取
s2 := s1[0:1] //从切片切取
//
- 声明长度为0的切片时,推荐使用变量声明获得一个nil切片,因为空切片不需要分配内存
- 使用make可以创建切片,切片元素均初始化为对应类型的零值。推荐指定长度的同时指定预估空间,可有效减少切片扩容时内存分配及拷贝次数。
2.2 切片操作
可以使用内置函数append()用于向切片中追加元素
s := make([]int, 0)
s = append(s, 1) //添加一个元素
s = append(s, 2,3,4) //添加多个元素
s = append(s, []int{5,6}...) //添加一个切片
当切片函数空间不足时,append()会先创建新的大容量切片,添加元素后再返回新切片。
由于切片的本质为结构体,结构体中直接存储了长度和容量,如源码所示。
3. 实现原理
3.1 数据结构
type slice struct{array unsafe.Pointer //指向底层数组len int //切片长度cap int //底层数组的容量
}
3.2 切片操作
- 使用make创建slice
- 使用数组创建slice,slice将与原数组共用一部分内存
- slice扩容
- 扩容实际上是重新分配一块更大的内存,将原slice数据拷贝进新slice,然后返回新slice,扩容后再将数据追加上去。
- 扩容原则:容量小于1024.新容量扩大到原来的2倍。大于1024,扩大到原来的1.25倍。
- slice拷贝
- copy()内置函数拷贝两个切片时会将原切片的数据逐个拷贝到目标切片指向的数组中,拷贝的数量取两个切片长度的最小值。
- 拷贝不会发生扩容
4. 切片表达式
4.1 简单表达式
a[low : high]
- 底层数组共享
- 当使用简单表达式生成的切片,与原数组或切片共享底层数组
- 边界问题
- 如果简单表达式截取的对象为字符串或数组,那么a[low:high]中的low和high需要满足0<=low<=high<=len(a)
- 如果简单表达式截取的对象为切片,那么a[low:high]中的low和high需要满足0<=low<=high<=cap(a)
- 切取string时会产生一个新的string
- low和high均有默认值,low为0,high为len(a)
4.2 扩展表达式
简单表达式生成的新切片与原数组或切片共享底层数组避免了拷贝元素,但节约内存的同时也会带来一定的风险。
新切片b(b:=a[low:high])不仅可以读写a[low]至a[high-1]之间的所有元素,而且在使用append(b,x)的时候,可能会覆盖a[high]及后面的元素。
这些操作往往是非预期的
在Go1.2中提供了a[low:high:max]
来限制新切片的容量
扩展表达式中的max用来限制新生成的切片的容量cap,新切片的容量为max-low。
表达式中的各参数需要满足0<=low<=high<=max<=cap(a)
当max==high时就不会发生之前的情况。
- 扩展表达式中只有low是可以省略的
- 扩展表达式可以用于数组和切片,但是不能用于字符串