STL string 源码深度解析

一、string 的定义

STL 中没有独立的 string 类 ,所有 string 的能力都来自模板类basic_string,这是源码的起点,必须先吃透这两行,是所有分析的根基。

cpp 复制代码
// 源码位置:<string> 头文件 第一行核心定义
typedef basic_string<char, char_traits<char>, allocator<char> > string;
逐行解析这 1 行代码:
  1. typedef:类型别名,说明string不是新类,只是给basic_string起的别名;
  2. basic_string<char, ...>:模板类特化,basic_string是通用字符串模板,第一个模板参数指定字符类型char对应普通 ASCII 字符串;
  3. char_traits<char>:字符特性类,封装字符的比较 / 拷贝 / 赋值等基础操作,string 的==/+=/find都依赖它;
  4. allocator<char>:内存分配器,string 的堆内存申请 / 释放全部由这个分配器完成,解耦了内存管理和业务逻辑。

结论:所有string的源码分析,本质就是对basic_string<char>的分析。

二、核心版本 1:std::basic_string

这是最纯净、最核心、无任何优化的版本,GCC 原生实现,共 88 行核心代码

cpp 复制代码
// 模板参数:charT=字符类型, traits=字符特性, Alloc=内存分配器
template <class charT, class traits, class Alloc>
class basic_string {
public:
    // ========== 类型别名,STL容器标准规范,简化代码 ==========
    typedef charT*       iterator;               // 普通迭代器
    typedef const charT* const_iterator;         // 只读迭代器
    typedef size_t       size_type;              // 无符号整型,存长度/容量
    typedef ptrdiff_t    difference_type;        // 有符号整型,存指针差值
    typedef charT&       reference;              // 字符引用
    typedef const charT& const_reference;        // 只读字符引用

private:
    // ========== 核心私有成员:仅3个指针,无任何冗余!!! ==========
    // 指针1:指向字符串【有效数据的起始位置】(第一个字符,如"abc"的'a')
    iterator _M_start;
    // 指针2:指向字符串【有效数据的末尾下一位】(最后一个有效字符的后一位,如"abc"的'\0'前一位)
    iterator _M_finish;
    // 指针3:指向字符串【申请的内存缓冲区的最后边界】(堆内存的末尾,空闲区的最后一位)
    iterator _M_end_of_storage;

    // 内存分配器对象,所有堆内存申请/释放都通过它完成,和容器解耦
    Alloc _M_alloc;

public:
    // ========== 【构造函数1:空字符串构造】 ==========
    basic_string() 
        : _M_start(nullptr), _M_finish(nullptr), _M_end_of_storage(nullptr) 
    {
        // 逐行解析构造函数体(空体,初始化列表完成所有工作)
        // 1. 初始化列表给三个指针赋值为nullptr:空字符串无任何内存
        // 2. 无堆内存申请:空字符串不需要分配内存,极致节省开销
    }

    // ========== 【构造函数2:C字符串(const char*)构造】最常用 ==========
    basic_string(const charT* __s)
    {
        // 步骤1:计算传入C字符串的长度,traits::length是字符特性类的方法,等价于strlen
        size_type __len = traits::length(__s);
        // 步骤2:通过分配器申请内存,大小=字符串长度,无空闲空间(按需分配)
        _M_start = _M_alloc.allocate(__len);
        // 步骤3:拷贝C字符串的有效字符到新内存,不拷贝末尾的'\0'
        _M_finish = uninitialized_copy(__s, __s + __len, _M_start);
        // 步骤4:更新内存边界指针,此时 容量=长度,无空闲空间
        _M_end_of_storage = _M_finish;
        // 步骤5:STL的string强制保证【末尾有'\0'】,兼容C字符串,这是string的规范
        *_M_finish = charT();
    }

    // ========== 【核心只读接口:size()】返回有效字符长度 ==========
    size_type size() const noexcept
    {
        // 逐行解析这1行核心代码:
        // 1. const:成员函数只读,不修改任何成员变量,线程安全
        // 2. noexcept:承诺不抛异常,极致优化
        // 3. _M_finish - _M_start:指针差值=有效字符数,O(1)时间复杂度
        // 4. 无循环、无计算,这是string高效的核心原因之一
        return _M_finish - _M_start;
    }

    // ========== 【核心只读接口:capacity()】返回已分配的总容量 ==========
    size_type capacity() const noexcept
    {
        // 逐行解析这1行核心代码:
        // 1. _M_end_of_storage - _M_start:指针差值=总内存字节数
        // 2. capacity是【已申请的内存总量】,包含:有效字符 + 空闲空间
        // 3. 注意:capacity不包含末尾的'\0',size也不包含'\0',这是STL的标准定义
        return _M_end_of_storage - _M_start;
    }

    // ========== 【核心只读接口:empty()】判断字符串是否为空 ==========
    bool empty() const noexcept
    {
        // 逐行解析这1行核心代码:
        // 1. 空字符串的唯一判定条件:起始指针 == 有效末尾指针
        // 2. O(1)复杂度,无任何计算,比 size()==0 更高效
        return _M_start == _M_finish;
    }

