C++库函数——String类的模拟实现

目录

①String类的主体

②String类的具体实现

1.构造函数、拷贝构造函数、赋值运算符、析构函数

⑴构造函数

⑵拷贝构造函数

⑶赋值运算符

⑷析构函数

2.迭代器(范围for的实现原理)

3.修改:push_back, apppend, +=, clear, swap, c_str

⑴push_back

⑵apppend

⑶+=

⑷clear

⑸swap

⑹c_str

4.容量:size, capacity, empty, resize, reverse

⑴size

⑵capacity

⑶empty

⑷resize

⑸reverse

5.下标[]重载

6.逻辑运算符:<, <=, >, >=, ==, !=

7.查找:find

8.固定位置插入元素:insert

9.清除固定位置元素:erase

10.流插入、流提取运算符重载:<<, >>

⑴流插入<<

⑵流提取>>


①String类的主体

为了与库函数里面的string分开,我们将模拟实现的string类存放在自己定义的命名空间中,同时尽可能的将对应功能函数的名字与库函数一一对应,即

namespace my_string
{class string{friend ostream& operator<<(ostream& _cout, const my_string::string& s);friend istream& operator>>(istream& _cin, my_string::string& s);public:typedef char* iterator;typedef const char* const_iterator;public:// 构造函数string(const char* str = "");// 拷贝构造函数string(const string& s);// 赋值运算符重载string& operator=(const string& s);// 析构函数~string();// 迭代器iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;// 修改void push_back(char c);string& operator+=(char c);void append(const char* str);string& operator+=(const char* str);void clear();void swap(string& s);const char* c_str()const;// 容量size_t size()const;size_t capacity()const;bool empty()const;void resize(size_t n, char c = '\0');void reserve(size_t n);// 下标char& operator[](size_t index);const char& operator[](size_t index)const;// 逻辑运算符bool operator<(const string& s);bool operator<=(const string& s);bool operator>(const string& s);bool operator>=(const string& s);bool operator==(const string& s);bool operator!=(const string& s);// 返回c在string中第一次出现的位置size_t find(char c, size_t pos = 0) const;// 返回子串s在string中第一次出现的位置size_t find(const char* s, size_t pos = 0) const;// 在pos位置上插入字符c/字符串str,并返回该字符的位置string& insert(size_t pos, char c);string& insert(size_t pos, const char* str);// 删除pos位置上的元素,并返回该元素的下一个位置string& erase(size_t pos, size_t len);private:char* _str;size_t _capacity;size_t _size;};};

②String类的具体实现

1.构造函数、拷贝构造函数、赋值运算符、析构函数

⑴构造函数

// 构造函数
string(const char* str = "")
{_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];// 字符串后有'\0'因此要+1memcpy(_str, str, sizeof(char) * (_size + 1));// 与上述同理
}

⑵拷贝构造函数

// 拷贝构造函数
string(const string& s)
{_size = s._size;_capacity = s._capacity;// 这里不能直接让_str=s._str(即浅拷贝),// 不然会导致两个string最后析构时对它们指向的空间析构两次// 此外,如果其中一个对象对这片空间的元素进行修改,会对另一个对象造成影响// 因此,此处只能使用深拷贝_str = new char[s._capacity + 1];memcpy(_str, s._str, sizeof(char) * (s._size + 1));
}

⑶赋值运算符

在模拟实现赋值运算符时,有两种写法,一种是与拷贝构造逻辑类似的写法,即

// 赋值运算符重载
string& operator=(const string& s)
{if (this != &s)// 与拷贝构造逻辑相同{_size = s._size;_capacity = s._capacity;_str = new char[s._capacity + 1];memcpy(_str, s._str, sizeof(char) * (s._size + 1));}return *this;
}

另一种则是通过拷贝构造一个临时对象tmp,将this与tmp所指向的空间交换,在结束时tmp还能顺便释放自己所指向的空间,即

void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}// 赋值运算符重载
string& operator=(const string& s)
{if (this != &s){string tmp(s);swap(tmp);}return *this;
}

