跳到主要内容C++ string 类模拟实现:从浅拷贝到深拷贝的深度解析 | 极客日志C++算法
C++ string 类模拟实现:从浅拷贝到深拷贝的深度解析
综述由AI生成C++ string 类模拟实现的核心在于处理动态内存管理。文章详细剖析了浅拷贝引发的资源竞争风险,并通过深拷贝策略解决析构时的重复释放问题。内容覆盖构造函数、赋值运算符及析构函数的完整逻辑,同时解析 reserve、push_back、insert 等关键成员函数的实现细节。结合传统与现代写法对比,帮助开发者掌握 C++ 内存分配机制与 STL 底层原理。
人间过客2 浏览 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