问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

【解码现代 C++】:实现自己的智能 String 类

创作时间:
作者:
@小白创作中心

【解码现代 C++】:实现自己的智能 String 类

引用
CSDN
1.
https://blog.csdn.net/2303_77720864/article/details/140204792

在C++中,std::string是一个非常常用的类,它封装了对C风格字符串的处理。但是,在某些情况下,我们可能需要自己实现一个类似string的类来展示对C++核心概念的掌握。本文将深入剖析一个自定义的String类的实现,特别关注其构造、拷贝构造、赋值运算符重载以及析构函数的实现。

1. 经典的String类问题

以下是一个初步的String类实现:

#include <iostream>
#include <cstring>
#include <cassert>
class String {
public:
    // 构造函数,默认参数为空字符串
    String(const char* str = "") {
        if (nullptr == str) {
            assert(false);  // 断言检查
            return;
        }
        _str = new char[strlen(str) + 1];  // 分配内存
        strcpy(_str, str);  // 拷贝字符串
    }
    // 析构函数
    ~String() {
        if (_str) {
            delete[] _str;  // 释放内存
            _str = nullptr;  // 避免悬挂指针
        }
    }
private:
    char* _str;
};
// 测试函数
void TestString() {
    String s1("hello bit!!!");
    String s2(s1);  // 默认拷贝构造
}
int main() {
    TestString();
    return 0;
}

1.1 构造函数

  • 功能:用于初始化对象。
  • 操作
  • 检查输入指针是否为nullptr
  • 分配足够的内存存储字符串。
  • 拷贝字符串内容到新分配的内存中。

1.2 析构函数

  • 功能:用于释放对象占用的资源。
  • 操作
  • 检查指针是否为空。
  • 释放内存。
  • 将指针置为nullptr以避免悬挂指针。

1.3 测试函数

  • 功能:验证构造函数和析构函数的工作情况。
  • 操作
  • 创建两个String对象。
  • 用默认的拷贝构造函数创建第二个对象。

1.4 需要记住的知识点

  • 默认的拷贝构造函数执行的是浅拷贝(shallow copy)。
  • 多个实例共享同一块内存,当其中一个实例被销毁时,其他实例会尝试访问已经释放的内存,导致程序崩溃。

2. 浅拷贝

浅拷贝是指编译器仅仅复制对象中的值(即指针地址),而不是指针所指向的内容。这意味着多个对象会共享同一份资源,如上例中的字符数组:

String s1("hello bit!!!");
String s2(s1); // 浅拷贝,s1和s2共享同一块内存

2.1 什么是浅拷贝

  • 定义:浅拷贝只复制指针地址,多个对象共享同一份资源。
  • 问题:当一个对象释放内存时,其他对象会访问无效内存,导致程序崩溃。

2.2 需要记住的知识点

  • 浅拷贝会导致多个对象共享同一块内存。
  • 释放其中一个对象时,其他对象会尝试访问已释放的内存,导致程序崩溃。

3. 深拷贝

深拷贝则会创建对象时复制资源的内容,使每个对象拥有一份独立的资源。这需要显式定义拷贝构造函数和赋值运算符。以下是一个实现深拷贝的String类:

3.1 传统版写法的String

#include <iostream>
#include <cstring>
#include <cassert>
class String {
public:
    // 构造函数
    String(const char* str = "") {
        if (nullptr == str) {
            assert(false);  // 断言检查
            return;
        }
        _str = new char[strlen(str) + 1];  // 分配内存
        strcpy(_str, str);  // 拷贝字符串
    }
    // 拷贝构造函数
    String(const String& s) {
        _str = new char[strlen(s._str) + 1];  // 分配内存
        strcpy(_str, s._str);  // 拷贝字符串
    }
    // 赋值运算符重载
    String& operator=(const String& s) {
        if (this != &s) {  // 自我赋值检查
            char* pStr = new char[strlen(s._str) + 1];  // 分配新内存
            strcpy(pStr, s._str);  // 拷贝字符串
            delete[] _str;  // 释放旧内存
            _str = pStr;  // 更新指针
        }
        return *this;
    }
    // 析构函数
    ~String() {
        if (_str) {
            delete[] _str;  // 释放内存
            _str = nullptr;  // 避免悬挂指针
        }
    }
private:
    char* _str;
};
// 测试函数
void TestString() {
    String s1("hello bit!!!");
    String s2(s1);  // 使用拷贝构造函数
    String s3 = s2;  // 使用赋值运算符重载
}
int main() {
    TestString();
    return 0;
}

3.1.1 拷贝构造函数

  • 功能:创建新对象时分配新的内存,并拷贝字符串内容。
  • 操作
  • 分配足够的内存存储字符串。
  • 拷贝字符串内容到新分配的内存中。

