C++ 从了解string类 到它的模拟实现

目录

string 介绍

string 的模拟实现

1.string类的成员变量

2.string的默认成员函数

<1> 构造函数

<3>拷贝构造函数

 <4> 赋值函数

3.后面会用到的一些基础接口函数

<1>  reserve

<2>  size

<3>  capacity

<4>  c_str

 <5> operator[] 

<6>  clear

<7>   resize

4.string类常用的插入操作接口函数

<1>   push_back

<2>   append

<3>   insert

5. string类常用的删除操作接口函数

  <1>  erase

 6. string类的查找接口函数

<1>  find

 7. string类  重载操作符、运算符和流插入、提取操作符

<1>   operatro+=

<2>  operator==

 <3>  operator>

 <4>  operator>=

 <5>  operator<

 <6>  operator<=

 <7>  operator!=

 <8>  operator>>

 <9>  operator<<

 8. string类  子串提取

<1>  substr 

9.string类的迭代器

总结


string 介绍

在C语言中,我们如果要实现一个字符串,只能用char str[] 和char* str。而在C++库里,有string可以更好地方便我们对字符串进行增删查改。

string - C++ Reference (cplusplus.com)中我们可以进行查询相关文档来了解string的各个用法。

e91934bf70b148fb86807c07da48abb3.png

可以从文档里看出,要实现一个string类,工作量很大,需要实现各个接口函数,而且需要注意很多的细节。但是就是因为c++库的函数做了过多的重载与不常用的函数接口,也被吐槽过不少次。

所以,本文章只模拟实现我们在日常生活中比较常用的接口函数,且为了方便理解,我们也尽量简化了它的实现。

string 的模拟实现

1.string类的成员变量

为了不与全局中的string类相冲突,我们可以使用namespace创建一个命名空间域,防止与全局的string相冲突。

而我们后续写的测试用例也是在命名域中实现。

088c150a0d844ec9ba76393c43c2c42b.png

class string
	{
	private:
		char* _str;        //用于存放字符串
		size_t _size;      //已存放的有效字符数量
		size_t _capacity;  //可存放的有效字符数量
	};

char* _str:  string所存放字符串本质其实也是用的char* ,并没有用其他的东西。而_str就是用于存放字符串。

size_t _size: 它用于记录我们已经存放的有效字符的数量。 '\0'不属于我们的有效字符

size_t _capacity:  它用于记录该字符串最大可以存放的有效字符的数量。 '\0'不属于我们的有效字符

注意:  C++库中的string类的成员变量其实比上述三种成员变量更加复杂,而string类的成员函数简化来看的话,其实就是以上三种。

2.string的默认成员函数

每个类都有自己对应的成员函数,string更是不例外,而且因为涉及深浅拷贝问题,我们必须要自己来实现他们的默认成员函数!

<1> 构造函数

d61a060de4ed4cbcbf02abad328610bd.png

既然是构造函数,我们首先要支持它可以初始化,而string的初始化只需要输入字符串即可,所以我们的参数是 const char* str = ""(注意这里的缺省是空字符串)

在构造函数,我们其实就已经面临了深浅拷贝问题,如果使用默认的构造函数,会使用浅拷贝,后面析构的时候会导致一个指针析构两次!

而我们深拷贝的过程则是先new一个足够空间大小的字符串,再使用strcpy进行字符串的深拷贝。

注意: 我们在进行new新空间的时候一定要记得给'\0'留下一个位置,因为'\0'是判断一个字符串的结束标志,所以我们上面写的是new char[_capacity + 1],而'\0'是不属于我们的有效字符。

<2>析构函数

a3d0061887684d2bb5c3abf221f57871.png

析构函数没有什么需要注意的地方,因为我们已经在构造函数实现了深拷贝,所以析构函数不会重复析构一个指针。

<3>拷贝构造函数

b3003b6520d543a8ac42502f4aa91478.png

拷贝构造也要注意深浅拷贝的问题,如果是默认的拷贝构造函数也会是浅拷贝,也会导致一个指针析构两次,所以要进行深拷贝。

如果说我们称上面那种写法为 传统写法,那么还有一种写法可以称之为现代写法。

561d3b7c292d42688349370428f2f329.png

不知道大家是否可以看出来这种写法的精髓所在

它最大的亮点就是通过一个临时的string 对象,用构造函数来完成我们的拷贝构造。

注意:  现代写法的初始化列表的_str必须初始化为nullptr,否则析构的时候会析构一个野指针,会报错。

 <4> 赋值函数

8eb6513b2b604f649ce18da6c3404acd.png

和拷贝构造一样,这种写法也有一种现代写法,更为巧妙

5236355e9652405897f334f47502b907.png

 它的精髓比拷贝构造还要精,这种写法让这个临时变量的作用到了极致。

 e13ceda6684145f49dc96bf983c06361.png它不带来了深拷贝的内容,还把原先的不需要的内容帮忙析构清理了,所谓是用到了极致。

3.后面会用到的一些基础接口函数

<1>  reserve

266d000fc58d4f75b27d9e958b2e03a7.png

reserve的作用就是重新开辟一块更大的空间用于存储字符串,后面我们进行插入操作的时候必须要用到。

<2>  size

