string的模拟实现

2024-05-14 1940阅读

string这个接口在C++里面是具有多种的功能的,比如一些很经典的头插尾插,求长度等等,我们可以把他理解成一个字符的顺序表,对于在写各种类型的代码来说,还是比较好用的。今天我就来模拟实现一下他的底层逻辑。

1.string的初始化

当说道初始化,我们首先就得能先想到string里面到底是有几个元素来供我们初始化。

首先第一个是存放内容的数组,第二个就是当前string的最大容量,第三个就是当前string的大小。那这么说,这三个对象就可以在我们的string类中进行声明了。

string的模拟实现

接下来就进行一个对象的初始化。

首先我们可以分为有参和无参两种情况,无参数的情况下,我们就可以直接使用初始化列表(可以理解为构造函数)来进行初始化。

string的模拟实现

而有参呢

string的模拟实现

我们这样来写,很明显是不正确的。因为如果要传一个参数进来,那么形参的那部分为了避免错误一般会加上一个const,如果我们硬要这样写的话,那只能在_str前面加上一个const,这是因为加上了const,就会使得权限相同了,拥有const的权限是比没有的大的,权限可以缩小,但不可以放大,但如此又会导致我们的string类型不会被修改因此两者都不是最佳的方案。而最好的解决办法就是不一定要用初始化列表,特别是对于内置类型来说,初始化列表在有的时候会给我们带来不必要的麻烦。

string的模拟实现

这种就是最恰当的有参情况下的初始化。因为在编译的时候,是先走初始化列表,首先通过初始化列表得到当前str的size,然后赋值给_capacity,给_str开辟_capacity+1个空间,为什么要加1?因为str里面会有一个\0来结尾,这个斜杠零也是会占用空间的,最后把str的值拷贝给_str,这就完成了一次string的有参初始化。

那我们怎么把上面的那两个情况合二为一呢?

string的模拟实现

首先想到这种写法,这种写法很明显,当str给了个空指针后,strlen(str)在解引用来求长度的时候是会报错的,你想想,怎么计算一个空指针的长度?

string的模拟实现

那这样呢???还是不行,因为,'\0'是一个char类型的字符,并不是字符串常量类型,是无法赋值给const char*的,因此,得把他改成一个字符串常量的类型。

string的模拟实现

我们在这可以看到,最后算出来的size就是0,因为strlen是碰到'\0'就会停下遍历,进行返回,虽然说目前的_capacity也是0,但不代表_str就没用开空间,一开始预留的一个_capacity+1就是给现在的时候,没有内容,但是有\0准备的,这个时候的_str里面就存放了一个\0。但如果加上了\0的话,那我们在传字符串进来的时候就会有两个\0了,因此,最好的办法就是直接给一个空串。

string的模拟实现

对于string来说还有一个小小的接口,就是c_str,这个接口是用来兼容c语言的,有的时候c++还会参杂着一点c语言,因此这个小接口就是一个不可忽视的东西

底层的逻辑也很简单,就是直接返回_str就可以了

string的模拟实现

有了构造函数,我们就得写析构函数了。

string的模拟实现

而这,就是整体的初始化的部分了

string的模拟实现

2.遍历

遍历首先得要有一个字符串的总长度,然后遍历的时候还得对字符进行类似的"解引用"操作。

string的模拟实现

还有一个是capacity

string的模拟实现

就类似这样,但是目前来看,size和[]都是会报错的,下面我就来实现以下这两个操作。

string的模拟实现

首先来说一下size()函数,这个没什么好说,直接返回他的大小就行了。至于后面的const,如果加上了他,那么普通对象可以对他进行调用,const类型的对象也可以对他进行调用。相当于把使用权限给放大了。

而下面的运算符重载,是要返回pos位置的字符,由于[]相当于解引用,且_str是通过new出来的,因此解引用出来的内容存放在堆上,而在堆上的数据出了作用域就不会被销毁,因此这个地方就可以使用引用返回,而引用返回的数据并不是他的拷贝,而是返回数据的别名,也就是说,这个数据具有可读和可写的权限。

string的模拟实现

还有一点值得一提的是,在string中,[]是有一个检查是否越界的功能的,这个在我上一章讲接口的时候提到过,如果是用[]来访问数据,如果越界的话,就会出现断言报错;如果用at()来访问的话,就会出现编译错误。这是一个很大的进步,为什么这么说呢,因为数组检查的越界有时候并不是非常的严格,越界读的时候他是检查不出来的,而越界写的话,他是一种抽查的状态,有时候偶尔能检查到你的越界,有时候并不能,这是数组的一个非常大的缺陷。

目前的[]只能被普通对象调用,如果想要被const调用的话,我们可以效仿库中的一种做法

string的模拟实现

让他们各走各的路,互不干扰。

string的模拟实现

 3.迭代器

