模拟实现string类
目录
一.构造与析构函数
二.基础小功能的实现
1.clear
2.c_str
3外部对私有的查看
三.实现string的迭代器
四.string的增删查改
1.push_back尾插
1.1reserve扩容
1.2尾插
3.运算符重载+=
4.insert在任意位置插入
5.erase删除
5.1npos的处理
5.2函数的实现
6.find查找
6.1查找字符
6.2查找一个串
7.substr
五.比较运算符的重载
1.流提取
2.流插入!!
3.getline(流插入的变形)
一.构造与析构函数
1.定义自己的命名空间,这里我们简便取名为my,防止和std里的string冲突
2.声明成员变量,实现string需要一个指向存储内容的char*指针,_size记录存储内容的长度(不包括\0),_capacity记录开辟的空间大小(同样也不包括\0)
3.声明构造函数,并且实现析构函数,析构函数直接释放掉str申请的内存,并且重置三个成员变量即可,实现比较简单
4.实现默认构造函数
4.1.首先,我们先实现这个默认构造函数,参数为缺省的一个指向字符串的指针
4.2.先用初始化列表对_size进行初始化,内容的长度与字符串长度一致
4.3.空间大小与长度一致
4.4.申请_capacity+1个空间,给_str
4.5.利用strcpy函数拷贝字符串到申请的空间里面
5.实现n个ch的构造函数
申请n+1个空间,然后利用for循环赋值,实现较为简单,不过多讲解,需要注意的是,需要自己手动将_str[_size]赋值为\0,之前的那个默认构造函数因为使用的strcpy函数就不需要
6.实现拷贝构造函数
简单来说,就是申请capacity+1个空间然后进行拷贝,不过多讲解了
二.基础小功能的实现
1.clear
clear函数的作业是清空string,由于string是线性结构,所以把_str的第一个元素改为\0,并且将_size改为0,那么在读取的时候,到第一个元素就停止了,就实现了clear
2.c_str
c_str的作用是返回一个指向存储内容的指针,这样就可以使用cout或者printf来打印string的内容了,方便我们查看,并且兼容c语言的功能
3外部对私有的查看
由于外部无法直接查看类的私有成员变量,所以我们可以通过public成员函数的方式来简介提供查看,_size用来查看长度,_capaciti用来查看容量,同时,由于string很像字符数组,所以我们运算符重载了[],方便他人使用
三.实现string的迭代器
begin返回初始位置,也就是_str,end返回最后一个元素的下一个位置,也就是_str+_size,有了迭代器的实现,我们就可以使用范围for来便利string了,效果如下
四.string的增删查改
1.push_back尾插
1.1reserve扩容
在尾插中,要检查空间是否足够,经常会出现扩容的情况,所以把扩容封装成一个函数
这是c加加标准库的解释,我们只完成扩容的情况
只有在n大于空间时,才进行扩容,我们只需要新申请一块空间然后将原来空间的内容拷贝过去就可以了,类似于深拷贝,最后不要忘记释放原有空间
1.2尾插
尾插之前,我们需要先判断空间是否足够,如果_size==_capacity说明空间已经满了,无法再插入,那么我们就需要使用reserve扩容,我们这里实现的是二倍扩容,但是要处理一下特殊情况,也就是_capacity为0的时候,此时如果二倍扩容,那么扩容后的空间也为0,所以,我们这里使用了三目操作符,如果空间为0,就扩容为4,其他情况就二倍扩容,然后插入即可
2.append
append函数通过在string的末尾加字符来扩展字符串,也算是增加的一种,我们只实现加一个串
首先,我们先判断空间是否足够再插入一个字符串,如果不够,我们就进行扩容,扩容的逻辑就是二倍扩容,如果二倍不够,那么就扩容到_size+len,接着使用strcpy函数来拷贝一下,最后记得更新size的值,就完成了
3.运算符重载+=
总体来说,和append差不多,我们实现+=一个字符和+=一个字符串
这里就是简单的函数复用,很容易就可以完成
4.insert在任意位置插入
作用就是在pos位置之前插入内容
这里我们实现了两种,在pos位置前插入n个字符和在pos位置前插入一个串
总体来说,思路就是先检查空间,然后把pos之后的内容后移n(n为插入元素的个数)个位置,然后进行插入,最后,一定要记得更新_size的值
5.erase删除
总的来说erase函数的作用就是在第pos个位置,删除len个字符,最后返回这个string对象。
5.1npos的处理
此函数的第二个参数有缺省值,为npos,就是最大的长度,我们可以把npos封装为一个静态成员变量,然后将其再类外初始化为-1,因为size_t是无符号整数,所以他的值将会为size_t的最大值,(在32位系统中,size_t
通常是32位无符号整数,最大值为 2^32 - 1
,即 4294967295
。在64位系统中,size_t
通常是64位无符号整数,最大值为 2^64 - 1
。)肯定可以到达字符串末尾
5.2函数的实现
如果删除的长度超出了剩余的长度,那么就是将pos及其后面的位置都删除掉,我们只需要把pos的位置设置为\0,并且更新_size的值就可以了
如果没有超出,那么我们用一个变量end记录要删除的最后一个位置的下一个位置,即pos后第一个不需要被删除的位置,然后进行数据的挪动,如上图代码所示,最后更新_size的值就好了
最后,不要忘记返回这个对象,即*this
6.find查找
find用来查找字符串中匹配的内容,并且返回它的位置
6.1查找字符
我们直接遍历整个字符串,找到就返回它的位置,找不到直接返回npos
6.2查找一个串
在这里,我们可以直接使用strstr函数来加快我们实现的速度,简单说一下strstr函数的作用就是,返回指向 str1 中第一次出现的 str2 的指针,如果 str2 不是 str1 的一部分,则返回 null 指针。
这样,我们如果没找到就返回npos,找到了返回指针相减的值,即位置
7.substr
简单来说,就是返回字符串的一部分
实现substr的功能,我们需要先更新len的值,如果长度超过了剩余长度,那么就是返回从pos到剩下的全部,然后,创建一个临时的string类来存储字符串的一部分,最后将它返回,需要注意的是,返回值不能为string&,因为tmp创建在栈中,出了栈会自动析构
五.比较运算符的重载
实现比较运算符的重载,我们一定要学会复用,我们可以先实现一个==(等于)的,那么!==不就是!=(不等于)了吗。以此类推,很快就可以实现了,在这里面,我们使用strcmp函数,它可以帮助我们比较两个字符串的大小,(大于就返回一个>0的数,小于就返回一个小于0的数,等于就直接返回0)
六.流插入和流提取的重载
istream是 C++ 标准库中的一个类,用于表示输入流(Input Stream),ostream表示输出流
我们重载流插入和流提取,一定要重载到全局,因为对象一定在>>或者<<的后面,如果直接重载为成员函数,那么第一个参数就被固定为this指针,无法正确使用
1.流提取
流提取直接使用范围for遍历即可
2.流插入!!
这段代码是经过优化后的代码,我在这里需要着重解释一下
首先,进入函数时,我们需要先清除字符串本身的内容,因为流提取的内弄会对原内容进行覆盖(std标准库就是这么实现的)
然后,我们为了防止输入的串过长,需要不断扩容降低运行效率,我们创建了一个buff数组来存储输入的内容,大小定为1024,然后通过+=的方式来对字符串更新(+=一个串会调用append,里面使用的是一次扩容,能够提高效率)
如果输入的内容过多,buff数组满了,那么就直接+=然后重置i为0,出了循环在判断一下,如果i不是0的话就再+=一次
3.getline(流插入的变形)
流插入是遇到空格或者\n时停止,getline可以自定义遇到停止的内容,所以,实现getlne只需要改变流提取的if判断即可(默认getline是遇到\n停止,即读取一行)