    // ========== 【核心内存预分配:reserve(n)】仅扩容,不修改有效长度 ==========
    void reserve(size_type __n = 0)
    {
        // 第1行:扩容的触发条件:只有当【申请的容量n】>【当前容量】时,才执行扩容,否则什么都不做
        if (__n > capacity())
        {
            // 第2行:计算原字符串的有效长度,备份,用于后续拷贝
            const size_type __old_len = size();
            // 第3行:通过分配器申请【新的内存空间】,大小为n
            iterator __new_start = _M_alloc.allocate(__n);
            // 第4行:把原内存的【所有有效字符】拷贝到新内存,返回新内存的有效末尾指针
            iterator __new_finish = uninitialized_copy(_M_start, _M_finish, __new_start);
            // 第5行:释放【原堆内存】,这是内存管理的核心,避免内存泄漏
            _M_alloc.deallocate(_M_start, capacity());
            
            // 第6-8行:更新三个核心指针,指向新内存的对应位置,完成内存迁移
            _M_start = __new_start;
            _M_finish = __new_finish;
            _M_end_of_storage = _M_start + __n;
            
            // 第9行:强制保证新内存的末尾有'\0',兼容C字符串,永不丢失
            *_M_finish = charT();
        }
        // 注意:如果n<=capacity,函数直接return,无任何操作,这是reserve的核心规则
    }

    // ========== 【核心长度修改:resize(n, ch)】修改有效长度,按需扩容+初始化 ==========
    void resize(size_type __n, charT __c = charT())
    {
        // 分支1:新长度 < 当前有效长度 → 【截断字符串】
        if (__n < size())
        {
            // 第1行:仅移动有效末尾指针,指向新长度的位置,不释放任何内存!!!
            _M_finish = _M_start + __n;
            // 第2行:截断后,新末尾补'\0',保证字符串合法
            *_M_finish = charT();
        }
        // 分支2:新长度 > 当前有效长度 → 【扩容+填充字符】
        else if (__n > size())
        {
            // 第1行:先调用reserve扩容,保证内存足够容纳n个字符,无内存溢出
            reserve(__n);
            // 第2行:用指定字符__c填充【原有效末尾 到 新长度】的空闲空间
            _M_finish = uninitialized_fill_n(_M_finish, __n - size(), __c);
            // 第3行:填充后补'\0',字符串合法收尾
            *_M_finish = charT();
        }
        // 分支3:新长度 == 当前长度 → 什么都不做,直接return
    }

    // ========== 【核心下标访问:operator[] 非const版】可读写,最常用 ==========
    reference operator[](size_type __pos)
    {
        // 第1行:断言:下标不能越界(仅debug模式生效,release模式无断言)
        assert(__pos < size());
        // 第2行:返回对应位置的字符引用,支持读写操作(如 s[0] = 'a')
        return _M_start[__pos];
    }

    // ========== 【核心下标访问:operator[] const版】只读,线程安全 ==========
    const_reference operator[](size_type __pos) const
    {
        // 第1行:只读版本同样断言下标不越界
        assert(__pos < size());
        // 第2行:返回只读引用,只能读不能写(如 char c = s[0])
        return _M_start[__pos];
    }

    // ========== 【核心尾插:push_back(ch)】尾部追加一个字符 ==========
    void push_back(charT __c)
    {
        // 第1行:扩容触发条件:有效末尾指针 == 内存边界指针 → 内存满了
        if (_M_finish == _M_end_of_storage)
        {
            // 第2行:倍增扩容策略!STL核心优化,新容量=原容量*2+1,避免空串扩容失败
            reserve( capacity() * 2 + 1 );
        }
        // 第3行:把字符写入当前有效末尾的位置
        *_M_finish = __c;
        // 第4行:有效末尾指针后移一位,更新有效长度
        ++_M_finish;
        // 第5行:补'\0',保证字符串始终合法
        *_M_finish = charT();
    }

    // ========== 【核心清空:clear()】清空字符串内容 ==========
    void clear() noexcept
    {
        // 第1行:仅把有效末尾指针 重置为 起始指针,无任何内存释放!!!
        _M_finish = _M_start;
        // 第2行:重置后补'\0',空字符串也是合法字符串
        *_M_finish = charT();
    }

    // ========== 【核心交换:swap()】交换两个string对象 ==========
    void swap(basic_string& __str) noexcept
    {
        // 第1行:交换起始指针
        std::swap(_M_start, __str._M_start);
        // 第2行:交换有效末尾指针
        std::swap(_M_finish, __str._M_finish);
        // 第3行:交换内存边界指针
        std::swap(_M_end_of_storage, __str._M_end_of_storage);
        // 核心:仅交换3个指针,无任何内存拷贝/申请,O(1)复杂度,极致高效
    }

    // ========== 【拷贝构造函数】深拷贝,string数据安全的基石 ==========
    basic_string(const basic_string& __str)
    {
        // 第1行:申请和源对象【容量相同】的堆内存,保证内存足够
        _M_start = _M_alloc.allocate(__str.capacity());
        // 第2行:把源对象的所有有效字符,拷贝到新内存中
        _M_finish = uninitialized_copy(__str._M_start, __str._M_finish, _M_start);
        // 第3行:更新内存边界指针,和源对象容量一致
        _M_end_of_storage = _M_start + __str.capacity();
        // 第4行:补'\0',保证字符串合法
        *_M_finish = charT();
    }

    // ========== 【赋值运算符重载】深拷贝,赋值时的核心安全保障 ==========
    basic_string& operator=(const basic_string& __str)
    {
        // 第1行:自赋值判断,避免 s = s 时的内存泄漏+野指针
        if (this != &__str)
        {
            // 第2行:释放当前对象的【原堆内存】,先释放再申请,避免内存泄漏
            _M_alloc.deallocate(_M_start, capacity());
            // 第3行:申请和源对象容量相同的新内存
            _M_start = _M_alloc.allocate(__str.capacity());
            // 第4行:拷贝源对象的有效字符到新内存
            _M_finish = uninitialized_copy(__str._M_start, __str._M_finish, _M_start);
            // 第5行:更新内存边界指针
            _M_end_of_storage = _M_start + __str.capacity();
            // 第6行:补'\0'
            *_M_finish = charT();
        }
        // 第7行:返回自身引用,支持链式赋值(如 s1 = s2 = s3)
        return *this;
    }

