跳到主要内容C++ STL string 类从零实现详解 | 极客日志C++算法
C++ STL string 类从零实现详解
综述由AI生成STL string 类的核心在于内存管理与接口模拟。本文从构造函数、拷贝控制、迭代器及常用操作函数入手,详细解析了深拷贝机制、Copy-and-Swap 惯用法以及扩容策略的实现细节。通过手动实现 string 类,可以深入理解 C++ 对象的生命周期管理、指针操作及资源所有权语义。
DevStack4 浏览 前言
在掌握 string 类接口函数的基本用法后,深入理解其底层实现机制至关重要。本文将带你从零开始,逐步构建一个完整的 string 类,重点剖析内存管理、拷贝控制及常用算法的实现细节。
一、string 类总览
为避免与标准库中的 string 产生命名冲突,本示例使用 mystd 命名空间进行封装。
namespace mystd {
class string {
public:
typedef char* iterator;
typedef const char* const_iterator;
string();
string(const char* str);
string(const string& s);
string& operator=(const string& s);
~string();
iterator begin();
iterator end();
const_iterator begin()const;
const_iterator end()const;
size_t size();
size_t capacity();
void reserve(size_t n);
;
;
;
;
string& +=( ch);
string& +=( * str);
;
;
;
;
;
;
& []( i);
& []( i);
;
;
;
;
:
* _str;
_size;
_capacity;
npos;
};
string::npos = ;
<( string& s1, string& s2);
<=( string& s1, string& s2);
>( string& s1, string& s2);
>=( string& s1, string& s2);
==( string& s1, string& s2);
!=( string& s1, string& s2);
ostream& <<(ostream& out, string& s);
istream& >>(istream& in, string& s);
}
void resize(size_t n, char ch = '\0')
bool empty()const
void push_back(char ch)
void append(const char* str)
operator
char
operator
const
char
string& insert(size_t pos, char ch)
string& insert(size_t pos, const char* str)
string& erase(size_t pos, size_t len)
void clear()
void swap(string& s)
const char* c_str()const
char
operator
size_t
const
char
operator
size_t
const
size_t find(char ch, size_t pos = 0)const
size_t find(const char* str, size_t pos = 0)const
size_t rfind(char ch, size_t pos = npos)const
size_t rfind(const char* str, size_t pos = 0)const
private
char
size_t
size_t
static
const
size_t
const
size_t
-1
bool
operator
const
const
bool
operator
const
const
bool
operator
const
const
bool
operator
const
const
bool
operator
const
const
bool
operator
const
const
operator
const
operator
char* _str:用于存储实际的字符数据。
size_t _size:记录当前字符串的有效长度(不包含末尾的 \0)。
size_t _capacity:记录当前分配的容量大小(不包含末尾的 \0)。
static const size_t npos:通常用作异常返回值或表示最大索引值。
迭代器设计:
string 类的迭代器本质上是字符指针。通过类型别名简化使用:
iterator:指向可读写字符的指针。
const_iterator:指向只读字符的指针。
二、默认成员函数
2.1 无参构造函数
string() : _str(new char[1]{'\0'}), _size(0), _capacity(0) {}
构造空字符串时,我们需要预留一个位置存放终止符 \0。此时逻辑长度 _size 和容量 _capacity 均初始化为 0,但实际内存中多分配了一个字节给 \0。
2.2 带参构造函数
string(const char* str) {
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
这里计算字符串长度并分配相应空间。注意,_capacity 仅记录有效字符数,实际开辟空间需额外加 1 以容纳 \0。虽然初始化列表能优化性能,但此处为了复用 strlen 结果,直接在函数体内处理更为直观。
2.3 拷贝构造函数
实现拷贝构造前,必须明确深浅拷贝的区别:浅拷贝会导致多个对象共享同一块内存,引发重复释放;深拷贝则为每个对象独立分配资源。
string(const string& s) : _size(s._size), _capacity(s._size) {
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
此方式显式分配新内存并复制内容,确保两个对象完全独立。
void swap(string& tmp) {
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
string(const string& s) {
string tmp(s._str);
swap(tmp);
}
这种惯用法利用按值传递参数自动触发深拷贝构造,生成临时对象 tmp,随后通过交换指针将数据转移给当前对象。临时对象析构时会自动清理旧内存,代码更简洁且异常安全。
注意: 若成员变量未初始化或缺乏默认值,直接交换可能导致悬空指针,因此务必确保对象状态一致。
2.4 赋值运算符重载
string& operator=(const string& other) {
if (this == &other) return *this;
delete[] _str;
_str = new char[other._capacity + 1];
strcpy(_str, other._str);
_size = other._size;
_capacity = other._capacity;
return *this;
}
关键点包括:检查自我赋值、释放旧资源、深拷贝新资源、返回自身引用以支持链式赋值。
string& operator=(string tmp) {
swap(tmp);
return *this;
}
参数按值传递,编译器自动调用拷贝构造函数完成深拷贝。进入函数体后,直接交换 this 与临时对象 tmp 的成员变量。函数结束时,tmp 析构,原对象的旧内存被自动释放。这种方式不仅减少了代码量,还天然具备异常安全性。
2.5 析构函数
~string() {
delete[] _str;
_size = 0;
_capacity = 0;
}
三、迭代器
iterator begin() { return _str; }
const_iterator begin() const { return _str; }
iterator end() { return _str + _size; }
const_iterator end() const { return _str + _size; }
begin() 返回首字符地址,end() 返回最后一个字符的后继地址(即 \0 的位置)。
四、容量和大小相关函数
4.1 size 与 capacity
size_t size() const { return _size; }
size_t capacity() const { return _capacity; }
4.2 reserve
void reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
当请求容量超过当前值时,重新分配更大内存并复制旧数据。一般只扩容不缩容,以减少频繁分配开销。
4.3 empty
bool empty() { return strcmp(_str, "") == 0; }
五、字符串修改函数
5.1 push_back
void push_back(char c) {
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size++] = c;
_str[_size] = '\0';
}
尾插字符前检查容量,不足则按 2 倍扩容。插入后记得更新 \0 终止符。
5.2 append
void append(const char* s) {
size_t len = strlen(s);
if (len + _size > _capacity) {
size_t newcapacity = (_size + len > _capacity * 2) ? _size + len : _capacity * 2;
reserve(newcapacity);
}
strcpy(_str + _size, s);
_size += len;
}
追加字符串时,优先保证足够空间,避免多次扩容。源字符串自带 \0,无需额外添加。
5.3 operator+=
string& operator+=(char c) {
this->push_back(c);
return *this;
}
string& operator+=(const char* s) {
this->append(s);
return *this;
}
5.4 insert
void insert(size_t pos, char c) {
assert(pos <= _size);
if (_size == _capacity) {
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
size_t end = _size + 1;
while (end > pos) {
_str[end] = _str[end - 1];
--end;
}
_str[pos] = c;
_size++;
}
插入字符需先移动后续元素腾出空间。注意: 循环条件应使用 end > pos 而非 end >= pos,防止 size_t 下溢导致死循环。
void insert(size_t pos, const char* s) {
assert(pos <= _size);
size_t len = strlen(s);
if (len == 0) return;
if (len + _size > _capacity) {
size_t newcapacity = len + _size > _capacity * 2 ? len + _size : _capacity * 2;
reserve(newcapacity);
}
size_t end = _size + len;
while (end > pos + len - 1) {
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0; i < len; i++) {
_str[pos + i] = s[i];
}
_size += len;
}
插入字符串时,先将末尾 \0 及后续字符整体后移,再填充新内容。
5.5 erase
void erase(size_t pos, size_t len) {
assert(pos < _size);
if (len >= _size - pos) {
_str[pos] = '\0';
_size = pos;
} else {
size_t begin = pos + len;
while (begin <= _size) {
_str[begin - len] = _str[begin];
++begin;
}
_size -= len;
}
}
5.6 substr
string substr(size_t pos, size_t len) {
assert(pos < _size);
if (len >= _size - pos) len = _size - pos;
string tmp;
tmp.reserve(len);
for (size_t i = 0; i < len; i++) {
tmp += _str[pos + i];
}
return tmp;
}
5.7 clear
void clear() {
_str[0] = '\0';
_size = 0;
}
六、访问字符串相关函数
6.1 operator[]
char& operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const {
assert(pos < _size);
return _str[pos];
}
6.2 find
size_t find(char c, size_t pos) {
assert(pos < _size);
for (size_t i = pos; i < _size; i++) {
if (_str[i] == c) return i;
}
return npos;
}
size_t find(const char* s, size_t pos) {
assert(pos < _size);
const char* ptr = strstr(_str, s);
if (ptr != nullptr) return ptr - _str;
else return npos;
}
七、比较运算符重载
bool operator<(const string& s1, const string& s2) {
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator==(const string& s1, const string& s2) {
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator<=(const string& s1, const string& s2) {
return s1 < s2 || s1 == s2;
}
bool operator>(const string& s1, const string& s2) {
return !(s1 <= s2);
}
bool operator>=(const string& s1, const string& s2) {
return !(s1 < s2);
}
bool operator!=(const string& s1, const string& s2) {
return !(s1 == s2);
}
八、字符串输入与输出
8.1 >> 运算符重载
istream& operator>>(istream& in, string& s) {
s.clear();
const int N = 256;
char buff[N] = { 0 };
int i = 0;
char ch = in.get();
while (ch == ' ' || ch == '\n' || ch == '\t') {
ch = in.get();
}
while (ch != ' ' && ch != '\n' && ch != '\t') {
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;
}
跳过前导空白,分块读取字符以避免频繁扩容,遇到空白字符停止。
8.2 << 运算符重载
ostream& operator<<(ostream& out, const string& s) {
for (auto ch : s) {
out << ch;
}
return out;
}
通过以上步骤,我们手动实现了 string 类的核心功能。这一过程不仅加深了对 C++ 内存模型的理解,也掌握了 Copy-and-Swap 等关键惯用法的实际应用。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online