当前位置: 首页 > article >正文

【C++指南】string(三):basic_string底层原理与模拟实现详解

.

💓 博客主页:倔强的石头的CSDN主页
📝Gitee主页:倔强的石头的gitee主页
⏩ 文章专栏:《C++指南》
期待您的关注

文章目录

    • 引言
    • 一、成员变量与内存管理
      • 1.1 核心成员变量
      • 1.2 内存分配策略
    • 二、默认成员函数的实现与优化
      • 2.1 拷贝构造函数
      • 2.2 赋值运算符重载
      • 2.3 析构函数
    • 三、迭代器与元素访问
      • 3.1 迭代器实现
      • 3.2 运算符重载
    • 四、容量管理
      • 4.1 reserve:预分配内存
      • 4.2 resize:调整字符串长度
    • 五、修改操作
      • 5.1 清空字符串:`clear`
      • 5.2 push_back与append
      • 5.3 insert与erase
    • 六、其他关键函数实现
      • 6.1 查找函数:`find`
        • 查找字符
        • 查找子串
      • 6.2 子串生成:`substr`
      • 6.3 流运算符重载
        • 流插入(`operator<<`)
        • 流提取(`operator>>`)
      • 6.4 比较运算符重载
        • 等于与不等于
        • 大小比较
      • 6.5 交换函数:`swap`
    • 七、性能优化与注意事项
    • 结语

引言

在前文中,我们深入探讨了C++标准库中basic_string的成员变量、默认成员函数及常用操作。
本文作为系列第三篇,将结合模拟实现的代码,逐行解析basic_string的底层原理,涵盖构造函数、拷贝控制、容量管理、修改操作等核心功能的实现细节与优化技巧
通过手写一个简化版string类,帮助读者彻底理解std::string的内部工作机制。

一、成员变量与内存管理

1.1 核心成员变量

标准库的basic_string通过三个核心变量管理字符串:

  • 字符指针 _str:指向动态分配的字符数组。
  • 当前长度 _size:字符串有效字符个数(不含\0)。
  • 总容量 _capacity:当前内存可容纳的最大字符数(含\0)。

模拟实现代码

namespace xc {
class string {
private:
    char* _str;         // 字符存储指针
    size_t _size;       // 有效字符数
    size_t _capacity;   // 总容量(含\0)
public:
    static const size_t npos = -1; // 特殊标记
};
}

1.2 内存分配策略

  • 默认构造:初始化为空字符串(_str指向\0)。 注意不能初始化为nullptr,否则调用c_str时,就会对空指针解引用
  • 动态扩容:当_size达到_capacity时,按2倍或需求大小扩容,避免频繁内存分配。

构造函数实现

// 默认构造(支持传入C字符串)
string::string(const char* str) : _size(strlen(str)) {
    _str = new char[_size + 1]; // 多分配1字节存放\0
    strcpy(_str, str);
    _capacity = _size;          // 初始容量等于长度
}

二、默认成员函数的实现与优化

2.1 拷贝构造函数

传统写法需要手动分配内存并拷贝数据,而现代C++写法通过“构造临时对象 + 交换资源”简化代码:
(关于swap函数的实现可跳转6.5查找)

// 传统写法(易错且冗余)
string::string(const string& s) {
    _str = new char[s._capacity + 1];
    strcpy(_str, s._str);
    _size = s._size;
    _capacity = s._capacity;
}

// 现代写法(利用临时对象)
string::string(const string& s) {
    string tmp(s._str); // 调用构造函数
    swap(tmp);          // 交换资源
}

2.2 赋值运算符重载

通过**“拷贝构造临时对象 + 交换”**避免自赋值问题,同时减少重复代码:

 //传统写法
 string& string::operator=(const string& s)
 {
     if (this != &s)
     {
         delete[] _str;
         _str = new char[s._capacity + 1];
         strcpy(_str, s._str);
         _size = s._size;
         _capacity = s._capacity;
     }
     return *this;
 }