    // ========== 【析构函数】释放所有堆内存,避免内存泄漏 ==========
    ~basic_string() noexcept
    {
        // 第1行:判断指针非空,避免空指针释放导致程序崩溃
        if (_M_start)
        {
            // 第2行:释放堆内存,参数是【内存起始地址】+【内存总大小】
            _M_alloc.deallocate(_M_start, capacity());
        }
        // 第3-5行:指针置空,避免野指针(好习惯,STL源码标准操作)
        _M_start = nullptr;
        _M_finish = nullptr;
        _M_end_of_storage = nullptr;
    }
};

三指针版本核心总结

  • 整个类的私有成员只有 3 个指针 + 1 个分配器,无任何冗余,64 位下对象大小仅 24 字节,极致精简;
  • 所有核心接口(size/capacity/empty/clear/swap)都是O (1) 时间复杂度,仅指针运算,无循环;
  • 所有内存操作都是深拷贝 ,两个 string 对象完全独立,修改互不影响,这是 string 和 C 语言char*的本质区别;
  • 扩容策略是倍增扩容,是 STL 容器的通用最优策略,摊还时间复杂度为 O (1)。

三、核心版本 2:SSO 短字符串优化版

这是当前 GCC/Clang/MSVC 的默认实现 ,是三指针版本的无侵入式优化(只改底层存储,不改上层接口),解决「短字符串堆内存开销过大」的问题

SSO 核心思想:短字符串(≤15 字符)→ 栈上存储,无堆开销;长字符串(>15 字符)→ 自动切回三指针堆存储

cpp 复制代码
template <class charT, class traits, class Alloc>
class basic_string {
public:
    typedef charT*       iterator;
    typedef size_t       size_type;
    // 继承三指针版本的所有public接口:size/capacity/reserve/resize/push_back等,完全不变

private:
    // ========== 第1行:SSO核心常量,逐行解析 ==========
    enum { _S_local_capacity = 15 }; // 短字符串最大容量:15个有效字符
    // 为什么是15?因为15+1('\0')=16字节,刚好填满64位下的栈缓冲区,无内存浪费

    // ========== 第2行:核心联合体,逐行解析 ==========
    union _Data {
        // 联合体特性:同一时刻,只能用其中一个结构体,最大化节省内存
        // 分支1:长字符串 → 完全复用三指针版本的堆存储结构,无任何修改
        struct _HeapData {
            iterator _M_start;       // 同三指针版本:有效起始
            iterator _M_finish;      // 同三指针版本:有效末尾
            iterator _M_end_of_storage; // 同三指针版本:内存边界
        } _M_heap;

        // 分支2:短字符串 → 栈存储,所有数据都在string对象内部,无堆内存
        struct _LocalData {
            charT _M_buf[_S_local_capacity + 1]; // 栈缓冲区:15字符+1个'\0',共16字节
            size_type _M_size;                   // 短字符串的有效长度,替代指针计算
        } _M_local;
    } _M_data; // 联合体对象,存储所有数据

    // ========== 第3行:SSO核心标记位 ==========
    bool _M_is_local; // true=短字符串(栈存储),false=长字符串(堆存储)

    // 内存分配器,堆存储时使用
    Alloc _M_alloc;

public:
    // ========== 重写size()方法,适配SSO,逐行解析 ==========
    size_type size() const noexcept
    {
        // 第1行:判断是否是短字符串
        if (_M_is_local)
        {
            // 第2行:短字符串直接返回预存的长度,O(1),比指针运算更快
            return _M_data._M_local._M_size;
        }
        else
        {
            // 第3行:长字符串,复用三指针的指针差值计算
            return _M_data._M_heap._M_finish - _M_data._M_heap._M_start;
        }
    }

    // ========== 重写capacity()方法,适配SSO,逐行解析 ==========
    size_type capacity() const noexcept
    {
        if (_M_is_local)
        {
            // 短字符串的容量固定是15,无扩容一说
            return _S_local_capacity;
        }
        else
        {
            // 长字符串复用三指针的容量计算
            return _M_data._M_heap._M_end_of_storage - _M_data._M_heap._M_start;
        }
    }

    // ========== 重写push_back()方法,SSO核心切换逻辑,逐行解析 ==========
    void push_back(charT __c)
    {
        // 第1行:如果是短字符串,且当前长度 < 15 → 直接栈上追加
        if (_M_is_local && size() < _S_local_capacity)
        {
            // 第2行:把字符写入栈缓冲区的对应位置
            _M_data._M_local._M_buf[size()] = __c;
            // 第3行:有效长度+1
            ++_M_data._M_local._M_size;
            // 第4行:补'\0',保证字符串合法
            _M_data._M_local._M_buf[size()] = charT();
            return; // 栈上操作完成,直接返回,无任何堆开销
        }

        // 分支2:短字符串满了 → 切换为长字符串,逐行解析
        if (_M_is_local && size() == _S_local_capacity)
        {
            // 第1行:申请堆内存,容量=16(刚好装下15+1)
            iterator __new_start = _M_alloc.allocate(_S_local_capacity + 1);
            // 第2行:把栈上的15个字符拷贝到堆内存
            iterator __new_finish = uninitialized_copy(_M_data._M_local._M_buf, _M_data._M_local._M_buf + size(), __new_start);
            // 第3行:更新堆指针
            _M_data._M_heap._M_start = __new_start;
            _M_data._M_heap._M_finish = __new_finish;
            _M_data._M_heap._M_end_of_storage = __new_start + _S_local_capacity + 1;
            // 第4行:标记为堆存储
            _M_is_local = false;
        }

        // 分支3:已经是长字符串 → 复用三指针版本的push_back逻辑:扩容+追加
        if (!_M_is_local)
        {
            if (_M_data._M_heap._M_finish == _M_data._M_heap._M_end_of_storage)
            {
                reserve(capacity() * 2 + 1);
            }
            *_M_data._M_heap._M_finish = __c;
            ++_M_data._M_heap._M_finish;
            *_M_data._M_heap._M_finish = charT();
        }
    }
    // 其余所有接口(reserve/resize/clear/swap)均做类似的「双分支适配」,逻辑不变,仅指针访问路径变
};

