String的增删查【C++】

news2024/12/26 0:02:05

String的增删查【C++】

  • 前言
  • string的增删查改
    • 构造与析构
      • 构造string(const char* str = "")
      • 赋值构造string(const string& s1)
    • 赋值重载
    • 析构函数
      • reserve
      • push_back
      • append
      • +=
      • insert
      • erase
    • 迭代器
    • 流插入流提取
      • 流插入
      • 流提取

前言

从这里开始可以算是进入了STL的学习中。

由于前面摸鱼摸得太狠了,国庆将会进行疯狂的补习。

这次就先带来string的实现。

这篇博客的本心主要是为了带大家了解string的底层逻辑
并不是对string的完美模拟实现

所以这里就打算挑几个比较常用的进行实现

string的增删查改

想要进行string的功能实现
首先就应该对string的类结构进行了解。

string在使用的过程中有点类似于动态的字符数组
能对字符串进行删除和增加数据。

说起动态的数组实现,我们应该能自然的想到顺序表

能进行扩容和计数,在内存上也是连续存储,符合数组的存储方式。

所以以顺序表为原型

namespace my_str
{
	class string
	{
		public:
	

		private:
		size_t _size;
		size_t _capacity;
		char* _str;
	};
	
}

构造与析构

设计好结构后,就可以进行功能的实现了

首先肯定是构造和析构

构造string(const char* str = “”)

当我们平常使用string时

大部分人可能都是这样去进行构造的把(这里就先不考虑流提取)
string s1("asd");

就是用常量字符串对string进行初始化。

那这样我们就可以写出构造函数的声明形式了。

string(const char* str = "")
给个缺省值,这样同时还能实现无参数的初始化。
string s1;

然后我们就能进行实现了。

string(const char* str = "")
//初始化列表
	:_size(strlen(str))//用常量字符串的长度进行赋值
	, _capacity(_size)//用常量字符串的长度进行赋值
	, _str(new char[_size + 1])//new一个新的char数组
{
	strcpy(_str, str);//将常量字符串的值拷贝给char数组中完成初始化。
}

这其实和顺序表的初始化顺序大差不差

就是多了一个strcpy对char数组初始化

——————————————————————————————————

赋值构造string(const string& s1)

复制构造也和顺序表一样。

不能用浅拷贝,而是要用深拷贝,因为向栈区申请了新的空间。

string(const string& s1)
{
	_capacity = s1._capacity;
	_str = new char[_capacity + 1];//申请新的空间
	_size = s1._size;
	strcpy(_str, s1._str);
}

赋值重载

string& operator=(string& s1)

按照常规来说,实现的方法
1.创建一个一样大的空间
2.拷贝数组内容
3.复制_size,_capacity
4.删除原空间

但是这里带来个更好的方法

在std中,自带了一个深度拷贝的交换函数。

所以我们可以用赋值对象构造新的一个对象。

然后用新对象赋值给老对象

		string& operator=(string& s1)
		{
			if (this != &s1)
			{
				string tmp(s1);
				std::swap(_str, tmp._str);
				std::swap(_size, tmp._size);
				std::swap(_capacity, tmp._capacity);
			}
			return *this;       
		}

这样可以说是更加方便。

析构函数

析构函数也和顺序表一样,没有啥特别的难度。

~string()
{
	_size = 0;
	_capacity = 0;
	delete[] _str;
	_str = NULL;
}

讲完了构造和析构
终于到了功能的实现。

reserve

当然在讲增加之前
最重要的扩容可不能落下了。

可以说扩容是表动态的前提。

这里讲一下扩容的思路

向堆区申请一块新的空间
然后将原数组的内容进行拷贝
将原数组进行释放就可以了。