// 优化版赋值重载
string& string::operator=(const string& s) {
    if (this != &s) {       // 防止自赋值
        string tmp(s);      // 调用拷贝构造
        swap(tmp);          // 交换资源
    }
    return *this;
}

2.3 析构函数

释放动态内存并将成员变量归零:

string::~string() {
    delete[] _str;  // 释放堆内存
    _size = 0;
    _capacity = 0;
}

三、迭代器与元素访问

3.1 迭代器实现

模拟原生指针的行为,提供begin()end()

using iterator = char*;
iterator begin() { return _str; }
iterator end() { return _str + _size; }

3.2 运算符重载

通过operator[]提供随机访问,并使用assert检查越界:

char& operator[](size_t i) {
    assert(i < _size);      // 越界检查
    return _str[i];
}

四、容量管理

4.1 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;      // 更新容量
    }
}

4.2 resize:调整字符串长度

根据新长度截断或填充字符:

void string::resize(size_t n, char c) {
    if (n < _size) {
        _str[n] = '\0';    // 截断
        _size = n;
    } else {
        reserve(n);         // 确保容量足够
        for (size_t i = _size; i < n; ++i) {
            _str[i] = c;   // 填充字符
        }
        _size = n;
        _str[_size] = '\0';
    }
}

五、修改操作

5.1 清空字符串:clear

清空字符串内容但不释放内存(保留容量):

void string::clear() {
    _str[0] = '\0';  // 首字符置为结束符
    _size = 0;       // 长度归零
}

5.2 push_back与append

  • 尾插字符:检查扩容后直接写入:
void string::push_back(char c) {
    if (_size == _capacity) {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    }
    _str[_size++] = c;
    _str[_size] = '\0';
}
  • 追加字符串:计算长度后扩容并拷贝:
void string::append(const char* str) {
    size_t len = strlen(str);
    if (_size + len > _capacity) {
        reserve(_size + len); // 按需扩容
    }
    strcpy(_str + _size, str); // 直接拷贝
    _size += len;
}

5.3 insert与erase

  • 插入字符:移动后续字符腾出位置:
string& string::insert(size_t pos, char c) {
    assert(pos <= _size);
    if (_size == _capacity) reserve(2 * _capacity);
    size_t end = _size + 1;
    while (end > pos) {      // 从后向前移动
        _str[end] = _str[end - 1];
        end--;
    }
    _str[pos] = c;
    _size++;
    return *this;
}
  • 删除字符:覆盖后续字符并更新长度:
string& string::erase(size_t pos, size_t len) {
    assert(pos < _size);
    if (len == npos || len > _size - pos) {
        _str[pos] = '\0';
        _size = pos;
    } else {
        strcpy(_str + pos, _str + pos + len); // 覆盖删除区域
        _size -= len;
    }
    return *this;
}

六、其他关键函数实现

6.1 查找函数:find

查找字符
size_t string::find(char c, size_t pos) const {
    assert(pos < _size);
    for (size_t i = pos; i < _size; ++i) {
        if (_str[i] == c) return i;
    }
    return npos; // 未找到返回特殊标记
}
查找子串

利用标准库的strstr函数优化子串查找:

size_t string::find(const char* s, size_t pos) const {
    assert(pos < _size);
    const char* ptr = strstr(_str + pos, s); // 直接调用C库函数
    return ptr ? ptr - _str : npos;
}

6.2 子串生成:substr

截取从pos开始的len个字符生成新字符串:

string string::substr(size_t pos, size_t len) const {
    assert(pos <= _size);
    len = (len == npos) ? _size - pos : len; // 默认取到末尾
    len = std::min(len, _size - pos);        // 防止越界
    
    string result;
    result.reserve(len);              // 预分配内存
    for (size_t i = 0; i < len; ++i) {
        result += _str[pos + i];      // 逐字符追加
    }
    return result;
}

6.3 流运算符重载

