[C++] string类常用接口的模拟实现

 
文章目录
- 1、前言
- 2、遍历
- 2.1 operator[ ]+下标方式
- 2.2 迭代器
- 2.3 范围for
- 2.4 c_str
 
- 3、容量相关
- 3.1 size(大小)
- 3.2 capacity(容量)
- 3.3 empty(判空)
- 3.4 clear(清理)
- 3.5 reserve
- 3.6 resize
 
- 4、增
- 4.1 push_back(尾插)
- 4.2 operator+=(char ch)
- 4.3 append
- 4.4 operator+=(char* str)
- 4.5 insert(任意位置插入)
- 4.5.1 任意位置插入字符
- 4.5.2 任意位置插入字符串
 
 
- 5、删
- 6、查
- 6.1 查找一个字符
- 6.2 查找一个字符串
 
- 7、字符串比较
- 8、流插入、流提取重载
- 8.1 流提取重载
- 8.2 流插入重载
 
1、前言
string上篇我们实现了 各类构造与赋值重载 接口,点这里看string类的介绍与构造的模拟实现
本篇我们对string常用的接口 增删查改+遍历 模拟实现一下。
以下就是要实现的接口:
namespace s
{class string{friend ostream& operator<<(ostream& _cout, const bit::string& s);friend istream& operator>>(istream& _cin, bit::string& s);public:typedef char* iterator;public://// iteratoriterator begin();iterator end();/// modifyvoid 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;/// capacitysize_t size()constsize_t capacity()constbool empty()constvoid resize(size_t n, char c = '\0');void reserve(size_t n);/// accesschar& operator[](size_t index);const char& operator[](size_t index)const;///relational operatorsbool 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;}
};
2、遍历
2.1 operator[ ]+下标方式
这种方式是我们最喜欢使用的一种,使用下标将字符逐个打印出来。
char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}//只读
const char& operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}int main()
{string s1("hello world");for (int i = 0; i < s1.size(); ++i){cout << s1[i] << " ";}cout << endl;return 0;
}

2.2 迭代器
string类的迭代器的底层是一个 char 原生指针*,string的迭代器使用方法就像是使用指针一样来用就可以了,但是不是所有的迭代器都是指针。(list,对于顺序表来说,并不是连续的空间,因此底层就不是指针)。
typedef char* iterator;
typedef const char* const_iterator;iterator begin()
{return _str;
}iterator end()
{return _str + _size;
}const_iterator begin() const
{return _str;
}const_iterator end() const
{return _str + _size;
}
我们来试一下迭代器的遍历:
int main()
{string s1("hello world");string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";++it;}cout << endl;return 0;
}
2.3 范围for
我们在之前就接触过范围for,使用起来很简单,这次我们来深究一下范围for。
int main()
{string s1("hello world");for (auto ch : s1)//范围for的底层就是迭代器,这里很严格,begin大小写变了都会出问题{cout << ch << " ";}cout << endl;return 0;
}
我们来看看汇编代码中迭代器与范围for。
 
 
我们可以看到在汇编代码中,迭代器与范围for都是调用了begin与end函数**,这里我们就能看到范围for的底层就是范围for。**
注意:范围for是傻瓜式调用,我们将迭代器的名称字符改为大写范围for都使用不了。
 
 
2.4 c_str

 由此我们知道,c_str返回的就是字符串以及末尾的 ‘\0’ ,因此我们就可以使用c_str来打印字符串。
const char* c_str() const
{return _str;
}
int main()
{cout << c_str << endl;return 0;
}

3、容量相关
3.1 size(大小)
直接返回字符串的大小,没什么讲的秒杀。
size_t size() const//对this不修改,加const保证安全
{return _size;
}
3.2 capacity(容量)
size_t capacity() const//对this不修改,加const保证安全
{return _capacity;
}
3.3 empty(判空)
bool empty() const//对this不修改,加const保证安全
{return _size == 0;
}
3.4 clear(清理)
void clear()
{_str[0] = '\0';_size = 0;
}
这里我们不需要删掉字符串内容,直接将字符串首元素改为 ‘\0’,大小置为0即可。
3.5 reserve

