一、浅拷贝(Shallow Copy) vs. 深拷贝(Deep Copy)

浅拷贝:新对象只是复制了原对象的第一层结构,内部的引用(如指针、子对象、底层数组)仍指向旧对象。

深拷贝:新对象及其内部所有可变子对象都要递归地重新分配并复制,互不影响。

二、Python

  1. 赋值(引用传递)

    1
    2
    3
    4
    a = [1, 2, 3]
    b = a # b 和 a 指向同一个列表
    b.append(4)
    print(a) # [1,2,3,4]

    函数参数:def f(x): … 中传入的是对象的引用,函数内部修改可变对象会影响外部。

  2. 浅拷贝

    1
    2
    3
    import copy
    b = a.copy() # 或 copy.copy(a)
    # 或 b = a[:] # list 特例:切片也会浅拷贝

    b 是新列表,但元素是同一组对象的引用(对基本类型来说即值复制)。

  3. 深拷贝

    1
    c = copy.deepcopy(a)

    递归复制所有层级子对象。适合嵌套列表、字典等。

三、C++

  1. 值语义 vs. 引用/指针
    1
    2
    3
    4
    vector<int> a = {1,2,3};
    vector<int> b = a; // 调用 vector 的拷贝构造:深拷贝底层数组
    b.push_back(4);
    // a 仍是 {1,2,3}
    vector 默认拷贝构造就是 深拷贝(会为新容器分配自己的存储并复制元素)。
1
2
vector<int>& r = a;  // 引用,不拷贝
vector<int>* p = &a; // 指针,也不拷贝
  1. 自定义类型
    如果成员里含原始指针或者动态分配的资源,需要自己写拷贝构造 & 赋值运算符来管理“深”或“浅”。

  2. 函数参数

    1
    2
    3
    void f1(vector<int> v);    // 按值传递,调用拷贝构造 → v 是独立副本
    void f2(const vector<int>& v); // 按引用传递,不拷贝
    void f3(vector<int>& v); // 可修改原对象

四、Java

  1. 引用语义 (类似 Python)
    1
    2
    3
    4
    List<Integer> a = new ArrayList<>();
    List<Integer> b = a; // b 和 a 引用同一个对象
    b.add(4);
    System.out.println(a); // [1,2,3,4]
  2. 浅拷贝
    1
    List<Integer> b = new ArrayList<>(a);
    新的 ArrayList,但底层存放的 Integer 引用与原来相同。

对象若包含可变字段,clone() 默认也是浅拷贝,需要重写。

  1. 深拷贝
    手动递归:对每个子对象都 new 一次并复制其字段。

或者用序列化/第三方库(如 Apache Commons SerializationUtils),效率上多一层序列化开销。

  1. 方法参数
    1
    void f(List<Integer> v) { … }  // 传引用,不拷贝

五、Go

  1. 值类型 vs. 引用类型
    值类型:基本类型、数组、结构体,赋值时会整体复制。

引用类型:切片(slice)、映射(map)、通道(chan)、指针、函数、接口 等,赋值时只复制了描述对象的 header,底层数据仍共享。

  1. 切片浅拷贝
    1
    2
    3
    4
    a := []int{1,2,3}
    b := a // b 与 a 共享底层数组
    b[0] = 9
    fmt.Println(a) // [9,2,3]
    若要深拷贝切片内容:
1
2
b := make([]int, len(a))
copy(b, a) // 逐元素复制到底层新数组
  1. 函数参数
    1
    2
    3
    4
    func 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_ptrunique_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 头拷贝则共享底层;

如果你要防止函数内部修改影响到外部状态,就要显式做一次深拷贝(在大部分语言里都需要手工调用)。