手写一个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函数实现,注意!=可以复用==的实现。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值