流插入(operator<<

直接遍历输出有效字符:

ostream& operator<<(ostream& os, const xc::string& s) {
    for (size_t i = 0; i < s.size(); ++i) {
        os << s[i]; // 支持链式调用
    }
    return os;
}
流提取(operator>>

优化版输入,通过缓冲区减少扩容次数:

istream& operator>>(istream& is, xc::string& s) {
    s.clear();        // 清空原内容
    char buff[256];   // 局部缓冲区
    char ch;
    int idx = 0;
    
    while (is.get(ch) && !isspace(ch)) {
        buff[idx++] = ch;
        if (idx == 255) {    // 缓冲区满时批量追加
            buff[idx] = '\0';
            s += buff;
            idx = 0;
        }
    }
    
    if (idx > 0) {    // 处理剩余字符
        buff[idx] = '\0';
        s += buff;
    }
    return is;
}

6.4 比较运算符重载

等于与不等于
bool string::operator==(const string& s) const {
    return strcmp(_str, s._str) == 0; // 直接比较C字符串
}

bool string::operator!=(const string& s) const {
    return !(*this == s); // 复用等于运算符
}
大小比较
bool string::operator<(const string& s) const {
    return strcmp(_str, s._str) < 0; // 字典序比较
}

bool string::operator<=(const string& s) const {
    return (*this < s) || (*this == s); // 组合逻辑
}

bool string::operator>(const string& s) const {
    return !(*this <= s);
}

bool string::operator>=(const string& s) const {
    return !(*this < s);
}

6.5 交换函数:swap

高效交换两个字符串的资源(避免深拷贝):

void string::swap(string& s) {
    std::swap(_str, s._str);      // 交换指针
    std::swap(_size, s._size);    // 交换长度
    std::swap(_capacity, s._capacity); // 交换容量
}

七、性能优化与注意事项

  1. substr的优化

    • 避免直接使用newstrcpy,通过reserve预分配内存减少扩容次数。
    • 若需要高性能,可实现“浅拷贝+引用计数”(需处理写时复制逻辑)。
  2. find的局限性

    • 当前实现为暴力匹配,标准库可能使用更高效的算法(如KMP)。
  3. 流提取的安全性

    • 缓冲区大小固定为256,若输入过长可能丢失数据,可动态调整缓冲区大小。
  4. swap的优势

    • 仅交换指针和元数据,时间复杂度为O(1),适合频繁交换场景。

结语

通过手写string类,我们深入理解了basic_string的底层机制。标准库的实现在此基础上进行了大量优化(如SSO、内存池),但核心逻辑与本文的模拟实现高度一致。掌握这些原理后,读者可以更高效地使用std::string,并能在需要时定制自己的字符串类。

相关阅读

  • 【C++指南】string(一):string从入门到掌握
  • 【C++指南】string(二):深入探究 C++ basic_string:成员变量、函数全解析

关注博主,第一时间获取更新!


http://www.kler.cn/a/593738.html

相关文章:

  • 【MyDB】6-TabelManager 字段与表管理 之1-TBM实现思路概览
  • 江小南的题目讲解
  • Vala编程语言教程-语言元素
  • 轮足式机器人运动控制系统设计(大纲)
  • 过程监控——lsof
  • DeepSeek(8):结合Kimi-PPT助手一键生成演示报告
  • 【智能体】| 知识库、RAG概念区分以及智能体是什么
  • Steam游戏实时数据获取API集成文档
  • 从两指到三指:Robotiq机器人自适应夹持器技术解析
  • 将COCO格式的物体检测数据集划分训练集、验证集和测试集
  • Word 小黑第34套
  • 在C语言基础上学Java【Java】【一】
  • 自然语言处理(Natural Language Processing,NLP)入门教程
  • leetcode106 从中序与后序遍历序列构造二叉树
  • 模型部署实战:PyTorch生产化指南
  • mac丝滑安装Windows操作系统【丝滑简单免费】
  • 【大模型-知识库之本地安装Embendding模型(BGE-M3)】
  • ROS机器人建模与仿真设计——模型控制理论
  • 【文章写作】以数字素养筑基,绘治理现代化蓝图
  • Linux笔记之Ubuntu22.04安装IBus中文输入法教程