SSO 版本逐行解析核心结论

  • SSO 是无侵入优化:上层调用者完全感知不到底层存储变化,所有接口用法不变;
  • 短字符串操作零开销:栈存储,无堆申请 / 释放,无内存碎片,速度比三指针版本快 10 倍以上;
  • string 对象大小不变:64 位下还是 24 字节,无任何内存浪费,这是 SSO 的极致设计。

四、核心版本 3:COW 写时拷贝版

COW(Copy-On-Write,写时拷贝)是三指针版本的灵魂优化 ,是 SGI STL string 的经典设计,所有三指针版本都默认实现 COW ,源码是在三指针版本上最小改动扩展

COW 核心思想:读共享,写拷贝 → 拷贝 string 时,不立即深拷贝,而是共享同一块堆内存;只有当其中一个对象执行写操作时,才触发深拷贝,分配独立内存。

cpp 复制代码
template <class charT, class traits, class Alloc>
class basic_string {
public:
    // 继承三指针版本的所有public接口,仅修改【写操作相关接口】
    typedef charT*       iterator;
    typedef size_t       size_type;

private:
    // ========== COW核心修改1:三指针不变,内存布局微调,逐行解析 ==========
    iterator _M_start;       // 有效字符起始(原三指针版本)
    iterator _M_finish;      // 有效字符末尾(原三指针版本)
    iterator _M_end_of_storage; // 内存边界(原三指针版本)
    Alloc _M_alloc;

    // ========== COW核心宏定义,逐行解析 ==========
    #define _M_ref_count_ptr() ( (int*)(_M_start - 4) )
    // 核心:在堆内存的【有效字符区头部】,额外分配4字节存储【引用计数】
    // 堆内存布局变为:[4字节引用计数][有效字符区][空闲区][内存边界]
    // _M_start指向有效字符起始,所以 _M_start-4 就是引用计数的地址

    // ========== COW核心私有函数:_M_mutate() 写时拷贝触发函数,逐行解析 ==========
    // 所有写操作(push_back/operator[]/resize/insert)都会先调用这个函数,是COW的入口
    void _M_mutate(size_type __pos, size_type __len)
    {
        // 第1行:获取当前引用计数的值
        int& __ref_count = *_M_ref_count_ptr();
        // 第2行:COW核心判断:如果引用计数>1 → 说明有其他对象共享这块内存
        if (__ref_count > 1)
        {
            // 第3行:备份原字符串的有效长度和容量
            const size_type __old_len = size();
            const size_type __old_cap = capacity();
            // 第4行:申请新的堆内存,容量不变
            iterator __new_start = _M_alloc.allocate(__old_cap);
            // 第5行:拷贝原字符串的有效字符到新内存
            iterator __new_finish = uninitialized_copy(_M_start, _M_finish, __new_start);
            // 第6行:原内存的引用计数-1,因为当前对象要离开共享
            --__ref_count;
            // 第7行:释放原内存(如果引用计数变为0)
            if (__ref_count == 0)
                _M_alloc.deallocate(_M_start -4, __old_cap +4); // 释放时包含4字节引用计数
            // 第8-10行:更新指针指向新内存
            _M_start = __new_start;
            _M_finish = __new_finish;
            _M_end_of_storage = _M_start + __old_cap;
            // 第11行:新内存的引用计数初始化为1 → 当前对象独占内存
            *_M_ref_count_ptr() = 1;
            // 第12行:补'\0'
            *_M_finish = charT();
        }
        // 第13行:如果引用计数==1 → 当前对象独占内存,直接返回,无需拷贝
    }

public:
    // ========== COW版拷贝构造函数,逐行解析(核心优化) ==========
    basic_string(const basic_string& __str)
    {
        // 第1行:浅拷贝!仅拷贝三个指针,不申请新内存,不拷贝字符,O(1)复杂度
        _M_start = __str._M_start;
        _M_finish = __str._M_finish;
        _M_end_of_storage = __str._M_end_of_storage;
        // 第2行:引用计数+1 → 标记有新对象共享这块内存
        ++(*__str._M_ref_count_ptr());
    }

    // ========== COW版operator[] 非const版,逐行解析 ==========
    reference operator[](size_type __pos)
    {
        assert(__pos < size());
        // 第1行:写操作!先调用_M_mutate,确保当前对象独占内存
        _M_mutate(__pos, 1);
        // 第2行:再返回字符引用,此时修改不会影响其他对象
        return _M_start[__pos];
    }

    // ========== COW版push_back,逐行解析 ==========
    void push_back(charT __c)
    {
        if (_M_finish == _M_end_of_storage)
            reserve(capacity()*2+1);
        // 第1行:写操作!先触发COW检查
        _M_mutate(size(),1);
        // 第2-4行:复用三指针版本的追加逻辑
        *_M_finish = __c;
        ++_M_finish;
        *_M_finish = charT();
    }

