第12章 对象的“记忆”与“克隆”
当对象拥有自己的“租借记忆”(动态内存)时,简单的复制会引发大灾难。本章将揭示如何正确地“克隆”对象,并管理它们的“遗产”。
环节 1:动态内存与“收房管家”
如果对象在构造时用 `new` “租借”了一块内存,那么在它消亡时,必须有一个“收房管家”——**析构函数**——用 `delete` 把这块内存“归还”给系统,否则就会造成内存泄漏。
class String {
private:
char* str; // 指向“租借”的内存
public:
String(const char* s) {
str = new char[strlen(s) + 1];
strcpy(str, s);
}
// 析构函数: 归还内存
~String() {
delete[] str;
}
};
内存租借与归还 Guesthouse
模拟 `String` 对象的创建和销毁,观察内存的分配与释放过程。
系统内存 (Heap)
空闲
环节 2:深拷贝 vs. 浅拷贝
默认的复制行为是**浅拷贝**,就像两个人共用一个“秘密日记本”的地址。一个人销毁了日记本,另一个人手里的地址就失效了,再次销毁就会导致程序崩溃。正确的做法是**深拷贝**:为新人复制一本全新的日记本。
克隆实验室 🧬
灾难现场: 浅拷贝
Original: char* str -> @0x123
Clone: char* str -> @0x123
"Diary"
正确方案: 深拷贝
Original: char* str -> @0xABC
Clone: char* str -> @0xDEF
"Diary"
...
环节 3:赋值运算符 —— “搬家工人”
当把一个**已存在的**对象赋给另一个时,需要一个专业的“搬家工人”——重载的赋值运算符。它会先“清理旧房”(释放目标对象的旧内存),再“建造新房”并“复制家具”(深拷贝)。
String& String::operator=(const String& s) {
// 1. 检查自我赋值
if (this == &s) return *this;
// 2. 清理旧房: 释放旧内存
delete[] str;
// 3. 深度复制: 分配新内存并复制
len = s.len;
str = new char[len + 1];
strcpy(str, s.str);
return *this;
}
赋值操作模拟器 🚚
观察执行 `s2 = s1` 时,内存如何一步步地变化。
对象 s1
"Hello"
对象 s2
"World"
> 等待操作...
终点站:编程挑战
练习 1:浅复制的陷阱
任务:
使用一个未实现复制构造函数和赋值运算符的 `StringBad` 类,观察并解释当对象被按值传递或初始化时发生的错误(内存冲突/双重释放)。
预期知识点:
默认复制的行为,析构函数的调用时机。
点击查看参考答案
// main_test.cpp
void call_me(StringBad s) {
// 浅拷贝在这里发生
std::cout << "函数内执行完毕。\n";
} // s 离开作用域, 其析构函数释放了原始内存
int main() {
StringBad original("Original");
// 按值传递, 发生浅拷贝
call_me(original);
// 当 main 结束, original 的析构函数
// 会尝试再次释放同一块内存, 导致崩溃!
return 0;
}
练习 2:实现深度复制
任务:
编写一个 `String` 类,确保其复制构造函数能够正确执行深度复制,从而避免练习 1 中的错误。
预期知识点:
显式复制构造函数,深度复制步骤。
点击查看参考答案
// String 复制构造函数实现
String::String(const String & s) {
// 1. 复制非指针成员
len = s.len;
// 2. 分配新内存 (深度复制的关键)
str = new char[len + 1];
// 3. 复制内容
std::strcpy(str, s.str);
}
练习 3:实现赋值运算符
任务:
为 `String` 类实现重载的赋值运算符 `operator=`,以处理对象间的赋值,确保它能处理自我赋值并进行深度复制。
预期知识点:
赋值运算符重载,检查自我赋值,清理旧内存。
点击查看参考答案
// String 赋值运算符实现
String& String::operator=(const String & s) {
// 1. 检查自我赋值
if (this == &s)
return *this;
// 2. 清理旧内存
delete[] str;
// 3. 深度复制新数据
len = s.len;
str = new char[len + 1];
std::strcpy(str, s.str);
// 4. 返回调用对象的引用
return *this;
}