看完这期图解,别再搞不清切片拷贝了

在刚使用 Go 时,菜刀曾将 Python 深拷贝手法[:]用于 Go 中 ,结果造成了 bug。相信不少转语言的 Gopher 也在切片拷贝上栽过跟头。

切片是 Go 中最基础的数据结构,之前我们谈过切片传递切换转换切片扩容等内容。

本文,我们将探讨切片拷贝,就切片的三种拷贝方式进行图解分析,希望帮助读者巩固一下基础知识。

深浅拷贝

所谓深浅拷贝,其实都是进行复制,主要区别在于复制出来的新对象和原来的对象,它们的数据发生改变时,是否会相互影响。

简单而言,B 复制 A,如果 A 的数据发生变化,B 也跟着变化,这是浅拷贝。反之, 如果 B 不发生变化,则为深拷贝。

深浅拷贝差异的根本原因在于,复制出来的对象与原对象是否会指向同一个地址。

以下是 Python 中 list 与 Go 中 slice  深浅拷贝的表现差异

// Python 版
if __name__ == '__main__':
    a = [1, 2, 3]
    b = a
    c = a[:]
    a[0] = 100
    print(a, b, c) // [100, 2, 3] [100, 2, 3] [1, 2, 3]

// Golang 版
func main() {
 a := []int{1, 2, 3}
 b := a
 c := a[:]
 a[0] = 100
 fmt.Println(a, b, c) // [100 2 3] [100 2 3] [100 2 3]
}

发现没有?在 Go 中 [:] 操作并不是深拷贝。

= 拷贝

通过=操作符拷贝切片,这是浅拷贝。

func main() {
 a := []int{1, 2, 3}
 b := a
 fmt.Println(unsafe.Pointer(&a))  // 0xc00000c030
 fmt.Println(a, &a[0])            // [100 2 3] 0xc00001a078
 fmt.Println(unsafe.Pointer(&b))  // 0xc00000c048
 fmt.Println(b, &b[0])            // [100 2 3] 0xc00001a078
}

图解

e5f73df0780a19898e19b8d39c689eaf.png

[:] 拷贝

通过[:]方式复制切片,同样是浅拷贝。

func main() {
 a := []int{1, 2, 3}
 b := a[:]
 fmt.Println(unsafe.Pointer(&a)) // 0xc0000a4018
 fmt.Println(a, &a[0])           // [1 2 3] 0xc0000b4000
 fmt.Println(unsafe.Pointer(&b)) // 0xc0000a4030
 fmt.Println(b, &b[0])           // [1 2 3] 0xc0000b4000
}

图解

df3bf8b50173bf45c281efbf7bcc8c08.png

我们有时会使用[start: end]进行拷贝。例如,b:=a[1:],那它的拷贝情况如何

d9731cc730b836c9c9540f953a0bc4a4.png

copy() 拷贝

上述两种方式都是浅拷贝,如果要切片深拷贝,需要用到copy()内置函数。

copy()函数签名如下

func copy(dst, src []Type) int

其返回值代表切片中被拷贝的元素个数

func main() {
 a := []int{1, 2, 3}
 b := make([]int, len(a), len(a))
 copy(b, a)
 fmt.Println(unsafe.Pointer(&a))  // 0xc00000c030
 fmt.Println(a, &a[0])            // [1 2 3] 0xc00001a078
 fmt.Println(unsafe.Pointer(&b))  // 0xc00000c048
 fmt.Println(b, &b[0])            // [1 2 3] 0xc00001a090
}

图解

b8d888c2b4db74af937becfa5c4edd00.png

copy 的元素数量与原始切片和目标切片的大小、容量有关系

func main() {
 a := []int{1, 2, 3}
 b := []int{-1, -2, -3, -4}
 copy(b, a)
 fmt.Println(unsafe.Pointer(&a))  // 0xc0000a4018
 fmt.Println(a, &a[0])            // [1 2 3] 0xc0000b4000
 fmt.Println(unsafe.Pointer(&b))  // 0xc0000a4030
 fmt.Println(b, &b[0])            // [1 2 3 -4] 0xc0000aa060
}

图解

d397afc4e019bc036c026a18578dfa0b.png

总结

切片是 Go 语言中最基本的数据结构,它的扩容与拷贝细节,在理解不当时,是很容易写出程序 bug 的。

本文分别就切片的三种拷贝方式,=[:]copy()进行了探讨。其中,=[:]是浅拷贝,copy()拷贝是深拷贝。

这样的图解方式,你喜欢吗~

往期推荐

999012b49afc23ffd9a4caab100142e8.png

机器铃砍菜刀

欢迎添加小菜刀微信

加入Golang分享群学习交流!

感谢你的点赞在看哦~

ca6b0ba56bcf77569f7e131743547d3b.gif

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值