图示如下

 从图中我们可以明显看出,在此函数调用完后,tmp也会顺带将s1先前所指向的空间释放 

⑷析构函数

// 析构函数
~string()
{_size = _capacity = 0;delete[] _str;_str = nullptr;
}

2.迭代器(范围for的实现原理)

从string类的主体中我们不难看出,string类的迭代器其实就是指针,即

这是因为string类的对象本身处在一个连续的空间内,可以直接通过指针来操控,因此我们便可以得到

// 迭代器
iterator begin()
{return _str;
}iterator end()
{return _str + _size;
}const_iterator begin() const
{return _str;
}const_iterator end() const
{return _str + _size;
}

在这里我们可以看看范围for是如何做到的,首先我们直接使用有

而当我们将任意一个迭代器(begin或end注释掉后可以发现) 

因此我们可以发现,其实所谓的范围for不过就是将这段代码固定的替换成如下这段代码

my_string::string::iterator it = s.begin();
while (it != s.end())
{cout << *it;it++;
}

而且范围for只会固定的寻找end和begin名字的迭代器,略微修改名字也会报错

3.修改:push_back, apppend, +=, clear, swap, c_str

⑴push_back

void push_back(char c)
{// 当size与capacity相等时需要扩容if (_size == _capacity){// 扩容逻辑与reserve类似char* tmp = new char[_capacity * 2];memcpy(tmp, _str, sizeof(char) * _size);delete[] _str;_str = tmp;_capacity *= 2;}_str[_size++] = c;
}

⑵apppend

void append(const char* str)
{// 与push_back的插入逻辑类似// 但是扩容大小需要作出变化size_t len = strlen(str);if (len + _size >= _capacity){reserve(2 * (len + _capacity));}for (size_t i = 0; i < len; i++){push_back(str[i]);}
}

⑶+=

// 直接复用即可
string& operator+=(char c)
{push_back(c);return *this;
}string& operator+=(const char* str)
{append(str);return *this;
}

⑷clear

void clear()
{// 容量一般不缩小,以防重复开辟空间降低效率_str[0] = '\0';_size = 0;
}

⑸swap

void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}

⑹c_str

const char* c_str()const
{return _str;
}

4.容量:size, capacity, empty, resize, reverse

⑴size

size_t size()const
{return _size;
}

⑵capacity

size_t capacity()const
{return _capacity;
}

⑶empty

bool empty()const
{return _size == 0;
}

⑷resize

在这里分为多种情况,如图

实现如下