由此我们可以得到,reserve函数特点是只扩不缩的。扩大到容量为n。
因此我们在实现的时候,先判断 n是否大于capacity,如果小于就不处理,大于进行扩容。
void reserve(size_t n)//reserve只扩不缩
{if (n > _capacity){char* tmp = new char[n + 1];//多一个是\0的位置strcpy(tmp, _str);//strcpy会将\0一同拷贝过去delete[] _str;_str = tmp;_capacity = n;}
}
3.6 resize

由此我们可以看出resize是对字符串的size进行扩充的。
 当n小于当前字符串的大小时,将字符串缩短到n,保留字符串前n个字符。
 当n大于当前字符串的大小时,字符串长度就增加,如果有指定字符,就用指定字符对大于n的空间初始化,如果没有指定,就初始化为 ‘\0’。
 我们对比一下resize与reserve:
 resize是对size的更改,可扩可缩,并支持初始化,会间接影响容量的大小;
 reserve是对capacity的更改,只能扩容,不支持初始化。
思路:
 先判断 n是否大于size
 如果小于,就是缩小,直接将n位置改为 ‘\0’,size改为n即可;
 如果大于,还需要判断n是否超过capacity,不超过原地修改,超过直接复用reserve,对超出当前字符串长度的空间初始化为c,并将最后一个位置置为 ‘\0’(reserve只负责扩容,不做初始化)。
void resize(size_t n, char c = '\0')//c给为缺省最合适
{if (n <= _size){_str[n] = '\0';_size = n;}else{if(n > _capacity)reserve(n);while (_size < n){_str[_size] = c;++_size;}_str[_size] = '\0';}
}
测试:
int main()
{string s1("hello world");s1.resize(5);cout << s1 << endl;s1.resize(8, 'x');cout << s1 << endl;return 0;
}

4、增
4.1 push_back(尾插)
思路:
 先判满,如果满了(_size == _capacity),就扩容(二倍方式扩容),再进行尾插字符;
 没有满,直接尾插字符。
void push_back(char ch)
{if (_size == _capacity){//reserve(2 * _capacity);//直接给二倍的话,对于空字符串来说,//二倍还是0,扩容里面有释放空间,会出问题reserve(_capacity == 0 ? 4 : 2 * _capacity);//三目才正确}_str[_size] = ch;_size++;_str[_size] = '\0';//别忘了还有\0,size指的是最后一个字符的下一个位置
}
注意:对于扩容的传参我们使用三目操作符,防止直接对空字符串进行尾插时给二倍,相当于没有扩容。
4.2 operator+=(char ch)
+=字符比尾插更常用,很有必要实现。逻辑与尾插是一致的,复用push_back就可以。
string& operator+=(char ch)
{push_back(ch);return *this;
}
4.3 append

由此我们知道,append函数是在原有的字符串基础上追加字符串。
此函数不常用,我们仅实现第三个接口就可以了。
 思路:
 先判断容量是否足够,不够先扩容,但是append的扩容与尾插的扩容的大小不一样,尾插在原容量基础上扩二倍一定足够,这里是插入字符串,扩二倍不一定足够,我们来分析一下:
 判断容量是否足够其实不难,我们对追加的字符串计算长度(strlen(str)),看看被追加的字符串+追加的字符串长度(_size+len)是否大于_capacity,大于就扩容,复用reserve,传值 size+len;
 再使用strcpy函数,从字符串的_size位置开始,将追加的字符串拷贝到_str后面。
 代码实现:
void append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);//这里扩容不能是二倍,因为插入的字符串长度可能大于二倍}strcpy(_str + _size, str);//strcpy会将\0一起拷贝过去_size += len;
}
4.4 operator+=(char* str)
+=很常用,尾插字符串,直接复用刚写的append即可。
string& operator+=(const char* str)
{append(str);return *this;
}
4.5 insert(任意位置插入)

虽然insert的函数很多,但是常用的就两个,在任意位置插入字符或字符串,我们就来实现这两个。
4.5.1 任意位置插入字符
思路:
 1、判断pos位置是否合法。
 2、先判断是否需要扩容,与尾插一样;
 3、挪动数据,从_size位置开始,依次往后挪,直到到pos位置停下来;
 4、在pos位置插入数据,再++_size。
 
代码实现:
string& insert(size_t pos, char ch)
{assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}size_t end = _size + 1;while (end > pos)//size_t是无符号整型,头插时end走到-1还是会进去,形成死循环{_str[end] = _str[end - 1];--end;}_str[end] = ch;_size++;return *this;
}
4.5.2 任意位置插入字符串
思路与任意位置插入字符是类似的,只需要注意一下边界就好了。
 
代码实现:
string& insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}//挪动数据int end = _size;while (end >= (int)pos)//这里也存在提升,end变为-1仍然进去循环,因此需要强转{_str[end + len] = _str[end];--end;}strncpy(_str + pos, str, len);_size += len;return *this;
}
5、删

这里的npos我们再来看看
 
