【C++】深度剖析string类的底层结构及其模拟实现(二)

简介: 【C++】深度剖析string类的底层结构及其模拟实现(二)

7. 数据插入删除及扩容操作

然后我们来实现一下push_back(),那就是尾插一个字符嘛。


那大家想插入数据的话是不是要考虑扩容啊。

那插入数据的接口除了push_back() ,是不是还有append()啊,append()可以在string对象后面追加字符串,那也需要考虑扩容。

8c69887631de4131a03df595c9bc198d.png那大家想一下,如果扩容的话,我们一次扩多少呢?

还像之前写的顺序表一样扩二倍可以吗?

对于push_back()来说一次扩二倍应该没问题,但是append()还一次扩二倍是不是有可能不行啊。

为什么?

你想如果当前的容量是10,现在追加一个长度为25的字符串,你扩容到原来的两倍才20,是不是也不够用啊。

那我们这里可以怎么扩呢?

🆗,string是不是还有一个成员函数叫做reserve啊,它可以改变容量为我们指定的大小,帮助我们扩容。

但是,我们是不是还没实现它。


7.1 reserve

所以:

我们先来实现一下reserve:

6109bbef4a794034a084acde27e860a0.png注意不能先释放旧空间,因为我们还要把旧空间的数据拷贝到新空间。

这就是我们的reserve。

大家看一下,我们的这个reserve有什么问题没有?


🆗,如果我们参数n的值小于_capacity,这里是不是就真的缩容去了,重新开一块小空间,拷贝原来的数据,释放旧空间。

但是我们说了,一般是不会轻易缩容的,标准库里string的reserve其实就不会缩的,我们可以看一下:

9450d5e5c89f4ddf968d298ae6c29a7a.png

是不是没有啊。

那我们自己实现的这个呢?

我们也来测试一下,当然我们还没有实现capacity()这个接口,实现一下:

efc6d79cd3dc4dceb814a8bb66d2c001.png

来看:

1a0e3fcd30ed4b2db7548c08aadb82cf.png

我们的确实缩了,但是程序也崩了。

为什么呢?

那我们通过调式一步步走其实可以推测出它应该是在析构的时候报错了

那原因出在哪里呢?

0101cc39a2744f53b2e44ee883e9848e.png

来看,如果n小于_capacity的话,这里会缩容,先开一块比原来小的空间,然后把原来的数据拷贝过去,但是,空间变小了,还能放的下原来的内容吗?

是不是就不能了,但是strcpy是遇到\0才停止,所以这里就越界了。

所以这里如果要拷贝的话是不是应该用strncpy,不能全拷,但是我们说了一般不缩容,库里面的就没缩,所以这里我们加一个判断:

679a6a6de18b4a5ca780161939da7bb5.png

n > _capacity才做处理。


6988430f4e494829bdbf07221d161638.png

小的话我也不缩。

但是其实这里还是有一个问题的,我们后面再说。

7.2 push_back和append

那reverse搞好了,我们就可以继续实现push_back()和append()了:

那在判断之后,需要扩容我们就扩容,然后我们插入数据就行了:

feadde30993646d6b76f960b36de31ca.png

那push_back()我们这里就选择扩两倍。

e8f7292064b046928d1f938b50b4fc86.png

另外给大家提一下我们这里选择用strcpy而没有用strcat,这里不推荐使用strcat,当然strcat也是可以完成的。

那为什么不推荐呢?

b2e83f9d7f55458b905bacb889c58c65.png

因为strcat追加字符串的时候是需要自己从头去找被追加字符串的\0,然后从\0开始追加新的串

而我们这里明确知道\0的位置,_str + _size就是\0的位置,用strcpy就直接把这个位置传给它直接开始追加了。

🆗,那我们来测试一下:

ba4d97a0998e4dc0beef0d8cc24e0b1d.png

没什么问题。

7.3 +=

虽然有push_back和append,但是我们说了,我们一般不喜欢用它们两个。

因为string还重载了+=,用起来就非常香,尽管+=的底层也是去调push_back和append。

🆗,我们来实现一下:

e7d8c50f250a4f7e8d2e34827cf4b035.png

很简单

来测试一下:

f4c932a6829c490fa74d7c62b97c59a2.png

没问题。

7.4 resize

然后我们来实现一下resize:

resize就是扩容加初始化,我们可以自己指定要初始化的字符,不指定默认填\0。

