Menu
CoddyTech

赋值运算符重载

本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 44 课,共 104 课。

当你将一个已有对象赋值给另一个对象时,会调用复制赋值运算符(=)。与复制构造函数(用于创建新对象)不同,赋值运算符处理的是一个已经存在的对象,而这个对象可能持有需要先清理的资源。

class Buffer {
    int* data;
    size_t size;
    
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    ~Buffer() { delete[] data; }
    
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {              // 1. 检查是否给自身赋值
            delete[] data;                  // 2. 清理现有资源
            size = other.size;              // 3. 复制数据
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;                       // 4. 返回 *this
    }
};

自赋值检查(this != &other)至关重要。如果没有它,b = b; 会在尝试复制数据之前将其删除,导致未定义行为。

该运算符通过引用返回 *this,以便进行类似 a = b = c; 的链式操作。这与内置类型的行为一致。

对于管理资源的类,你通常会同时实现复制赋值运算符和移动赋值运算符:

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;           // 释放当前资源
        data = other.data;       // 接管资源
        size = other.size;
        other.data = nullptr;    // 使源对象保持有效状态
        other.size = 0;
    }
    return *this;
}

移动赋值运算符会转移所有权,而不是进行复制,因此像 vec[0] = createBuffer(); 这样的操作会高效得多。

challenge icon

挑战

简单

让我们创建一个 DynamicString 类,用于管理动态分配的字符数组,并妥善处理现有对象之间的赋值。这是一个实际场景:你需要将字符串数据从一个对象安全地传递给另一个对象,同时避免内存泄漏或悬空指针。

你将创建两个文件来组织代码:

  • DynamicString.h:定义一个 DynamicString 类,使用动态内存存储 C 风格字符串。你的类应管理以下内容:
    • 一个私有的 char* 指针,用于存储字符串数据
    • 一个私有的 size_t,用于存储长度
    • 一个接收 const char* 并创建深拷贝的构造函数
    • 一个能够正确释放已分配内存的析构函数
    • 一个拷贝赋值运算符,执行深拷贝并防止自赋值
    • 一个移动赋值运算符,高效地转移所有权
    • 一个返回内部字符串的 c_str() 方法(const)
    • 一个返回字符串长度的 length() 方法(const)

    你的拷贝赋值运算符必须遵循四个必要步骤:检查是否为自赋值、清理现有资源、拷贝新数据,并返回 *this。移动赋值运算符应接管源对象的指针,并让源对象处于有效的空状态(nullptr,长度为 0)。

  • main.cpp:从输入中读取三个字符串(每个字符串单独占一行)。创建三个 DynamicString 对象:str1 使用第一个输入,str2 使用第二个输入,str3 使用第三个输入。

    演示你的赋值运算符:

    1. 打印三个字符串的初始状态
    2. 使用拷贝赋值:str1 = str2;
    3. 拷贝赋值后打印 str1 和 str2(两者的内容应相同)
    4. 使用移动赋值:str2 = std::move(str3);
    5. 打印移动赋值后的 str2,以及 str3 的长度(移动后长度应为 0)

    输出格式:

    Initial:
    str1: <value>
    str2: <value>
    str3: <value>
    After copy (str1 = str2):
    str1: <value>
    str2: <value>
    After move (str2 = std::move(str3)):
    str2: <value>
    str3 length: 0

使用 <cstring> 来调用 strlen 和 strcpy。将你的移动赋值运算符标记为 noexcept。在 main.cpp 中包含 <utility>,以便使用 std::move。别忘了在头文件中添加头文件保护。

亲手试试

#include <iostream>
#include <string>
#include <utility>
#include "DynamicString.h"

using namespace std;

int main() {
    // 从输入中读取 3 个字符串
    string input1, input2, input3;
    getline(cin, input1);
    getline(cin, input2);
    getline(cin, input3);

    // 创建 3 个 DynamicString 对象
    DynamicString str1(input1.c_str());
    DynamicString str2(input2.c_str());
    DynamicString str3(input3.c_str());

    // TODO: 输出 3 个字符串的初始状态
    // 格式:
    // Initial:
    // str1: <value>
    // str2: <value>
    // str3: <value>

    // TODO: 使用复制赋值:str1 = str2;

    // TODO: 复制赋值后,输出 str1 和 str2
    // 格式:
    // After copy (str1 = str2):
    // str1: <value>
    // str2: <value>

    // TODO: 使用移动赋值:str2 = std::move(str3);

    // TODO: 移动后,输出 str2 和 str3 的长度
    // 格式:
    // After move (str2 = std::move(str3)):
    // str2: <value>
    // str3 length: 0

    return 0;
}
quiz icon自我测试

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

面向对象编程中的所有课

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