void reserve(size_t n//需要多少空间)//扩容
{
	if (n > _capacity)//判断新开辟的空间是否大于原空间
	{
		char* new_ptr = new char[n + 1];//申请新空间
		strcpy(new_ptr, _str);//拷贝内容
		delete[] _str;//释放原数组
		_str = new_ptr;
		_capacity = n;

	}
}

push_back

接下来就到了激动人心的尾插环节了。
在这里插入图片描述
这个就是基本思路了。

比顺序表多了个\0
所以对\0进行考虑

	void push_back(char a)// 二倍扩容
	{
		if (_capacity == _size)
		{
			reserve(_capacity * 2);
		}
		_str[_size] = a;//赋上需要的值
		_size++;
		_str[_size] = '\0';//补上\0
	}

append

append功能就是进行字符串的尾插。

比如

my_str::string s1("asd");
s1.append("asd");

就是对s1字符串的尾部插入asd

类似于push_back的实现方法,但是两个字符串相接有一个好处:
就是不用考虑最后的’‘\0’’
只需要把前一个字符串的尾部的零覆盖掉就行了

		void append(const char* a="")//扩到新字节+_size
		{
			size_t len = strlen(a);//提取要插入的字符串长度
			if (len + _size > _capacity)//进行扩容判断
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, a);//进行覆盖
			_size += len;//++size
		}

+=

+=可以说是append和push_back的集大成者。

但这也说明了+=可以通过append和push_back进行实现。

		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		string& operator+=(const char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const string& str)
		{
			append(str);
			return *this;
		}

这实现的也是十分方便

insert

insert就是选定位置进行插入

在这里插入图片描述

这里我们向实现这个功能的话,可以用这个思路。

在这里插入图片描述
先是最基本的检查是否扩容
在这里插入图片描述
将插入位置后的字符进行后移

	void insert(size_t pos, const char* s)
	{
		assert(pos <= _size);//防止插入位子大于字符串长度
		size_t n = strlen(s);//记录插入字符串的长度
		if (n + _size > _capacity)//进行扩容检查
		{
			reserve(n + _size);
		}
		size_t end = size();
		while (pos <= end)//将字符串进行后移
		{
			_str[end + n] = _str[end];
			end--;
		}
		_size += n;//扩展size
		for (int i = 0; i < n; i++)//将插入字符串拷贝至位置处
		{
			_str[pos + i] = s[i];
		}

	}

这上面算是完成实现了

但其实这里还有个隐藏bug。

size_t pos;
size_t end;
while (pos <= end)

当要插入的位置为0时

也就是说 pos=0,

再看看end和pos的类型

都是size_t的类型

试想一下,当end不停自减,当end为0,下一次就要变成-1,跳出循环的时候。

就会发现程序还在继续运行

这是因为end为size_t类型,所以不会变为-1。

这个时候就需要用到npos这个成员变量了

namespace my_str
{
	class string
	{
		public:
	

		private:
		size_t _size;
		size_t _capacity;
		char* _str;
		const static size_t npos = -1;//添加了一个npos静态变量
	};
	
}

STL中的string同时也是这样实现的。

接下来只要把判断条件稍微加一下就行了
while (pos <= end && end != npos)
这样就会发现可以正常运行了
在这里插入图片描述

最后代码

void insert(size_t pos, const char* s)
{
	assert(pos <= _size);
	size_t n = strlen(s);
	if (n + _size > _capacity)
	{
		reserve(n + _size);
	}
	size_t end = size();
	while (pos <= end && end != npos)
	{
		_str[end + n] = _str[end];
		end--;
	}
	_size += n;
	for (int i = 0; i < n; i++)
	{
		_str[pos + i] = s[i];
	}

}

相比于查有着如此多的成员函数,剩下的就比较少了

erase

删除的话有两种情况

当要删除的量大于剩下量或者没有输入需要删除的量
将删除位置之后的所有量全都删除。
那再删除位置,简简单单加个‘\0’不就ok了

当要删除的量小于剩下字符量
则需要进行删除位置的控制,就是说不能随便添加’\0’。
需要同时保留删除后面的数据
实现方法和上面的插入很像,就是将后面的不停向前插入,来达到删除的效果

