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函数。