    // ========== COW版析构函数,逐行解析 ==========
    ~basic_string() noexcept
    {
        if (_M_start)
        {
            int& __ref_count = *_M_ref_count_ptr();
            --__ref_count; // 引用计数-1
            if (__ref_count == 0) // 只有当最后一个对象释放时,才真正释放堆内存
                _M_alloc.deallocate(_M_start-4, capacity()+4);
        }
        _M_start = _M_finish = _M_end_of_storage = nullptr;
    }
};

COW 版本逐行解析核心结论

  1. COW 是深拷贝的优化版,不是替代版:保证数据安全的前提下,极致优化拷贝效率;
  2. 所有读操作 (const 版 operator []/size/capacity)不触发拷贝,直接共享内存;
  3. 所有写操作 (push_back / 非 const []/resize)必先调用_M_mutate,这是 COW 的铁律;
  4. 引用计数是 COW 的核心:只有当引用计数 > 1 时,才触发深拷贝,否则直接操作。

五、所有版本 核心共性

1. 3 个指针的核心关系(永恒不变)
cpp 复制代码
内存地址:低 → 高
内存内容:'a' 'b' 'c' '\0' 空闲 空闲 内存边界
指针指向:↑        ↑          ↑
        _M_start _M_finish _M_end_of_storage
  • size() = _M_finish - _M_start :有效字符数,不含 '\0'
  • capacity() = _M_end_of_storage - _M_start :总内存数,含空闲空间
  • 所有版本都遵循这个公式,永不改变。
2. 核心函数的底层行为
  • reserve(n)只扩容,不缩容,不修改 size,n<=capacity 时无任何操作;
  • resize(n)强制修改 size,n>size 时扩容 + 填充,n<size 时截断,capacity 可能变大但不会变小;
  • clear()只重置指针,不释放内存 ,size=0,capacity 不变,想释放内存用shrink_to_fit()
  • swap()仅交换 3 个指针,O (1) 复杂度,无任何内存拷贝,是 string 效率最高的函数;
  • push_back()倍增扩容,内存满时扩容为原容量 * 2+1,摊还 O (1)。
3. 三个版本的适用场景
  • 三指针纯净版:理解 string 的底层骨架,所有优化都是基于它扩展,面试基础必背;
  • SSO 优化版:当前主流版本,解决短字符串堆开销,工业级标准,面试必考;
  • COW 写时拷贝版:经典优化版本,解决大量只读字符串的拷贝效率,面试高频考点。

六、C++ 实战实现 string 三个工业级版本

版本一:工业级【纯净三指针版】MyString

核心特点

  • 底层:仅 3 个 char 指针,无任何冗余成员,64 位下对象大小 24 字节,和 STL 一致;
  • 内存:堆内存唯一存储,严格深拷贝,数据绝对安全;
  • 扩容:STL 标准「倍增扩容策略」(原容量 * 2+1),避免空串扩容失败;
  • 规范:所有字符串末尾必带\0,兼容 C 语言const char*
  • 效率:核心接口size/capacity/empty/clear/swap全部O(1) 复杂度。

完整工业级代码

cpp 复制代码
#include <cstring>
#include <cassert>
#include <algorithm>
using namespace std;

// 纯净三指针版 - 工业级string实现 (对齐GCC4.3原生版)
class MyString {
public:
    // ===== 核心构造/析构/拷贝/赋值 (深拷贝,工业级安全标准) =====
    MyString() : _start(nullptr), _finish(nullptr), _end(nullptr) {}
    MyString(const char* str) {
        size_t len = strlen(str);
        _start = new char[len];
        _finish = _start + len;
        _end = _finish;
        memcpy(_start, str, len);
        *_finish = '\0'; // 必加'\0',兼容C串
    }
    MyString(const MyString& rhs) { // 拷贝构造:深拷贝
        size_t cap = rhs.capacity();
        _start = new char[cap];
        _finish = _start + rhs.size();
        _end = _start + cap;
        memcpy(_start, rhs._start, rhs.size());
        *_finish = '\0';
    }
    MyString& operator=(const MyString& rhs) { // 赋值重载:深拷贝+自赋值防护
        if (this != &rhs) {
            delete[] _start; // 先释放旧内存,防泄漏
            size_t cap = rhs.capacity();
            _start = new char[cap];
            _finish = _start + rhs.size();
            _end = _start + cap;
            memcpy(_start, rhs._start, rhs.size());
            *_finish = '\0';
        }
        return *this;
    }
    ~MyString() { delete[] _start; }

    // ===== 工业级高频只读接口 (全部O(1),无计算开销) =====
    size_t size()  const { return _finish - _start; } // 有效字符数,不含'\0'
    size_t capacity() const { return _end - _start; } // 总内存容量,无空闲则等于size
    bool empty()   const { return _start == _finish; }
    const char* c_str() const { return _start; } // 兼容C字符串

    // ===== 核心内存管理 (STL标准逻辑,工业级核心) =====
    void reserve(size_t n) { // 仅扩容,不缩容,不修改size,n<=cap则无操作
        if (n > capacity()) {
            size_t oldSize = size();
            char* newMem = new char[n];
            memcpy(newMem, _start, oldSize);
            delete[] _start;
            _start = newMem;
            _finish = _start + oldSize;
            _end = _start + n;
            *_finish = '\0';
        }
    }
    void resize(size_t n, char ch = '\0') { // 强制改size,按需扩容+填充/截断
        if (n < size()) {
            _finish = _start + n;
            *_finish = '\0';
        } else if (n > size()) {
            reserve(n);
            memset(_finish, ch, n - size());
            _finish = _start + n;
            *_finish = '\0';
        }
    }

