本文已参与「新人创作礼」活动,一起开启掘金创作之路。
相似的外形,不同的内心
slice和数组,差别只是定义上,数组是规定固定长度,slice是可以不定长度,但是实际实现上确实截然相反
slice和数组的关系,有点像java中ArrayList和数组的关系
//定义一个数组
var arrayA [5]int = [5]int{10, 11, 12, 13, 14}
//定义一个slice 区别就在于[]是空的,当然也可以用另外一个形式的make([]int, 5)来定义
var sliceB []int = []int{20, 21, 22, 23, 24}
复制代码
但是其内在却是截然不同
数组是连续的空间:
而slice更像是一个ArrayList类
采用make方法生成
扩容
如果不注意,会出现bug
在这种情况下,扩容以后并没有新建一个新的数组,扩容前后的数组都是同一个,这也就导致了新的切片修改了一个值,也影响到了老的切片了。并且 append() 操作也改变了原来数组里面的值。一个 append() 操作影响了这么多地方,如果原数组上有多个切片,那么这些切片都会被影响!无意间就产生了莫名的 bug!
如何证明数组和slice的不同
func branch7() {
fmt.Println("...")
//定义一个数组
var arrayA [5]int = [5]int{10, 11, 12, 13, 14}
fmt.Printf("prtArray: %p \n", &arrayA)
for i := 0; i < len(arrayA); i++ {
fmt.Println("i:", i, " | v:", arrayA[i], " | prt:", &arrayA[i])
}
//定义一个slice
var sliceB []int = []int{20, 21, 22, 23, 24}
fmt.Printf("prtSlice: %p \n", &sliceB)
for i := 0; i < len(sliceB); i++ {
fmt.Println("i:", i, " | v:", sliceB[i], " | prt:", &sliceB[i])
}
}
复制代码
结果打印,其中prtArray指向的指针,是和第一个元素重叠的;但是prtSlice,指向slice的指针,却和第一个元素地址完全不同
vincent@minipc:~/foreverC$ go run study_go.go
...
prtArray: 0xc4200161b0
i: 0 | v: 10 | prt: 0xc4200161b0 //地址和prtArray相同
i: 1 | v: 11 | prt: 0xc4200161b8
i: 2 | v: 12 | prt: 0xc4200161c0
i: 3 | v: 13 | prt: 0xc4200161c8
i: 4 | v: 14 | prt: 0xc4200161d0
prtSlice: 0xc42000a080
i: 0 | v: 20 | prt: 0xc4200161e0 //地址和prtSlice不同
i: 1 | v: 21 | prt: 0xc4200161e8
i: 2 | v: 22 | prt: 0xc4200161f0
i: 3 | v: 23 | prt: 0xc4200161f8
i: 4 | v: 24 | prt: 0xc420016200
复制代码
扩容都是2倍的扩容方式,编码习惯是每个slice的声明都要指定长度,不能慢慢扩容上去
s := []int{5}
fmt.Println("len:", len(s), " | cap:", cap(s), " | ptr(s)=", &s[0])
s = append(s, 7)
fmt.Println("len:", len(s), " | cap:", cap(s), " | ptr(s)=", &s[0])
s = append(s, 9)
fmt.Println("len:", len(s), " | cap:", cap(s), " | ptr(s)=", &s[0])
s = append(s, 11)
fmt.Println("len:", len(s), " | cap:", cap(s), " | ptr(s)=", &s[0])
s = append(s, 13)
fmt.Println("len:", len(s), " | cap:", cap(s), " | ptr(s)=", &s[0])
复制代码
打印语句
len: 1 | cap: 1 | ptr(s)= 0xc42001a110
len: 2 | cap: 2 | ptr(s)= 0xc42001a140
len: 3 | cap: 4 | ptr(s)= 0xc420012400
len: 4 | cap: 4 | ptr(s)= 0xc420012400
len: 5 | cap: 8 | ptr(s)= 0xc420018200
复制代码
slice实现的源代码解读
cgo里面调用了c或者汇编的实现
/cmd/cgo/out.go 从go和native代码的接口角度指出slice的结构包括:头指针,len和cap
// Map an ast type to a Type.
func (p *Package) cgoType(e ast.Expr) *Type {
switch t := e.(type) {
case *ast.StarExpr:
x := p.cgoType(t.X)
return &Type{Size: p.PtrSize, Align: p.PtrSize, C: c("%s*", x.C)}
case *ast.ArrayType:
if t.Len == nil {
// Slice: pointer, len, cap.
return &Type{Size: p.PtrSize * 3, Align: p.PtrSize, C: c("GoSlice")}
}
复制代码
array.go里面有test
//go:noinline
func testSliceLenCap2_ssa(a [10]int, i, j int) (int, int) {
b := a[:j]
return len(b), cap(b)
}
复制代码
golang的slice实现和python的比较
相同
表达了类似的概念(类似java中的List) 切片操作都是slice[start : end]
不同
- 本质不同,golang中是在同一个底层数组上新建了一个slice对象而已,但是
python是完全深度拷贝了一个数组
- python有step,但是golang没有
- python可以负数切片,但是golang没有
总结golang求快,python求方便