28625acb5d504865a7c0de509fdb0df9.png

那这里是不是也分几种情况啊:

首先第一种情况n小于_size,那是不是要删除数据啊,只保留前n个,怎么做?
是不是直接把第n个位置置成\0就行了

10872020d5c54beb94da52035b1ac3e1.png

当然等于也可以走这里(等于其实可以不做处理)。

然后就是n>_size ,但是大于也分两种情况,n在_size和_capacity之间以及n>_capacity两种:

n在_size和_capacity之间的话,之间插入数据就行了,如果n>_capacity,那就要先扩容,然后再插入数据。

195dcc36c96c4de7becf71ec78ac68d6.png

测试一下:adab8488053948619bce252da3ddb824.png

没问题。

7.5 insert

我们先来实现一下insert,当然库里面提供好多个版本,我们不可能全部实现。

首先我们来实现一下在pos位置插入一个字符

aef3ff36c3b54868a2681279c3a8efdb.png

怎么写,思考一下:

逻辑是不是很简单啊,首先判断一下,需要扩容的话要进行扩容,然后就去插入数据就行了嘛,如果往中间插的话挪动数据就行了嘛,跟我们之前实现顺序表的insert一样嘛17a0cec3ff2c41e4a8ff39b8becc60a1.png

就写好了,有没有什么问题呢?测试一下:

25fbc6c05b684cacbd71a5cb9f8389ce.png

可以,但是,这样呢?

bf57aa5b4cd44d45867d038a5fa5a5c8.png

在下标为0的位置再插入一个*,发现程序挂掉了,怎么回事呢?

e22a3827108446ae89c87a602adf4be8.png

大家思考一下,为什么?

当然库里面的肯定是没问题的,不过库里面的跟我们实现的这个还有一点差异

16ce9c363ac245da84ef2332e8338018.png

库里面指定位置插入字符的话还有一个参数n指定插入的个数,直接插入一个的是传迭代器,所以这里我们没有完全跟着标准库走

ebfa19e4b6f44744836e4eec50f468be.png

8f39d84b9bb74bc1a320b4057c229c19.png

那我们这里为什么在下标0位置插入就崩了呢?

5030bb78fddd4ac8aca73855359cc333.png

当pos为0时,end等于0时还会进入循环,end再- -变成多少?

是-1吗?

这里end的类型是szie_t,无符号整型,所以这里end为0后再- -并不是-1,而是整型最大值,那就越界了,循环也没正常结束,所以程序崩了。

那怎么解决?

把end的类型变成int?

🆗,这里变成int也不行,为什么?

这就用到我们C语言学过的知识了

9729e2effffa48abaf4560036704ec36.png

end和pos比较,就算把end变成int,但是pos是不是size_t类型啊,那这里是不是会发生算术转换啊。

34c43173342941bea48e309c09f395e4.png

end 还是会变成size_t。

那怎么搞?在参数列表把pos的类型也改成int?

f1d6691a44514e87b072e94cd67de7e4.png

eb3d6f7e8630444a800ef9066ae1c312.png

确实可以了,但是我们还是不要这么搞吧!

304054c6966545f79fcdd0f565f18d2d.png

我们看到表示下标一般都是size_t。

那我们这里呢这样来解决:d94b0a8a79744bc1817aa81f249dc984.png

刚才是>=,end减到-1才结束,那我们改成>,那到0就结束了,就不会出现刚才的情况了。

但是,这时我们要让end的初始值为_size + 1,然后依次把end - 1位置的元素挪到end

d790cad48c5742978e6fc9230b88858f.png

fe34d8126420442c84534b98de4e4d14.png

然后我们测试一下:

51d0aa06b0ea489c8a42dcab90de532a.png

就可以了。

那刚才是插入一个字符,现在我们再来实现一个插入字符串的:

6bd2a03bf4664497a67158ffb53d52e9.png

那逻辑是不是跟上面插入一个一样啊,只不过上面我们只需要挪出一个空间就可以了,那这里我们是不是需要挪动数据腾出strlen(str)个空间啊。

给大家画个图:

f10620393a3941cfa98537d811297cd8.png

理清思路,代码就简单了:

fa4ad6acd91d4e4d8c3ce62837507a54.png

仔细观察我们会发现把这里的len换成1就和上面那个insert的循环控制一样了。

我们来测试一下:

6c92ec948bc04198b0e84b4fca348674.png

没问题。

当然这只是一种写法,大家有自己的想法,只要能实现也可以。

当然:

现在我们的insert实现之后,前面的push_back和append是不是就可以复用它了。

5da8dd463cfc404b89d1a4a09331904d.png

7.6 erase

那接着我们来实现一下erase,从pos位置开始删除len个字符:

那这里的len呢我们看到有一个缺省值:7a8daa8d7dd74b2e9e853b651ac0c89d.png

npos,npos是啥呢:

5ad0d92a88444f8daff7097176f4b880.png

🆗,它是一个const静态成员变量,值为-1,但是呢,因为这里它的类型是size_t(无符号整型),所以它在这里其实是整型的最大值。

那在这里就要给大家提一个东西:

我们知道C++11开始支持类的成员变量在声明的时候给缺省值,但是呢有个前提,必须是非静态成员变量才可以在类中声明的时候可以给缺省值。

9c8ab603e1ba4992abe8fbd09ddd903d.png

静态成员变量是不能在声明时给缺省值的,这个我们在之前类和对象的文章里也有讲解过。

我们说了对于静态成员变量:规定静态成员变量的初始化(定义的时候赋初值)一定要在类外,定义时不添加static关键字,类中只是声明。

2e7d11e9f5e64cf8acbe15880931782a.png

正常应该这样写。

不过呢,我们看到库里的npos还加了const:

820ccff3351e4253bb4ced50d9ab7fdf.png

那加const就加嘛?但是呢,加了const之后

e1c817eb8fb34f199914de51fb421a4b.png

这样直接给缺省值就可以了,又不报错了。

而且呢,这样的写法,这种语法呢,还只支持整型

78bfd8cf79634a0aa9c93f74a636f971.png

所以,这个地方就感觉有点怪啊。

好吧,那这个大家就了解一下,我们还是按正常的写法好吧:

f8a8332c537a41e29b65bccc74db111f.png那我们来实现一下erase:

41790ea82db64ebb8b4553cef02c041e.png

🆗,那这里是不是也分这样几种情况啊:

首先第一种情况就是pos+len小于字符串的长度,那我们是不是要把pos位置开始的后len个字符删掉,但是后面的还要保留啊。

那这种情况怎么做?

是不是挪动后面的数据,把需要删除的覆盖掉就行了啊。

那其它的情况就是给的len比较大,pos+len直接大于等于字符串的长度了,那就把pos后面的全部删掉。

或者就是没有传pos这个参数,缺省值npos,那也要把后面的全删,所以这两种情况可以统一处理。

怎么做?直接把pos位置置成\0是不是就🆗 了。

我们来写一下代码:b4ab91b32996465b9028f0e1e7dcb68c.png就写好了。

测试一下:

7a4903844969435b942c7527dbd714fa.png

没什么问题。

不过呢:

标准库里string的insert和erase最后都返回了对象的引用,那我们也可以加一下。

1766ef49cea04fe180bb2d38d3244560.png

8. swap和find

然后我们在来实现一下string的swap:


那我们在之前讲解string使用的文章里也说了,对比算法库里的swap,string::swap的效率是更高一点的,也给大家简单的解释了原因,那这下我们实现之后相信大家就理解的更透彻了。


来,写一下:8f0707b74a84401c9a9984c99d45755e.png

直接使用算法库里的swap去交换成员变量。

简单测试一下:

7e2c3f03e4064a36a4911163f8be83b4.png

然后我们实现一下find:

那find是不是也很简单啊,我们去遍历找就行了嘛,找到了就返回下标,找不到返回npos。

a47912d2d73641aab504540ae3d0cbf7.png

当然库里面string的find还有一个参数pos,就是支持从pos位置开始向后找,pos缺省值为0

ccfa8924f8de498790678cd9d69b1c16.png

另外记得加个断言判断pos是否合法。

那这样就行了嘛。

那除此之外find还支持从pos位置开始查找一个字符串:

我们来实现一下,是不是可以考虑直接复用C语言里面的strstr去找啊。

8f06c8071510494584596a71406f835d.png

写一下:

ce77110ba7274b15b7f8532c9527f238.png

由于strstr返回的是地址,这里我们可以通过指针-指针的方式得到它的位置。

那我们来简单测试一下:66c79037007347748e51e4e822d62809.png

