赋值运算符重载
本课属于 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(); 这样的操作会高效得多。
挑战
简单让我们创建一个 DynamicString 类,用于管理动态分配的字符数组,并妥善处理现有对象之间的赋值。这是一个实际场景:你需要将字符串数据从一个对象安全地传递给另一个对象,同时避免内存泄漏或悬空指针。
你将创建两个文件来组织代码:
DynamicString.h:定义一个DynamicString类,使用动态内存存储 C 风格字符串。你的类应管理以下内容:- 一个私有的
char*指针,用于存储字符串数据 - 一个私有的
size_t,用于存储长度 - 一个接收
const char*并创建深拷贝的构造函数 - 一个能够正确释放已分配内存的析构函数
- 一个拷贝赋值运算符,执行深拷贝并防止自赋值
- 一个移动赋值运算符,高效地转移所有权
- 一个返回内部字符串的
c_str()方法(const) - 一个返回字符串长度的
length()方法(const)
你的拷贝赋值运算符必须遵循四个必要步骤:检查是否为自赋值、清理现有资源、拷贝新数据,并返回
*this。移动赋值运算符应接管源对象的指针,并让源对象处于有效的空状态(nullptr,长度为 0)。- 一个私有的
main.cpp:从输入中读取三个字符串(每个字符串单独占一行)。创建三个DynamicString对象:str1使用第一个输入,str2使用第二个输入,str3使用第三个输入。演示你的赋值运算符:
- 打印三个字符串的初始状态
- 使用拷贝赋值:
str1 = str2; - 拷贝赋值后打印 str1 和 str2(两者的内容应相同)
- 使用移动赋值:
str2 = std::move(str3); - 打印移动赋值后的 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;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器