C++——string模拟实现
前言:上篇文章我们对string类及其常用的接口方法的使用进行了分享,这篇文章将着重进行对这些常用的接口方法的内部细节进行分享和模拟实现。
目录
一.基础框架
二.遍历字符串
1.[]运算符重载
2.迭代器
3.范围for
三.常用方法
1.增加
2.删除
3.调整
4.交换
5.查找
6.截取
7.比较
四.流操作
总结
一.基础框架
首先我们要清楚,string类定义的是字符串对象,所以就类似于线性表,有长度,容量等成员变量:
class string{public://构造函数string(const char* str = ""):_size(strlen(str)){_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}//析构函数~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}//转换C语言格式const char* c_str() const{return _str;}//清除void clear(){_size = 0;_str[_size] = '\0';}//深拷贝s2(s1)string(const string& s){_str = new char[_capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}//s1 = s2string& operator=(const string& s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}private:char* _str;size_t _size;size_t _capacity;};
其中不能缺少的就是构造函数、析构函数和拷贝构造函数,这里我们直接用缺省函数将无参构造和带参构造结合为一体。
但是由于如果我们不自己写一个深拷贝函数,就会默认执行浅拷贝成员函数,这样会导致两个字符串同源,所以需要给出深拷贝函数。
值得注意的是真正的strlen不会统计字符串中的‘\0’,所以我们给_str开空间时应+1。
二.遍历字符串
1.[]运算符重载
上篇文章中我们知道遍历字符串有三种方式:[]运算符重载,迭代器,以及范围for。下面我们就来一一实现。
首先我们需要将字符串的长度方法size和容量方法capacity定义出来:
//长度size_t size() const{return _size;}//容量size_t capacity() const{return _capacity;}
一般情况下当方法里调用的成员无需发生改变时,都会将这些方法用const修饰。
而[]运算符重载自然是通过运算符重载函数来实现:
//遍历char& operator[](size_t pos){assert(pos < _size);return _str[pos];}
这里我们添加assert函数来断言,防止越界访问。
返回值使用引用格式,能够实现可读可写:

但此时会产生一个问题,如果我想让一个const修饰的对象来调用该方法,就会导致权限放大而出错。
如果给这个方法加上const,那我们就无法修改其内容了。
所以我们使用函数重载,为其单独创造一个只读的const修饰的函数方法:
const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}
2.迭代器
我们已经了解迭代器的本质和指针类似,我们这里我们就先用指针的实现迭代器的功能:
//迭代器typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}
用typedef将char*指针重命名为iterator,再定义出begin和end两个方法,便能实现迭代器功能:

值得注意的是,const修饰的对象想要调用迭代器,也必须调用对应const修饰的迭代器,所以迭代器我们也需要进行重载:
typedef const char* const_iterator;const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}
迭代器这样的模拟实现方法也能帮助我们更深入的了解其内部构造。
3.范围for
范围for实际上并没有那么复杂,其本质也是迭代器。
所以只要有迭代器的存在,保证iterator、begin。end这些单词不变,不管是我们自己实现的还是C++库内的,都可以使用范围for:

但是如果iterator、begin。end这些单词发生变化,就无法在使用范围for。
三.常用方法
1.增加
增加无非有四种方式:尾插单个字符push_back、尾插字符串append和+=运算符重载,以及任意位置的插入insert,增加字符就意味着要考虑扩容问题,这就要实现reserve方法来配合使用。
尾插单个字符可以通过每次扩容两倍容量,但是如果尾插一个长度为len的字符串,每次扩容两倍或是更多倍都并不一定就能满足容量, 所以这里直接扩容size+len个空间:
//扩容void reserve(size_t len){if (len > _capacity){char* tmp = new char[len + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = len;}}//尾插单字符void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}//尾插字符串void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;}
因为字符串的末尾都要有'\0'的存在,所以扩容时,要始终保持实际容量比字符串容量多1。
当尾插单个字符时,因为该字符是直接覆盖了'\0',所以尾插之后要再字符串末尾再补上'\0'。
而尾插字符串时,因为strcpy在进行拷贝时也会直接将'\0'拷贝,所以无需再补。
而+=运算符的重载,就是以上述两个方法为基层的扩展:
//+=运算符重载string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}
测试如下:

