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

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

[[438643]]

本文转载自微信公众号「Golang技术分享」,作者机器铃砍菜刀 。转载本文请联系Golang技术分享公众号。

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

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

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

深浅拷贝

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

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

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

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

  1. // Python 版 
  2. if __name__ == '__main__'
  3.     a = [1, 2, 3] 
  4.     b = a 
  5.     c = a[:] 
  6.     a[0] = 100 
  7.     print(a, b, c) // [100, 2, 3] [100, 2, 3] [1, 2, 3] 
  8.  
  9. // Golang 版 
  10. func main() { 
  11.  a := []int{1, 2, 3} 
  12.  b := a 
  13.  c := a[:] 
  14.  a[0] = 100 
  15.  fmt.Println(a, b, c) // [100 2 3] [100 2 3] [100 2 3] 

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

= 拷贝

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

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

图解

[:] 拷贝

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

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

图解

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

copy() 拷贝

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

copy()函数签名如下

  1. func copy(dst, src []Type) int 

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

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

图解

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

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

图解

总结

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

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

 

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

 

责任编辑:武晓燕 来源: Golang技术分享
相关推荐

2017-08-09 15:07:08

大数据数据分析户画像

2021-06-16 00:57:16

JVM加载机制

2021-02-24 07:38:50

Redis

2021-03-18 23:28:45

Python反斜杠字符串

2020-12-01 11:33:57

Python拷贝copy

2024-12-05 11:03:04

2015-02-28 10:01:00

云计算IT架构虚拟化

2020-12-04 10:05:00

Pythonprint代码

2022-05-27 21:56:55

索引存储MySQL 存储引擎

2023-08-18 15:22:54

工业物联网工业互联网

2020-12-02 11:18:50

print调试代码Python

2021-07-06 14:32:54

机器学习人工智能计算机

2020-06-18 10:48:44

Linux 系统 数据

2019-08-01 11:04:10

Linux磁盘I

2017-08-16 09:55:36

2018-09-28 05:25:53

TopK算法代码

2021-06-09 06:41:11

OFFSETLIMIT分页

2020-01-07 11:09:04

Web服务器容器

2019-08-27 10:55:59

MySQLJOINSQLite

2020-10-12 06:33:18

Zero-Copy零拷贝CPU
点赞
收藏

51CTO技术栈公众号