string
前言
在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;
}
插入与删除
实现insert
和erase
方法:
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;
}
字符串查找操作
实现find
和find_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
张碧晨–光的方向
张杰–追风赶月的人
学技术学累了时可以听歌放松一下。