手写一个C++字符串类:从底层理解String的实现

这是我通过自己动手实现一个字符串类,希望能够让你更好地理解C++的类设计、内存管理和运算符重载等概念。

基本结构设计

首先我们来看一下字符串类的基本框架:

复制代码
namespace yzq {
    class String {
    private:
        char* _str;         // 指向动态分配的字符数组
        size_t _size;       // 当前字符串长度
        size_t _capacity;   // 当前容量
        static const size_t npos;  // 表示无效位置
    };
}

这里我们使用动态分配的字符数组来存储字符串,_size记录当前字符串的实际长度,_capacity记录当前分配的内存容量。

构造函数和析构函数

构造函数

复制代码
String(const char* s1 = "") {
    _size = strlen(s1);
    _str = new char[_size + 1];  // 多分配一个位置存放'\0'
    _capacity = _size;
    strcpy(_str, s1);
}

构造函数接收一个C风格字符串,默认是空字符串。我们使用strlen获取字符串长度,然后动态分配足够的内存(记得要多分配一个位置给结束符'\0'),最后用strcpy拷贝内容。

拷贝构造函数

复制代码
String(const String& s1) {
    char* tmp = new char[s1._capacity + 1];
    strcpy(tmp, s1._str);
    _str = tmp;
    _size = s1._size;
    _capacity = s1._capacity;
}

拷贝构造函数实现深拷贝,先创建新内存,再拷贝内容,最后更新成员变量。

析构函数

复制代码
~String() {
    delete[] _str;
    _str = nullptr;
}

析构函数负责释放动态分配的内存,避免内存泄漏。

内存管理:reserve函数

复制代码
void String::reserve(size_t n) {
    if (n > _capacity) {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
    }
}

reserve函数用于提前分配足够的内存。只有当请求的大小大于当前容量时才进行扩容,这样可以避免频繁的内存重新分配。

字符串操作函数

尾插字符 push_back

复制代码
void String::push_back(char s1) {
    if (_size == _capacity) {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);  // 2倍扩容
    }
    _str[_size++] = s1;
    _str[_size] = '\0';
}

在尾部插入字符时,先检查容量是否足够,不够就进行2倍扩容,然后插入字符并更新大小。

追加字符串 append

复制代码
void String::append(const char* s1) {
    size_t len = strlen(s1);
    if (_size + len > _capacity) {
        reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
    }
    strcpy(_str + _size, s1);
    _size += len;
}

追加字符串时,先计算需要追加的长度,如果空间不够就扩容(采用更智能的扩容策略:需要的大小超过2倍容量就按需分配,否则按2倍扩容),然后用strcpy拷贝内容。

运算符重载 +=

复制代码
String& String::operator+=(char s1) {
    push_back(s1);
    return *this;
}

String& String::operator+=(const char* s1) {
    append(s1);
    return *this;
}

+=运算符重载直接复用push_backappend函数,让代码更简洁。

插入和删除操作

插入 insert

复制代码
void String::insert(size_t pos, char s1) {
    assert(pos <= _size);
    if (_size == _capacity) {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    }
    size_t end = _size + 1;
    while (end > pos) {
        _str[end] = _str[end - 1];
        end--;
    }
    _str[pos] = s1;
    _size++;
}

插入字符时,先把从插入位置开始的字符都向后移动一位,腾出位置后再插入新字符。

删除 erase

复制代码
void String::erase(size_t pos, size_t len) {
    assert(pos < _size);
    if (len == npos || pos + len >= _size) {
        _str[pos] = '\0';
        _size = pos;
    }
    else {
        for (size_t i = pos + len; i <= _size; i++) {
            _str[i - len] = _str[i];
        }
        _size -= len;
    }
}

删除操作分两种情况:如果要删除到字符串末尾,直接在删除位置设置结束符;否则把后面的字符向前移动覆盖要删除的部分。

查找和子串

查找 find

复制代码
size_t String::find(char c, size_t pos) const {
    assert(pos < _size);
    for (size_t i = pos; i < _size; i++) {
        if (_str[i] == c) return i;
    }
    return String::npos;
}

size_t String::find(const char* s, size_t pos) const {
    assert(pos < _size);
    char* pos_ptr = strstr(_str + pos, s);
    if (pos_ptr == nullptr) {
        return String::npos;
    }
    return pos_ptr - _str;
}

查找字符使用遍历,查找子串使用strstr函数,找到后通过指针相减计算位置。

获取子串 substr

复制代码
String String::substr(size_t pos, size_t len) const {
    assert(pos < _size);
    String s1;
    if (pos + len > _size) {
        len = _size - pos;
    }
    s1.reserve(len);
    for (size_t i = 0; i < len; i++) {
        s1 += _str[i + pos];
    }
    return s1;
}

获取子串时先处理长度越界的情况,然后预分配内存,最后逐个字符追加。

输入输出重载

流输出 operator<<

复制代码
ostream& operator<<(ostream& out, const String& s) {
    for (auto ch : s) {
        out << ch;
    }
    return out;
}

使用范围for循环遍历字符串输出每个字符。

流输入 operator>>

复制代码
istream& operator>>(istream& in, String& s) {
    char ch = in.get();
    const int N = 256;
    char buff[N];
    int i = 0;
    while (ch != ' ' && ch != '\n') {
        buff[i++] = ch;
        if (i == N - 1) {
            buff[i] = '\0';
            s += buff;
            i = 0;
        }
        ch = in.get();
    }
    if (i > 0) {
        buff[i] = '\0';
        s += buff;
    }
    return in;
}

这里使用缓冲区机制:先积累256个字符再一次性添加到字符串中,避免频繁扩容,提高效率。

比较运算符重载

复制代码
bool String::operator==(const String& d) {
    return strcmp(_str, d._str) == 0;
}

bool String::operator!=(const String& d) {
    return !(*this == d);
}

// 其他比较运算符类似...

比较运算符都基于strcmp函数实现,注意!=可以复用==的实现。

相关推荐
起这个名字3 小时前
Langchain4j Rag 知识库教程
java·后端
Autism1143 小时前
javase-day22-stream
java·开发语言·windows·笔记
江塘3 小时前
机器学习-KNN算法实战及模型评估可视化(C++/Python实现)
开发语言·c++·人工智能·python·算法·机器学习
KL41803 小时前
【QT】窗口
c++·qt
fouryears_234173 小时前
IDEA推送github,身份认证错误:Cannot assign requested address: getsockopt 解决方法
java·ide·intellij-idea
我命由我123453 小时前
Android PDF 操作 - AndroidPdfViewer 显示 PDF 异常清单(数据为 null、数据为空、PDF 文件损坏、非 PDF 文件)
android·java·java-ee·pdf·android studio·android-studio·android runtime
CodeCraft Studio4 小时前
【金融行业案例】基于Vaadin全栈Java框架重构内部系统,全面提升开发效率与用户体验
java·金融·重构·vaadin·银行内部系统·纯java开发框架·java web框架
潇I洒4 小时前
Linux写sh开机启动脚本-bash报错的两种解决方法
java·linux·bash
艾莉丝努力练剑4 小时前
【C++:继承和多态】多态加餐:面试常考——多态的常见问题11问
开发语言·c++·人工智能·面试·继承·c++进阶