C++:深拷贝与浅拷贝

拷贝构造函数 (Copy Constructor) 单独拿出来,放在显微镜下看。

这部分逻辑之所以难,是因为它同时也涉及到了内存模型。要彻底理解深拷贝,你脑海里必须有一个动态的画面。

我用**"复印机与仓库"**的类比,配合一行行的代码拆解,带你彻底跑通这个逻辑。


一、 核心逻辑:从"配钥匙"到"建新房"

假设 Lidar 对象就是你家

  • rpm(转速)是你家墙上的挂钟

  • data(指针)是你手里的一把钥匙 ,通向一个外部仓库(堆内存),里面放着雷达数据。

1. 浅拷贝 (Shallow Copy) ------ 仅仅是配了一把钥匙

编译器默认干的事,就是简单的"配钥匙"。

  • 它把挂钟(rpm)复制了一个给你朋友。

  • 它把钥匙(data)复制了一把给你朋友。

  • 后果 :你和你朋友手里拿着一样的钥匙,打开的是同一个仓库

    • 你朋友打扫仓库(delete),你的仓库也没了。

    • 你再去仓库拿东西,发现空了或者塌了(Crash)。

2. 深拷贝 (Deep Copy) ------ 建一个一模一样的仓库

深拷贝构造函数的逻辑是:

  • 给我朋友也建一个新的仓库new int[])。

  • 把我仓库里的东西,一件一件搬运 到他的新仓库里(for 循环赋值)。

  • 最后把新仓库的钥匙给朋友。


二、 代码解剖台:逐行解析

我们把刚才那段代码拆开,每一行都有它的深意。

C++

复制代码
// 语法解读:
// 1. Lidar: 函数名和类名相同,说明是构造函数。
// 2. const Lidar& other: 
//    - const: 我保证在拷贝过程中,不会修改原来的那个对象(other)。
//    - &: 必须用引用!(后面解释为什么)
Lidar(const Lidar& other) {

    // --- 第一步:基本数据的"浅拷贝" ---
    // 像 int, bool 这种死板的数据,直接赋值就行。
    // 这一步相当于复制了"挂钟"。
    this->is_active = other.is_active;
    this->rpm = other.rpm;
    this->m_size = other.m_size; // 关键:必须知道仓库有多大

    // --- 第二步:申请"新地盘" (最关键的一步) ---
    // 如果没有这一行,data 就是野指针,或者只是复制了别人的地址。
    // new int[m_size] 会在堆内存里开辟一块【全新的、独立的】空间。
    // this->data 拿到了这个新空间的地址。
    this->data = new int[m_size];

    // --- 第三步:搬运数据 ---
    // 此时,新仓库是空的(或者里面是垃圾值)。
    // 我们需要把 old(other) 仓库里的数据,一个一个填进来。
    for(int i = 0; i < m_size; i++) {
        // 左边是新仓库,右边是老仓库
        this->data[i] = other.data[i];
    }
    
    // 此时,两个对象完全独立了。
}

三、 灵魂拷问:为什么要用引用 &

你可能会问:"我在普通函数传参时,偶尔不用 & 也没事,为什么拷贝构造函数里的参数 (const Lidar& other) 必须加 &?"

这是一个经典的死循环陷阱

假设你写成这样(去掉了 &):

C++

复制代码
Lidar(const Lidar other) { ... } // ❌ 错误写法

推演过程:

  1. main 函数调用 Lidar lidar2 = lidar1;

  2. 系统准备调用拷贝构造函数。

  3. 看到参数是 Lidar other(传值)。

  4. 根据 C++ 规则,传值参数需要被拷贝

  5. 为了拷贝 lidar1other,系统需要再次调用拷贝构造函数

  6. 再次调用时,又要传参,又要拷贝......

  7. 无限套娃 (Infinite Recursion) -> 栈溢出 (Stack Overflow) -> 程序崩溃。

所以,拷贝构造函数的参数必须是引用,这打断了无限拷贝的链条。


四、 什么时候需要深拷贝?

并不是所有类都需要这么麻烦。只有当你的类满足以下**"有产阶级"**特征时才需要:

  1. 有指针成员 (int* data, char* buffer)。

  2. 这个指针指向的是动态分配的内存 (new, malloc)。

  3. 你希望每个对象拥有这份内存的独占权

车企开发小贴士:

为了避免手写深拷贝这么麻烦且容易出错,现代 C++ (C++11) 给了我们两个神器:

  1. std::vector :它自己内部就实现了深拷贝。如果你的 datavector<int>,你根本不需要写拷贝构造函数,编译器默认生成的就够用了(它会调用 vector 的深拷贝)。

  2. std::shared_ptr / std::unique_ptr :智能指针明确了所有权,不用你手动 new/delete

相关推荐
程序员zgh2 小时前
C语言 指针用法与区别(指针常量、常量指针、指针函数、函数指针、二级指针)
c语言·开发语言·jvm·c++
冉佳驹2 小时前
C++ ——— 仿函数的使用、继承方式、赋值转换规则、菱形继承与虚继承
c++·继承·virtual·仿函数·菱形继承·模板特化·虚继承
咔咔咔的3 小时前
955. 删列造序 II
c++
xu_yule3 小时前
算法基础(数论)—欧拉函数
c++·算法·欧拉函数
xu_yule3 小时前
算法基础(数学)—数论
c++·算法·数论·最大公约数和最小公倍数·质数的判定·筛质数
Sheep Shaun3 小时前
二叉搜索树(下篇):删除、优化与应用
数据结构·c++·b树·算法
superman超哥3 小时前
仓颉借用检查器工作原理深度解析
c语言·开发语言·c++·python·仓颉
CoderCodingNo4 小时前
【GESP】C++五级真题(数论考点) luogu-B3871 [GESP202309 五级] 因数分解
开发语言·c++
ComputerInBook4 小时前
C++编程语言:标准库:第43章——C语言标准库(Bjarne Stroustrup)
c语言·c++·c语言标准库