3.1.2 赋值运算符重载

  • 功能:确保自我赋值时不会出错,并实现深拷贝。
  • 操作
  • 检查自我赋值(如s = s)。
  • 分配新内存,拷贝字符串,释放旧内存,并更新指针。

3.1.3 需要记住的知识点

  • 拷贝构造函数和赋值运算符必须显式定义以实现深拷贝。
  • 深拷贝确保每个对象都有独立的资源,避免共享同一块内存。

4. 现代版写法的String

现代C++中,可以利用临时对象和swap函数简化赋值运算符的实现:

#include <iostream>
#include <cstring>
#include <cassert>
#include <algorithm>  // 包含swap函数
class String {
public:
    // 构造函数
    String(const char* str = "") {
        if (nullptr == str) {
            assert(false);  // 断言检查
            return;
        }
        _str = new char[strlen(str) + 1];  // 分配内存
        strcpy(_str, str);  // 拷贝字符串
    }
    // 拷贝构造函数
    String(const String& s)
        : _str(nullptr) {
        String strTmp(s._str);  // 创建临时对象
        swap(_str, strTmp._str);  // 交换内容
    }
    // 赋值运算符重载
    String& operator=(String s) {
        swap(_str, s._str);  // 交换内容
        return *this;
    }
    // 析构函数
    ~String() {
        if (_str) {
            delete[] _str;  // 释放内存
            _str = nullptr;  // 避免悬挂指针
        }
    }
private:
    char* _str;
};
// 测试函数
void TestString() {
    String s1("hello bit!!!");
    String s2(s1);  // 使用拷贝构造函数
    String s3 = s2;  // 使用赋值运算符重载
}
int main() {
    TestString();
    return 0;
}

4.1 拷贝构造函数

  • 功能:利用临时对象实现深拷贝。
  • 操作
  • 创建一个临时对象。
  • 交换临时对象和当前对象的内容。

4.2 赋值运算符重载

  • 功能:利用临时对象和swap函数简化赋值运算符的实现。
  • 操作
  • 利用临时对象进行深拷贝。
  • 交换临时对象和当前对象的内容。

4.3 需要记住的知识点

  • 现代C++中,利用swap和临时对象简化赋值运算符的实现,可以确保异常安全。
  • 这种方法使得代码简洁且高效。

5. 写时拷贝(了解)

写时拷贝(Copy-On-Write, COW)是一种优化技术,在实现浅拷贝的基础上增加引用计数。每次拷贝时增加引用计数,只有在实际写操作发生时才进行深拷贝:

#include <iostream>
#include <cstring>
#include <cassert>
class String {
public:
    // 构造函数
    String(const char* str = "") {
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
        _refCount = new int(1);  // 引用计数
    }
    // 拷贝构造函数
    String(const String& s)
        : _str(s._str), _refCount(s._refCount) {
        ++(*_refCount);  // 增加引用计数
    }
    // 赋值运算符重载
    String& operator=(const String& s) {
        if (this != &s) {
            if (--(*_refCount) == 0) {  // 释放旧资源
                delete[] _str;
                delete _refCount;
            }
            _str = s._str;
            _refCount = s._refCount;
            ++(*_refCount);  // 增加引用计数
        }
        return *this;
    }
    // 析构函数
    ~String() {
        if (--(*_refCount) == 0) {  // 释放资源
            delete[] _str;
            delete _refCount;
        }
    }
private:
    char* _str;
    int* _refCount;  // 引用计数指针
};
// 测试函数
void TestString() {
    String s1("hello bit!!!");
    String s2(s1);  // 增加引用计数
    String s3 = s2;  // 增加引用计数
}
int main() {
    TestString();
    return 0;
}

5.1 写时拷贝

  • 定义:通过引用计数实现资源共享,仅在写操作时进行深拷贝。
  • 操作
  • 每次拷贝时增加引用计数。
  • 只有在实际写操作发生时才进行深拷贝。

5.2 需要记住的知识点

  • 写时拷贝通过引用计数优化资源管理,减少不必要的深拷贝操作。
  • 这种技术在某些情况下能提高性能,但也有复杂性增加和多线程不安全的问题。

6. 总结

实现一个自定义的String类,最重要的是理解和正确实现构造函数、拷贝构造函数、赋值运算符重载和析构函数。通过深拷贝和写时拷贝等技术,可以确保对象管理资源的正确性和高效性。

  • 构造函数:初始化对象,确保资源正确分配。
  • 析构函数:释放资源,避免内存泄漏。
  • 拷贝构造函数:深拷贝确保每个对象有独立资源。
  • 赋值运算符重载:自我赋值检查,深拷贝确保安全赋值。
  • 现代C++:利用swap和临时对象简化代码,实现异常安全。
  • 写时拷贝:优化资源管理,通过引用计数延迟深拷贝操作。
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号