手撕 C++ string:从零实现一个简易字符串类

一、为什么要模拟实现 string?

在 C++ 中,std::string 是 STL 中最常用的容器之一。

平时我们写:

复制代码
std::string s = "hello";

实际上背后进行了很多工作:

  • 动态内存申请

  • 字符串拷贝

  • 深拷贝管理

  • 构造和析构

  • 运算符重载

  • 迭代器支持

  • 越界检查

如果不了解这些底层机制,就很难真正理解 STL。

因此,通过手写一个简易版 string,可以深入理解:

  • 类的资源管理

  • 动态内存

  • 深浅拷贝

  • RAII思想

  • 运算符重载

  • Rule of Three / Rule of Five


二、string 的底层结构

一个简单的 string,本质上就是:

一块动态开辟的字符数组 + 当前字符串长度

例如:

复制代码
string s = "hello";

内存:

复制代码
+---+---+---+---+---+----+
| h | e | l | l | o | \0 |
+---+---+---+---+---+----+

len = 5
capacity = 5

成员变量:

复制代码
char* _str;       // 存储字符串
size_t _size;     // 字符数量
size_t _capacity; // 容量

三、基本框架设计

首先创建一个自己的字符串类:

复制代码
#include <iostream>
#include <cstring>

using namespace std;


class String
{
private:

    char* _str;

    size_t _size;

    size_t _capacity;


};

四、构造函数

1. 默认构造

创建一个空字符串:

复制代码
String()
{
    _str = new char[1];

    _str[0] = '\0';

    _size = 0;

    _capacity = 0;
}

效果:

复制代码
_str

+----+
| \0 |
+----+

size = 0

2. 字符串构造

例如:

复制代码
String s("hello");

实现:

复制代码
String(const char* str)
{
    _size = strlen(str);

    _capacity = _size;


    _str = new char[_capacity + 1];


    strcpy(_str, str);
}

为什么:

复制代码
_capacity + 1

因为 C 风格字符串必须包含:

复制代码
'\0'

结束标志。


五、析构函数

因为我们自己申请了空间:

复制代码
new char[]

所以必须释放:

复制代码
~String()
{
    delete[] _str;

    _str = nullptr;

    _size = 0;

    _capacity = 0;
}

否则会造成:

内存泄漏


六、深拷贝问题

浅拷贝

例如:

复制代码
String s1("hello");

String s2 = s1;

如果直接复制:

复制代码
s1
 |
 v
+---------+
| hello   |
+---------+

s2
 |
 v
+---------+
| hello   |
+---------+

两个对象指向同一块空间。

析构时:

复制代码
delete[]

会释放两次。

导致:

double free


七、拷贝构造函数

需要重新申请空间:

复制代码
String(const String& s)
{

    _size = s._size;

    _capacity = s._capacity;


    _str = new char[_capacity + 1];


    strcpy(_str, s._str);
}

这叫:

深拷贝


八、赋值运算符重载

例如:

复制代码
s2 = s1;

需要:

复制代码
String& operator=(const String& s)
{

    if(this != &s)
    {

        char* tmp = new char[s._capacity + 1];


        strcpy(tmp, s._str);


        delete[] _str;


        _str = tmp;


        _size = s._size;

        _capacity = s._capacity;
    }


    return *this;
}

为什么:

复制代码
return *this;

支持连续赋值:

复制代码
a = b = c;

九、获取字符串长度

实现:

复制代码
size_t size() const
{
    return _size;
}

使用:

复制代码
String s("hello");

cout << s.size();

输出:

复制代码
5

十、operator\[\] 下标访问

我们希望:

复制代码
s[0]

访问字符。

实现:

复制代码
char& operator[](size_t pos)
{
    return _str[pos];
}

使用:

复制代码
String s("hello");

s[0] = 'H';

cout << s;

输出:

复制代码
Hello

十一、输出运算符重载

让:

复制代码
cout << s;

成立。

实现:

复制代码
ostream& operator<<(ostream& out,const String& s)
{
    out << s._str;

    return out;
}

十二、扩容机制

string 最大的问题:

空间不够怎么办?

例如:

复制代码
String s("hello");

s += " world";

容量不足。

需要扩容:

复制代码
void reserve(size_t capacity)
{
    if(capacity > _capacity)
    {

        char* tmp = new char[capacity + 1];


        strcpy(tmp,_str);


        delete[] _str;


        _str = tmp;


        _capacity = capacity;
    }
}

十三、追加字符串 operator+=

实现:

复制代码
String& operator+=(const char* str)
{

    size_t len = strlen(str);


    if(_size + len > _capacity)
    {
        reserve(_size + len);
    }


    strcpy(_str + _size,str);


    _size += len;


    return *this;
}

使用:

复制代码
String s("hello");

s += " world";

cout << s;

输出:

复制代码
hello world

十四、完整简易版 String

复制代码
class String
{

private:

    char* _str;

    size_t _size;

    size_t _capacity;


public:


    String()
    {
        _str = new char[1];

        _str[0]='\0';

        _size=0;

        _capacity=0;
    }



    String(const char* str)
    {
        _size=strlen(str);

        _capacity=_size;

        _str=new char[_capacity+1];

        strcpy(_str,str);
    }



    ~String()
    {
        delete[] _str;
    }



    String(const String& s)
    {

        _size=s._size;

        _capacity=s._capacity;


        _str=new char[_capacity+1];


        strcpy(_str,s._str);
    }



    String& operator=(const String& s)
    {

        if(this!=&s)
        {

            char* tmp=new char[s._capacity+1];

            strcpy(tmp,s._str);


            delete[] _str;


            _str=tmp;

            _size=s._size;

            _capacity=s._capacity;
        }


        return *this;
    }



    size_t size()const
    {
        return _size;
    }



    char& operator[](size_t pos)
    {
        return _str[pos];
    }


};

十五、模拟实现 string 学到了什么?

通过实现 string,我们掌握了:

1. 动态内存管理

复制代码
new
delete

2. 深浅拷贝

理解:

  • 浅拷贝的问题

  • 深拷贝解决方案


3. RAII思想

对象生命周期管理资源:

构造申请资源,析构释放资源。


4. 运算符重载

包括:

复制代码
=
[]
<<
+=

5. STL 容器设计思想

真正的 STL 容器,本质也是:

复制代码
数据管理
+
资源管理
+
接口封装

十六、总结

手写 string 虽然代码量不大,但是涵盖了 C++ 面向对象编程中最核心的知识:

  • 类和对象

  • 构造函数

  • 析构函数

  • 拷贝构造

  • 运算符重载

  • 动态内存

  • 深浅拷贝

  • RAII

理解了 string 的实现,再学习:

  • vector

  • list

  • map

  • unordered_map

会更加容易。

因为 STL 容器的核心思想都是:

管理资源,并提供安全、高效、易用的接口。

相关推荐
mqiqe16 分钟前
线程调度与 Schedulers:Project Reactor 并发模型的核心引擎
java·开发语言·网络
专注仿真1 小时前
问答大模型技术方案算法实现-熵权法融合算法 + 交叉编码器重排算法
人工智能·python·算法·语言模型·问答大模型关键算法·熵权融合算法·交叉编码器重排算法
xixiaoyunya1 小时前
JavaScript 异步编程全解析:从回调地狱到 async/await 的演进之路
开发语言·javascript·ecmascript
精英的英2 小时前
记一次 Qt5 Language Server 开发
开发语言·vscode·qt
烧酒同学2 小时前
【C++】记录size of std::vector的巧妙坑
开发语言·c++·图形渲染
周周哈哈哈2 小时前
线程创建、执行、退出、回收
java·开发语言
gb42152872 小时前
python中Web应用服务器
开发语言·前端·python
地平线开发者2 小时前
量化为什么会有损失:从量化误差到精度下降
算法
手写码匠3 小时前
华为云Flexus+DeepSeek征文|Dify 多 Agent 会话管理与上下文工程实战:让智能体“记住该记住的,忘掉该忘掉的“
人工智能·深度学习·算法·aigc
2401_862880823 小时前
数据结构 --- 栈
c语言·数据结构·算法