9. 流插入<< 和 流提取>>重载

9.1 流插入<<重载

先来重载一下流插入<<:


那不知道大家还记不记得,之前我们学习类和对象的时候不是练习过一个日期类嘛,日期类里面我们也重载了流插入和流提取,但是我们讲到我们自己重载的如果想像正常cout打印那样用的话,要把流插入<< 和 流提取>>重载到类外面,因为this指针会抢占第一个参数的位置,但正常的话应该是让cout是第一个参数。

那我们来写一下代码,那我们在函数内部是不是直接遍历打印就行了:

bf84f1bae40c4a31b3d3b528bb14a495.png

这样就行了,参数out接收cout,s接收我们要打印的string对象。

当然这里不能用out<

打印c_str返回的const char*的指针,它是遇到’\0’就停止了。所以大家可以理解成c_str就是返回C格式字符串。

而我们打印string对象应该是看s对应的size 的,size是多大,总共有多少字符,全部打印完。

6658dff5e1234ab6be1e3572cdba8e85.png

注意这两种方式的区别。

测试一下:

33a04106f1304788a27bbca2a3a48544.png

9.2 reverse修改

然后:

上面我们实现reverse的时候最后不是还留了一个问题嘛,我们说之前的实现还是有点问题的,什么问题呢?

来看,对于我们上面实现的reverse如果是这种情况

a72d07a5659c4657a842866e0ee78be5.png

这里怎么出现乱码了呢?

🆗,因为我们之前实现的reverse用的strcpy

ee9bc20551de4b5696f1b3a8d3dd0b47.png

它拷贝到\0就结束了,但是我们这里的s1\0后面是不是还有有效字符啊,所以\0后面放的就是随机值,但是打印又是按照size去打印的,所以就打印出了乱码。

所以不能用strcpy,要按照size的大小去拷贝数据:

056e813dcb7e48d7b56aa0e10288a5df.png

可以使用memcpy。

b20f63ec6503469b99f2630482163385.png

这下就可以了。

9.3 流提取>>重载

那接着我们再来重载一下流提取:

那就可以这样搞:cf7573ae12af4a23b1ccf60ff96f90d7.png

用一个循环,一个字符一个字符的去缓冲区里提取,然后插入到s里,遇到空格或者换行就停止。

试一下:d171ff62864240d480df66c7b8207832.png

但是我们发现好像有点不对劲啊,为什么不行啊?

🆗,原因在于cin呢它读不到缓冲区里的空格和换行,为什么读不到呢?

之前也提到过,C语言里的scanf,包括这里的cin,我们在用它们输入的时候是不是有可能输入多个值啊,那当我们输入多个值的时候,它们默认是以空格或者换行来区分我们输入的多个值的。

所以它遇到缓冲区里的空格或者换行的时候,它会认为这是你输入多个值的一个区分,会自动忽略掉它们,不会去提取,所以这里就读不到空格和换行,那循环就不会结束。

那要怎么解决呢?

我们可以用这个:

ab4221c4dd874d6888b220e440a5700a.png

get是istream类的一个成员函数,它一次读取一个字符,就可以提取到空格和换行。

09190ac734e2462691344c6a48c19a1a.png

再来测试一下:

5caf2d6cbb924d0982e74377cdd7759c.png

78d775e42e9b40b39de052f82efa4302.png

就可以了。

但是如果是这种情况呢:

a3dbd5ada2ca46b6be47bbcf95c43572.png

string对象原来就有数据,看一下库里的实现:

273357da9d1840038cebd513929bb843.png

是不是要把之前的数据清掉啊。

那怎么清除一个string对象?

是不是用clear啊bab3df21f4b04ec6885ee09594bae9b4.png

那我们先实现一下clear:

8fb0d63cc79e4061896113a3dca344e6.png

非常简单。47739e2ce2094039a39f5da61556a5d4.png

再来试一下:

5a1d0ee759f34a78b1d8024a268846ac.png

就好了。

那还有没有什么其它的问题?

🆗,如果我们输入一个特别长的字符串,那这个地方在不断+=字符的过程中是不是可能会频繁扩容啊,那我们有没有什么办法可以解决一下呢?

首先这里解决的方法肯定不是唯一的:

那库里面呢用了一种类似于这样的方式:

dc4125588f5d4bc68a2956d543472312.png