我们可以看到文档中nops是size_t类型(无符号整型),值为-1,无符号整型的-1就是int类型的max。
 对于erase函数,我们仅实现常用的第一个接口就可以。
 文档中可以看到,erase是从pos位置开始往后删长度为len个字符。
 这里erase我们要分情况来讨论:
 情况一:len == npos 或者 pos+len >= _size,那么就是从pos位置到末尾全部删除,我们直接将pos位置改为 ‘\0’,_size改为pos即可。
 
情况二:删除其中的一段,我们定义 begin=pos+len,_str[begin-len] = _str[begin],begin++不断挪动数据,当 begin==_size 的时候挪动完数据。因此循环结束的条件是 begin<=_size,这时把 ‘\0’ 也就挪过去了。
 
string& erase(size_t pos, size_t len = npos)
{assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{size_t begin = pos + len;while (begin <= _size){_str[begin - len] = _str[begin];++begin;}_size -= len;}return *this;
}
6、查

6.1 查找一个字符
由文档中我们可以知道,找到后返回的是字符的位置,即就是下标。如果没有找到返回npos。
size_t find(char c, size_t pos = 0) const
{for (size_t i = pos; i < _size; ++i){if (_str[i] == c)return i;}return npos;
}
6.2 查找一个字符串
我们使用strstr来找。
size_t find(const char* s, size_t pos = 0) const
{const char* p = strstr(_str + pos, s);if (p){return p - _str;}else{return npos;}
}
7、字符串比较
比较的本质是ASCII码值,因此我们套用strcmp就可以。
bool operator<(const string& s)
{return strcmp(_str, s._str) < 0;
}bool operator==(const string& s) const
{return strcmp(_str, s._str) == 0;
}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);
}bool operator!=(const string& s)
{return !(*this == s);
}
写好<与==,其他的直接复用就可以。
8、流插入、流提取重载
8.1 流提取重载
流提取使用在赋值或初始化上,因此,在赋值前我们先对空间清理一下,在对变量赋值。
istream& operator>>(istream& _cin, string& s)
{s.clear();char buff[129];//这样可以避免扩容问题size_t i = 0;char ch;//_cin >> ch//本身是拿不到' '或'\n'的,因此循环条件就判断不了ch = _cin.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 128){buff[i] = '\0';s += buff;i = 0;//下一轮}//_cin >> ch;ch = _cin.get();}if (i != 0){buff[i] = '\0';s += buff;}return _cin;
}
8.2 流插入重载
ostream& operator<<(ostream& _cout, const string& s)//for (int i = 0; i < s.size(); ++i)//{//	_cout << s[i];//}for (auto ch : s){_cout << ch;}return _cout;
}
*** 本篇结束 ***
相关文章:
 
[C++] string类常用接口的模拟实现
文章目录 1、前言2、遍历2.1 operator[ ]下标方式2.2 迭代器2.3 范围for2.4 c_str 3、容量相关3.1 size(大小)3.2 capacity(容量)3.3 empty(判空)3.4 clear(清理)3.5 reserve3.6 res…...
每日一学——防火墙
防火墙是网络安全的重要组成部分,可以帮助保护网络免受恶意攻击和未经授权的访问。以下是防火墙的基本配置步骤: 定义安全策略:防火墙通过安全策略来决定允许或拒绝网络流量。你需要定义适当的安全策略来保护你的网络。安全策略通常包括源IP地…...
 
常用数据库备份方法,sql数据库备份方法
在信息时代,数据成为了公司的主要资产。然而,数据的安全性和完整性也成为企业管理的重要组成部分。因此,数据库备份至关重要。本文将详细介绍几种常见的数据库备份方法。 全备份 全备份是指数据库中所有数据的备份,包括数据文件、…...
 
常见前端面试之VUE面试题汇总八
22. Vue 子组件和父组件执行顺序 加载渲染过程: 1.父组件 beforeCreate 2.父组件 created 3.父组件 beforeMount 4.子组件 beforeCreate 5.子组件 created 6.子组件 beforeMount 7.子组件 mounted 8.父组件 mounted 更新过程: 1. 父组件 befor…...
 
弯道超车必做好题集锦二(C语言选择题)
前言: 编程想要学的好,刷题少不了,我们不仅要多刷题,还要刷好题!为此我开启了一个弯道超车必做好题锦集的系列,每篇大约10题左右。此为第二篇选择题篇,该系列会不定期更新,后续还会…...
 
PROFIBUS主站转MODBUS TCP网关
1.产品功能 YC-DPM-TCP网关在Profibus总线侧实现主站功能,在以太网侧实现ModbusTcp服务器功能。可将Profibus DP从站接入到ModbusTcp网络;通过增加DP/PA耦合器,也可将Profibus PA从站接入ModbusTcp网络。YC-DPM-TCP网关最多支持125个Profibu…...
 
