本文转载请联系Golang技术分享公众号。刚开始用Go的时候,菜道在Go中使用了Python的深拷贝方式[:],导致了bug。相信很多转过语言的Gopher也曾在复制切片上跌跌撞撞。Slice是Go中最基本的数据结构。之前我们讲过切片传输、切换转换、切片扩展等。在本文中,我们将讨论切片复制,并对切片的三种复制方式进行图解分析,希望能帮助读者巩固基础知识。深浅拷贝所谓深浅拷贝,其实就是一种拷贝。主要区别在于复制的新对象和原对象在数据变化时是否会相互影响。简单的说,B复制了A,如果A的数据改变了,B也随之改变。这是一个浅拷贝。反之,如果B不变,就是深拷贝。造成深拷贝和浅拷贝差异的根本原因在于拷贝的对象和原对象是否会指向同一个地址。下面是Python中的list和Go中的slice的深浅拷贝的性能差异//Python版本if__name__=='__main__':a=[1,2,3]b=ac=a[:]a[0]=100print(a,b,c)//[100,2,3][100,2,3][1,2,3]//Golang版本funcmain(){a:=[]int{1,2,3}b:=ac:=a[:]a[0]=100fmt.Println(a,b,c)//[10023][10023][10023]}你找到了吗?在Go中,[:]操作不是深拷贝。=复制切片通过=运算符进行复制,这是一种浅拷贝。funcmain(){a:=[]int{1,2,3}b:=afmt.Println(unsafe.Pointer(&a))//0xc00000c030fmt.Println(a,&a[0])//[10023]0xc00001a078fmt.Println(unsafe.Pointer(&b))//0xc00000c048fmt.Println(b,&b[0])//[10023]0xc00001a078}说明[:]Copyslice通过[:]复制,也是浅拷贝.funcmain(){a:=[]int{1,2,3}b:=a[:]fmt.Println(unsafe.Pointer(&a))//0xc0000a4018fmt.Println(a,&a[0])//[123]0xc0000b4000fmt.Println(unsafe.Pointer(&b))//0xc0000a4030fmt.Println(b,&b[0])//[123]0xc0000b4000}Diagram我们有时候会用[start:end]来拷贝。例如,b:=a[1:],它的复制呢?copy()以上两种复制方式都是浅拷贝。如果你想切片深拷贝,你需要使用copy()内置函数。copy()函数的签名如下:funccopy(dst,src[]Type)int其返回值表示切片中复制元素的个数funcmain(){a:=[]int{1,2,3}b:=make([]int,len(a),len(a))copy(b,a)fmt.Println(unsafe.Pointer(&a))//0xc00000c030fmt.Println(a,&a[0])//[123]0xc00001a078fmt.Println(unsafe.Pointer(&b))//0xc00000c048fmt.Println(b,&b[0])//[123]0xc00001a090}图形复制元素个数与大小和容量有关原始切片和目标切片funcmain(){a:=[]int{1,2,3}b:=[]int{-1,-2,-3,-4}copy(b,a)fmt.Println(unsafe.Pointer(&a))//0xc0000a4018fmt.Println(a,&a[0])//[123]0xc0000b4000fmt.Println(unsafe.Pointer(&b))//0xc0000a4030fmt.Println(b,&b[0])//[123-4]0xc0000aa060}图解总结Slice是Go语言中最基本的数据结构。如果对扩展和复制的细节理解不当,很容易写出程序错误。本文讨论切片的三种复制方法,=、[:]、copy()。其中=、[:]为浅拷贝,copy()拷贝为深拷贝。这样的插画你们喜欢吗~