这里开了一个数组,每次先把字符一个个放到数组中,满了的话就+=到s里(以字符串的形式),然后把i置成0,后面继续放数组里。

那这样做相对而言扩容就不会那么频繁了。

11f332d04f114f6992152d9768750a96.png

当然,如果输入结束buff数组没满,那就把里面有多少数据就+=多少。

那buff数组的大小大家也可以自己调整。

1d7944ac6d2048f09fc29f68f26fb1c5.png

🆗,那我们string的模拟实现差不多就到这里:


当然实际库里面string提供的接口是很多的,我们不可能全部实现完,而且有的基本不怎么用,还有的虽然没实现,但是想要实现也很简单,我们带这大家一起实现的都是一些比较重要和常用的。

那这里模拟实现最重要的目的呢,其实还是让大家更好的理解string,同时也作为一个练习。

10. 写时拷贝(了解)

最后我们再来了解一个东西叫做写时拷贝:d6628d8afe7c431d953c27cfc28e9cd5.png

大家说这里打印出来的大小是多少?


cef44d40162146d785deec093876cd07.png

是28,为什么是28呢?

🆗,这个我们在string的使用那篇文章是不是给大家解释过啊。

vs上在这里做了一个优化,多开了一个大小为16的数组,这样如果字符串比较短的话就不用去堆上申请空间了,直接存到这个数组里。


那大家知不知道G++下面一个string对象的大小是多少?


G++下,string是通过写时拷贝实现的,string对象总共占4个字节(32位平台下,64位下8个字节),内部只包含了一个指针。

那只有一个指针,它具体是怎么实现的呢?

该指针指向一块堆空间,内部包含了如下字段:


空间总大小

字符串有效长度

引用计数

指向堆空间的指针,用来存储字符串

比如我们现在有一个string对象s1,那它大概是这样一个样子:

91535b4b47d549039c830cf841d0e13a.png

那写时拷贝又是什么东西呢?


如果现在又有一个s2是s1拷贝构造出来的,我们vs上面是深拷贝。

vs是PJ版本STL,g++是SGI版本STL。

而Linux的G++(采用的是SGI版本)下面是写时拷贝:

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。

引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给

计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象是资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

2807e490ebe14ba5b16833a1b66a1635.png

此时只有s1一个对象使用这块资源,引用计数就是1 。

23963d1e3c784055b6b929c20a925f3a.png

现在如果有s2是s1的拷贝,那这里是一个浅拷贝,只是把引用计数加1,表示现在有两个对象使用这块资源。

那释放的时候怎么办呢?

da7232cd91b8407da34d79dcf71f23cf.png

🆗,释放s2的时候,就把引用计数减1,而不是真的释放这块空间,等到s1释放的时候,引用计数为1,表示只有一个对象使用这块资源,就可以释放了。


那这个地方是不是不拷贝啊?


不是的,写时拷贝,写时拷贝,就是写的时候才拷贝。

还拿上面那个例子来说,如果s2只是拷贝s1,我们并没有修改s2,那它们两个就可以共用一块空间,如果我们去修改了s2的内容,那这个时候才会进行真正的拷贝,为s2开一块独立的空间,然后把s1的内容拷贝下来,然后你要修改数据就在你自己的这块空间上进行修改。

0ed625e054284849b37701f381b3e725.png

修改数据才会触发写时拷贝(Copy-On-Write),不修改当然就不会改。这就是托延战术的真谛,非到要做的时候才去做。

11. 源码展示

string .h