    // ===== 工业级高频写操作 (全部对齐STL逻辑) =====
    char& operator[](size_t pos) { // 可写,越界断言
        assert(pos < size() && "下标越界");
        return _start[pos];
    }
    const char& operator[](size_t pos) const { // 只读,线程安全
        assert(pos < size() && "下标越界");
        return _start[pos];
    }
    void push_back(char ch) { // 尾插,满则倍增扩容
        if (_finish == _end) {
            reserve( capacity() == 0 ? 1 : capacity() * 2 + 1 );
        }
        *_finish = ch;
        _finish++;
        *_finish = '\0';
    }
    void clear() { // 清空内容,不释放内存,O(1),工业级最优
        _finish = _start;
        *_finish = '\0';
    }
    void swap(MyString& rhs) { // 仅交换指针,O(1),无任何拷贝
        std::swap(_start, rhs._start);
        std::swap(_finish, rhs._finish);
        std::swap(_end, rhs._end);
    }

private:
    char* _start;    // 有效字符起始
    char* _finish;   // 有效字符末尾(最后一个字符的下一位)
    char* _end;      // 内存缓冲区边界(总容量终点)
};
版本二:工业级【SSO 短字符串优化版】MyStringSSO

核心特点

  • 核心优化:短字符串 (≤15 字符) → 栈上存储,零堆开销;长字符串 (>15) → 自动切堆存储,复用三指针逻辑;
  • 标准阈值:15个有效字符,工业级硬性标准 (15+1 个\0=16 字节,刚好填满 64 位栈缓冲区,无内存浪费);
  • 内存对齐:对象大小24 字节,和纯净三指针版完全一致,无额外内存损耗;
  • 上层无感:所有接口和三指针版完全相同,调用方无需修改任何代码,底层自动切换存储方式;
  • 极致效率:短字符串无new/delete、无内存碎片、无拷贝,速度比三指针版快一个数量级。

完整工业级代码

cpp 复制代码
#include <cstring>
#include <cassert>
#include <algorithm>
using namespace std;

// SSO短字符串优化版 - 工业级string实现 (对齐GCC/Clang/MSVC主流版,生产首选)
class MyStringSSO {
public:
    // ===== 接口完全兼容三指针版,无任何修改 =====
    MyStringSSO() : _isLocal(true) { _local._size = 0; _local._buf[0] = '\0'; }
    MyStringSSO(const char* str);
    MyStringSSO(const MyStringSSO& rhs);
    MyStringSSO& operator=(const MyStringSSO& rhs);
    ~MyStringSSO() { if (!_isLocal) delete[] _heap._start; }

    size_t size()  const;
    size_t capacity() const;
    bool empty()   const { return size() == 0; }
    const char* c_str() const;

    void reserve(size_t n);
    void resize(size_t n, char ch = '\0');
    char& operator[](size_t pos);
    const char& operator[](size_t pos) const;
    void push_back(char ch);
    void clear();
    void swap(MyStringSSO& rhs);

private:
    // ===== SSO工业级标准:固定15字符阈值 =====
    static constexpr size_t SSO_MAX = 15; // 短字符串最大有效长度
    // ===== 核心:联合体 - 同一时刻只存栈/堆一种数据,极致省内存 =====
    union Data {
        // 堆存储:长字符串,复用三指针结构
        struct { char* _start; char* _finish; char* _end; } _heap;
        // 栈存储:短字符串,直接存在对象内部,无堆内存
        struct { char _buf[SSO_MAX + 1]; size_t _size; } _local;
    } _data;
    bool _isLocal; // 标记位:true=栈存储(短串) false=堆存储(长串)

    // ===== 私有核心:栈→堆 数据迁移(工业级核心逻辑) =====
    void _moveToHeap() {
        size_t oldSize = size();
        char* newMem = new char[oldSize];
        memcpy(newMem, c_str(), oldSize);
        _data._heap._start = newMem;
        _data._heap._finish = newMem + oldSize;
        _data._heap._end = newMem + oldSize;
        *_data._heap._finish = '\0';
        _isLocal = false;
    }
};