任意位置的插入, 则需要进行字符串的挪动:
//任意位置插入//单字符void insert(char ch, size_t pos){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size++;}//字符串void insert(const char* str, size_t pos){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}size_t end = _size + len;while (end >= pos + len){_str[end] = _str[end - len];end--;}for (size_t i = 0; i < len; i++){_str[pos++] = str[i];}_size += len;}
在进行字符串插入时,要注意的是我们要从pos位置将后边的字符向后空出len个位置。
同时我们不能使用strcpy进行插入,因为它会将要插入的字符串的'\0'一并插入,导致一个位置的字符被覆盖,所以这里我们采用循环插入。
测试如下:

2.删除
string类中只有一个常用的删除方法:erase,它的功能是在指定位置后删除若干个字符,如果没有指定要删除的字符数量,则默认将后边的字符全删除:
//删除void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || len >= _size - pos){_str[pos] = '\0';_size = pos;}strcpy(_str + pos, _str + pos + len);_size -= len;}
采用缺省函数的方式,如果我们没有传入len,他就会等于npos,npos在C++中表示一个常数,表示不存在的位置。
如果len无传入或是len的长度不小于要删除的字符串长度,这都可以认为是要将pos位置后的字符串全部删除,此时便可直接在pos位置用'\0'。
使用npos需要在类中定义public成员变量:
public:
static const int npos;
以及在类外赋值:
const int string::npos = -1;
测试如下:

3.调整
在string中有一个方法可以兼备增加和删除两种简单功能,名为resize,它的作用是调整字符串:
传入一个参数n,和一个字符ch,如果当前字符串长度小于n,则扩容字符串长度至n个,并将多出的位置用字符ch填充,如果不传字符ch,则默认填充'\0'。
如果当前字符串长度大于n,则将字符串长度缩减到n:
//调整void resize(size_t n, char ch = '\0'){if (n <= _size){_str[n] = '\0';_size = n;}else{reserve(n);for (size_t i = _size; i < n; i++){_str[i] = ch;}_str[n] = '\0';_size = n;}}
测试如下:

4.交换
自己实现string类中的swap交换函数,有一个很好用的方法,那就是借用std库中的swap函数:
//交换void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}
因为std库中的swap函数是模版函数,可以进行任意类型的交换,所以我们直接投机取巧,将两者的成员变量依次进行交换,测试如下:

但是这样的写法并不是我们所熟悉的swap(s1,s2),所以我们可以通过函数重载扩展一下:
void swap(string& x, string& y){x.swap(y);}
值得注意的是这个函数要写在string类的外边,按照就近原则去调用它,否则会默认先调用库里的模版swap函数。
测试如下:

5.查找
string类中的查找也分为查找单个字符、查找字符串以及在指定的pos位置向后去查找,找到返回下标,找不到返回npos,所以依然要使用缺省函数:
//查找//单字符size_t find(char ch, size_t pos = 0) const{assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch)return i;}return npos;}//字符串size_t find(const char* str, size_t pos = 0) const{assert(pos < _size);const char* p = strstr(_str + pos, str);if (p)return p - _str;elsereturn npos;}
在查找字符串时我们使用到了strstr函数,其返回值为所找到的字符串的首字符指针。
测试如下:

6.截取
截取字符串方法substr,其作用是从字符串的pos位置开始向后截取len长度的字符,当然无论是pos位置还是长度len都可以没有,依然是缺省函数:
//截取string substr(size_t pos = 0, size_t len = npos){string sub;if (len >= _size - pos){for (size_t i = pos; i < _size; i++){sub += _str[i];}}else{for (size_t i = pos; i < pos + len; i++){sub += _str[i];}}return sub;}
测试如下:

7.比较
字符串直接的比较需要我们实现运算符重载:
bool operator==(const string& s1, const string& s2){int ret = strcmp(s1.c_str(), s2.c_str());return ret == 0;}bool operator<(const string& s1, const string& s2){int ret = strcmp(s1.c_str(), s2.c_str());return ret < 0;}bool operator<=(const string& s1, const string& s2){return s1 < s2 || s1 == s2;}bool operator>(const string& s1, const string& s2){return !(s1 <= s2);}bool operator>=(const string& s1, const string& s2){return !(s1 < s2);}bool operator!=(const string& s1, const string& s2){return !(s1 == s2);}
这一块的方法,我们建议实现在类外,定义两个参数,这样能够允许一个字符串和一个string对象进行比较。
因为在类内定义因为默认类内的成员函数的第一个参数都是隐藏的非静态string对象,所以静态的普通字符串传入就会使权限放大而出错。
测试如下:

四.流操作
直接上代码:
//流输出ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}//流提取istream& operator>>(istream& in, string& s){s.clear();char ch;ch = in.get();while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;}
输出较为简单,直接使用范围for循环输出。
而对于提取到的字符会直接覆盖s中原有的字符串,所以要先进行清除;此外,因为in默认会跳过空格和回车而不提取它们,这会导致死循环,所以我们使用in.get()函数来提取。
测试如下:

