copy拷贝
一、浅拷贝(Shallow Copy) vs. 深拷贝(Deep Copy)
浅拷贝:新对象只是复制了原对象的第一层结构,内部的引用(如指针、子对象、底层数组)仍指向旧对象。
深拷贝:新对象及其内部所有可变子对象都要递归地重新分配并复制,互不影响。
二、Python
赋值(引用传递)
1
2
3
4a = [1, 2, 3]
b = a # b 和 a 指向同一个列表
b.append(4)
print(a) # [1,2,3,4]函数参数:def f(x): … 中传入的是对象的引用,函数内部修改可变对象会影响外部。
浅拷贝
1
2
3import copy
b = a.copy() # 或 copy.copy(a)
# 或 b = a[:] # list 特例:切片也会浅拷贝b 是新列表,但元素是同一组对象的引用(对基本类型来说即值复制)。
深拷贝
1
c = copy.deepcopy(a)
递归复制所有层级子对象。适合嵌套列表、字典等。
三、C++
- 值语义 vs. 引用/指针vector 默认拷贝构造就是 深拷贝(会为新容器分配自己的存储并复制元素)。
1
2
3
4vector<int> a = {1,2,3};
vector<int> b = a; // 调用 vector 的拷贝构造:深拷贝底层数组
b.push_back(4);
// a 仍是 {1,2,3}
1 | vector<int>& r = a; // 引用,不拷贝 |
自定义类型
如果成员里含原始指针或者动态分配的资源,需要自己写拷贝构造 & 赋值运算符来管理“深”或“浅”。函数参数
1
2
3void f1(vector<int> v); // 按值传递,调用拷贝构造 → v 是独立副本
void f2(const vector<int>& v); // 按引用传递,不拷贝
void f3(vector<int>& v); // 可修改原对象
四、Java
- 引用语义 (类似 Python)
1
2
3
4List<Integer> a = new ArrayList<>();
List<Integer> b = a; // b 和 a 引用同一个对象
b.add(4);
System.out.println(a); // [1,2,3,4] - 浅拷贝新的 ArrayList,但底层存放的 Integer 引用与原来相同。
1
List<Integer> b = new ArrayList<>(a);
对象若包含可变字段,clone() 默认也是浅拷贝,需要重写。
- 深拷贝
手动递归:对每个子对象都 new 一次并复制其字段。
或者用序列化/第三方库(如 Apache Commons SerializationUtils),效率上多一层序列化开销。
- 方法参数
1
void f(List<Integer> v) { … } // 传引用,不拷贝
五、Go
- 值类型 vs. 引用类型
值类型:基本类型、数组、结构体,赋值时会整体复制。
引用类型:切片(slice)、映射(map)、通道(chan)、指针、函数、接口 等,赋值时只复制了描述对象的 header,底层数据仍共享。
- 切片浅拷贝若要深拷贝切片内容:
1
2
3
4a := []int{1,2,3}
b := a // b 与 a 共享底层数组
b[0] = 9
fmt.Println(a) // [9,2,3]
1 | b := make([]int, len(a)) |
- 函数参数
1
2
3
4func f1(s []int) { … } // s header 按值复制,底层数组指针仍共享
func f2(s []int) {
s = append(s, 4) // 若容量不足,会分配新底层数组,不影响原调用者的 slice
}
六、跨函数 & 跨语言对比要点
特性 | Python | C++ | Java | Go |
---|---|---|---|---|
默认赋值 | 引用(object pointer) | 值拷贝(拷贝构造)或引用/指针 | 引用(object reference) | 值/引用混合:数组值、slice header 浅拷贝 |
浅拷贝方式 | list.copy() copy.copy() |
vector<T> 默认拷贝构造自定义需写 |
new ArrayList<>(old) 或 clone() |
b := a (slice header 拷贝) |
深拷贝方式 | copy.deepcopy() |
自定义拷贝构造/手动递归 | 手动递归/序列化 | make([]T, len(a)); copy(b, a) |
函数参数传递 | 引用语义 | 按值(拷贝)或按引用(&/const &) | 引用语义 | 值类型拷贝/引用类型(slice header)拷贝 |
修改后影响原对象 | 会(直接操作同一对象) | 按值:不影响 按引用:影响 |
会(同指针) | slice 元素修改会影响;append 视容量决定是否分配新底层 |
特性 | Python | C++ | Java | Go |
---|---|---|---|---|
自定义类对象复制 | - 默认赋值 obj2 = obj1 只是引用同一个对象- 浅拷贝: copy.copy(obj) 只复制 __dict__ 一级字段- 深拷贝: copy.deepcopy(obj) 递归复制所有子对象- 可以通过实现 __copy__ /__deepcopy__ 钩子定制行为 |
- 默认拷贝构造(memberwise copy)对每个成员调用其拷贝构造:浅拷贝底层指针 - 需要深拷贝时自行在拷贝构造里对指针成员 new 并复制- 或者用智能指针( shared_ptr 、unique_ptr )并自定义复制策略 |
- 默认赋值和拷贝构造(生成的)都是浅拷贝:字段直接复制,包括引用字段 - 要深拷贝需自己写 Copy Constructor 或者实现 Cloneable 并重写 clone() - 可用序列化/反序列化来做深拷贝 |
- 结构体赋值(b := a )对每个字段做值拷贝;如果字段是指针、slice、map,则只复制 header(浅拷贝)- 要对嵌套引用类型做深拷贝,必须手写: new /make 并递归 copy 或循环赋值 |
继承/多态复制 | - Python 对象本身保存实际类型,copy.copy /deepcopy 会保留子类实例- 如果子类有特殊资源,需在 __copy__ /__deepcopy__ 中调用父类并处理额外字段 |
- 切片风险:将派生类对象赋给基类(按值)时会发生对象切片,只保留基类部分 - 通常用 虚拟 clone() 接口:cpp\n struct Base { virtual Base* clone() const = 0; };\n struct Derived : Base { Derived* clone() const override { return new Derived(*this); } };\n |
- Java 对象按引用传递,子类实例赋给父类引用不会切片 - clone() 通常声明在父类并为子类重写,返回具体子类类型- 也可用 Copy Constructor 模式: new SubClass(superFields…, subFields…) |
- Go 没有继承,只要用 struct embedding 模拟,赋值同样是字段拷贝(浅) - 多态用接口,底层还是指针实现,需要自行用类型断言和手写复制函数来“克隆”接口指向的具体类型 |
核心差异解读
Python
万物皆对象,赋值永远是引用;
拷贝由标准库 copy 模块管理,可通过魔法方法无限定制;
继承后 copy 照样保留子类类型。
C++
默认拷贝构造做成员逐一拷贝(浅拷贝指针),需要手写拷贝构造来深拷贝;
按值传递派生类给基类会切片,需用虚函数 clone 模式避免;
Java
所有对象都是引用,拷贝也得自己写(拷贝构造或重写 clone());
继承下 clone() 可在父类定义并在子类中 super.clone() 后补充字段。
Go
没有类继承,只有 struct 值拷贝和接口多态;
结构体直接赋值深度拷贝值类型字段,但 slice/map/header 仍共享底层。
总结
理解语言的“赋值模型”:先分清是「引用」「值」还是「指针」;
浅拷贝 vs. 深拷贝:浅拷贝只复制顶层结构,深拷贝要递归;
函数参数的语义:按值传递通常会调用拷贝;按引用/指针/slice 头拷贝则共享底层;
如果你要防止函数内部修改影响到外部状态,就要显式做一次深拷贝(在大部分语言里都需要手工调用)。