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

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

在刚使用 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> 我们正在讨论Python中的深拷贝(deep copy浅拷贝(shallow copy)。这两种拷贝方式在处理复合对象(如列表、字典等包含其他对象的对象)时有着显著的区别。 ### 浅拷贝(Shallow Copy浅拷贝会创建一个新对象,然后将原对象中的元素(引用)复制到新对象中。这意味着,如果原对象中的元素是可变对象(如列表、字典),那么在新对象原对象中,这些元素实际上是同一个对象(即共享内存地址)。因此,修改这些可变元素会同时影响原对象拷贝后的对象。 ### 深拷贝(Deep Copy深拷贝会创建一个新对象,然后递归地复制原对象中的所有元素(包括子对象)。这样,新对象原对象完全独立,修改其中一个不会影响另一个。 ### 代码示例 以下代码将展示浅拷贝深拷贝的区别: ```python import copy # 原始列表,包含一个子列表 original_list = [1, [2, 3], 4] # 浅拷贝 shallow_copied_list = copy.copy(original_list) # 深拷贝 deep_copied_list = copy.deepcopy(original_list) # 修改原始列表中的子列表 original_list[1].append(5) print("原始列表:", original_list) # [1, [2, 3, 5], 4] print("浅拷贝列表:", shallow_copied_list) # [1, [2, 3, 5], 4] 子列表被修改 print("深拷贝列表:", deep_copied_list) # [1, [2, 3], 4] 子列表不变 ``` ### 浅拷贝深拷贝的应用场景 1. **浅拷贝适用场景**: - 当对象中的元素都是不可变对象(如数字、字符串、元组)时,浅拷贝就足够了,因为不可变对象无法被修改。 - 当需要快速创建一个新对象,并且不关心内部可变元素是否共享时。 2. **深拷贝适用场景**: - 当对象中包含可变元素,并且希望新对象与原对象完全独立(即修改其中一个不会影响另一个)时。 - 在需要完全复制一个复杂对象(如嵌套列表、字典)时。 ### 实现拷贝的方式 1. **浅拷贝**: - 使用`copy.copy()`函数。 - 对于列表,可以使用切片操作`list_copy = original_list[:]`。 - 对于字典,可以使用`dict_copy = original_dict.copy()`。 2. **深拷贝**: - 使用`copy.deepcopy()`函数。 ### 注意事项 - 深拷贝浅拷贝慢,因为它需要递归地复制所有对象。 - 深拷贝可能会遇到循环引用的问题,但`copy.deepcopy()`能够处理循环引用。 - 对于自定义对象,可以通过实现`__copy__()``__deepcopy__()`方法来控制拷贝行为。 ### 自定义对象的拷贝 ```python class MyClass: def __init__(self, value): self.value = value self.numbers = [1, 2, 3] # 自定义浅拷贝 def __copy__(self): new_obj = MyClass(self.value) new_obj.numbers = self.numbers # 共享同一个列表 return new_obj # 自定义深拷贝 def __deepcopy__(self, memo): new_obj = MyClass(self.value) # 深拷贝numbers列表 new_obj.numbers = copy.deepcopy(self.numbers, memo) return new_obj obj1 = MyClass(10) shallow_obj = copy.copy(obj1) deep_obj = copy.deepcopy(obj1) obj1.numbers.append(4) print("obj1.numbers:", obj1.numbers) # [1, 2, 3, 4] print("shallow_obj.numbers:", shallow_obj.numbers) # [1, 2, 3, 4] 共享同一个列表 print("deep_obj.numbers:", deep_obj.numbers) # [1, 2, 3] 独立列表 ``` ### 总结 - 浅拷贝:只复制对象的顶层,子对象共享。 - 深拷贝:复制对象的所有层次,子对象独立。 §§相关问题§§: 1. 在Python中,如何实现一个自定义类的深拷贝? 2. 深拷贝在处理循环引用时会发生什么?如何避免无限递归? 3. 除了使用copy模块,还有哪些方式可以实现列表的浅拷贝? 4. 为什么在Python中,元组的浅拷贝可能不会创建新对象? </think>### 深入解析 Python 中的深拷贝浅拷贝 #### 1. 核心概念对比 | **特性** | **浅拷贝 (Shallow Copy)** | **深拷贝 (Deep Copy)** | |------------------|---------------------------------------------------|-------------------------------------------------| | **复制层级** | 仅复制顶层对象 | 递归复制所有嵌套对象 | | **内存关系** | 嵌套对象共享引用 | 所有对象完全独立 | | **适用对象** | 不可变对象或简单结构 | 嵌套可变对象的复杂结构 | | **修改影响** | 嵌套对象修改会相互影响 | 完全隔离,互不影响 | | **性能** | ✅ 快速(仅复制顶层) | ⚠️ 较慢(递归复制所有层级) | | **内存占用** | ✅ 较少 | ⚠️ 较多 | | **copy模块方法** | `copy.copy()` | `copy.deepcopy()` | #### 2. 代码实例演示 ```python import copy # 原始数据结构 original = [1, [2, 3], {'a': [4, 5]}] # 浅拷贝 shallow = copy.copy(original) # 深拷贝 deep = copy.deepcopy(original) # 修改原始数据的嵌套部分 original[1].append(4) original[2]['a'].append(6) print("原始数据:", original) # 输出: [1, [2, 3, 4], {'a': [4, 5, 6]}] print("浅拷贝数据:", shallow) # 输出: [1, [2, 3, 4], {'a': [4, 5, 6]}] ← 嵌套部分被修改 print("深拷贝数据:", deep) # 输出: [1, [2, 3], {'a': [4, 5]}] ← 保持原始状态 ``` #### 3. 底层原理图解 ``` 原始数据: [1, [2, 3] , {a: [4, 5]}] │ │ │ ▼ ▼ ▼ 内存A 内存B 内存C 浅拷贝: [1, [2, 3] , {a: [4, 5]}] │ │ │ └──────┼─────────┘ ▼ 原始内存BC 深拷贝: [1, [2, 3] , {a: [4, 5]}] │ │ │ ▼ ▼ ▼ 新内存A 新内存B 新内存C │ │ ▼ ▼ 新内存D 新内存E ``` #### 4. 常见拷贝方式对比 ```python # ========== 浅拷贝方式 ========== # 1. copy.copy() shallow1 = copy.copy(original) # 2. 列表切片 shallow2 = original[:] # 3. 工厂函数 shallow3 = list(original) # 4. 字典的copy方法 dict_shallow = original_dict.copy() # ========== 深拷贝方式 ========== # 唯一安全方式 deep_copy = copy.deepcopy(original) # 手动实现深拷贝(简化版) def manual_deepcopy(obj): if isinstance(obj, list): return [manual_deepcopy(item) for item in obj] elif isinstance(obj, dict): return {k: manual_deepcopy(v) for k, v in obj.items()} elif hasattr(obj, '__dict__'): new_obj = type(obj)() new_obj.__dict__ = manual_deepcopy(obj.__dict__) return new_obj else: return obj # 不可变对象直接返回 ``` #### 5. 特殊场景处理 **场景1: 循环引用** ```python a = [1, 2] b = [3, 4] a.append(b) b.append(a) # 循环引用 # 浅拷贝无法解决循环引用 # 深拷贝可安全处理 c = copy.deepcopy(a) # 成功复制 ``` **场景2: 自定义对象拷贝** ```python class User: def __init__(self, name, friends): self.name = name self.friends = friends # 控制深拷贝行为 def __deepcopy__(self, memo): new_user = User(copy.deepcopy(self.name, memo), []) memo[id(self)] = new_user # 防止无限递归 new_user.friends = [copy.deepcopy(f, memo) for f in self.friends] return new_user user1 = User("Alice", []) user2 = User("Bob", [user1]) user1.friends.append(user2) # 深拷贝自定义对象 user_copy = copy.deepcopy(user1) ``` #### 6. 最佳实践指南 1. **默认使用浅拷贝**:当确定没有嵌套可变对象时 2. **必须使用深拷贝**: - 需要完全独立的副本时 - 处理包含嵌套可变对象的结构 - 多线程环境下共享数据 3. **避免拷贝**: - 对于不可变对象(元组、字符串、数字) - 大型数据结构优先考虑视图(如NumPy数组) 4. **性能优化**: ```python # 使用memoization减少重复拷贝 memo = {} deep_obj = copy.deepcopy(large_obj, memo) ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值