总结
关于string类及其内部常用方法的模拟实现就分享到这里啦。
最后希望能得到您的一键三连支持,我们下期再见!
相关文章:
C++——string模拟实现
前言:上篇文章我们对string类及其常用的接口方法的使用进行了分享,这篇文章将着重进行对这些常用的接口方法的内部细节进行分享和模拟实现。 目录 一.基础框架 二.遍历字符串 1.[]运算符重载 2.迭代器 3.范围for 三.常用方法 1.增加 2.删除 3.调…...
从零开始:神经网络(2)——MP模型
声明:本文章是根据网上资料,加上自己整理和理解而成,仅为记录自己学习的点点滴滴。可能有错误,欢迎大家指正。 神经元相关知识,详见从零开始:神经网络——神经元和梯度下降-CSDN博客 1、什么是M-P 模型 人…...
Python调用edge-tts实现在线文字转语音
edge-tts是一个 Python 模块,允许通过Python代码或命令的方式使用 Microsoft Edge 的在线文本转语音服务。 项目源码 GitHub - rany2/edge-tts: Use Microsoft Edges online text-to-speech service from Python WITHOUT needing Microsoft Edge or Windows or an…...
植物病害识别:YOLO甘蔗叶片病害识别分类数据集
YOLO甘蔗叶片病害识别数据集, 包含尾孢菌叶斑病,眼斑病,健康,红腐病,锈病,黄叶病6个常见病类别,3300多张图像,yolo标注完整,全部原始图像,未应用增强。 适用于CV项目&…...
pyqt QTextEdit 捕获enter按键
参考: https://blog.csdn.net/qq_27061049/article/details/101550616 方法一: 在PyQt中,可以通过重写QTextEdit的keyPressEvent()函数来捕获Enter按键。下面是示例代码: from PyQt5.QtWidgets import QApplication, QMainWindo…...
一劳永逸的方法解决:LNK1168无法打开 xxx.exe 进行写入 报错问题
这种错误的产生原因: 运行程序退出不是按正常流退出,是按窗口右上角的 “X” 来关闭程序,但是后台的xxx.exe控制台程序还在运行;修改程序的代码后再运行,就会报LNK1168的错误; 报错示例: 解决方…...
程序员的金三银四求职宝典:面试技巧分享
随着春天的到来,程序员们迎来了求职的旺季——金三银四。在这个时期,各大公司纷纷开放招聘,为求职者提供了丰富的选择机会。然而,如何在众多的面试中脱颖而出,成功获得心仪的职位,就需要掌握一些有效的面试技巧。下面,就让我们一起来探讨一下金三银四求职…...
【DevOps基础篇之k8s】如何应用Kubernetes中的Role Based Access Control(RBAC)
【DevOps基础篇之k8s】如何应用Kubernetes中的Role Based Access Control(RBAC) 目录 【DevOps基础篇之k8s】如何应用Kubernetes中的Role Based Access Control(RBAC)背景Kubernetes身份验证和授权基于角色的访问控制(RBAC)用户账户 vs. 服务账户角色 vs. 集群角色RoleBi…...
python知网爬虫论文pdf下载+立即可用(动态爬虫)
文章目录 使用代码 使用 自己工作需要,分享出来,刚刚修改完。 知需要修改keyword就可以完成自动搜索和下载同时翻页。 但是需要安装Chrome,也支持linux爬虫,也要安装linux Chrome非可视化版。 代码 import selenium.webdriver …...
DataFunSummit 2023:洞察现代数据栈技术的创新与发展(附大会核心PPT下载)
随着数字化浪潮的推进,数据已成为企业竞争的核心要素。为了应对日益增长的数据挑战,现代数据栈技术日益受到业界的关注。DataFunSummit 2023年现代数据栈技术峰会正是在这样的背景下应运而生,汇聚了全球数据领域的精英,共同探讨现…...
运行 Jmeter 文件生成 HTML 测试报告,我选择 ANT 工具
概述 ant 是一个将软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于 Java 环境中的软件开发。 在与 Jmeter 生成的 jmx 文件配合使用中,ant 会完成jmx计划的执行和生成jtl文件,并将jtl文件转化为html页面进行查看。 还可…...
TensorRT是什么,有什么作用,如何使用
TensorRT 是由 NVIDIA 提供的一个高性能深度学习推理(inference)引擎。它专为生产环境中的部署而设计,用于提高在 NVIDIA GPU 上运行的深度学习模型的推理速度和效率。以下是关于 TensorRT 的详细介绍: TensorRT 是 NVIDIA 推出的…...
同比和环比
1.同比就是今年的某时期与去年这个时期 进行对比 (消除季节性差异) 例子:2018年一季度销量 2019年一季度销量 上升/下滑 2.环比是今年的某个时期与当前上一个时期进行对比(两个时期是连续的) 例子:2024年1月 营收额1000万元 2024年2月营收额3000万元 同比增长...
js中批量修改对象属性
首先,有这个对象 let a {id: 1,name: 张三,age: 18,sex: 0 }需求:同时修改name,id,并添加一个新属性c 常规写法: a.id 2; a.name 李四; a.c 1;但这种写法遇到批量就会很麻烦 解决方法: 方法1: 使用Object.assi…...
应用案例 | Softing echocollect e网关助力汽车零部件制造商构建企业数据库,提升生产效率和质量
为了提高生产质量和效率,某知名汽车零部件制造商采用了Softing echocollect e多协议数据采集网关——从机器和设备中获取相关数据,并直接将数据存储在中央SQL数据库系统中用于分析处理,从而实现了持续监控和生产过程的改进。 一 背景 该企业…...
使用大带宽服务器对网站有什么好处?
近年来大带宽服务器频频出现在咱们的视野当中,选用的用户也在与日增长。那么究其主要原因是什么?租用大带宽服务器的好处又有哪些? 今天德迅云安全带您来了解下。1.有效提升网站访问速度 一般来说,正规的网站对用户体验度都是非常有讲究的,…...
17-Java解释器模式 ( Interpreter Pattern )
Java解释器模式 摘要实现范例 解释器模式(Interpreter Pattern)实现了一个表达式接口,该接口解释一个特定的上下文 这种模式被用在 SQL 解析、符号处理引擎等 解释器模式提供了评估语言的语法或表达式的方式,它属于行为型模式 …...
mysql的安装启动
下载 2.解压后放在某个目录下: 3.修改系统变量 4.修改配置文件 (创建一个ini文件放在解压后的目录下) 内容如下 5.初始化mysql 1.用管理员模式下输入: mysqld --initialize --console C:\WINDOWS\system32>mysqld --initia…...
[Java安全入门]三.CC1链
1.前言 Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强大的数据结构类型和实现了各种集合工具类。Commons Collections触发反序列化漏洞构造的链叫做cc链,构造方式多种,这里先学习cc1链…...
为什么虚拟dom比真实dom更快
虚拟DOM(Virtual DOM)之所以在某些情况下比直接操作真实DOM更快,主要有以下几个原因: 批量更新:虚拟DOM可以将多个DOM操作批量更新为一次操作。当需要对真实DOM进行多次修改时,直接操作真实DOM会导致浏览器…...
国产DCU卡实战:手把手教你用Docker部署通义千问Qwen2.5-7B推理服务
国产DCU卡实战:从零部署通义千问Qwen2.5-7B推理服务 在AI算力国产化浪潮中,海光DCU(Deep Computing Unit)作为国产高性能计算卡的代表,正逐步成为大模型推理部署的新选择。本文将带您完整走通在Hygon C86服务器上&…...
Embedding算法入门到精通:拆解腾讯二面必考题,收藏这一篇就够了!
👔面试官:RAG 里用的 Embedding 算法有哪些?你了解过几代演进? 🙋♂️我:Embedding 算法我知道,Word2Vec 嘛,把词变成向量。 👔面试官:Word2Vec 是 2013 …...
MinerU文档理解服务保姆级教程:错误识别案例复盘与提示词优化
MinerU文档理解服务保姆级教程:错误识别案例复盘与提示词优化 1. 引言:为什么你的文档识别总出错? 你是不是也遇到过这种情况:上传一份重要的合同文档,想让AI帮你提取关键条款,结果它却把甲方乙方搞混了&…...
get_iplayer代码架构分析:Perl模块化设计与核心功能实现原理
get_iplayer代码架构分析:Perl模块化设计与核心功能实现原理 【免费下载链接】get_iplayer A utility for downloading TV and radio programmes from BBC iPlayer and BBC Sounds 项目地址: https://gitcode.com/gh_mirrors/ge/get_iplayer get_iplayer作为…...
告别依赖管理噩梦:Renovate多语言依赖自动更新全攻略
告别依赖管理噩梦:Renovate多语言依赖自动更新全攻略 【免费下载链接】renovate Home of the Renovate CLI: Cross-platform Dependency Automation by Mend.io 项目地址: https://gitcode.com/GitHub_Trending/re/renovate 在现代软件开发中,依赖…...
如何高效集成DVA与Apollo Client:GraphQL状态管理终极指南
如何高效集成DVA与Apollo Client:GraphQL状态管理终极指南 【免费下载链接】dva 🌱 React and redux based, lightweight and elm-style framework. (Inspired by elm and choo) 项目地址: https://gitcode.com/gh_mirrors/dv/dva DVA是一个基于R…...
用于街头拍摄的摄像头
1 充电宝2 太阳能板我觉得用充电宝,物联网低功耗摄像头就可以了,因为录制的视频是等到拿回去以后再分析的,不用联网,联网是为了确保摄像头的安全,不被别人拿走。但是这个安装的过程:1 如果晚上12点去安装&a…...
告别玄学调试:用J-Flash给STM32芯片“洗个澡”,解决RT-Thread Studio下载疑难杂症
嵌入式开发实战:用J-Flash彻底解决STM32下载异常问题 当你满怀期待地点击"下载"按钮,RT-Thread Studio却无情地显示"执行完毕"而板子毫无反应时,那种挫败感每个嵌入式开发者都深有体会。更令人抓狂的是,编译器…...
多模态大模型混沌测试四大禁区(含图像噪声注入、音频时序扰动、文本语义漂移、跨模态对齐断连)
第一章:多模态大模型混沌工程实践 2026奇点智能技术大会(https://ml-summit.org) 多模态大模型在真实生产环境中面临图像、文本、语音、视频等异构输入的动态组合与不确定性扰动,传统可靠性验证手段难以覆盖其跨模态语义坍塌、注意力漂移与隐式模态冲突…...
计算机毕业设计:Python天气数据可视化与聚类预测系统 Flask框架 随机森林 K-Means 可视化 数据分析 大数据 机器学习 深度学习(建议收藏)✅
博主介绍:✌全网粉丝50W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业项目实战8年之久,选择我们就是选择放心、选择安心毕业✌ > 🍅想要获取完整文章或者源码,或者代做,拉到文章底部即可与…...
