模拟实现C++中的string类型:从底层理解字符串操作

发布于:2025-08-31 ⋅ 阅读:(23) ⋅ 点赞:(0)


前言

在C++中,std::string是我们日常开发中频繁使用的字符串类,它封装了复杂的内存管理和字符串操作细节。今天我们来手动模拟实现一个简化版的string类,深入理解其底层工作原理。


核心成员变量设计

一个基础的string类需要包含三个核心成员变量:

private:
    char* _a = new char[1] {'\0'};  // 存储字符串的字符数组
    size_t _size = 0;               // 当前字符串长度
    size_t _capacity = 0;           // 当前容量(可容纳的最大字符数,不含终止符)
  • _a:指向动态分配的字符数组,用于存储字符串内容
  • _size:记录当前字符串中有效字符的数量
  • _capacity:记录当前分配的内存可容纳的最大字符数(不包括末尾的’\0’)

构造函数与析构函数

默认构造函数

默认构造一个空字符串:

string::string()
    :_size(0)
    , _a(new char[1] {'\0'})
    , _capacity(0)
{}

从C风格字符串构造

通过const char*构造string,需要先计算字符串长度,再分配内存并复制内容:

string::string(const char* s)
{
    size_t n = strlen(s);
    reserve(n);  // 预留足够空间
    *this += s;  // 使用+=操作符复制内容
}

填充构造

创建包含n个字符c的字符串:

string::string(size_t n, char c)
{
    reserve(n);
    while (n--)
    {
        *this += c;
    }
}

拷贝构造函数

实现深拷贝,避免浅拷贝导致的内存问题:

string::string(const string& str)
{
    string tmp = str._a;  // 利用已有的构造函数
    swap(tmp);            // 交换当前对象和临时对象的资源
}

迭代器范围构造

通过迭代器范围构造字符串:

template <class InputIterator>
string::string(InputIterator first, InputIterator last)
{
    reserve(last - first);  // 预留足够空间
    while (first != last)
    {
        *this += *first;
        first++;
    }
}

析构函数

释放动态分配的内存:

string::~string()
{
    delete[] _a;
    _a = nullptr;
    _size = 0;
    _capacity = 0;
}

基本操作实现

迭代器支持

实现begin和end迭代器,方便使用范围for循环:

iterator begin() { return _a; }
const_iterator begin() const { return _a; }
iterator end() { return _a + _size; }
const_iterator end() const { return _a + _size; }

容量管理

reserve函数用于预留内存空间,避免频繁的内存分配:

void string::reserve(size_t n)
{
    if (_capacity <= n)
    {
        _capacity = n;
        char* tmp = new char[_capacity + 1] {};  // +1 用于存储终止符'\0'
        memcpy(tmp, _a, _size);
        delete[] _a;
        _a = tmp;
    }
}

resize函数用于调整字符串长度:

void string::resize(size_t n)
{
    if (_capacity < n)
    {
        char* tmp = new char[n + 1] {};
        memcpy(tmp, _a, _size);
        delete[] _a;
        _a = tmp;
        _capacity = n;
    }
    _size = n;
    _a[_size] = '\0';
}

// 带填充字符的重载版本
void string::resize(size_t n, char c)
{
    // 实现略...
}

元素访问

重载[]操作符实现元素访问:

char& operator[](size_t pos)
{
    assert(_size > pos);  // 边界检查
    return _a[pos];
}

const char& operator[](size_t pos) const
{
    assert(_size > pos);
    return _a[pos];
}

字符串修改操作

拼接操作

实现+=操作符和append方法:

string& string::operator+=(const string& str)
{
    for (size_t i = 0; i < str._size; i++)
    {
        if (_size == _capacity)
        {
            // 容量不足时扩容,翻倍增长
            _capacity = _capacity == 0 ? 4 : _capacity * 2;
            reserve(_capacity);
        }
        _a[_size] = str[i];
        _size++;
    }
    _a[_size] = '\0';
    return *this;
}

// 其他重载版本:+= const char*、+= char
// append方法通过调用+=实现
string& string::append(const string& str)
{
    *this += str;
    return *this;
}

插入与删除

实现inserterase方法:

string& string::insert(size_t pos, const string& str)
{
    assert(pos <= _size);
    if (_size + str.size() > _capacity)
    {
        reserve(_size + str.size());  // 确保有足够空间
    }
    // 移动现有字符为新内容腾出空间
    memmove(_a + pos + str._size, _a + pos, _size - pos);
    // 复制新内容
    for (size_t i = 0; i < str._size; i++)
    {
        _a[pos + i] = str[i];
    }
    _size += str.size();
    return *this;
}

string& string::erase(size_t pos, size_t len)
{
    assert(pos < _size);
    if (len > _size || pos + len > _size)
    {
        // 删除到末尾
        _a[pos] = '\0';
        _size = pos;
        return *this;
    }
    // 移动后面的字符覆盖被删除的部分
    while (pos + len < _size)
    {
        _a[pos] = _a[pos + len];
        pos++;
    }
    _size -= len;
    _a[_size] = '\0';
    return *this;
}

字符串查找操作

实现findfind_first_of系列方法:

size_t string::find(const string& str, size_t pos) const
{
    for (size_t i = pos; i < _size; i++)
    {
        if (_a[i] == str[0])
        {
            size_t j = 1;
            for (j = 1; j < str._size && (i + j) < _size; j++)
            {
                if (_a[i + j] != str[j])
                {
                    break;
                }
            }
            if (j == str._size)
            {
                return i;  // 找到匹配,返回起始位置
            }
        }
    }
    return npos;  // 未找到,返回npos
}

运算符重载

重载比较运算符:

bool operator== (const string& lhs, const string& rhs)
{
    if (strcmp(lhs._a, rhs._a) == 0)
    {
        return true;
    }
    return false;
}

bool operator> (const string& lhs, const string& rhs)
{
    if (strcmp(lhs._a, rhs._a) > 0)
    {
        return true;
    }
    return false;
}

// 其他比较运算符(!=, >=, <, <=)可通过上述运算符组合实现

重载输入输出运算符:

ostream& operator<< (ostream& os, const string& str)
{
    for (size_t i = 0; i < str.size(); i++)
    {
        os << str[i];
    }
    return os;
}

istream& operator>> (istream& is, string& str)
{
    str.clear();
    char buffer[64] = { 0 };
    int i = 0;
    char c = is.get();
    while (c != ' ' && c != '\n')  // 遇到空格或换行符停止
    {
        buffer[i++] = c;
        if (i == 63)  // 缓冲区满,先存入string
        {
            buffer[i] = '\0';
            str.reserve(str.capacity() + 64);
            str += buffer;
            i = 0;
        }
        c = is.get();
    }
    if (i != 0)  // 处理剩余字符
    {
        buffer[i] = '\0';
        str += buffer;
    }
    return is;
}

总结

通过手动实现string类,我们深入理解了动态内存管理、字符串操作的底层实现以及类设计的基本原理。这个简化版的string包含了标准库string的核心功能,包括构造/析构、容量管理、元素访问、字符串修改和查找等操作。

实际的std::string实现会更加复杂,还会考虑异常安全性、小字符串优化(SSO)、多线程安全等问题,但核心思想是一致的。理解这些底层实现有助于我们更好地使用标准库,写出更高效的代码。

如需源码,可在我的gitee上找到,下面是链接:
string
如对您有所帮助,可以来个三连,感谢大家的支持。


每文推荐

Aimer–Ref:rain
张碧晨–光的方向
张杰–追风赶月的人

学技术学累了时可以听歌放松一下。



网站公告

今日签到

点亮在社区的每一天
去签到