手撕 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 容器的核心思想都是:

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

相关推荐
秋田君1 小时前
QT_QT布局常用类QSplitter窗口分割类与QDockWidget窗口停靠类
开发语言·数据库·qt
老王生涯1 小时前
rust开发环境配置-Windows & GNU
开发语言·windows·rust
手写码匠1 小时前
华为云Flexus+DeepSeek征文|DeepSeek+RAG知识库实战:基于Flexus X实例与Dify构建企业级智能问答系统
人工智能·深度学习·算法·aigc
nLif1 小时前
进程管道通讯-伪终端方式
c++·windows
Yeauty1 小时前
自建 HLS 第一问:fMP4 还是 TS?用 Rust 在进程内把两种都跑出来
开发语言·后端·rust
颜x小2 小时前
[C#]泛型类与泛型方法
开发语言·c++·c#
benben0442 小时前
大模型之基于PEFT的SFT微调实战篇
开发语言·python
Lhan.zzZ2 小时前
深入理解 windeployqt:混合 C++/Qt 项目的打包指南
开发语言·c++·qt
zander2582 小时前
LeetCode 46. 全排列
算法·leetcode·深度优先