拷贝构造函数
本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 20 课,共 104 课。
复制构造函数会将现有对象复制并创建一个新对象。在用同一类型的另一个对象初始化对象、按值传递对象或按值返回对象时,会调用它。
复制构造函数接受对同一个类的对象的 const 引用。它必须是引用:按值传递 other 本身就需要进行一次复制,这会不断调用复制构造函数,永远不会停止。它是 const,因为复制不应改变原对象:
class Player {
std::string name;
int health;
public:
Player(std::string n, int h) : name(n), health(h) {}
// 拷贝构造函数
Player(const Player& other) {
name = other.name;
health = other.health;
}
};
Player original("Hero", 100);
Player copy = original; // 调用了拷贝构造函数
Player another(original); // 也调用了拷贝构造函数如果你没有定义复制构造函数,编译器会生成一个执行浅拷贝的复制构造函数,直接复制每个成员的值。这对于简单类型来说没有问题,但如果你的类管理动态内存,就会导致问题。
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
// 深拷贝构造函数
Buffer(const Buffer& other) : size(other.size) {
data = new int[size]; // 分配新内存
for (size_t i = 0; i < size; i++)
data[i] = other.data[i]; // 复制内容
}
~Buffer() { delete[] data; }
};如果没有自定义复制构造函数,两个对象都会指向同一块内存。其中一个对象销毁后,另一个对象就会持有悬空指针。深拷贝会分配新的内存并复制实际数据,确保每个对象都拥有自己的资源。
挑战
简单让我们构建一个消息历史记录系统,展示当类管理动态内存时,深拷贝的重要性。你将创建一个 MessageLog 类,用动态分配的数组存储消息,并实现合适的拷贝构造函数,确保每个副本都有独立的内存。
你将创建两个文件来组织代码:
MessageLog.h:定义一个MessageLog类,使用动态内存管理消息集合。你的类应包含:- 私有成员:一个指向字符串数组的指针,用于存储消息;一个
capacity,表示消息的最大数量;以及一个count,表示当前消息数量 - 一个带参数的构造函数,接收容量,在堆上分配数组,并将 count 初始化为 0
- 一个执行深拷贝的拷贝构造函数:分配新内存,并从源对象复制所有消息
- 一个释放已分配内存并打印
"MessageLog destroyed"的析构函数 - 一个
addMessage(string msg)方法,有空间时添加一条消息 - 一个
getCount()方法,返回当前消息数量 - 一个
getMessage(int index)方法,返回给定索引处的消息
- 私有成员:一个指向字符串数组的指针,用于存储消息;一个
main.cpp:演示你的拷贝构造函数会创建独立副本。读取一个容量值和两条输入消息,然后:- 使用输入的容量创建一个名为
original的MessageLog - 将两条消息都添加到
original - 使用拷贝构造函数创建一个名为
backup的副本 - 只向
original添加第三条消息"New message" - 打印
"Original count: <count>" - 打印
"Backup count: <count>" - 打印
backup中的所有消息,每行一条
- 使用输入的容量创建一个名为
输入格式如下:
- 第一行:容量(整数)
- 第二行:第一条消息(字符串)
- 第三行:第二条消息(字符串)
如果深拷贝实现正确,backup 应有 2 条消息,而 original 有 3 条,这证明它们拥有独立的内存。如果没有正确的拷贝构造函数,它们就会共用同一个数组,对其中一个对象所做的更改也会影响另一个对象!
在 main.cpp 中使用 #include "MessageLog.h" 包含头文件。
亲手试试
#include <iostream>
#include <string>
#include "MessageLog.h"
using namespace std;
int main() {
// 读取输入
int capacity;
cin >> capacity;
cin.ignore();
string msg1, msg2;
getline(cin, msg1);
getline(cin, msg2);
// TODO: 用输入的容量创建一个名为 'original' 的 MessageLog
// TODO: 将两条消息都添加到 original
// TODO: 使用复制构造函数创建一个名为 'backup' 的副本
// TODO: 只将 "New message" 添加到 original
// TODO: 打印 "Original count: <count>"
// TODO: 打印 "Backup count: <count>"
// TODO: 打印 backup 中的所有消息,每行一条
return 0;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器