核心定义
-
浅拷贝 (Shallow Copy) :只复制对象本身和对象内的指针地址,不复制指针所指向的资源。
-
深拷贝 (Deep Copy) :不仅复制对象本身,还 重新分配内存 ,完整地复制指针所指向的资源。 可以把它们比作 复印笔记 :
-
浅拷贝 :只复印了笔记的 目录 。两份目录指向的都是 同一本原始笔记 。你修改了原始笔记,两份复印件看到的内容都变了。
-
深拷贝 :把整本笔记 从头到尾完整地复印 了一遍。你现在有了两本完全独立、互不相干的笔记。
详细对比

代码层面(面试时可以口述这个逻辑)
想象一个简单的字符串类,它内部有一个 char* 指针指向动态分配的内存:
arduino
class MyString {
public:
MyString(const char* text) {
p_data = new char[strlen
(text) + 1];
strcpy(p_data, text);
}
~MyString() {
delete[] p_data;
}
// 如果我们不写下面的拷贝构造函数,编
译器会生成一个浅拷贝的版本
// MyString(const MyString&
other) { p_data = other.
p_data; } // 浅拷贝
// 这是我们必须手动实现的深拷贝
MyString(const MyString& other)
{
// 1. 为新对象分配独立的内存
p_data = new char[strlen
(other.p_data) + 1];
// 2. 将内容复制过来
strcpy(p_data, other.
p_data);
}
private:
char* p_data;
};
向面试官总结:
"深拷贝和浅拷贝的核心区别在于如何处理 堆上 的资源。
浅拷贝 是编译器默认的行为,它只是简单地复制指针的值,导致多个对象共享同一个资源。这会带来两个致命问题:一是数据修改会互相影响;二是对象析构时会重复释放同一块内存,导致程序崩溃。
为了解决这个问题,我们就必须实现 深拷贝 。这意味着在拷贝构造函数和拷贝赋值运算符中,我们要手动为新对象申请一块全新的内存,并将原始资源的内容完整地复制过去,从而确保每个对象都有自己独立的资源副本,互不干扰。"
加分项(体现你的知识广度):
"在现代C++中,我们更推荐使用 智能指针 (如 std::shared_ptr 和 std::unique_ptr )来管理动态资源。这样,我们就可以利用它们自带的拷贝和移动语义,让编译器自动处理好深拷贝和所有权问题,从而遵循' 零法则 (Rule of Zero) ',避免手动编写这些复杂的拷贝控制函数,让代码更安全、更简洁。"
深拷贝怎么写
在C++中,当你的类里包含了 动态分配的资源 (比如通过 new 创建的指针),为了实现深拷贝,你必须遵守一个被称为" 三法则 "(Rule of Three)的经典规则。这意味着你需要亲自实现三个特殊的成员函数:
- 析构函数 (Destructor)
- 拷贝构造函数 (Copy Constructor)
- 拷贝赋值运算符 (Copy Assignment Operator)
我们就以上一题的 MyString 类为例,一步步写出完整的深拷贝实现。
1. 基础类定义
首先,我们有一个管理动态字符串的类,它在构造时 new 一块内存,在析构时 delete 它。
c
#include <cstring> // for strlen,
strcpy
#include <iostream>
class MyString {
private:
char* p_data; // 指向堆上内存的指
针
public:
// 构造函数: 从一个C风格字符串创建
MyString
MyString(const char* text = "")
{
std::cout << "调用了构造函数"
<< std::endl;
p_data = new char[strlen
(text) + 1];
strcpy(p_data, text);
}
// 1. 析构函数: 释放资源
~MyString() {
std::cout << "调用了析构函数,
释放了 " << (void*)p_data
<< std::endl;
delete[] p_data;
}
// ... 接下来实现拷贝控制函数 ...
void print() const {
std::cout << "[" << (void*)
p_data << "] " << p_data <<
std::endl;
}
};
2. 实现拷贝构造函数
当用一个已有的对象来 创建 一个新对象时,拷贝构造函数会被调用。例如: MyString s2 = s1; 或 MyString s2(s1); 。
核心步骤:
c
// ... 在MyString类中添加 ...
// 2. 拷贝构造函数 (深拷贝)
MyString(const MyString& other) {
std::cout << "调用了拷贝构造函数
(深拷贝)" << std::endl;
// 为新对象分配独立的内存
p_data = new char[strlen(other.
p_data) + 1];
// 将源对象的内容复制过来
strcpy(p_data, other.p_data);
}
3. 实现拷贝赋值运算符
当用一个已有的对象去 赋值给 另一个已有的对象时,拷贝赋值运算符会被调用。例如: s2 = s1; 。
核心步骤:
- 检查自赋值 :这是最重要的第一步!防止 s1 = s1; 这种操作导致提前释放资源。
- 释放旧资源 : delete 掉当前对象 p_data 指向的旧内存。
- 分配新资源 : new 一块新内存。
- 复制内容 :将源对象的内容复制过来。
- 返回 *this :为了支持链式赋值,如 s3 = s2 = s1; 。
arduino
// ... 在MyString类中添加 ...
// 3. 拷贝赋值运算符 (深拷贝)
MyString& operator=(const MyString&
other) {
std::cout << "调用了拷贝赋值运算符
(深拷贝)" << std::endl;
// 1. 检查是否是自我赋值
if (this != &other) {
// 2. 释放当前对象已有的资源
delete[] p_data;
// 3. 分配新内存
p_data = new char[strlen
(other.p_data) + 1];
// 4. 复制内容
strcpy(p_data, other.
p_data);
}
// 5. 返回当前对象的引用
return *this;
}
完整的示例代码
把它们组合起来,就是一个完整的、实现了深拷贝的 MyString 类。
c
#include <cstring>
#include <iostream>
class MyString {
private:
char* p_data;
public:
// 构造函数
MyString(const char* text = "")
{
std::cout << "调用了构造函数"
<< std::endl;
p_data = new char[strlen
(text) + 1];
strcpy(p_data, text);
}
// 1. 析构函数
~MyString() {
std::cout << "调用了析构函数,
释放了 " << (void*)p_data
<< std::endl;
delete[] p_data;
}
// 2. 拷贝构造函数 (深拷贝)
MyString(const MyString& other)
{
std::cout << "调用了拷贝构造函
数 (深拷贝)" << std::endl;
p_data = new char[strlen
(other.p_data) + 1];
strcpy(p_data, other.
p_data);
}
// 3. 拷贝赋值运算符 (深拷贝)
MyString& operator=(const
MyString& other) {
std::cout << "调用了拷贝赋值运
算符 (深拷贝)" << std::endl;
if (this != &other) {
delete[] p_data;
p_data = new char[strlen
(other.p_data) + 1];
strcpy(p_data, other.
p_data);
}
return *this;
}
void print() const {
std::cout << "[" << (void*)
p_data << "] " << p_data <<
std::endl;
}
};
int main() {
std::cout << "--- 创建 s1 ---"
<< std::endl;
MyString s1("Hello");
s1.print();
std::cout << "\n--- 创建 s2 (拷贝
构造) ---" << std::endl;
MyString s2 = s1; // 调用拷贝构造
函数
s1.print();
s2.print(); // s1和s2的指针地址不
同,证明是深拷贝
std::cout << "\n--- 创建 s3
---" << std::endl;
MyString s3("World");
s3.print();
std::cout << "\n--- s3 赋值 (拷贝
赋值) ---" << std::endl;
s3 = s1; // 调用拷贝赋值运算符
s1.print();
s3.print(); // s1和s3的指针地址也
不同
std::cout << "\n--- 程序结束,自动
调用析构 ---" << std::endl;
return 0;
}
当你运行这段代码,你会清晰地看到构造、拷贝、赋值和析构函数的调用顺序,并且每个对象的指针地址都是独立的,这就完美地实现了深拷贝。