图解 Go 切片的深拷贝和浅拷贝

本文探讨了Go语言中切片的三种拷贝方式:=、[:]及copy()函数,并通过图解展示了每种方式的工作原理及其区别。了解这些基础知识有助于避免程序bug。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在刚使用 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
}

图解

44b779ea8f28357cca7e82d3c42a23a1.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
}

图解

59b602128de4a2f50d33785147b92504.png

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

eb0043e1bf2c744b51f4663ee9d5c459.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
}

图解

3c1105db89a95ef572210f41ddb10955.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
}

图解

7f124fa0237bd09ea3317ca7aec61d85.png

总结

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

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

感谢你的点赞在看哦~

11c22d9dc5a1652f7c00df46d1074de0.gif

<think>好的,我现在要帮用户理解浅拷贝深拷贝引用拷贝的区别。首先,我需要回忆这些概念的基本定义,确保自己理解正确。 引用拷贝应该是最简单的,就是两个变量指向同一个对象,比如在Java中,Object a = new Object(); Object b = a; 这时候ab都指向同一个内存地址,修改任何一个都会影响另一个。这点我记得没错,对吧? 然后是浅拷贝浅拷贝会创建一个新对象,复制原始对象的所有字段。但如果字段是基本类型,就拷贝值;如果是引用类型,就拷贝引用地址,也就是不递归复制引用指向的对象。例如,一个对象里面有一个数组,浅拷贝后的新对象里的数组还是指向原来的数组,所以修改其中一个对象的数组会影响另一个。这时候可能需要举例子,比如Java中的clone()方法默认是浅拷贝,或者Python里的copy.copy()。 深拷贝的话,就是完全复制整个对象,包括里面的所有引用类型字段,递归地复制,直到所有层级都是新的对象。这样原对象拷贝对象完全独立,互不影响。例如Pythoncopy.deepcopy(),或者自己实现递归克隆。 用户可能的问题点在于容易混淆浅拷贝引用拷贝。引用拷贝只是多个引用指向同一对象,而浅拷贝至少创建了一个新对象,但里面的引用类型字段还是共享的。需要明确区分这点。 另外,用户可能需要具体的例子来理解不同拷贝方式的效果。比如,假设有一个Person类,里面有一个Address对象。引用拷贝的话,两个Person变量指向同一个实例;浅拷贝的话,新Person对象有自己的基本类型字段,但address字段指向原对象的同一个Address实例;深拷贝的话,address也会被复制一份新的,这样修改其中一个的address不会影响另一个。 还需要注意不同编程语言的实现可能不同,比如Java需要重写clone方法,而Pythoncopy模块。不过用户的问题可能不涉及具体语言,而是概念上的区别,所以应该保持语言中立,但必要时可以举常见的例子。 另外,是否需要提到深拷贝可能的问题?比如循环引用导致无限递归,或者性能开销较大?可能用户暂时不需要这么深入,但作为知识扩展可以稍微提一下。 总结一下,回答的结构应该先分别介绍三个概念,然后比较它们的区别,最好用例子说明。确保语言清晰,步骤分明,帮助用户逐步理解。</think>### 浅拷贝深拷贝引用拷贝的区别 #### 1. **引用拷贝** - **定义**:多个变量指向**同一个对象**(共享内存地址),修改任意变量会影响所有引用。 - **示例**: ```python list1 = [1, 2, 3] list2 = list1 # 引用拷贝 list2.append(4) print(list1) # 输出 [1, 2, 3, 4] ``` #### 2. **浅拷贝** - **定义**:创建**新对象**,复制原始对象的所有字段: - 基本类型字段:直接复制值。 - 引用类型字段:复制引用地址(新旧对象共享内部引用对象)。 - **示例**: ```python import copy list1 = [1, [2, 3]] list2 = copy.copy(list1) # 浅拷贝 list2[0] = 99 # 不影响 list1(基本类型) list2[1].append(4) # 修改引用对象,会影响 list1 print(list1) # 输出 [1, [2, 3, 4]] ``` #### 3. **深拷贝** - **定义**:创建**完全独立的新对象**,递归复制所有层级字段: - 基本类型引用类型字段均被完全复制,新旧对象彻底隔离。 - **示例**: ```python import copy list1 = [1, [2, 3]] list2 = copy.deepcopy(list1) # 深拷贝 list2[1].append(4) print(list1) # 输出 [1, [2, 3]](原对象不受影响) ``` --- ### 三者的核心区别 | | **是否创建新对象** | **引用类型字段的复制方式** | **独立性** | |----------|--------------------|--------------------------|--------------------------| | 引用拷贝 | 否(共享对象) | 直接共享原引用 | 完全依赖,修改互相影响 | | 浅拷贝 | 是 | 复制引用地址(共享内部对象) | 部分独立,内部引用对象仍共享 | | 深拷贝 | 是 | 递归复制所有层级对象 | 完全独立,互不影响 | --- ### 图解说明 ``` 原始对象: [A, [B, C]] ↗ ↑ 引用拷贝: 直接指向同一内存 浅拷贝: 新对象 [A', [B, C]](A'是副本,[B,C]共享) 深拷贝: 新对象 [A'', [B', C']](所有层级独立) ``` --- ### 常见应用场景 - **引用拷贝**:传递对象避免内存占用(需注意副作用)。 - **浅拷贝**:快速复制结构简单或无需独立内部引用的对象。 - **深拷贝**:需要完全隔离的场景(如撤销操作、多线程安全)。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值