void erase(size_t pos, size_t len = npos//判断是否给了删除长度)
{
	if (len == npos || len + pos >= _size)//需要删除的长度大于剩下的字符
	{
		_str[pos] = '\0';//直接添加'\0'即可
		_size = pos;
	}
	else//需要删除的量小于剩下的量
	{
		int n = 0;
		while ((_str + pos + len + n) != end())//控制删除的量
		{
			//讲后面的不停前移
			_str[pos + n] = _str[pos + len + n];
			n++;
		}
		_size -= len;
	}
}

查找就十分简单了,在以前的C中就有自带的查找函数——strstr

		size_t find(const char* str, size_t pos = 0//从什么位置开始查找)
		{
			assert(pos <= _size);
			const char* ptr = strstr(_str + pos, str);//调用函数进行查找
			if (ptr)//判断是否有结果
			{
				return ptr - _str;
			}
			else
				return npos;

		}

直接使用即可

迭代器

由于以前没有给迭代器出过博客,这里就小小讲一下

迭代器可以理解为STL中的指针

因为成员变量中的指针一般都是私有成员
无法达到数据在各个容器中互通。

但是迭代器可以

	vector<char> v1;
	string s1("asdasdasdasd");
	string::iterator it = s1.begin();
	while(it!=s1.end())
	{
		v1.push_back(*it);
		it++;
	}

这里就用迭代器将string中的每一个丢进了vctor中。

这里通过迭代器使两个不同类产生互动。
这个我们在string中也要实现。

typedef char* iterator;
typedef const char* const_iterator;

在命名域内通过强转类型,将char*转为iterator。
所以不要看起来觉得它非常高大上,但其实就强转一下的事。

剩下的就很简单了

iterator begin()
{
	return _str;
}
iterator end()
{
	return _str + _size;
}

const_iterator begin()const
{
	return _str;
}
const_iterator end()const
{
	return _str + _size;
}
			

直接return对应的地址就行了

范围for
还记得之前的语法糖吗

string s1;
for(auto i:s1)
{

} 

其实它的底层就是迭代器实现的。

当你完成迭代器的时候就能发现范围for也能使用了

流插入流提取

流插入没啥特别的难度,所以这里就直接实现了。

流插入

std::ostream& operator<<(std::ostream& out, const string& s1)
{
	for (auto i : s1)
	{
		cout << i;
	}
	return out;   
	
}

流提取

流提取按照以前我们的惯性思维来说

应该是用这样的方式进行实现的。

std::istream& operator>>(std::istream& in, my_str::string& s1)
{
	char ch;
	cin >> ch;
	while (ch != ' ' && ch != '\n')//遇到空格和换行停止
	{
		s1 += ch;
		cin >> ch;
	}
	return in;
}

但是进行运行测试的时候

会发现程序在函数体内无限循环,无法停止了。

原因就是在C++中默认空格和分行就是用来进行隔断输入内容的。
所以用cin无法进行读取

这里就需要用到in自带的一个函数:

in.get()

	std::istream& operator>>(std::istream& in, my_str::string& s1)
{
	char ch;
	ch=in.get();
	while (ch != ' ' && ch != '\n')//遇到空格和换行停止
	{
		s1 += ch;
		ch=in.get();
	}
	return in;
}

这样的话基本功能是实现了,但是还有几个问题在:
1.多次引用+=,申请空间,严重影响效率

我们知道+=的实现引用了reverse,但是reverse进行扩容的时候需要进行拷贝和申请空间并且销毁原空间,多次引用会严重影响效率

2.如果在前面添加了空格和换行之类的,就不能进行提取了。

首先是第一个问题,我们可以自己申请一个数组,然后将提取出来的内容放进数组里面,最后将数组赋值给对象。

	std::istream& operator>>(std::istream& in, my_str::string& s1)
	{
		s1.clear();
		char buff[128];
		char ch = in.get();
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)//判断数组是否满
			{
				buff[i] = '\0';//添加\0
				s1 += buff;//添加至数组
				i = 0;
			}
			ch = in.get();
		}
		if (i != 0)//检查数组是否满了
		{
			buff[i] = '\0';'尾部添上'\0'
			s1 += buff;
		}
		return in;

	}

}



