Menu
CoddyTech

拷贝构造函数

本课属于 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; }
};

如果没有自定义复制构造函数,两个对象都会指向同一块内存。其中一个对象销毁后,另一个对象就会持有悬空指针。深拷贝会分配新的内存并复制实际数据,确保每个对象都拥有自己的资源。

challenge icon

挑战

简单

让我们构建一个消息历史记录系统,展示当类管理动态内存时,深拷贝的重要性。你将创建一个 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;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器