对于string,我们除了用[]来遍历以外,还会使用到迭代器和范围for来进行遍历,现在让我来模拟实现一下。

string的模拟实现

其实粗略来看,我们可以把迭代及直接简单的理解为指针,但在某种情况下,他又不是指针,就好像"如指针"这种状态。我们在这里定义迭代器的话,就可以把他当作指针来定义。

因此,在上面这个迭代器中,我是让他返回_str的指针,也就是让他指向了_str的头部,end()就让他指向了_str的尾部。

string的模拟实现

如此定义完后,就可以在这里使用他了。

string的模拟实现

对于范围for,我们好像什么都没干,为什么他能成功运行呢?

这是因为范围for虽然看起来很吊,但是他的底层仍然是一个迭代器,只不过这里被封装好了,编译过后,他自己就会调用iterator的begin()函数和end()函数,但这并不代表他很厉害。因为他仅仅是套用了那个与库相同的名字,并不是一个灵活的替换,如果我把begin的b改成大写的B,那他照样会报错。

这里对于迭代器的定义是不能对const对象使用的,他只能对普通对象使用,而想要对const对象使用,只需要加个const就行了。

string的模拟实现

string的模拟实现

4.增删查改

1.reserve,push_back,append

首先来实现以下尾插的这个功能,尾插这个功能在string中叫做push_back和append。

string的模拟实现

说一下这个push_back函数,首先我们是想插入一个字符,那么就得先判断_size和_capacity是否是相等,如果相等,那就进行扩容。由于我们还有可能是初始化空字符串,因此还要判断_capacity是否为0,如果是0就得给一个初始值给他。那怎么扩容呢?那就得用到另一个原本就是string 里面的接口reserve了,这个接口的作用就是给他开辟空间,那我们在这里就手动开辟。

由于reserve不仅仅是在尾插里面可以使用,他自己也可以单独使用,因此,我们首先就可以判断想要开辟空间的个数是否是大于_capacity的,如果是,那么首先就开n+1个空间,多开一个空间的作用是给'\0'开一个,然后把_str的内容拷贝到tmp里面,把_str释放掉,最后把_str的指针指向tmp。

然后是append追加,由于他追加的是一个字符串常量,因此我们首先需要考虑的是当前的string的大小和即将要尾插的字符串常量的和是否已经大于当前的_capacity,如果是,那就要开辟一个总的和大小的空间,然后通过strcpy来进行拷贝尾插,把str尾插到_str+_size这个位置,最后总大小也要加上_size。

最后把他们进行运算符重载的封装。

string的模拟实现string的模拟实现

2.insert,erase

1.insert

接下来实现一下insert这个接口,我实现的其中一个功能就是在某个位置插入一个字符。

string的模拟实现

首先仍然是经典的判断语句,可以看到,这个部分的很多实现都是跟顺序表是类似的。首先都是判断空间够不够,然后遍历,把元素一个一个的向后挪动,最后在空出来的pos位置上插入一个字符,整体的_size++.

但是这里他会有一个问题

string的模拟实现

我们可以看到,我本想在0这个位置插入一个5,但是可以看到代码却是报错了,这是为什么?

string的模拟实现

经过调试我们可以发现了诡异的一幕,我在这里设置的明明是end>=pos才进入,他这里end已经是-1了,-1明明是不满足条件的,为什么还会进入这个循环?

这里就涉及到一个叫做类型提升的问题了。类型提升就是一个运算符的两边的操作数的类型不同的时候,一般就会发生类型提升,一般来说,类型提升就是范围小的向范围大的类型提升,这里的话,发生的就是整形提升,提升成了无符号整型,因此,虽然他那里看着是负数,但他实则并不是负数,而循环就可以一直进去。

string的模拟实现

而我们只需要在pos前面给他一个强转成int类型即可。

除了这种方式,还有一种方式就是修改以下遍历的方式。

string的模拟实现

这种的遍历方式就是把size向后挪动了一个位置,然后把前面的数据放到后面,当循环走完的时候,刚刚好能剩下第一个位置0。这个时候就可以方便我们的数据插入了

2.erase的实现

首先我们要分两种情况,第一种是把pos位置后面的所有内容给删掉,第二种就是普通的删除。

我们知道,erase在string里面的其中一个功能是可以pos位置开始,删除len个字符。那我们可以直接在pos这个位置放一个\0,然后让_size = pos。

string的模拟实现

string的模拟实现

我们这里把npos设置成了-1,这也就是说当删除的长度是-1的时候,就意味着把pos后面的所有东西都给删掉了。

但是这里还是会存在一个问题,那就是pos+len这个值他有可能会溢出,因此我们只需要把len移项以下就可以解决这个问题了

string的模拟实现

接下来就是正常的删除。删除pos开始的len个字符。

string的模拟实现

整体代码就是这样的。

string的模拟实现

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]