go语言slice类型学习及和py的比较

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

相似的外形,不同的内心

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类 在这里插入图片描述

slice精彩图示

采用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

复制代码

如何用uintptr看slice内部结构,证实和数组不一样


go中文学习网文章

go当中append的详解

扩容都是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的比较

go和python切片的不同

相同

表达了类似的概念(类似java中的List) 切片操作都是slice[start : end]

不同

  1. 本质不同,golang中是在同一个底层数组上新建了一个slice对象而已,但是python是完全深度拷贝了一个数组
  2. python有step,但是golang没有
  3. python可以负数切片,但是golang没有

总结golang求快,python求方便

猜你喜欢

转载自juejin.im/post/7108541458595971085