运算符重载综合案例

【C++学习笔记】手写一个简单的 MyString 类

最近在学习 C++ 的内存管理,为了彻底搞懂深拷贝(Deep Copy)和运算符重载,我尝试手写了一个 MyString 类。

虽然看起来只有几十行代码,但实际写起来发现细节非常多,尤其是指针处理和内存释放的问题。下面是我的完整代码(包含了一些注释掉的错误写法,用来提醒自己),以及我对这些“坑”的总结。

源代码

#include <iostream>
#include <cstring>

using namespace std;

class MyString {
    friend ostream& operator<<(ostream& out, const MyString& m);

   private:
    char* m_chars;
    size_t m_size;

   public:
    MyString(const char* str) {
        if (str == nullptr) {
            m_size = 0;
            m_chars = new char[m_size + 1]();
        } else {
            m_size = strlen(str);
            m_chars = new char[m_size + 1];
            strcpy(m_chars, str);
        }
    }
    MyString(const MyString& other) : m_size(other.m_size) {
        // 错误点1:C风格字符串,m_size+1而不是m_size,为了末尾的 \0
        m_chars = new char[m_size + 1];
        strcpy(m_chars, other.m_chars);
    }
    MyString& operator=(const MyString& other) {
        // 防止自赋值
        if (this == &other) return *this;
        // 释放旧内存
        delete[] m_chars;

        m_chars = nullptr;
        m_size = other.m_size;
        // 深拷贝
        // 错误点1
        m_chars = new char[m_size + 1];
        // strncpy(m_chars, other.m_chars, sizeof(m_chars) - 1);
        // 错误点2:sizeof(指针)理解错误。不要用 sizeof(ptr),这只是返回一个指针的大小,建议直接strcpy:
        strcpy(m_chars, other.m_chars);
        // 错误点3:函数忘记写返回值
        return *this;
    }
    ~MyString() {
        delete[] m_chars;
        m_chars = nullptr;
    }
    // 加法 (+):实现字符串拼接。
    // MyString s3 = s1 + s2;
    // 提示:新长度 = len1 + len2。申请新内存,先 strcpy 前半段,再 strcat 后半段。
    MyString operator+(const MyString& other) const {
        size_t newSize = m_size + other.m_size;
        char* temp = new char[newSize + 1];  // 错误点1
        // 错误点2 strncpy(temp, m_chars, sizeof(m_chars) - 1);
        strcpy(temp, m_chars);
        strcat(temp, other.m_chars);
        // 错误点4:忘记delete。new 出来的temp,忘记delete,直接return temp;
        // 正确:利用构造函数生成新对象
        MyString newStr(temp);
        delete[] temp;
        return newStr;
    }
    // 相等 (==):判断两个字符串内容是否相同 (提示:用 strcmp)。
    // 支持两个对象对比
    bool operator==(const MyString& other) const {
        // 错误点5:strcmp 在相等时返回 0 (false),不相等时返回非 0 (true)。所以应该加上==0
        return strcmp(m_chars, other.m_chars) == 0;
    }
    // 错误点6:漏了直接传入常量来对比的情况, 支持 s3 == "Hello"
    bool operator==(const char* str) const { return strcmp(m_chars, str) == 0; }
    // 索引 ([]):允许像数组一样访问字符 str[0]。
    char& operator[](size_t index) { return m_chars[index]; }
};
// 输出 (<<):可以直接 cout << str。
ostream& operator<<(ostream& out, const MyString& ms) {
    for (int i = 0; i < ms.m_size; ++i) {
        out << ms.m_chars[i];
    }
    // 错误点3
    return out;
}

void test_final() {
    MyString s1("Hello");
    MyString s2(" World");
    cout << s1 << endl;
    cout << s2 << endl;

    // 1. 测试加法
    MyString s3 = s1 + s2;
    cout << "s3: " << s3 << endl;  // 预期: Hello World

    // 2. 测试比较,(支持与 C字符串直接比较,也支持两个MyString对象比较)
    MyString m1("Hello World");
    if (s3 == "Hello World" && m1 == s3) {
        cout << "s3 内容正确" << endl;
    }

    // 3. 测试赋值 (深拷贝)
    MyString s4 = s3;
    cout << "s4: " << s4 << endl;

    // 4. 测试索引
    s4[0] = 'h';
    cout << "s4 修改首字母后: " << s4 << endl;  // 预期: hello World
}
int main() { test_final(); }

踩坑总结

在写这段代码的过程中,我犯了不少低级错误,总结下来主要有以下几点:

  1. 内存分配记得 +1
    C 风格字符串以 \0 结尾,strlen 计算长度时不包含这个结束符。所以在 new char[] 时,一定要写 m_size + 1,否则 strcpy 会导致内存越界。

  2. sizeof 指针的误区
    我习惯性地用了 sizeof(m_chars),结果发现它返回的是指针本身的大小(通常是 4 或 8 字节),而不是字符串的实际容量。操作字符串内容时,应该直接使用 strcpy 配合正确的长度逻辑。

  3. 运算符重载的返回值
    operator=operator<< 时很容易只顾着实现逻辑,忘了返回值。为了支持链式调用(如 a = b = ccout << s1 << s2),必须返回 *this 或者 ostream&

  4. 临时内存泄露
    在实现 operator+ 时,我 new 了一个 temp 数组来拼接字符串,最后返回了构造好的新对象,却忘了 delete[] temp。这会导致每次字符串拼接都产生内存泄漏。

  5. strcmp 的返回值逻辑
    strcmp 在两个字符串相等时返回 0。我一开始直觉以为相等返回 true,结果逻辑写反了。判断相等必须写 strcmp(...) == 0

  6. 比较运算符的重载不够全
    写了 MyString == MyString 后,发现 s1 == "text" 这种代码报错。原来还需要额外重载一个参数为 const char* 的版本,才能支持与字面量字符串的比较。

通过手写这个类,对 C++ 的资源管理(RAII)有了更直观的理解。虽然现在都用 std::string,但理解底层原理还是很重要的。

posted @ 2026-01-21 00:58  iewiq  阅读(3)  评论(0)    收藏  举报