一、浅拷贝
本质:简单地复制对象的成员值。如果成员里有指针,新对象和原对象的指针会指向同一块内存。
比如你有对象A
,里面指针p
指向堆内存0x123
;用A
拷贝出对象B
,B
的指针p
也指向0x123
。问题:
- 若其中一个对象修改指针指向的内容,另一个对象也会受影响(因为共享内存)。
- 当对象销毁时(调用析构函数),两个对象的指针会重复释放同一块内存,程序会崩溃(C++ 不允许重复释放同一块堆内存 )。
类比:你和朋友共用一把家里的钥匙(指针),你把家里东西改了(修改内存内容),朋友看到的也会变;但如果你们先后 “销毁钥匙权限”(调用析构释放内存),第二次释放就会出问题。
二、深拷贝
本质:给新对象的指针重新开一块独立的堆内存,再把原对象指针指向的内容完整复制到新内存里。
还是用上面的例子:对象A
指针p
指向0x123
;拷贝出对象B
时,B
会新申请一块内存0x456
,再把0x123
里的内容复制到0x456
,B
的指针p
指向0x456
。优点:
- 两个对象的指针各自指向独立内存,修改内容互不影响。
- 析构时,各自释放自己的内存,不会重复释放,程序更安全。
类比:你和朋友各自配一把钥匙(新内存),各自钥匙开各自家门(独立内存),改自己家东西不影响对方,销毁钥匙(析构)也不会冲突。
三、如何实现深拷贝?(以代码为例)
假设我们有一个简单的 STRING
类(简化版,类似你提供的代码):
cpp
运行
class STRING {
private:char* _str; // 指向堆内存的指针
public:// 构造函数:初始化字符串STRING(const char* str) {_str = new char[strlen(str) + 1]; // 开堆内存存字符串strcpy(_str, str);}// 析构函数:释放堆内存~STRING() {delete[] _str;}// ... 其他成员函数
};
1. 深拷贝的拷贝构造函数
cpp
运行
STRING(const STRING& s) {// 1. 给新对象的指针开独立堆内存(大小和原字符串一样,+1 存 '\0')_str = new char[strlen(s._str) + 1]; // 2. 把原对象字符串内容,复制到新内存里strcpy(_str, s._str);
}
作用:创建新对象时,不共用原对象内存,而是 “另开新内存 + 复制内容”,避免浅拷贝的问题。
2. 深拷贝的赋值运算符重载
cpp
运行
STRING& operator=(const STRING& s) {// 防御性检查:避免自己赋值给自己(比如 a = a; 这种情况,释放内存会出问题)if (this != &s) { // 1. 先释放当前对象旧的堆内存(防止内存泄漏)delete[] _str; // 2. 开新内存,复制内容(和拷贝构造逻辑一样)_str = new char[strlen(s._str) + 1]; strcpy(_str, s._str); }return *this; // 返回当前对象,支持链式赋值(比如 a = b = c; )
}
作用:处理 “对象赋值” 场景(比如 a = b;
)。需要先释放自己旧的内存,再深拷贝新内容,否则会内存泄漏(旧内存没释放,又开新内存,原内存就丢了,无法释放 )。
四、总结
- 浅拷贝:简单复制指针值,共享内存,容易出 “重复释放” 或 “内容互相影响” 的问题。
- 深拷贝:给新对象指针重新开内存、复制内容,让对象互相独立,解决浅拷贝的隐患。
- 实现关键:在拷贝构造函数和赋值运算符重载里,手动 “开新内存 + 复制内容”,别依赖编译器默认的浅拷贝逻辑。
理解后,写涉及指针成员(动态内存)的类时,记得补全深拷贝的这两个函数,否则程序大概率会崩溃或内存泄漏~