【力扣】盛最多水的容器
目录 题目 题目初步解析 水桶效应 代码实现逻辑 第一步 第二步 第三步 代码具体实现 注意 添加容器元素的函数 计算迭代并且判断面积是否是最大值 总代码 运行结果 总结 题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是…...
 
【SQL应知应会】索引(三)• MySQL版:聚簇索引与非聚簇索引;查看索引与删除索引;索引方法
欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享,与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习,有基础也有进阶,有MySQL也有Oracle 索引 • MySQL版 前言一、索引1.简介2.索引类型之逻…...
 
rtmp直播
技术要求:nginxnginx-rtmpffmpegVLC 跟着大佬走的: 传送门 准备工作: 首先需要一台公网ip的服务器 这是使用天翼云的弹性云主机:免费试用1个月 天翼云官网 点击关机,更多里面选择重置密码, 默认用户名为…...
 
4.14 tcp_tw_reuse 为什么默认是关闭的?
开启 tcp_tw_reuse 参数可以快速复用处于 TIME_WAIT 状态的 TCP 连接时,相当于缩短了 TIME_WAIT 状态的持续时间。 tcp_tw_reuse 是什么? TIME_WAIT 状态的持续时间是 60 秒,这意味着这 60 秒内,客户端一直会占用着这个端口。端…...
Python数据分析和爬虫:解析数据的强大工具
引言: 在当今数据爆炸的时代,数据分析和数据提取变得越来越重要。作为一种简洁而强大的编程语言,Python在数据分析和爬虫领域有着广泛的应用。本文将详细介绍Python在数据分析和爬虫中的常用库和技术,并探讨其在实际应用中的优势…...
机器学习之SGD(Stochastic Gradient Descent,随机梯度下降)
SGD(Stochastic Gradient Descent,随机梯度下降)是深度学习中最基本的优化算法之一。它是一种迭代式的优化方法,用于训练神经网络和其他机器学习模型。以下是关于SGD优化器的重要信息: 基本原理:SGD的基本思…...
leetcode做题笔记100. 相同的树
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 思路一: bool isSameTree(struct TreeNode* p, struct TreeNode* q){if(p NULL &…...
 
【Hadoop】Hadoop入门概念简介
🍁 博主 "开着拖拉机回家"带您 Go to New World.✨🍁 🦄 个人主页——🎐开着拖拉机回家_Linux,Java基础学习,大数据运维-CSDN博客 🎐✨🍁 🪁🍁 希望本文能够给您带来一定的…...
前端监控之异常监控(一)
前言 当我们的项目中假设出现了下面几种场景: 点击按钮后,页面无响应页面跳转后显示白屏页面卡顿...... 这些情况都是非常影响用户体验的,对于用户来说,是难以接受的,用户可能就此流失掉了。 因此前端非常有必要针对…...
 
sql server 、mysql CTE 公用表表达式
sql server 详细 mysql CTE CTE 是一个命名的临时结果集,作用范围是当前语句。CTE可以理解成一个可以复用的子查询,当然跟子查询还是有点区别的,CTE可以引用其他CTE,但子查询不能引用其它子查询。所以,开发中建议…...
Oracle dataguard 和Oracle rac的区别和联系
RAC服务器共用一套存储,同时提供服务,没有主备之分.宕一个其它的可以继续服务. 双机热备,共用一套存储,一个提供服务一个备份,主机宕了切换到备份服务器提供服务. data guard 完全两套系统,存储是单独的,用日志同步. RAC: 实例层冗余 DG :数据库层冗…...
JUC工具类-LockSupport概述
前言  多线程并发场景中,时常需要线程协同,故而需要对当前线程进行阻塞,并唤醒需要协同的线程来一起完成任务。  通常处理方式有三种: 1)Synchronized加锁的线程 使用Object类下所提供的方法: wai…...
 
大数据:AI大模型对数据分析领域的颠覆(文末送书)
随着数字化时代的到来,大数据已经成为了各行各业中不可或缺的资源。然而,有效地分析和利用大数据仍然是一个挑战。在这个背景下,OpenAI推出的Code Interpreter正在对数据分析领域进行颠覆性的影响。 如何颠覆数据分析领域?带着这…...
 
CEdit 选中文字实时更新到另一个控件中
有时候,我们会遇到需求,软件中需要让选中一个CEdit控件中的文字实时更新到另一个控件中,实现效果如下所示: 代码如下: BOOL CEditDemoDlg::PreTranslateMessage(MSG* pMsg) { CEdit* pOldEdit (CEdit*)GetDlgIte…...
 
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
 
cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
 
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
 
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
 
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
 
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
 
浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
 
【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
