在学习Go语言的slice时发现了这样的一个问题,当对slice进行追加的时候,数据会被追加回原数组当中。
问题重现
|
|
输出的结果是
|
|
我创建了两个相同的队列,然后对他们当中的一个(Array_list_1)进行切片,然后对其中一个切片进行追加,结果发现对切片进行追加后,原数组的内容也发生了改变,由102变为了112。
原理解析
查阅资料后得知,slice是引用类型,在内存中并没有属于自己的内存空间,而是通过指针指向进行切片的队列。由于队列分配的内存空间是连续的,所以如果slice的最后一个元素不是list的最后一个元素,那么在append的时候,新追加的元素就会覆盖掉原数组的元素。而由于slice是指针组织的,所以这个list的所有slice都会被影响。如果切片末尾元素就是队列的末尾元素,返回的 slice 数组指针将指向这个空间,而原数组的内容将保持不变,其它引用此数组的 slice 则不受影响。
|
|
输出结果
|
|
由此拓展可以知道,所有对slice的修改都会对原list产生修改。所以使用的时候一定要小心。
补充,实际应用当中的“意外”
上边我们一直在说,Slice是引用类型,指向的都是内存中的同一块内存,不过在实际应用中,有的时候却会发生“意外”,这种情况只有在像切片append元素的时候出现,Slice的处理机制是这样的,当Slice的容量还有空闲的时候,append进来的元素会直接使用空闲的容量空间,但是一旦append进来的元素个数超过了原来指定容量值的时候,内存管理器就是重新开辟一个更大的内存空间,用于存储多出来的元素,并且会将原来的元素复制一份,放到这块新开辟的内存空间。
|
|
可以看到执行了append操作后,内存地址发生了变化,说明已经不是引用传递。
再拓展
那么有的小伙伴可能会想,Python也是引用,会不会也存在同样的问题?
|
|
通过Ipython做实验,发现结果与Golang并不像同,这是为什么呢?
这其实与Python内部的内存管理机制有关。在Python当中,为了节省内存,所有相同的值都只会有一个实体存在于内存当中,其他的对象指示对这个值的引用。Python内存管理通过引用计数器来判断某个内存是否无效,然后进行垃圾清理。
而虽然Python是引用同一个地址,但是知识值是引用同一个地址。通过id函数我们可以发现Python当中的切片与原数组的关系。
|
|
再来看一下下面这个例子,相信你也马上就能明白了。
这是对这两个list当中相同元素的地址。
|
|
由此拓展,同理,如果slice的元素发生改变,也会修改相应的