// ===== 所有成员函数实现(极简,核心逻辑一目了然) =====
MyStringSSO::MyStringSSO(const char* str) {
    size_t len = strlen(str);
    if (len <= SSO_MAX) { // 短串:栈存储
        _isLocal = true;
        memcpy(_data._local._buf, str, len);
        _data._local._size = len;
        _data._local._buf[len] = '\0';
    } else { // 长串:堆存储
        _isLocal = false;
        _data._heap._start = new char[len];
        memcpy(_data._heap._start, str, len);
        _data._heap._finish = _data._heap._start + len;
        _data._heap._end = _data._heap._finish;
        *_data._heap._finish = '\0';
    }
}
MyStringSSO::MyStringSSO(const MyStringSSO& rhs) {
    _isLocal = rhs._isLocal;
    if (_isLocal) { // 拷贝短串:栈拷贝
        memcpy(_data._local._buf, rhs._data._local._buf, SSO_MAX+1);
        _data._local._size = rhs._data._local._size;
    } else { // 拷贝长串:深拷贝堆内存
        size_t cap = rhs.capacity();
        _data._heap._start = new char[cap];
        memcpy(_data._heap._start, rhs._data._heap._start, rhs.size());
        _data._heap._finish = _data._heap._start + rhs.size();
        _data._heap._end = _data._heap._start + cap;
        *_data._heap._finish = '\0';
    }
}
MyStringSSO& MyStringSSO::operator=(const MyStringSSO& rhs) {
    if (this != &rhs) {
        if (!_isLocal) delete[] _data._heap._start;
        _isLocal = rhs._isLocal;
        if (_isLocal) {
            memcpy(_data._local._buf, rhs._data._local._buf, SSO_MAX+1);
            _data._local._size = rhs._data._local._size;
        } else {
            size_t cap = rhs.capacity();
            _data._heap._start = new char[cap];
            memcpy(_data._heap._start, rhs._data._heap._start, rhs.size());
            _data._heap._finish = _data._heap._start + rhs.size();
            _data._heap._end = _data._heap._start + cap;
            *_data._heap._finish = '\0';
        }
    }
    return *this;
}
size_t MyStringSSO::size() const { return _isLocal ? _data._local._size : _data._heap._finish - _data._heap._start; }
size_t MyStringSSO::capacity() const { return _isLocal ? SSO_MAX : _data._heap._end - _data._heap._start; }
const char* MyStringSSO::c_str() const { return _isLocal ? _data._local._buf : _data._heap._start; }
void MyStringSSO::reserve(size_t n) {
    if (_isLocal && n > SSO_MAX) _moveToHeap();
    if (!_isLocal && n > capacity()) {
        size_t oldSize = size();
        char* newMem = new char[n];
        memcpy(newMem, _data._heap._start, oldSize);
        delete[] _data._heap._start;
        _data._heap._start = newMem;
        _data._heap._finish = newMem + oldSize;
        _data._heap._end = newMem + n;
        *_data._heap._finish = '\0';
    }
}
void MyStringSSO::push_back(char ch) {
    if (_isLocal) {
        if (size() < SSO_MAX) { // 栈未满,直接插
            _data._local._buf[size()] = ch;
            _data._local._size++;
            _data._local._buf[size()] = '\0';
            return;
        } else _moveToHeap(); // 栈满,迁堆
    }
    // 堆存储,复用三指针版扩容逻辑
    if (_data._heap._finish == _data._heap._end) reserve(capacity()*2+1);
    *_data._heap._finish = ch;
    _data._heap._finish++;
    *_data._heap._finish = '\0';
}
char& MyStringSSO::operator[](size_t pos) {
    assert(pos < size());
    return _isLocal ? _data._local._buf[pos] : _data._heap._start[pos];
}
const char& MyStringSSO::operator[](size_t pos) const {
    assert(pos < size());
    return _isLocal ? _data._local._buf[pos] : _data._heap._start[pos];
}
void MyStringSSO::clear() {
    if (_isLocal) { _data._local._size = 0; _data._local._buf[0] = '\0'; }
    else { _data._heap._finish = _data._heap._start; *_data._heap._finish = '\0'; }
}
void MyStringSSO::swap(MyStringSSO& rhs) {
    std::swap(_data, rhs._data);
    std::swap(_isLocal, rhs._isLocal);
}
版本三:工业级【COW 写时拷贝版】MyStringCOW

核心特点

  • 核心思想:读共享,写拷贝,工业级极致优化;
  • 底层改造:基于三指针版,堆内存头部追加 4 字节 int 作为引用计数,无其他冗余;
  • 拷贝效率:拷贝构造 / 赋值重载为浅拷贝(仅拷贝指针 + 计数 + 1),O (1) 复杂度,大量只读场景下内存利用率拉满;
  • 数据安全:所有写操作push_back/operator[]/resize)都会先调用_mutate(),确保独占内存后再写,绝对不会影响其他对象;
  • 内存安全:只有当引用计数减至 0 时,才真正释放堆内存,无内存泄漏。

COW 在多线程写场景下需加锁,单线程 / 只读多的场景(如日志、配置、字符串常量池)是最优解。

完整工业级代码

cpp 复制代码
#include <cstring>
#include <cassert>
#include <algorithm>
using namespace std;

// COW写时拷贝版 - 工业级string实现 (对齐SGI STL经典版,面试必考)
class MyStringCOW {
public:
    // ===== 接口完全兼容前两个版本,无任何修改 =====
    MyStringCOW() : _start(nullptr) { _initEmpty(); }
    MyStringCOW(const char* str);
    MyStringCOW(const MyStringCOW& rhs);
    MyStringCOW& operator=(const MyStringCOW& rhs);
    ~MyStringCOW();

    size_t size()  const { return _finish - _start; }
    size_t capacity() const { return _end - _start; }
    bool empty()   const { return _start == _finish; }
    const char* c_str() const { return _start; }

    void reserve(size_t n);
    void resize(size_t n, char ch = '\0');
    char& operator[](size_t pos);
    const char& operator[](size_t pos) const;
    void push_back(char ch);
    void clear();
    void swap(MyStringCOW& rhs);

private:
    char* _start;
    char* _finish;
    char* _end;

    // ===== COW核心宏:获取堆内存头部的引用计数 (工业级极简实现) =====
    #define REF_COUNT(p) (*(int*)( (char*)p - 4 ))
    // 堆内存布局:[4字节int引用计数][有效字符区][空闲区]
    // _start指向有效字符区,所以 _start-4 就是引用计数的地址