void resize(size_t n, char c = '\0')
{if (n < _size){_str[_size] = '\0';// 直接将截止位置提前到size的位置即可}else{// 如果是情况三先进行扩容,然后统一插入数据if (n > _capacity){reserve(n);}for (int i = _size; i < n; i++){_str[i] = c;}}_size = n;
}

⑸reverse

在这里,我们一般不选择将capacity缩小,因为缩小也是需要代价的(可能会开辟一片新的空间),因此实现如下

void reserve(size_t n)
{if (n > _capacity){// 扩容可能会开辟新空间char* tmp = new char[n + 1];memcpy(tmp, _str, sizeof(char) * (_size + 1));delete[] _str;_str = tmp;_capacity = n;}
}

5.下标[]重载

// 下标
char& operator[](size_t index)
{assert(index < _size);//此处因为传入参数为size_t,因此不需要判断index是否小于0return *(_str + index);
}const char& operator[](size_t index)const
{assert(index < _size);return *(_str + index);
}

6.逻辑运算符:<, <=, >, >=, ==, !=

字符串比较大小的规则是逐字符比较ASCII码值,相等的话就比较下一个,在这里两个字符串相比较也会有多种情况,如下图所示

我们可以实现任一比较大小和相等之后,不断复用来做到简化,即

// 逻辑运算符
bool operator<(const string& s)
{int i = 0;// 一直迭代到两字符不相等或下标i越界while (i < _size && i < s.size() && s[i] == _str[i]){i++;}// 如果下标i超过了string1的size,需要进行特殊判断if (i >= _size){//如果两个字符串的大小相等说明此时它们均相同if (_size == s.size()){return false;}else//反之表示string1截止在i这个下标处,string1<string2{return true;}}// 如果下标i超过了string2的size,此时string1始终是>或=string2的// 因此返回falseif (i >= s.size()){return false;}if (_str[i] < s[i]){return true;}else{return false;}
}bool operator<=(const string& s)
{return (*this < s || *this == s);
}bool operator>(const string& s)
{return !(*this <= s);
}bool operator>=(const string& s)
{return (*this > s || *this == s);
}bool operator==(const string& s)
{int i = 0;while (i < _size && i < s.size() && s[i] == _str[i]){i++;}// 此时,下标i要么越界,要么就在两个字符串中分别指向不同的字符// 因此,只有当i越界,且两个字符串大小相等时,才能相等,其余情况都不行if ((i >= _size || i >= s.size()) && _size == s.size()){return true;}else{return false;}
}bool operator!=(const string& s)
{return !(*this == s);
}

7.查找:find

// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const
{assert(pos < _size);int i = 0;// 寻找到与c相同的字符或者越界为止while (i < _size && _str[i] != c){i++;}if (_str[i] == c){return i;}else{return -1;}
}// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const
{assert(pos < _size);const char* tmp = strstr(_str + pos, s);if (tmp != nullptr){return tmp - s;}else{return -1;}
}

8.固定位置插入元素:insert

这里先实现插入单个字符

// 在pos位置上插入字符c,并返回该字符
string& insert(size_t pos, char c)
{assert(pos <= _size);// 检查是否需要扩容if (_size == _capacity){reserve(2 * _capacity);}// 从最后一个数据开始将每个数据向后挪动int i = 0;for (i = _size; i > pos; i--){_str[i] = _str[i - 1];}_str[i] = c;_size++;return *this;
}

然后是实现插入一串子串,举例如下

// 在pos位置上插入字符串str,并返回该字符的位置
string& insert(size_t pos, const char* str)
{assert(pos <= _size);int len = strlen(str);if (_size + len >= _capacity){reserve(2 * (_size + len));}// 向后挪位置腾出空间int i = 0;// 在这里i与pos比较时会发生隐式类型转换,因此还需要添加一个条件for (i = _size - 1; i != -1 && i >= pos; i--){_str[i + len] = _str[i];}// 从pos位置开始将str的内容拷贝到string中memcpy(_str + pos, str, sizeof(char) * len);_size += len;return *this;
}

9.清除固定位置元素:erase

// 删除pos位置上的元素,并返回字符
string& erase(size_t pos, size_t len)
{assert(pos < _size);// 如果pos+len超过了_size,则表示要将pos及其之后的字符全部删除if (pos + len >= _size){_str[pos] = '\0';_size = pos;}else{int i = 0;// 从第pos+len的位置开始将数据分别向前挪动覆盖for (i = pos+len; i < _size; i++){_str[i - len] = _str[i];}_size -= len;}return *this;
}

10.流插入、流提取运算符重载:<<, >>

⑴流插入<<

friend ostream& operator<<(ostream& _cout, const my_string::string& s)
{for (char c : s){cout << c;}cout << endl;return _cout;
}

⑵流提取>>

friend istream& operator>>(istream& _cin, my_string::string& s)
{// 在每次输入前需要对缓冲区作出处理s.clear();// 删除存在于最前方的空格与换行符char ch = _cin.get();while (ch == ' ' || ch == '\n'){ch = _cin.get();}// 在这里,我们创建一个临时的缓冲器来存放s// 这样的话每次插入字符数只有超过了128时,将其尾插到s中// 之后归零i,从而避免扩容,以此提高插入效率char buff[128] = { 0 };int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;// 当i到达127时将此位置置为'\0',然后将i重置为0if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = _cin.get();}if (i != 0){buff[i] = '\0';s += buff;}return _cin;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/18858.shtml

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

相关文章

如何设计一个自动化测试框架?

一个成熟的测试框架主要由 4 部分组成&#xff1a;基础模块、管理模块、运行模块和统计模块 基础模块 底层核心库 一般指用于操作被测试应用程序的第三方库&#xff0c;例如在 Web 端的 Selenium/WebDriver。如API端的Requests 对象库 PO模式中的页面对象 可重用组件 如一些…

Spring系列二:基于注解配置bean【建议收藏】

文章目录 &#x1f497;通过注解配置bean&#x1f35d;基本介绍&#x1f35d;快速入门&#x1f35d;注意事项和细节 &#x1f497;自己实现Spring注解配置Bean机制&#x1f35d;需求说明&#x1f35d;思路分析&#x1f35d;注意事项和细节 &#x1f497;自动装配 Autowired&…

特性快闪:使用 Databend 玩转 Iceberg

作者&#xff1a;尚卓燃&#xff08;PsiACE&#xff09;澳门科技大学在读硕士&#xff0c;Databend 研发工程师实习生 Apache OpenDAL(Incubating) Committer https://github.com/PsiACE 几周前&#xff0c;Databricks 和 Snowflake 召开了各自的年度大会&#xff0c;除了今年一…

【WebRTC---序篇】(七)RTC多人连麦方案

服务端可以选择mediasoup&#xff0c;作为SFU服务器&#xff0c;只负责转发数据 下图举例三个Client (browser或者客户端)同时加入一个房间&#xff0c;每个app同时发布一路视频和一路音频&#xff0c;并且接受来自其他app的音视频流&#xff0c;mediasoup内部的结构如下&…

python爬虫基础

文章目录 前言爬虫简介urllib库的使用如何获取网页的源码一个类型六个方法一个类型六个方法1、read()方法2、readline()方法3、readlines()方法4、getcode()5、geturl()6、getheaders() urllib下载下载网页下载图片下载视频 请求对象的定制 未完待续 前言 爬虫爬的好牢饭吃的早…

Linux编辑器 - vim使用

1.vim的基本概念 Vim是一个广泛使用的文本编辑器&#xff0c;它是在Unix和Linux系统中常用的命令行文本编辑器之一。 vim的主要三种模式 ( 其实有好多模式&#xff0c;目前掌握这 3 种即可 ), 分别是 命令模式 &#xff08; command mode &#xff09;、 插入模式 &#xff0…

/bin/bash: Resource temporarily unavailable

有现场反馈plsql无法连接数据库了&#xff0c;登录环境查看时发现从root切换到grid时报错/bin/bash: Resource temporarily unavailable [rootdb1 ~]# su - grid Last login: Thu Jul 27 18:45:04 CST 2023 su: failed to execute /bin/bash: Resource temporarily unavailab…

瑞吉外卖项目----(2)缓存优化

1 缓存优化 1.0 问题说明 1.1 环境搭建 将项目推送到远程仓库里&#xff0c;教程在git 提交远程仓库前建议取消代码检查 创建新的分支v1.0&#xff08;用于实现缓存优化&#xff09;并推送到远程仓库 1.1.1 maven坐标 导入spring-data-redis的maven坐标&#xff1a; &l…

2023年DevOps和云趋势报告!

要点 ●云创新已从革命性阶段转变为演进性阶段&#xff0c;重点是迁移和重新架构工作负载。云空间已发展为提供对可扩展资源和托管服务的按需访问&#xff0c;强调简化交互并减少团队的认知负担。 ●人工智能 (AI) 和大型语言模型 (LLM) 可以通过解决认知过载问题并支持即时管…

【ChatGLM_01】ChatGLM2-6B本地安装与部署(大语言模型)

基于本地知识库的问答 1、简介&#xff08;1&#xff09;ChatGLM2-6B&#xff08;2&#xff09;LangChain&#xff08;3&#xff09;基于单一文档问答的实现原理&#xff08;4&#xff09;大规模语言模型系列技术&#xff1a;以GLM-130B为例&#xff08;5&#xff09;新建知识库…

AgileBoot - 全栈项目启动

AgileBoot-Back-End: 基于Ruoyi做了大量重构优化的基础快速开发框架。采用Springboot Vue 3 Mybatis Plus 更面向对象的业务建模 面向生产的项目。&#xff08;非玩具项目&#xff09; 首先克隆代码&#xff0c;同是克隆前端和后端的代码。 前端代码启动&#xff1a; np…

飞桨AI Studio可以玩多模态了?MiniGPT4实战演练!

MiniGPT4是基于GPT3的改进版本&#xff0c;它的参数量比GPT3少了一个数量级&#xff0c;但是在多项自然语言处理任务上的表现却不逊于GPT3。项目作者以MiniGPT4-7B作为实战演练项目。 创作者&#xff1a;衍哲 体验链接&#xff1a; https://aistudio.baidu.com/aistudio/proj…

uniApp 对接安卓平板刷卡器, 读取串口数据

背景: 设备: 鸿合 电子班牌 刷卡对接 WS-B22CS, 安卓11; 需求: 将刷卡器的数据传递到自己的App中, 作为上下岗信息使用, 以完成业务; 对接方式: 1. 厂家技术首先推荐使用 接收自定义广播的方式来获取, 参考代码如下 对应到uniApp 中的实现如下 <template><view c…

阿里云域名备案

最好的爱情&#xff0c;不是因为我们彼此需要在一起&#xff0c;而是因为我们彼此想要在一起。 阿里云的域名如何备案&#xff0c;域名备案和ICP备案一样吗&#xff1f;&#xff1f; 截至我所掌握的知识&#xff08;2021年9月&#xff09;&#xff0c;阿里云的域名备案和ICP备案…

《cuda c编程权威指南》04 - 使用块和线程索引映射矩阵索引

目录 1. 解决的问题 2. 分析 3. 方法 4. 代码示例 1. 解决的问题 利用块和线程索引&#xff0c;从全局内存中访问指定的数据。 2. 分析 通常情况下&#xff0c;矩阵是用行优先的方法在全局内存中线性存储的。如下。 8列6行矩阵&#xff08;nx,ny&#xff09;&#xff08;…

针对高可靠性和高性能优化的1200V硅碳化物沟道MOSFET

目录 标题&#xff1a;1200V SiC Trench-MOSFET Optimized for High Reliability and High Performance摘要信息解释研究了什么文章创新点文章的研究方法文章的结论 标题&#xff1a;1200V SiC Trench-MOSFET Optimized for High Reliability and High Performance 摘要 本文详…

Arcgis画等高线

目录 数据准备绘制等高线3D等高线今天我们将学习如何在ArcGIS中绘制等高线地图。等高线地图是地理信息系统中常见的数据表现形式,它通过等高线将地形起伏展现得一目了然,不仅美观,还能提供重要的地形信息。 数据准备 在开始之前,确保已经准备好了高程数据,它通常以栅格数…

SolidWorks二次开发系列入门100篇之97-极点坐标

什么是极点 一个模型中的极点是指在某个方向上的最高或最低点。在三维模型中&#xff0c;通常有三个方向&#xff1a;x轴、y轴和z轴。因此&#xff0c;在x轴&#xff0c;y轴和z轴的正方向和负方向上&#xff0c;每个模型可能都有两个极点。极点通常是一些锐角或骨刺&#xff0…

【雕爷学编程】Arduino动手做(180)---Seeeduino Lotus开发板

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

C# XML文档相关操作

C# 创建XML文档 XML文档知识点创建XML文档向XML中追加读取XML文档读取带属性的XML文档删除节点 XML文档知识点 XML 是可扩展的标记语言 XML:用来存储数据 注意点&#xff1a;XML是严格区分大小写的&#xff0c;XML标签也是成对出现的 XML文档有且只能有一个根节点&#xff1b;…