#pragma once
#include <assert.h>
namespace yin
{
  class string
  {
  public:
    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;
    }
    /*string()
      :_str(new char[1])
      ,_size(0)
      ,_capacity(0)
    {
      _str[0] = '\0';
    }*/
    //构造函数
    //string(const char* str = nullptr)//不可以
    //string(const char* str = '\0')
    string(const char* str = "")
      :_size(strlen(str))
    {
      _capacity = _size;
      _str = new char[_capacity + 1];
      strcpy(_str, str);
    }
    //拷贝构造
    string(const string& s)
      :_size(s._size)
      ,_capacity(s._capacity)
    {
      _str = new char[s._capacity + 1];
      strcpy(_str, s._str);
    }
    //赋值重载
    string& operator=(const string& s)
    {
      if (this != &s)
      {
        /*delete[] _str;
        _str = new char[s._capacity + 1];
        strcpy(_str, s._str);
        _size = s._size;
        _capacity = s._capacity;*/
        char* tmp = new char[s._capacity + 1];
        strcpy(_str, s._str);
        delete[] _str;
        _str = tmp;
        _size = s._size;
        _capacity = s._capacity;
      }
      return *this;
    }
    bool operator>(const string& s)const
    {
      return strcmp(_str, s._str) > 0;
    }
    bool operator==(const string& s)const
    {
      return strcmp(_str, s._str) == 0;
    }
    bool operator>=(const string& s)const
    {
      //return *this > s || *this == s;
      return *this > s || s == *this;
    }
    bool operator<(const string& s)const
    {
      return !(*this >= s);
    }
    bool operator<=(const string& s)const
    {
      return !(*this > s);
    }
    bool operator!=(const string& s)const
    {
      return !(*this == s);
    }
    void reserve(size_t n)
    {
      if (n > _capacity)
      {
        char* tmp = new char[n + 1];//多开一个给\0
        memcpy(tmp, _str, _size);
        //strcpy(tmp, _str);错误,不能用strcpy
        delete[] _str;
        _str = tmp;
        _capacity = n;
      }
    }
    void resize(size_t n, char ch = '\0')
    {
      if (n <= _size)
      {
        _size = n;
        _str[_size] = '\0';
      }
      else
      {
        if (n > _capacity)
          reserve(n);
        size_t i = _size;
        while (i < n)
        {
          _str[i] = ch;
          ++i;
        }
        _size = n;
        _str[_size] = '\0';
      }
    }
    void push_back(char ch)
    {
      /*if (_size + 1 > _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
      _str[_size] = ch;
      _size++;
      _str[_size] = '\0';*/
      insert(_size, ch);
    }
    void append(const char* str)
    {
      /*size_t len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
      strcpy(_str + _size, str);
      _size += len;*/
      insert(_size, str);
    }
    string& operator+=(char ch)
    {
      push_back(ch);
      return *this;
    }
    string& operator+=(const char* str)
    {
      append(str);
      return *this;
    }
    string& insert(size_t pos, char ch)
    {
      assert(pos <= _size);
      //扩容
      if (_size + 1 > _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
      //挪动数据
      size_t end = _size + 1;
      while (end > pos)
      {
        _str[end] = _str[end - 1];
        --end;
      }
      //插入数据
      _str[pos] = ch;
      ++_size;
      return *this;
    }
    string& insert(size_t pos, const char* str)
    {
      assert(pos <= _size);
      size_t len = strlen(str);
      //扩容
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
      //挪动数据
      size_t end = _size + len;
      while (end > pos + len - 1)
      {
        _str[end] = _str[end - len];
        --end;
      }
      //插入数据
      strncpy(_str+pos, str, len);
      _size += len;
      return *this;
    }
    string& erase(size_t pos = 0, size_t len = npos)
    {
      assert(pos < _size);
      if (len == npos || pos + len >= _size)
      {
        _str[pos] = '\0';
        _size = pos;
      }
      else
      {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
      }
      return *this;
    }
    void swap(string& s)
    {
      std::swap(_str, s._str);
      std::swap(_size, s._size);
      std::swap(_capacity, s._capacity);
    }
    size_t find(char ch, size_t pos = 0)
    {
      assert(pos < _size);
      for (size_t i = pos; i < _size; ++i)
      {
        if (_str[i] == ch)
          return i;
      }
      return npos;
    }
    size_t find(const char* str, size_t pos = 0)
    {
      assert(pos < _size);
      char* p = strstr(_str + pos, str);
      if (p == nullptr)
        return npos;
      else
        return p - _str;
    }
    //析构
    ~string()
    {
      delete[] _str;
      _str = nullptr;
      _size = _capacity = 0;
    }
    const char* c_str()
    {
      return  _str;
    }
    const char& operator[](size_t pos)const
    {
      assert(pos < _size);
      return _str[pos];
    }
    char& operator[](size_t pos)
    {
      assert(pos < _size);
      return _str[pos];
    }
    size_t size()const
    {
      return _size;
    }
    size_t capacity()const
    {
      return _capacity;
    }
    void clear()
    {
      _str[0] = '\0';
      _size = 0;
    }
  private:
    char* _str;
    size_t _size;
    size_t _capacity;
    static const size_t npos;
  };
  const size_t string::npos = -1;
  ostream& operator<<(ostream& out, const string& s)
  {
    for (auto e : s)
      out << e;
    return out;
  }
  istream& operator>>(istream& in, string& s)
  {
    s.clear();
    char ch = in.get();
    char buff[128];
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
      buff[i++] = ch;
      if (i == 127)
      {
        buff[127] = '\0';
        s += buff;
        i = 0;
      }
      ch = in.get();
    }
    if (i != 0)
    {
      buff[i] = '\0';
      s += buff;
    }
    return in;
  }
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include "string.h"
//int main()
//{
//  yin::string s1("hello world");
//  yin::string s2(s1);
//
//  cout << s1.c_str() << endl;
//  cout << s2.c_str() << endl;
//
//  yin::string s3;
//  s1 = s1;
//  cout << s1.c_str() << endl;
//  cout << s3.c_str() << endl;
//  return 0;
//}
void Print(const yin::string& s)
{
  for (auto e : s)
    cout << e << " ";
  cout << endl;
}
//int main()
//{
//  yin::string s1("hello world");
//  for (yin::string::iterator it = s1.begin(); it != s1.end(); it++)
//  {
//    (*it)='*';
//  }
//  for (auto e : s1)
//    cout << e << " ";
//  cout << endl;
//  /*for (yin::string::iterator it = s1.begin(); it != s1.end(); it++)
//  {
//    cout << *it << " ";
//  }*/
//  cout << endl;
//  Print(s1);
//  return 0;
//}
//int main()
//{
//  yin::string s1("hello word");
//  yin::string s2("xxxxxxxxxxxx");
//  cout << (s1 > s2) << endl;
//  cout << (s1 == s2) << endl;
//  cout << (s1 < s2) << endl;
//
//  return 0;
//}
//int main()
//{
//  yin::string s1("hello world");
//  s1.push_back('X');
//  s1.append("*************");
//
//  /*s1 += 'x';
//  s1 += "***********";*/
//  cout << s1.c_str() << endl;
//  return 0;
//}
//int main()
//{
//  yin::string s("hello world");
//  //s.insert(5, '*');
//  s.insert(5, "***");
//  cout << s.c_str() << endl;
//
//  //s.insert(0,'*');
//  s.insert(0,"xxxxxxxxxxxxxx");
//  cout << s.c_str() << endl;
//  return 0;
//}
//int main()
//{
//  yin::string s("hello word*****************");
//  cout << s.capacity() << endl;
//  s.reserve(10);
//  cout << s.capacity() << endl;
//
//  return 0;
//}
//int main()
//{
//  yin::string s;
//  s.resize(20, '*');
//  cout << s.c_str() << endl;
//  s.resize(30, 'x');
//  cout << s.c_str() << endl;
//  s.resize(10);
//  cout << s.c_str() << endl;
//
//  return 0;
//}
//int main()
//{
//  yin::string s("0123456789");
//  cout << s.c_str() << endl;
//  s.erase(4, 3);
//  cout << s.c_str() << endl;
//  s.erase(4, 30);
//  cout << s.c_str() << endl; 
//  s.erase(2);
//  cout << s.c_str() << endl;
//  return 0;
//}
//int main()
//{
//  yin::string s1("hello world");
//  yin::string s2("yhq dyf");
//  s1.swap(s2);
//  cout << s1.c_str() << endl;
//  cout << s2.c_str() << endl;
//
//  return 0;
//}
//int main()
//{
//  yin::string s1("hello world");
//  s1 += '\0';
//  s1 += "xxxxxxxx";
//  
//  cin >> s1;
//  cout << s1<< endl;
//
//  /*yin::string s1("hello world");
//  s1 += '\0';
//  s1 += "xxxxxxxx";
//  cout << s1 << endl;
//  s1.reserve(50);
//  cout << s1 << endl;*/
//
//  return 0;
//}
int main()
{
  string s;
  cout << sizeof(s) << endl;
  return 0;
}

🆗,那这篇文章的内容就先到这里,欢迎大家指正!!!

013b489c74af44888f17358aa6d5d99c.png


目录
相关文章
|
29天前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
70 5
|
29天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
51 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
103 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
91 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
109 4
|
2月前
|
安全 Java 测试技术
Java零基础-StringBuffer 类详解
【10月更文挑战第9天】Java零基础教学篇,手把手实践教学!
55 2
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
29 1
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
32 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
32 4
|
2月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)