Go slice那些事
今晚闲来无事,总结一下Go的slice
slice是什么
slice在Go中的原型?
slice类似数组,是一种定长的数组。在底层slice是这样的:
type slice struct {
array unsafe.Pointer
len int
cap int
}
如何创建slice
1.创建nil slice
var lst []int
2.创建无容量的空slice
lst := make([]int, 0, 0)
3.创建有容量的空slice
lst := make([]int, 0, 6)
4.创建零值slice(10个0)
lst := make([]int, 10)
5.用引用创建slice
lst := array[:]
slice的其他操作函数
len(lst) 获得slice的长度
cap(lst) 获得slice的容量
append(lst, 6) 给lst添加1个数字6
append(lst, lst2…) 给lst添加一个slice,lst2是一个slice
copy(lst, srcList) //拷贝srcList中内容到lst中去,仅能拷贝len(lst)个元素
部分细节陷阱
1.函数形参为slice
函数形参传递slice是值传递,并不是引用传递,在函数中修改形参的值,并不会影响函数外的传入的slice
解决办法
(1):传入slice指针
func modify(lst *[]int) {
//修改lst
}
(2):函数内修改了slice后返回
func modify(lst []int) []int { //修改lst return s }
(3):将函数作为slice的指针
type sliceInt []int
func(this *sliceInt) modify() {
//修改lst
}
2.数据同源
func main() {
lst := [2]int{}
lst[0] = 1
fmt.Printf("lst: %v , len: %d, cap: %d\n", lst, len(lst), cap(lst)) dlst := lst[0:1] dlst[0] = 5 fmt.Printf("lst: %v ,len: %d, cap: %d\n", lst, len(lst), cap(lst)) fmt.Printf("dlst: %v, len: %d, cap: %d\n", dlst, len(dlst), cap(dlst))
}
输出如下
lst: [1 0] , len: 2, cap: 2
lst: [5 0] ,len: 2, cap: 2
dlst: [5], len: 1, cap: 2
原因:dlst会指向来源于lst,底层数据指针是指向lst的,修改dlst的数据也同时会修改lst的数据。
3.slice扩容陷阱
func main() {
lst := [2]int{}
lst[0] = 1
fmt.Printf("lst: %v , len: %d, cap: %d\n", lst, len(lst), cap(lst)) dlst := lst[0:1] dlst = append(dlst, []int{2, 3}...) dlst[1] = 1 fmt.Printf("lst: %v ,len: %d, cap: %d\n", lst, len(lst), cap(lst)) fmt.Printf("dlst: %v, len: %d, cap: %d\n", dlst, len(dlst), cap(dlst))
}
输出如下:
lst: [1 0] , len: 2, cap: 2
lst: [1 0] ,len: 2, cap: 2
dlst: [1 1 3], len: 3, cap: 4
原因:因为存储空间不够,需要扩容,dlst会被分配新的空间,所以此时修改dlst数据并不会改变lst中的源数据。因为dlst数据指针已经不再指向源数据地址。
Empty Slice和Nil Slice和零slice
1.零slice
lst := make([]int, 10)
10个零值slice
2.空Slice
lst := make([]int, 0)
没有长度和容量的slice
3.nil Slice
var lst []int
此时lst是一个nil的slice,底层数据 len:0,cap:0,*Elem:nil
注:可以对nil的slice 使用append操作。
扩容策略
append的扩容规则为:
1.如果新申请容量大于2倍的旧容量,最终容量就是新申请的容量;否则执行第2步
2.如果旧切片的长度小于1024,则最终容量就是旧容量的两倍,否则执行第3步
3.如果旧切片长度大于等于1024,则最终容量从旧容量开始循环增加原来的 1/4,即直到最终容量大于等于新申请的容量
4.如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)
Go slice那些事注:具体的代码参考go目录 src/runtime/slice.go中的growslice函数。