跳到主要内容
C++ STL string 类从零实现详解 | 极客日志
C++ 算法
C++ STL string 类从零实现详解 从零模拟实现 C++ STL string 类,涵盖构造函数、拷贝构造、赋值运算符及析构函数的深拷贝处理,重点讲解 Copy-and-Swap 惯用法。同时实现了迭代器、容量管理、字符串修改、访问查找及运算符重载等核心功能。通过手写代码解析内存分配、指针操作及边界条件处理,帮助开发者深入理解 C++ 底层机制与资源管理策略。
黑客 发布于 2026/3/28 更新于 2026/4/25 1 浏览前言
在之前的学习中,我们已经初步掌握了 std::string 接口函数的使用方法。本文将带大家从零开始,逐步实现一个完整的 string 类,深入理解其底层机制。
为了避免与标准库中的 string 产生命名冲突,我们在实现中统一使用 mystd 命名空间进行封装。
一、string 类总览
首先来看类的整体结构。核心成员变量包括存储字符串的指针 _str、记录当前有效长度的 _size 以及记录容量的 _capacity。此外,还有一个静态常量 npos,通常用作异常返回值或表示最大值。
迭代器方面,我们直接使用 char* 作为 iterator,const char* 作为 const_iterator,这符合 C++ 早期实现的风格,也便于理解指针操作。
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 () ;
;
;
;
;
;
string& +=( ch);
string& +=( * str);
;
;
;
;
;
;
& []( i);
& []( i) ;
;
;
;
;
:
* _str;
_size;
_capacity;
npos;
};
string::npos = ;
}
void
reserve
(size_t n)
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
二、默认成员函数实现
2.1 无参构造函数 无参构造需要预留一个 '\0' 作为空字符的表示,初始化 _size 和 _capacity 为 0。
string () : _str(new char [1 ]{'\0' }), _size(0 ), _capacity(0 ) {}
这里需要注意,虽然 _size 是 0,但我们依然分配了一个字节的空间存放终止符,确保后续操作不会越界。
2.2 带参构造函数 带参构造时,我们先计算字符串长度,再分配内存。为了复用 strlen 结果,这里不推荐使用初始化列表。
string (const char * str) {
if (str == nullptr ) {
str = "" ;
}
_size = strlen (str);
_capacity = _size;
_str = new char [_capacity + 1 ];
strcpy (_str, str);
}
注意:_capacity 不包含 '\0',所以实际开辟空间需加 1。
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,然后交换 this 和 tmp 的成员变量。函数结束时,tmp 析构,自动清理掉旧的内存,无需手动 delete。
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 ;
}
关键点在于先检查 self-assignment,再释放旧资源,最后深拷贝新资源。
通过按值传递参数,编译器自动调用拷贝构造生成临时对象,再进行交换。
string& operator =(string tmp) {
swap (tmp);
return *this ;
}
这里利用了参数 tmp 的生命周期。当函数返回时,tmp 被销毁,它持有的旧内存随之释放,既安全又简洁。
2.5 析构函数 ~string () {
delete [] _str;
_size = 0 ;
_capacity = 0 ;
}
三、迭代器实现 迭代器本质上是字符指针。begin 返回首字符地址,end 返回最后一个字符后一个位置(即 '\0' 的地址)。
iterator begin () { return _str; }
iterator end () { return _str + _size; }
const_iterator begin () const { return _str; }
const_iterator end () const { return _str + _size; }
四、容量和大小相关函数
4.1 size 与 capacity size_t size () const { return _size; }
size_t capacity () const { return _capacity; }
bool empty () const { return _size == 0 ; }
4.2 reserve reserve 用于预分配内存。只有当请求的大小超过当前 capacity 时才扩容,且只扩不缩。
void reserve (size_t n) {
if (n > _capacity) {
char * tmp = new char [n + 1 ];
strcpy (tmp, _str);
delete [] _str;
_str = tmp;
_capacity = n;
}
}
五、字符串修改函数
5.1 push_back 尾插字符前需检查容量,不足则扩容。插入后记得补上 '\0'。
void push_back (char c) {
if (_size == _capacity) {
reserve (_capacity == 0 ? 4 : _capacity * 2 );
}
_str[_size++] = c;
_str[_size] = '\0' ;
}
5.2 append 尾插字符串时,同样先判断是否需要扩容,规则类似 push_back。
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;
}
5.3 operator+= 直接复用 push_back 和 append 函数,保持代码一致性。
string& operator +=(char c) {
push_back (c);
return *this ;
}
string& operator +=(const char * s) {
append (s);
return *this ;
}
5.4 insert 插入操作涉及内存移动,需注意下标边界,防止 size_t 下溢。
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,避免 pos=0 时 end 减到 0 后继续减导致整型下溢。
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;
}
5.5 erase 删除从 pos 开始的 len 个字符。若删除剩余所有字符,只需置位 '\0' 并更新 size。
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 = 0 ) const {
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 = 0 ) const {
assert (pos < _size);
const char * ptr = strstr (_str, s);
if (ptr != nullptr ) {
return ptr - _str;
}
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++ 容器背后的内存管理与资源控制逻辑。
相关免费在线工具 加密/解密文本 使用加密算法(如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