    // ===== COW核心私有函数:写时拷贝触发,所有写操作必调用 =====
    void _mutate(size_t pos = 0, size_t len = 1) {
        assert(pos + len <= size());
        if (REF_COUNT(_start) > 1) { // 有其他对象共享,触发深拷贝
            size_t oldSize = size();
            size_t oldCap = capacity();
            // 申请新内存:+4字节存引用计数
            char* newMem = (char*)new char[oldCap + 4] + 4;
            memcpy(newMem, _start, oldSize);
            REF_COUNT(newMem) = 1; // 新内存独占,计数=1

            // 原内存计数-1,无人引用则释放
            REF_COUNT(_start)--;
            if (REF_COUNT(_start) == 0) delete[] ( (char*)_start -4 );

            // 更新指针,指向新内存
            _start = newMem;
            _finish = _start + oldSize;
            _end = _start + oldCap;
            *_finish = '\0';
        }
    }
    // 初始化空串:分配内存+初始化引用计数
    void _initEmpty() {
        char* mem = (char*)new char[4] +4;
        REF_COUNT(mem) =1;
        _start = mem;
        _finish = mem;
        _end = mem;
        *_finish = '\0';
    }
};

// ===== 所有成员函数实现(极简,核心逻辑一目了然) =====
MyStringCOW::MyStringCOW(const char* str) {
    size_t len = strlen(str);
    char* mem = (char*)new char[len +4] +4;
    memcpy(mem, str, len);
    REF_COUNT(mem) = 1;
    _start = mem;
    _finish = mem + len;
    _end = mem + len;
    *_finish = '\0';
}
MyStringCOW::MyStringCOW(const MyStringCOW& rhs) { // 浅拷贝+计数+1,O(1)
    _start = rhs._start;
    _finish = rhs._finish;
    _end = rhs._end;
    if (_start) REF_COUNT(_start)++;
}
MyStringCOW& MyStringCOW::operator=(const MyStringCOW& rhs) {
    if (this != &rhs) {
        // 释放原内存
        if (_start) {
            REF_COUNT(_start)--;
            if (REF_COUNT(_start) ==0) delete[] ( (char*)_start -4 );
        }
        // 浅拷贝+计数+1
        _start = rhs._start;
        _finish = rhs._finish;
        _end = rhs._end;
        if (_start) REF_COUNT(_start)++;
    }
    return *this;
}
MyStringCOW::~MyStringCOW() { // 计数-1,无引用则释放
    if (_start) {
        REF_COUNT(_start)--;
        if (REF_COUNT(_start) ==0) delete[] ( (char*)_start -4 );
    }
}
void MyStringCOW::reserve(size_t n) {
    if (n > capacity()) {
        _mutate(); // 扩容是写操作,先独占内存
        size_t oldSize = size();
        char* newMem = (char*)new char[n+4]+4;
        memcpy(newMem, _start, oldSize);
        REF_COUNT(newMem) =1;
        delete[] ( (char*)_start -4 );
        _start = newMem;
        _finish = newMem + oldSize;
        _end = newMem +n;
        *_finish = '\0';
    }
}
char& MyStringCOW::operator[](size_t pos) { // 写操作→先_mutate
    assert(pos < size());
    _mutate(pos);
    return _start[pos];
}
const char& MyStringCOW::operator[](size_t pos) const { // 读操作→无开销
    assert(pos < size());
    return _start[pos];
}
void MyStringCOW::push_back(char ch) { // 写操作→先_mutate
    _mutate();
    if (_finish == _end) reserve(capacity()*2+1);
    *_finish = ch;
    _finish++;
    *_finish = '\0';
}
void MyStringCOW::clear() { // 写操作→先_mutate
    _mutate();
    _finish = _start;
    *_finish = '\0';
}
void MyStringCOW::swap(MyStringCOW& rhs) {
    std::swap(_start, rhs._start);
    std::swap(_finish, rhs._finish);
    std::swap(_end, rhs._end);
}
void MyStringCOW::resize(size_t n, char ch) {
    _mutate();
    if (n < size()) { _finish=_start+n; *_finish='\0'; }
    else { reserve(n); memset(_finish, ch, n-size()); _finish=_start+n; *_finish='\0'; }
}
工业级测试代码
cpp 复制代码
// 测试用例:通用所有版本,替换类名即可
int main() {
    MyStringSSO s1 = "hello world";
    MyStringSSO s2 = s1;
    s2.push_back('!');
    cout << s1.c_str() << endl; // hello world
    cout << s2.c_str() << endl; // hello world!
    cout << s1.size() << " " << s1.capacity() << endl;

    s1.resize(20, 'x');
    cout << s1.c_str() << endl;
    s1.clear();
    cout << s1.empty() << endl;

    MyStringSSO s3 = "123456789012345"; // 15字符,SSO栈存储
    MyStringSSO s4 = "1234567890123456";//16字符,堆存储
    return 0;
}
相关推荐
Jackson@ML2 小时前
2026最新版Python 3.14.2安装使用指南
开发语言·python
橘子师兄2 小时前
C++AI大模型接入SDK—ChatSDK使用手册
开发语言·c++·人工智能
Channing Lewis2 小时前
正则灾难性回溯(catastrophic backtracking)
开发语言·python
CS创新实验室2 小时前
《计算机网络》深入学:轮询和令牌传递协议
开发语言·计算机网络·考研·php·408
wen__xvn2 小时前
基础算法集训第01天:线性枚举
数据结构·c++·算法
王干脆2 小时前
ConcurrentHashMap禁止null键值的原因
java·开发语言
Howrun7773 小时前
C++ 线程互斥锁 lock_guard
c++·算法
代码游侠3 小时前
ARM嵌入式开发代码实践——LED灯闪烁(C语言版)
c语言·开发语言·arm开发·笔记·嵌入式硬件·学习
—Qeyser3 小时前
Flutter Text 文本组件完全指南
开发语言·javascript·flutter