String的增删查【C++】

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;//扩展sizefor (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;}elsereturn 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';//添加\0s1 += 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.mzph.cn/news/88113.shtml

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

相关文章

CRM客户管理系统英文专业版

外资公司日常沟通的语言以英文为主&#xff0c;业务往来也是涉及到国内外&#xff0c;专业的英文版CRM系统很适合这样的业务团队&#xff0c;尤其CRM供应商是国际化企业&#xff0c;在海外也有分公司、办事处。 多语言 ZOHO支持多语种如英语、汉语、日语等28种语言&#xff0…

MySQL基础篇-函数

目录 1.字符串函数 2.数值函数 3.日期函数 4.流程函数 5.小结 在MySQL中&#xff0c;函数是一种数据库对象&#xff0c;用于执行特定的操作或计算&#xff0c;并返回结果。函数通常用于查询、数据处理和转换&#xff0c;以及在SQL语句中执行其他操作。MySQL提供了许多内置函…

linux驱动之input子系统简述

文章目录 一、什么是input子系统二、内核代码三、代码分析 一、什么是input子系统 Input驱动程序是linux输入设备的驱动程序&#xff0c;我们最常见的就按键&#xff0c;触摸&#xff0c;插拔耳机这些。其中事件设备驱动程序是目前通用的驱动程序&#xff0c;可支持键盘、鼠标…

C++ -- IO流

目录 C语言的输入与输出 CIO流 C标准IO流 C文件IO流 文件常见的打开方式如下 以二进制的形式操作文件 以文本的形式操作文件 读写结构体 stringstream的简单介绍 C语言的输入与输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输…

零基础学空手道_3_空手道的站姿(上)

欢迎回来一起学习刚柔流空手道。 讲一些比较严肃的内容&#xff0c;就是礼仪和站姿。 空手道一开始不是要学习怎么打&#xff0c;而是要学习怎么去尊重别人和不打。所以礼仪很重要。 一切事情都是以礼仪开始&#xff0c;以礼仪结束。这叫以理始以理终。 空手道也是这样&#xf…

MySQL - DML数据增删改

功能介绍&#xff1a; DML&#xff08;Data Manipulation Language&#xff09;数据操作语言&#xff0c;用来对数据库中表的数据记录进 行增、删、改操作。 添加数据&#xff08;INSERT&#xff09; 基本语法&#xff1a;insert into 表名(字段列表) values (值列表); …

【问题解决】Android Studio 无法连接手机(荣耀90)无法识别手机usb

问题描述&#xff1a; 使用AS调试的时候遇到一个问题&#xff0c;由于是重装后的电脑&#xff0c;什么都没配置&#xff0c;但是两个旧手机都在安装SDK tools里的Google usb driver后直接连上AS&#xff0c;而我的新手机却死活连不上&#xff0c;查了一下午&#xff0c;啥方法都…

Redis原理(一):Redis数据结构(上)

文章目录 1、 Redis数据结构-动态字符串2、 Redis数据结构-intset3、 Redis数据结构-Dict4、 Redis数据结构-ZipList5、 Redis数据结构-ZipList的连锁更新问题6、 Redis数据结构-QuickList1、 Redis数据结构-动态字符串 我们都知道Redis中保存的Key是字符串,value往往是字符串…

MongoDB(二)基础操作 创建、删除,查询等

mongodb有一个特点&#xff0c;如果某个库&#xff0c;库下面没数据&#xff08;mongodb成集合&#xff09;&#xff0c;该库等于不存在的 mongodb只要创建一个库&#xff0c;在库下写入数据&#xff0c;该库才会生成 mongoshe [-hhost -pxxx] 创建数据库 use 数据库名 # 如果…

c语言常见字符函数、内存函数(详讲)

前言&#xff1a; 其实在c语言当中是没有字符串这一概念的&#xff0c;不像c里面有string类型用来存放字符串。在c语言中我们只能把字符串放在字符串常量以及字符数组中。 1.常见字符串函数 1.1strlen size_t strlen ( const char * str );作用&#xff1a;用来求字符串中 …