0b6cfc6a47104bfa8296d4d003fbf1d3.png

作用:  返回目前的_size

<3>  capacity

2f7637d45e874a1a95569438b246fdc3.png

作用:  返回目前的_capacity

<4>  c_str

e7d97f470c5d463cbf5d13dd1ad8c53b.png

 作用:返回字符串内容以字符串形式

 <5> operator[] 

e68b1fd9095a4dc8ace9c8a2b0270213.png

重载完[],后面对_str进行操作会轻松很多。

7891906be2174fddb5c0234ec7844462.png

再也写一个const 版本兼容const string。

注意:pos不能比_size小,否则会出现非法访问,导致运行出错,最好使用assert断言一下。

<6>  clear

 作用:让字符串成为一个空字符串

注意:'\0'后面可能会有内容,但是不影响,因为'\0'是字符串结束的标志,并且_size被置为了0

<7>   resize

 作用:如其名,重置字符串的_size为n,如果空间不够就调用reserve来扩容,如果_size要大于n,就会删除n后面的内容,相当于调用了erase,这个也可以用来删除数据,但是我们习惯用erase来进行删除数据。

4.string类常用的插入操作接口函数

<1>   push_back

2c5148c2cbde4c8b9d6cb7b3d277adce.png

  过程很简单,先判断原先的空间(_capacity)是否足够存放添加后的内容,如果不够就调用reserve函数进行扩容,再进行插入。

后面如果实现了insert函数可以直接调用insert。

 注意:  记得手动去添加‘\0’,否则会出现字符串找不到停止标志

<2>   append

 2c8dc650ba8548a5a90ffcd44b63d019.png

它的作用是用于在字符串后面追加一个字符串或者字符内容。

ef81c60be66f4598854ddb0cdbfb465c.png

2f51d23d04474265a898c0b3c57a18b5.png

 过程很简单,先判断原先的空间(_capacity)是否足够存放添加后的内容,如果不够就调用reserve函数进行扩容,再进行一个一个插入。

addda4ce19fc4f8980d4f4f491716ead.png

 调用push_back。

注意:  记得手动去添加‘\0’,否则会出现字符串找不到停止标志

<3>   insert

c66b5804a39c4fb5b46c51a1bb2c4cda.png

 insert可以在pos位置的下标位置进行插入一个字符串或者字符内容。

b9760f34ad7a4b47917772c791c94516.png

它的过程也和append一样,先检查空间够不够,不够就扩容。然后再把pos位置后面的内容往后挪,再进行pos的插入,以免数据重叠丢失。

df6c558fcd7c4ed1b57546372bb52a54.png

47b5ebbabaef46de916f75f52236ddba.png

注意:  一定要对下标pos的位置有很清晰的空间概念,不然很有可能导致挪动位置不对等问题,最好先画图理解

5. string类常用的删除操作接口函数

  <1>  erase

09be5b1b14624d2babf8232a6de5ae6b.png

erase的作用是删除pos位置下坐标后面的len长度的字符。

删除的过程是先将pos+len位置后面的数据包括'\0'覆盖掉pos位置要删除的数据

erase很简单,只不过在这里我要补充一个string类重要的特殊常量npos

286ae2a62cc349d282e57cbd997a4c4a.png

nops 是一个size_t 类型 ,而又被赋值为-1,所以-1的补码如果变成一个无符号整形将会变成一个非常大的数字,所以它的作用一般用于取到字符串的尾。

比如说上面的erase,如果你没有传len的值,他会默认删除pos位置后面的全部内容

npos是一个string类中的const静态成员变量

 6. string类的查找接口函数

<1>  find

如果找到了则返回下标,如果没找到则返回npos

 查找string类的字符串

 查找c形式的字符串

查找字符 

C++库里还有其他的查找函数,但是我们一般只会用到原版的find,所以这里只实现这一个,大家有兴趣可以实现其他的

 7. string类  重载操作符、运算符和流插入、提取操作符

<1>   operatro+=

重载+=其实可以直接理解为调用append,并且我们在使用string时也习惯使用+=,而不是append,+=用着更舒服。  

<2>  operator==

我们可以调用全局中的strcmp来直接进行比较。

 <3>  operator>

 <4>  operator>=

 只要实现了两个重载条件操作符,就可以实现套用

 <5>  operator<

 <6>  operator<=

 <7>  operator!=

 <8>  operator>>

注意:流插入提取操作符是声明定义在类的外面的,并且string类的重载流操作符可以不需要添加友元函数

 <9>  operator<<

 流提取操作符需要注意,如果仅仅使用<<进行提取字符,会导致忽略" ",永远没有办法提取完,所以我们需要使用in.get()来提取空格和'\n', 

 8. string类  子串提取

<1>  substr 

 作用:从pos位置提取len长度的字符串

注意:如果不穿len的长度,则默认为npos,即取到尾

9.string类的迭代器

string类的迭代器其实就是原生指针

当我们实现了begin和end,范围for也可以使用 

总结

以上便是本章的内容,我们一步步从了解string类到 初步模拟实现。

实际的C++库中的string更加复杂,其实也能说是冗杂,有许多不必要的功能,但是如果大家有兴趣,也可以去了解一下。 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风君子吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值