【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字节存放\0strcpy(_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.rhkb.cn/news/36832.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

函数的介绍

1.函数的概念 在C语言中也有函数的概念&#xff0c;有些翻译为&#xff1a;子程序&#xff0c;这种翻译更为准确。C语言的函数就是一个完成某项特定的任务的一小段代码。这段代码是有特殊的写法和调用方法的。 C语言的程序其实是有无数个小的函数组合而成的&#xff0c;也可以…

MES汽车零部件制造生产监控看板大屏

废话不多说&#xff0c;直接上效果 预览效果请在大的显示器查看&#xff0c;笔记本可能有点变形 MES汽车零部件制造生产监控看板大屏 纯html写的项目结构如下 主要代码分享 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UT…

JS—原型与原型链:2分钟掌握原型链

个人博客&#xff1a;haichenyi.com。感谢关注 一. 目录 一–目录二–原型三–原型链 二. 原型 什么是原型&#xff1f; 每个JavaScript对象都有一个原型&#xff0c;这个原型也是一个对象。比方说 function Person(name) {this.name name; } let person new Person(&quo…

TCP 协议

文章目录 TCP 协议简介数据包格式TCP的特性连接机制确认与重传缓冲机制全双工通信流量控制差错控制拥塞控制 端口号三次握手数据传输四次挥手抓包参考 本文为笔者学习以太网对网上资料归纳整理所做的笔记&#xff0c;文末均附有参考链接&#xff0c;如侵权&#xff0c;请联系删…

二分查找的应用

什么时候用二分查找&#xff1f; 数据具有二段性的时候 第一题&#xff1a; 题解代码&#xff1a; class Solution { public:int search(vector<int>& nums, int target) {int left 0,right nums.size()-1;while(left<right){int mid left (right-left)/2;//中…

cmake 之 CMakeLists.txt 中的函数是从哪里来的

我们都知道&#xff0c;cmake会解释执行 CMakeLists.txt 以及其他 *.cmake 脚本&#xff0c; 这里先给出一个“先验” 的知识点&#xff1a; 任何一个独立脚本或脚本函数命令的执行&#xff0c;都是通过 CPP 函数 RunListFile(...) 调用的 void cmMakefile::RunListFile(cmL…

QT 实现信号源实时采集功能支持频谱图,瀑布图显示

利用QT实现信号源实时采集功能&#xff0c;先看效果 支持双光标显示 &#xff0c;功率测量&#xff0c;带宽测量&#xff0c;载噪比测量&#xff0c;波形框选&#xff0c;水平移动等功能&#xff0c;下载链接 https://download.csdn.net/download/ZuoYueXian/90501632 实现方…

【Kafka】深入了解Kafka

集群的成员关系 Kafka使用Zookeeper维护集群的成员信息。 每一个broker都有一个唯一的标识&#xff0c;这个标识可以在配置文件中指定&#xff0c;也可以自动生成。当broker在启动时通过创建Zookeeper的临时节点把自己的ID注册到Zookeeper中。broker、控制器和其他一些动态系…

神聖的綫性代數速成例題10. N維矢量綫性運算、矢量由矢量組綫性表示、N個N維矢量相關性質

N 維矢量綫性運算&#xff1a; 設&#xff0c;是維矢量&#xff0c;是數。加法&#xff1a;。數乘&#xff1a;。 矢量由矢量組綫性表示&#xff1a; 設是n維矢量&#xff0c;若存在一組數&#xff0c;使得&#xff0c;則稱矢量可由矢量組綫性表示。 N 個 N 維矢量相關性質&…

在CentOS 7.6中安装openGauss 5.1.0 (Preview)数据库并使用Navicat进行远程连接的过程记录

部署环境 华为云Flexus应用服务器 操作系统&#xff1a;CentOS 7.6 openGauss版本&#xff1a;openGauss 5.1.0 (Preview) 参考文档 官方安装文档&#xff1a; https://docs.opengauss.org/zh/docs/5.1.0/docs/InstallationGuide/%E4%BA%86%E8%A7%A3%E5%AE%89%E8%A3%85%E6%B…

SysOM 可观测体系建设(一):万字长文解读低开销、高精度性能剖析工具livetrace

可观测性是一种通过分析系统输出结果并推断和衡量系统内部状态的能力。谈及可观测性一般包含几大功能&#xff1a;监控指标、链路追踪、告警日志&#xff0c;及 Continues Profiling 持续剖析能力。对于操作系统可观测&#xff0c;监控指标可以帮助查看各个子系统&#xff08;I…

Shell脚本学习笔记:从入门到变量(一)

前言 最近在看 Shell 脚本相关的内容&#xff0c;以下是我从入门到变量部分的整理笔记&#xff0c;内容有点多&#xff0c;但都是干货。 先从基础开始&#xff0c;再逐步深入。 一、Shell 脚本入门 1. Linux 如何控制硬件&#xff1f; Linux 靠内核操作硬件&#xff08;CP…

Linux应用:进程间通信

linux的进程间通信概述 进程间通信&#xff08;IPC&#xff0c;Inter - Process Communication&#xff09;是指在不同进程之间进行数据交换和同步的机制。由于每个进程都有自己独立的地址空间&#xff0c;直接共享内存存在困难&#xff0c;因此需要专门的 IPC 机制来实现进程…

el-input 不可编辑,但是点击的时候出现弹窗/或其他操作面板,并且带可清除按钮

1.focus“getFocus”鼠标聚焦的时候写个方法&#xff0c;弹窗起来 getFocus(){ this.定义的弹窗状态字段 true;} 2.点击确定的时候&#xff0c;数值赋值到el-input的输入框,弹窗取消&#xff08;this.定义的弹段字端 false&#xff09; 3.但是会有个问题就是el-input 不可点…

Weblogic未授权远程命令执行漏洞复现

1 漏洞简介 Weblogic是Oracle公司推出的J2EE应用服务器&#xff0c;CVE-2020-14882允许未授权的用户绕过管理控制台的权限验证访问后台&#xff0c;CVE-2020-14883允许后台任意用户通过HTTP协议执行任意命令。使用这两个漏洞组成的利用链&#xff0c;可通过一个GET请求在远程W…

海康SDK协议在智联视频超融合平台中的接入方法

一. 海康SDK协议详解 海康SDK协议原理 海康SDK协议是海康威视为开发者提供的一套软件开发工具包&#xff0c;用于与海康设备&#xff08;如摄像头、NVR、DVR等&#xff09;进行通信和控制。其核心原理包括&#xff1a; 网络通信&#xff1a;基于TCP/IP协议&#xff0c;实现设…

五模型对比!Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量时间序列预测

目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 光伏功率预测&#xff01;五模型对比&#xff01;Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量时间序列预测(Matlab2023b 多输入单输出) 1.程序已经调试好&#xff0c;替换数据集后&#xff0c;仅运…

20250319在荣品的PRO-RK3566开发板的buildroot系统下使用集成的QT应用调试串口UART3

stty -F /dev/ttyS3 115200 -echo cat /dev/ttyS3 & echo serialdata > /dev/ttyS3 20250319在荣品的PRO-RK3566开发板的buildroot系统下使用集成的QT应用调试串口UART3 2025/3/19 14:17 缘起&#xff1a;在荣品的PRO-RK3566开发板的buildroot系统下&#xff0c;在命令…

Git 使用笔记

参考链接&#xff1a; 创建版本库 - Git教程 - 廖雪峰的官方网站 Git使用教程,最详细&#xff0c;最傻瓜&#xff0c;最浅显&#xff0c;真正手把手教 - 知乎 命令使用 cd f: 切换目录到 F 盘 cd gitCxl 切换目录到 gitCxl 文件夹 mkdir gitCxl 创建新文件…

Xilinx系列FPGA视频采集转HDMI2.0输出,基于HDMI 1.4/2.0 Transmitter Subsystem方案,提供6套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我已有的 GT 高速接口解决方案我已有的FPGA图像处理方案 3、详细设计方案设计框图硬件设计架构FPGA开发板输入Sensor之-->OV5640摄像头动态彩条Video In To AXI4-S…