人工智能的未来:从 Jetson 到 GPT,沙龙见闻与洞察

前言 在当今数字化时代&#xff0c;人工智能正以惊人的速度改变着我们的生活和工作方式。从智能语音助手到自动驾驶汽车&#xff0c;从智能家居到医疗诊断&#xff0c;人工智能技术已经广泛渗透到各个行业&#xff0c;并为其带来了巨大的变革和创新。越来越多的行业专家、学者…

postman发送图片

POSTMAN 如何发送携带图片的请求? 闲话不叙 步骤如下&#xff1a; 新建一个请求&#xff0c;在Headers中添加一对k-v : Content-Type > multipart/form-data 请求的接口: RequestMapping("/fileUploadController")public String fileUpload(MultipartFile fil…

【C++】构造函数和析构函数第一部分(构造函数和析构函数的作用)--- 2023.9.25

目录 前言初始化和清理的概念构造函数和析构函数的作用构造函数的作用析构函数的作用 使用构造函数和析构函数的注意事项默认的构造函数和析构函数结束语 前言 在使用c语言开发的项目场景中&#xff0c;我们往往会遇到申请空间的需求&#xff0c;同时也肯定遇到过程序运行一段…

积跬步致千里 || 可视化动图展示

可视化动图展示 目前只能在 jupyter notebook 中测试成功 %matplotlib notebook import numpy as np import matplotlib.pyplot as plt import timen 500 data np.random.normal(0,1,n)fig plt.figure() ax fig.add_subplot(111)fig.show() fig.canvas.draw()for i in ra…

什么是Redux?它的核心概念有哪些?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是Redux&#xff1f;⭐ 它的核心概念有哪些&#xff1f;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发…

C++——模板

目录 泛型编程 函数模板 函数模板概念 函数模板格式 类模板 类模板的定义格式 类模板的实例化 泛型编程 泛型编程是什么呢&#xff1f;泛型编程&#xff1a;编写与类型无关的通用代码&#xff0c;是代码复用的一种手段。模板是泛型编程的基础。型就是类型&#xff0c;不…

【Java】医院智能导诊小程序源码,springboot框架

智能导诊 可以根据用户症状描述精准推荐科室及医生智能学习医院历史数据及自动进行科室对照,与医院的系统连接后,患者可直接完成预约。 一、系统概述 “智能导诊”以人工智能手段为依托&#xff0c;为人们提供智能分诊、问病信息等服务&#xff0c;在一定程度上满足了人们自我…

低照度增强算法(图像增强+目标检测+代码)

本文介绍 在增强低光图像时&#xff0c;许多深度学习算法基于Retinex理论。然而&#xff0c;Retinex模型并没有考虑到暗部隐藏的损坏或者由光照过程引入的影响。此外&#xff0c;这些方法通常需要繁琐的多阶段训练流程&#xff0c;并依赖于卷积神经网络&#xff0c;在捕捉长距…

python+vue驾校驾驶理论考试模拟系统

管理员的主要功能有&#xff1a; 1.管理员输入账户登陆后台 2.个人中心&#xff1a;管理员修改密码和账户信息 3.用户管理&#xff1a;管理员可以对用户信息进行添加&#xff0c;修改&#xff0c;删除&#xff0c;查询 4.添加选择题&#xff1a;管理员可以添加选择题目&#xf…

ElementUI首页导航和左侧菜单静态页面的实现,以及Mockjs和总线的介绍

目录 前言 一. Mock.js 1.1 什么是Mock.js 1.2 Mockjs的安装与配置 1.2.1 安装Mock.js 1.2.2 引入Mock.js 1.3 Mockjs的使用 1.3.1 定义数据测试文件 1.3.2 mock拦截ajax请求 二. 首页导航以及左侧菜单的搭建 2.1 什么是总线 2.2 创建三个vue组件 首页AppMain.vue组…