跳到主要内容
C++ string 类模拟实现:从浅拷贝到深拷贝的深度解析 | 极客日志
C++ 算法
C++ string 类模拟实现:从浅拷贝到深拷贝的深度解析 C++ string 类模拟实现的核心在于处理动态内存管理。文章详细剖析了浅拷贝引发的资源竞争风险,并通过深拷贝策略解决析构时的重复释放问题。内容覆盖构造函数、赋值运算符及析构函数的完整逻辑,同时解析 reserve、push_back、insert 等关键成员函数的实现细节。结合传统与现代写法对比,帮助开发者掌握 C++ 内存分配机制与 STL 底层原理。
人间过客 发布于 2026/3/15 更新于 2026/6/15 22 浏览C++ string 类模拟实现
面试中的经典问题
在 C++ 面试中,面试官常要求手写 string 类的模拟实现。这不仅是考察语法,更是为了验证你对内存管理、资源获取即初始化(RAII)以及三法则(Rule of Three)的理解。
我们先看一个基础版本,看看它存在什么问题:
String {
public :
String (const char * str = "" ) {
if (nullptr == str) {
assert (false );
return ;
}
_str = new char [strlen (str) + 1 ];
strcpy (_str, str);
}
~String () {
if (_str) {
delete [] _str;
_str = nullptr ;
}
}
private :
char * _str;
};
测试代码:
void TestString () {
String s1 ("hello bit!!!" ) ;
String s2 (s1) ;
}
问题分析:
上述 String 类没有显式定义拷贝构造函数与赋值运算符重载,编译器会合成默认的。当用 s1 构造 s2 时,调用的是默认拷贝构造。最终导致的问题是,s1、s2 共用同一块内存空间。在释放时,同一块空间被释放多次,引起程序崩溃。这种拷贝方式称为浅拷贝 。
浅拷贝的隐患
浅拷贝也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源。当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进行操作时,就会发生访问违规。
就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一不想分享就你争我夺,玩具损坏。
深拷贝解决方案 如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供,即每个对象都有一份独立的资源,不要和其他对象共享。
核心功能模拟实现
构造函数的现代写法 传统写法是直接分配内存并拷贝数据,而现代写法利用临时对象和交换语义来简化逻辑,减少重复代码。
string (const string& s) {
string tmp (s._str) ;
swap (tmp);
}
赋值运算符重载 赋值操作需要处理自赋值的情况,同时尽量降低开销。这里采用传值参数配合 swap 的方式,既安全又高效。
string& operator =(string tmp) {
swap (tmp);
return *this ;
}
注意:拷贝构造必须用引用,不然会无穷递归,但赋值不用,因为我们需要构造一个临时副本。
析构函数 清理资源是析构函数的职责,记得将指针置空,防止悬垂指针。
~string () {
delete [] _str;
_str = nullptr ;
_capacity = _size = 0 ;
}
常用成员函数实现
clear void clear () {
_str[0 ] = '\0' ;
_size = 0 ;
}
reserve 预留空间,避免频繁扩容带来的性能损耗。注意永远多开一个空间存放结束符 。
void string::reserve (size_t n) {
if (n > _capacity) {
char * tmp = new char [n + 1 ];
strcpy (tmp, _str);
delete [] _str;
_str = tmp;
_capacity = n;
}
}
push_back void string::push_back (char ch) {
if (_size == _capacity)
reserve (_capacity == 0 ? 4 : _capacity * 2 );
_str[_size] = ch;
++_size;
_str[_size] = '\0' ;
}
append void string::append (const char * str) {
size_t len = strlen (str);
if (_size + len > _capacity)
reserve (_size + len > _capacity * 2 ? _size + len : _capacity * 2 );
strcpy (_str + _size, str);
_size += len;
}
insert 插入字符或字符串。涉及大量内存移动,需要注意边界条件。
void string::insert (size_t pos, char ch) {
assert (pos <= _size);
if (_size == _capacity)
reserve (_capacity == 0 ? 4 : _capacity * 2 );
size_t end = _size + 1 ;
while (end > pos) {
_str[end] = _str[end - 1 ];
--end;
}
_str[pos] = ch;
++_size;
}
void string::insert (size_t pos, const char * str) {
assert (pos <= _size);
size_t len = strlen (str);
if (_size + len > _capacity)
reserve (_size + len > _capacity * 2 ? _size + len : _capacity * 2 );
size_t end = _size + len;
if (len == 0 ) return ;
while (end > pos + len - 1 ) {
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0 ; i < len; ++i)
_str[pos + i] = str[i];
_size += len;
}
erase void string::erase (size_t pos, size_t len) {
assert (pos < _size);
if (len > _size - pos) {
_str[pos] = '\0' ;
_size = pos;
} else {
for (size_t i = pos + len; i <= _size; i++)
_str[i - len] = _str[i];
_size -= len;
}
}
find & substr size_t string::find (char ch, size_t pos) {
for (size_t i = pos; i < _size; i++) {
if (_str[i] == ch)
return i;
}
return npos;
}
size_t string::find (const char * str, size_t pos) {
assert (pos < _size);
const char * ptr = strstr (_str + pos, str);
if (ptr == nullptr )
return npos;
else
return ptr - _str;
}
string string::substr (size_t pos, size_t len) {
assert (pos < _size);
if (len > _size - pos)
len = _size - pos;
string sub;
sub.reserve (len);
for (size_t i = 0 ; i < len; ++i)
sub += _str[pos + i];
return sub;
}
比较运算符 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 || s1 == s2;
}
bool operator >=(const string& s1, const string& s2) {
return !(s1 < s2);
}
bool operator !=(const string& s1, const string& s2) {
return !(s1 == s2);
}
流插入与提取 ostream& operator <<(ostream& out, const string& s) {
for (auto ch : s)
out << ch;
return out;
}
istream& operator >>(istream& in, string& s) {
s.clear ();
const int N = 256 ;
char buff[N];
int i = 0 ;
char ch;
ch = in.get ();
while (ch != ' ' && ch != '\n' ) {
buff[i++] = ch;
if (i == N - 1 ) {
buff[i] = '\0' ;
s += buff;
i = 0 ;
}
s += ch;
ch = in.get ();
if (i > 0 ) {
buff[i] = '\0' ;
s += buff;
}
}
return in;
}
完整代码结构
string.h #pragma once
#include <iostream>
#include <assert.h>
#include <string.h>
using namespace std;
namespace bit {
class string {
public :
typedef char * iterator;
iterator begin () { return _str; }
iterator end () { return _str + _size; }
typedef const char * const_iterator;
const_iterator begin () const { return _str; }
const_iterator end () const { return _str + _size; }
size_t capacity () const { return _capacity; }
void reserve (size_t n) ;
void push_back (char ch) ;
void append (const char * str) ;
string& operator +=(char ch);
string& operator +=(const char * str);
void insert (size_t pos, char ch) ;
void insert (size_t pos, const char * str) ;
void erase (size_t pos, size_t len = npos) ;
size_t find (char ch, size_t pos = 0 ) ;
size_t find (const char * str, size_t pos = 0 ) ;
string substr (size_t pos = 0 , size_t len = npos) ;
string (const char * str = "" ) {
_size = strlen (str);
_capacity = _size;
_str = new char [_capacity + 1 ];
strcpy (_str, str);
}
string (const string& s) {
string tmp (s._str) ;
swap (tmp);
}
string& operator =(string tmp) {
swap (tmp);
return *this ;
}
void swap (string& s) {
std::swap (_str, s._str);
std::swap (_size, s._size);
std::swap (_capacity, s._capacity);
}
~string () {
delete [] _str;
_str = nullptr ;
_capacity = _size = 0 ;
}
void clear () {
_str[0 ] = '\0' ;
_size = 0 ;
}
const char * c_str () const { return _str; }
size_t size () const { return _size; }
char & operator [](size_t pos) {
assert (pos < _size);
return _str[pos];
}
const char & operator [](size_t pos) const {
assert (pos < _size);
return _str[pos];
}
private :
char * _str = nullptr ;
size_t _size = 0 ;
size_t _capacity = 0 ;
static const size_t npos = -1 ;
};
}
string.cpp #include "string.h"
namespace bit {
void string::reserve (size_t n) {
if (n > _capacity) {
char * tmp = new char [n + 1 ];
strcpy (tmp, _str);
delete [] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back (char ch) {
if (_size == _capacity)
reserve (_capacity == 0 ? 4 : _capacity * 2 );
_str[_size] = ch;
++_size;
_str[_size] = '\0' ;
}
string& string::operator +=(char ch) {
push_back (ch);
return *this ;
}
void string::append (const char * str) {
size_t len = strlen (str);
if (_size + len > _capacity)
reserve (_size + len > _capacity * 2 ? _size + len : _capacity * 2 );
strcpy (_str + _size, str);
_size += len;
}
string& string::operator +=(const char * str) {
append (str);
return *this ;
}
void string::insert (size_t pos, char ch) {
assert (pos <= _size);
if (_size == _capacity)
reserve (_capacity == 0 ? 4 : _capacity * 2 );
size_t end = _size + 1 ;
while (end > pos) {
_str[end] = _str[end - 1 ];
--end;
}
_str[pos] = ch;
++_size;
}
void string::insert (size_t pos, const char * str) {
assert (pos <= _size);
size_t len = strlen (str);
if (_size + len > _capacity)
reserve (_size + len > _capacity * 2 ? _size + len : _capacity * 2 );
size_t end = _size + len;
if (len == 0 ) return ;
while (end > pos + len - 1 ) {
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0 ; i < len; ++i)
_str[pos + i] = str[i];
_size += len;
}
void string::erase (size_t pos, size_t len) {
assert (pos < _size);
if (len > _size - pos) {
_str[pos] = '\0' ;
_size = pos;
} else {
for (size_t i = pos + len; i <= _size; i++)
_str[i - len] = _str[i];
_size -= len;
}
}
size_t string::find (char ch, size_t pos) {
for (size_t i = pos; i < _size; i++) {
if (_str[i] == ch)
return i;
}
return npos;
}
size_t string::find (const char * str, size_t pos) {
assert (pos < _size);
const char * ptr = strstr (_str + pos, str);
if (ptr == nullptr )
return npos;
else
return ptr - _str;
}
string string::substr (size_t pos, size_t len) {
assert (pos < _size);
if (len > _size - pos)
len = _size - pos;
string sub;
sub.reserve (len);
for (size_t i = 0 ; i < len; ++i)
sub += _str[pos + i];
return sub;
}
void test_string1 () {
string s1;
string s2 ("hello world" ) ;
cout << s1. c_str () << endl;
cout << s2. c_str () << endl;
for (size_t i = 0 ; i < s2. size (); ++i)
s2[i] += 2 ;
cout << s2. c_str () << endl;
for (auto e : s2)
cout << e << " " ;
cout << endl;
string::iterator it = s2. begin ();
while (it != s2. end ()) {
cout << *it << " " ;
++it;
}
}
void test_string2 () {
string s1 ("hello wwww" ) ;
s1 += 'x' ;
cout << s1. c_str () << endl;
s1. insert (3 , "sss" );
cout << s1. c_str () << endl;
s1. erase (3 , 2 );
cout << s1. c_str () << endl;
}
void test_string3 () {
string s ("666.cpp.zip" ) ;
size_t pos = s.find ('.' );
string suffix = s.substr (pos);
cout << suffix.c_str () << endl;
string copy (s) ;
cout << copy.c_str () << endl;
}
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 || s1 == s2;
}
bool operator >=(const string& s1, const string& s2) {
return !(s1 < s2);
}
bool operator !=(const string& s1, const string& s2) {
return !(s1 == s2);
}
ostream& operator <<(ostream& out, const string& s) {
for (auto ch : s)
out << ch;
return out;
}
istream& operator >>(istream& in, string& s) {
s.clear ();
const int N = 256 ;
char buff[N];
int i = 0 ;
char ch;
ch = in.get ();
while (ch != ' ' && ch != '\n' ) {
buff[i++] = ch;
if (i == N - 1 ) {
buff[i] = '\0' ;
s += buff;
i = 0 ;
}
s += ch;
ch = in.get ();
if (i > 0 ) {
buff[i] = '\0' ;
s += buff;
}
}
return in;
}
void test_string4 () {
string s1 ("hello" ) ;
cin >> s1;
cout << s1 << endl;
cout << "he" ;
}
int main () {
bit::test_string4 ();
return 0 ;
}
}
相关免费在线工具 加密/解密文本 使用加密算法(如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