接下来就是第二个问题。

while (ch==' '||ch=='\n')
{
	ch = in.get();
}

直接在前面添加一个循环用来专门提取空格和换行就可。

最后代码

	std::istream& operator>>(std::istream& in, my_str::string& s1)
	{
		s1.clear();
		char buff[128];
		char ch = in.get();
		while (ch==' '||ch=='\n')
		{
			ch = in.get();
		}
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				s1 += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s1 += buff;
		}
		return in;

	}

}



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

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

相关文章

火山引擎DataLeap推出两款大模型应用: 对话式检索与开发 打破代码语言屏障

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 自上世50年代&#xff0c;以“计算机”作为代表性象征的信息革命开始&#xff0c;社会对于先进生产力的认知便开始逐步更迭——从信息化&#xff08;通常认为是把企…

kafka latest 模式消费偏移丢数据

Flink消费kafka&#xff0c;这种情况会丢数据

Vue.js 2 —组件(Component)化编程

一、模块与组件 模块 1. 理解 : 向外提供特定功能的 js 程序, 一般就是一个 js 文件 2. 为什么 : js 文件很多&#xff0c;很复杂 3. 作用 : 复用 js, 简化 js 的编写, 提高 js 运行效率 组件 组件是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素&#xff0c;封装…

在呼叫中心领域,人工智能目前处在什么阶段

在呼叫中心这个行业&#xff0c;人工智能已经逐渐走向实用化阶段。目前&#xff0c;很多企业已经开始采用人工智能技术来改善其呼叫中心的效率和服务质量。 具体来说&#xff0c;人工智能已经被用于呼叫中心自动语音应答、自然语言处理、智能路由、智能客服机器人等方面。通过这…

机器学习中的分类问题:如何选择和理解性能衡量标准

文章目录 &#x1f34b;引言&#x1f34b;为什么需要分类问题的性能衡量标准&#xff1f;&#x1f34b;常用的分类问题衡量标准&#x1f34b;混淆矩阵-精确率-召回率&#x1f34b;PR曲线和ROC曲线&#x1f34b;PR曲线&#x1f34b;ROC曲线&#x1f34b;PR vs. ROC &#x1f34b…

外汇天眼:外汇新手开展交易需要做哪些准备,你都知道么?

外汇交易&#xff0c;如同任何一项专业工作&#xff0c;需要不断积累知识和经验&#xff0c;以及稳定的心态。正如古语所说&#xff1a;“工欲善其事&#xff0c;必先利其器。” 在外汇市场&#xff0c;这句话同样适用。在踏上外汇交易之旅之前&#xff0c;我们迫切需要做好外汇…

Windows清除激活标志的方法

大家在购买电脑或笔记本的时候&#xff0c;有的商家给出的7天无理由退货&#xff0c;并不是真正的无理由&#xff0c;往往附件条件windows是不能激活的&#xff0c;如果激活了就只能换不能退了。 卖家提出的条件也特别滑稽可笑&#xff0c;你想不联网怎么体验啊&#xff1f;不…

一百八十五、大数据离线数仓完整流程——步骤四、在Hive的DWD层建动态分区表并动态加载数据

一、目的 经过6个月的奋斗&#xff0c;项目的离线数仓部分终于可以上线了&#xff0c;因此整理一下离线数仓的整个流程&#xff0c;既是大家提供一个案例经验&#xff0c;也是对自己近半年的工作进行一个总结。 二、数仓实施步骤 &#xff08;四&#xff09;步骤四、在Hive的…

最新Python大数据之Excel进阶

文章目录 Excel图表类型了解有哪些图表类型 Excel图表使用图表的创建方式利用固定数据区域创建图表编辑数据系列添加数据标签格式化图表 Excel数据透视表数据透视表对原始数据的要求创建数据透视表数据透视表字段布局将数据透视图变成普通图表 Excel图表类型 为了揭示数据规律…

入门级制作电子期刊的网站推荐

