前言之前在Go语言的for循环上做了一个经验分享《Go for range 一不小心就掉坑里了》,大家都说很有用。今天,我也分享一下我对Slice的追加操作的心得。希望对朋友们有所帮助。如果有用,请多多支持。知识回顾切片底层结构定义:包含指向底层数组、长度和容量类型的指针记得加上……);添加元素时当容量不足时,会自动触发切片扩容机制生成切片的副本,同时指向底层数组的指针会发生变化varnums[]intnums=append(nums,1)nums=append(nums,2,3,4)nums2:=[]int{5,6,7}nums=append(nums,nums2...)fmt.Println(nums)//[1234567]case1:先传Value+unexpanded,看看下面会输出什么?funcmain(){s1:=make([]int,0,5)fmt.Println("s1slice:",s1)appendFunc(s1)fmt.Println("s1slice:",s1)fmt.Println("s1sliceexpression:",s1[:5])}funcappendFunc(s2[]int){s2=append(s2,1,2,3)fmt.Println("s2slice:",s2)}输出结果:s1slice:[]s2slice:[123]s1slice:[]s1sliceexpression:[12300]看到这个结果大家会有疑问,明明slice是引用类型,为什么s2追加之后新元素,s2有值,s1还是空,在s1上用切片表达式可以得到值?在分析原因之前,我们先看看s1和s2是否是同一个slice,打印地址来验证funcmain(){s1:=make([]int,0,5)fmt.Printf("s1sliceaddress:%p\n",s1)appendFunc(s1)//...}funcappendFunc(s2[]int){s2=append(s2,1,2,3)fmt.Printf("s2sliceaddress:%p\n",s2)//...}Outputresult:s1sliceaddress:0xc000018150s2sliceaddress:0xc000018150看到这里你会傻眼,两个slice的地址是一样的,s2修改后,s1应该也同步修改了,应该是有值的,还要继续深究,fmt包%p打印出来的地址是谁的地址//fmt/print.gofunc(p*pp)fmtPointer(valuereflect.Value,verbrune){varuuintptrswitchvalue.Kind(){casereflect.Chan,reflect.Func,reflect.Map,reflect.Ptr,reflect.Slice,reflect.UnsafePointer:u=value.Pointer()default:p.badVerb(verb)return}//...}//reflect/value.gofunc(vValue)Pointer()uintptr{k:=v.kind()switchk{//...caseSlice:return(*SliceHeader)(v.ptr).Data}panic(&ValueError{"reflect.Value.Pointer",v.kind()})}通过分析fmt包的源码,不难发现打印出来的地址其实是指向底层数组的指针在切片中存放的地址,而不是两个切片本身的地址。它还表明这两个切片指向同一个底层数组。形式化分析原因:传值操作,s1和s2是两个不同的slice变量,但是指向底层数组的指针是一样的;length和capacity变化:s1Len=0andCap=5,此后没有变化;当s2初始赋值为Len=0,Cap=5,追加操作后,Len=3,Cap=5,底层数组值由[0,0,0,0,0]变为[1,2,3,0,0];输出结果,s1输出空[],因为Len=0,而s1使用slice表达式,根据底层数组[1,2,3,0,0]进行切片,所以输出结果为[1,2,3,0],0];案例二:价值转移+扩张。情况1,追加元素数量超过分片容量,触发自动扩容。输出结果会是什么?funcmain(){s1:=make([]int,0,5)fmt.Println("s1slice:",s1)appendFunc(s1)fmt.Println("s1slice:",s1)fmt.Println("s1切片表达式:",s1[:5])}funcappendFunc(s2[]int){s2=append(s2,1,2,3,4,5,6)fmt.Println("s2切片:",s2)}输出结果:s1slice:[]s2slice:[123456]s1slice:[]s1sliceexpression:[00000]原因分析:扩容后s2指向A副本将生成底层数组,导致s1和s2不再指向同一个底层数组;长度和容量的变化:Len=6,Cap=10s2append后底层数组的值为[1,2,3,4,5,6,0,0,0,0];s2的操作完全不影响s1的数据,s1仍然是Len=0,Cap=5,底层数组值为[0,0,0,0,0];output结果,s2输出[1,2,3,4,5,6],因为Len=6,s1输出空[],因为Len=0,s1使用slice表达式,根据底层数组[0,0,0,0,0]为切片,所以输出结果为[0,0,0,0,0];Case3:Addressing+don'tcareaboutexpansion会影响原来slices1的长度和容量。如果我们希望在修改s2的同时也修改原来的slices1,就需要使用slice指针基于地址传递进行操作。funcmain(){s1:=make([]int,0,5)fmt.Println("s1slice:",s1)fmt.Printf("s1sliceaddress:%plen:%dcap:%d\n",&s1,len(s1),cap(s1))appendFunc(&s1)fmt.Println("s1切片:",s1)fmt.Println("s1切片表达式:",s1[:5])}funcappendFunc(s2*[]int){fmt.Printf("s2切片地址:%plen:%dcap:%d\n",s2,len(*s2),cap(*s2))//*s2=append(*s2,1,2,3)*s2=append(*s2,1,2,3,4,5,6,7,8,9,10)fmt.Printf("s2sliceafterappendAddress:%plen:%dcap:%d\n",s2,len(*s2),cap(*s2))fmt.Println("s2slice:",*s2)}输出结果:s1slice:[]s1切片地址:0xc00000c030len:0cap:5s2切片地址:0xc00000c030len:0cap:5appends2切片地址:0xc00000c030len:10cap:10s2切片:[12345678910]s1切片:[12345678910]s1切片表达式:[12345]始终相同。地址操作总是对同一个切片变量进行操作。追加操作后,length大小和容量都会同时变化,如果触发扩容,底层数组的指针也会同时变化。总结切片传值操作,append不会触发扩容,同时会修改底层数组的值,但不会影响原切片的长度和容量;当触发扩容时,会生成一个副本,后续的修改会与原来的底层数组分离,互不影响。如果希望在修改切片后修改原始切片,则可以使用传递操作始终对同一个切片变量进行操作。需要一起学习,需要简历优化和就业辅导的朋友可以私信我,欢迎关注我的公众号:我的文章首发于我的公众号:程序员的升职加薪之旅,欢迎大家关注关注,第一时间阅读我的文章。也欢迎大家关注我,点赞,评论,转发。您的支持是我继续写下去的最大动力!
