string
- 手写C++字符串类
- 类的基本结构与成员变量
- 一、构造函数与析构函数
- 二、赋值运算符重载
- 三、迭代器支持
- 四、内存管理与扩容机制
- 五、字符串操作函数
- 六、运算符重载
- 总结

手写C++字符串类
从零实现一个简易版std::string
类的基本结构与成员变量
namespace zzh {
class string {
private:
char* _str; // 存储字符串的字符数组
size_t _size; // 当前字符串长度
size_t _capacity; // 已分配的容量
public:
static const size_t npos; // 表示"不存在的位置"
// 各类成员函数...
};
}
这个自定义字符串类主要通过动态分配的字符数组_str
来存储字符串内容,并维护两个重要状态:_size
表示当前字符串长度,_capacity
表示已分配的内存容量。
- 基本成员变量
在自定义 string 类中,我们需要定义一些基本的成员变量来存储字符串的内容和相关信息:
_str:用于存储字符串的字符数组,通常是一个动态分配的 char 类型数组。
_size:表示当前字符串的实际长度,不包括结尾的空字符 \0。
_capacity:表示分配的内存容量,通常大于或等于 _size,用于优化内存分配效率。 - 构造函数和析构函数
构造函数用于初始化 string 对象,常见的构造方式包括:
从 C 风格字符串构造(const char*):通过 strlen 计算字符串长度,并动态分配内存来存储字符串内容。
拷贝构造函数:用于从另一个 string 对象构造新对象,需要深拷贝内存以避免悬挂指针问题。
默认构造函数:用于创建一个空字符串。
析构函数则负责释放动态分配的内存,避免内存泄漏。 - 赋值运算符重载
为了支持对象之间的赋值操作,我们需要重载赋值运算符 =。在实现时,需要注意自赋值的情况,并进行深拷贝以确保两个对象的内存独立。 - 内存管理
字符串操作中,内存管理是一个关键问题。我们需要在字符串长度超过当前容量时动态扩展内存。通常的做法是将容量加倍,以减少频繁的内存分配操作。 - 迭代器支持
为了方便遍历字符串中的字符,我们可以提供迭代器支持。通过定义 begin() 和 end() 方法,返回指向字符串首尾的指针,可以方便地使用标准库算法。 - 常见操作实现
追加字符或字符串:通过 push_back 和 append 方法,可以在字符串末尾添加字符或另一个字符串的内容。在实现时,需要注意内存容量是否足够,并在必要时进行扩展。
查找和替换:提供 find 方法用于查找字符或子字符串的位置,insert 和 erase 方法用于插入和删除字符或子字符串。
比较操作:重载比较运算符(如 <、>、== 等),以便可以直接比较两个 string 对象的大小。
一、构造函数与析构函数
// 1. 从C风格字符串构造
string::string(const char* str)
{
_size = strlen(str);
_str = new char[_size + 1];
_capacity = _size;
memcpy(_str, str, _size + 1);
}
// 2. 拷贝构造函数
string::string(const string& s)
{
_size = s._size;
_capacity = s._capacity;
_str = new char[_capacity + 1];
memcpy(_str, s._str, _size + 1);
}
// 3. 析构函数
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
关键点:
- 构造函数负责分配内存并复制字符串内容
- 拷贝构造函数实现深拷贝,避免内存共享
- 析构函数必须释放动态分配的内存,防止内存泄漏
二、赋值运算符重载
string& string::operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
memcpy(tmp, s._str, s._size + 1);
delete[] _str; // 注意:原代码此处顺序有误,已修正
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
技术要点:
- 使用临时变量确保异常安全
- 自我赋值检查避免无效操作
- 先分配新内存再释放旧内存,防止内存泄漏
三、迭代器支持
string::iterator string::begin() { return _str; }
string::iterator string::end() { return _str + _size; }
说明:
- 迭代器本质是字符指针
begin()
返回字符串首地址end()
返回字符串末尾的下一个位置
四、内存管理与扩容机制
// 预分配内存
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
memcpy(tmp, _str, _size + 1);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
// 追加字符
void string::push_back(char c)
{
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
_str[_size++] = c; // 注意:原代码此处错误地写入了固定字符'c'
_str[_size] = '\0';
}
内存管理策略:
- 采用指数级扩容(2倍)减少内存分配次数
reserve()
实现预分配,避免频繁扩容- 每次扩容后保留额外空间,提高插入效率
五、字符串操作函数
// 追加C风格字符串
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;
reserve(newcapacity);
}
memcpy(_str + _size, str, len); // 注意:原代码此处多复制了一个终止符
_size += len;
_str[_size] = '\0'; // 手动添加终止符
}
// 查找字符
size_t string::find(char c, size_t pos = 0) const
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
return i;
}
return npos;
}
// 插入字符
string& string::insert(size_t pos, char c)
{
assert(pos <= _size);
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
// 从后向前移动元素
for (size_t i = _size; i > pos; i--)
_str[i] = _str[i - 1];
_str[pos] = c;
_size++;
return *this;
}
核心算法:
append()
通过内存拷贝实现高效追加find()
线性查找目标字符insert()
通过元素后移实现插入操作- 使用
memmove()
处理内存重叠情况
六、运算符重载
// 比较运算符
bool string::operator<(const string& s)
{
size_t i1 = 0, i2 = 0;
while (i1 < _size && i2 < s._size)
{
if (_str[i1] < s._str[i2])
return true;
else if (_str[i1] > s._str[i2])
return false;
i1++; i2++;
}
return i1 < s._size; // 注意:原代码此处逻辑有误,已修正
}
// 索引运算符
char& string::operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
实现要点:
- 比较运算符按字典序逐字符比较
- 索引运算符提供随机访问能力
- 提供常量和非常量两个版本的重载
总结
通过手写这个简易版string
类,我们深入理解了标准库字符串类的核心机制:动态内存管理、深拷贝实现、迭代器设计、扩容策略等。虽然现代C++编程中应优先使用std::string
,但掌握这些底层原理有助于写出更高效、更安全的代码。