随着数字化时代的到来&#xff0c;越来越多的人开始尝试制作自己的电子期刊。如果你也是其中的一员&#xff0c;那么这篇文章可以帮助你制作电子期刊。无论是初学者还是有一定经验的制作者&#xff0c;都能快速完成高质量的电子期刊制作 小编经常使用的工具是-----FLBOOK在线制…

Python爬虫在Web应用自动化测试中的应用

在Web应用开发过程中&#xff0c;自动化测试是确保应用质量和稳定性的重要环节。本文将介绍如何使用Python爬虫与自动化测试技术相结合&#xff0c;实现对Web应用进行自动化测试的方法和步骤。通过这种结合&#xff0c;我们可以提高测试效率、减少人力成本&#xff0c;并确保应…

RocketMQ 消息重试机制

文章目录 消息发送重试重试触发条件重试流程重试间隔重试常见问题消息流控机制流控触发条件 生产者控制消息发送重试次数gRPC 客户端remoting 客户端 消费重试重试触发条件PushConsumer 消费重试策略PushConsumer 重试间隔时间修改 PushConsumer 最大重试次数gRPC 协议端口Remo…

华为数字能源,开启超充新纪元

编辑&#xff1a;阿冒 设计&#xff1a;沐由 在过去很长的一段时间里&#xff0c;国内某著名品牌火锅是从来不担心获客的。顶峰时期&#xff0c;该品牌每年服务超过1.6亿人次的顾客&#xff0c;翻台率达到了5次/天&#xff0c;几乎创下了餐饮界的最高翻台率。 翻台率是餐饮企业…

调用CFCA金信反欺诈服务相关接口,很详细

调用CFCA金信反欺诈服务相关接口&#xff0c;很详细 一、准备二、调用接口1、查询接口文档2、查看代码示例3、测试调用接口 三、工具类1、CFCA金信反欺诈服务接口码枚举类2、CFCA金信反欺诈服务的公共参数配置3、加密解密工具类4、请求参数dto5、调用接口工具类&#xff08;关键…

【N年测试总结】证券行业的测试特点

每个行业由于其业务形式&#xff0c;产品形态&#xff0c;行业要求等等的不同&#xff0c;都有其不同于其他行业的测试特点&#xff0c;对测试人员的重点能力要求也不同。 一、证券行业业务系统简介 证券行业的业务系统这里按照C端系统和B端业务系统两大类进行介绍。 C端系统…

tensorrt C++推理

char* trtModelStream{ nullptr }; //char* trtModelStreamnullptr; 开辟空指针后 要和new配合使用&#xff0c;比如89行 trtModelStream new char[size]size_t size{ 0 };//与int固定四个字节不同有所不同,size_t的取值range是目标平台下最大可能的数组尺寸,一些平台下size_…

通讯网关软件012——利用CommGate X2OPC实现MS SQL数据写入OPC Server

本文推荐利用CommGate X2OPC实现从MS SQL服务器获取数据并写入OPC Server。CommGate X2OPC是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;实现从MS SQL数据库获取数据并写入OPC Server。 【…

(Vue2)智慧商城项目

新增两个目录api、utils api接口模块&#xff1a;发送ajax请求的接口模块 utils工具模块&#xff1a;自己封装的一些工具方法模块 第三方组件库vant-ui PC端&#xff1a;element-ui&#xff08;element-plus&#xff09; ant-design-vue 移动端&#xff1a;vant-ui Mint UI…

Vue3最佳实践 第五章 Vue 组件应用 3( Slots )

5.4 Slots 我们已经了解到组件能够接收任意类型的 JavaScript 值作为 props&#xff0c;但组件要如何接收模板内容呢&#xff1f;在某些场景中&#xff0c;我们可能想要为子组件传递一些模板片段&#xff0c;让子组件在它们的组件中渲染这些片段。Slots 可用于将Html内容从父组…

怎么样深入学习一门技术(Python)

进入官网 Python官网文档 https://docs.python.org/zh-cn/ 边敲代码边理解 多看教学视频 狠狠的花时间