【C++】自主实现string类

大家好,我是苏貝,本篇博客带大家了解C++如何自主实现string类,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
在这里插入图片描述


将自主实现的string放在命名空间中,string类有3个成员变量:_str(数组,里面存放字符),_size(数组的有效字符的个数,不算’\0’),_capacity(数组的容量,不算’\0’)
在这里插入图片描述

(A) 构造函数+析构函数+拷贝构造+operator=

a. 构造函数

写2个构造函数:无参+有参。
在这里插入图片描述

有参构造报错了,为什么?因为str是const修饰的,_str没有被const修饰,非const->const,权限放大,报错。

那能用const修饰_str吗?这样就不会报错了

在这里插入图片描述

不能,如果用const修饰_str,那么_str就不能被修改,即string的对象不能修改,这是错的。

在这里插入图片描述

因此我们先给_str开空间,空间为_capacity+1的原因是:因为_capacity不包括’\0’,所以要额外开辟一个字节的空间取存储’\0’。再将str的内容拷贝到_str中,strcpy会将str的‘\0’拷贝给_str

问:无参的构造函数有问题吗?
有的,不能将_str初始化为nullptr,因为如果这样,在c_str()函数的作用下可能会报错
在这里插入图片描述

为什么?因为如果我们创建一个没有给初始值的对象,那么它将调用无参构造,因此_str为空,c_str()返回_str,所以会打印nullptr,这是不被允许的

在这里插入图片描述
在这里插入图片描述

因此,无参构造也要修改一下
在这里插入图片描述

最后,我们想将无参和有参的构造函数合起来写,也就是写一个有缺省值的构造函数。
可以这样写吗?
在这里插入图片描述

不行,因为不能strlen(nullptr)
在这里插入图片描述

那可以将nullptr换成’\0’吗?不行,’\0’是字符字面量,类型是char。str类型是const char*,类型不同。那可以将’\0’换成”\0”吗?它是字符串
在这里插入图片描述

可以的。但是其实””是常量字符串,按C语言的规则,常量字符串的最后本身就有\0,所以再写一个\0就是”\0\0”,有些多余了。因此可以直接用””
在这里插入图片描述

b. 析构函数

在这里插入图片描述

如果我们定义的是一个不带初始值的对象,那_str就是new char(‘\0’),不应该用delete _str吗?
其实这种情况用delete[ ]也是对的,因为char是内置类型,所以编译器不需要在开辟的空间_str前再开辟4个字节存放new char 的个数,所以delete[ ]也可以。

c. 拷贝构造+operator=

因为两个函数的思路类似,所以放在一起写
在这里插入图片描述

operator=是两个对象都已存在,将一个赋值给另一个。拷贝构造是用已经存在的同类型的对象取初始化新创建的对象。operator=的被赋值的对象已被定义,它可能有初始值,因此要释放掉原来开辟的空间。拷贝构造创建的对象是新的,因此在拷贝构造前没有初始值,所以没有开辟空间,也就不需要释放

(B) 实现3种遍历

a. begin/end

在这里插入图片描述

先查看string类的begin/end,发现它们的返回值类型是迭代器,因此我们自主实现的返回值类型也要是迭代器。下面我们实现的是指针版的迭代器
在这里插入图片描述

指针版本的迭代器很好理解,就是将指针的类型char*取别名为iterator(最好是这个英文单词,原因后面说)

再来一个const修饰的this指针的begin/end
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

b. 范围for

在这里插入图片描述

事实上,只要我们将begin/end函数写好,就可以使用范围for,因为范围for的底层是迭代器。但是注意:要想只将begin/end函数写好,就可以使用范围for有条件,即迭代器类型名是iterator,且begin/end函数的函数名只能是begin/end,哪怕是Begin都不行

c. operator[ ]

在这里插入图片描述

要想用[ ]来遍历数组,就要写size()和[ ]重载
在这里插入图片描述

length()和size()都是返回_size,一起写了。capacity()也简单

现在来写[ ]重载,如果string对象是非const,那么它的内容就可以被修改。如果string对象是const,那么它的内容就不能被修改,需要对operator[ ]的this指针和返回值加const
在这里插入图片描述

operator[ ]是用断言来进行检查的。传统C语言的检查是否越界是一种抽查,对越界读检查不出来,对越界写可能检查出来。比如下图的a[11]就能检查出来,但对a[15]就检查不出来。这是因为C语言在设计越界检查时,是通过检查数组(如int a[n])的[n]和[n+1]等相距较近的这些位置,如果编译器发现这些位置的值被修改,就报错。所以如果修改比较远的空间,编译器就不会报错,因为不会检查这些位置

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

因此,C++的string类用断言就能确保[ ]重载不会越界,这相对于C语言是一个大的进步

在这里插入图片描述

C语言的a[i]是转换成*(a+i)来处理,可是C++中[ ]是函数,如果我们循环10000次,岂不是要建立10000个栈帧再销毁10000个栈帧,这是不是消耗太大了?其实,祖师爷已经考虑过这个问题,将[ ]设为内联函数,所以它在预处理阶段就会在调用位置展开,不用建立栈帧

© 扩容:resize/reserve

a. reserve

在这里插入图片描述

要求:当n<=_capacity时,什么都不做(一般不会缩容)。当n>_capacity时,一般会在异地扩容,扩容后的_capacity==n

在这里插入图片描述

b. resize

在这里插入图片描述

要求:当n<=_size时,string类对象的内容变为_str[0,n],_str[n]=’\0’,即有效的字符个数变为n,不缩容。当n>_size但n<=_capacity时,将_size=n,用c来初始化新增的有效字符的空间。当n>_capacity时,扩容,将_size=n,用c来初始化新增的有效字符的空间。

在这里插入图片描述

(D) 插入:push_back/append/+=/insert

a. push_back

在这里插入图片描述

要求:尾插一个字符

要先判断是否要扩容,当_size==_capacity时就要扩容,可以2倍扩容
在这里插入图片描述

b. append

在这里插入图片描述

append在string类里实现了许多重载,我们只实现(3),要求:尾插一个字符串

append也要先判断是否要扩容,也是当_size==_capacity时扩容吗?不是的,是要看_size+len(插入的字符串的长度)是否大于_capacity。
那是2倍扩容吗?不是,万一未扩容前的_size=_capacity=5,要插入长度len为10的字符串,那么2倍扩容_capacity==10<15就不够。所以我们选择reserve(_size+len)

在这里插入图片描述

c. +=

在这里插入图片描述
我们在这里实现(2)(3),其实就是复用前面的push_back和append即可

在这里插入图片描述

d. insert

在这里插入图片描述

我们在这里实现(3)和在pos位置插入一个字符的insert

  1. 先写插入一个字符的,因为只插入一个字符,所以如果要扩容,2倍扩容即可,和push_back相似

问:下面的代码有哪里错吗?
在这里插入图片描述

我们发现,给其他位置插入一个字符时没有问题,但给pos=0位置插入时好像进入了死循环,这是为什么?
在这里插入图片描述

因为end是int类型的,pos是size_t的,不同类型比较时要先转换成同种类型,所以将int转换成size_t,因为size_t是无符号整型unsigned int,它>=0,因此将end转换成size_t类型的临时变量与pos==0相比较,永久成立,因此会死循环(end会变成负数,但是将end转换成size_t类型的临时变量,该临时变量是>=0的)

在这里插入图片描述

所以该如何修改呢?

方法1:将pos强转成int,这样end和pos比较就是用int比较,end- -到-1就不再进入循环

在这里插入图片描述
在这里插入图片描述

方法2:
让end初始化为‘\0’后一位的下标,把_str[end-1]赋值给_str[end],这样的话,循环条件就是end>pos,当将第一个字符移到第二个位置上后,end–,end==0,不符合循环条件,退出循环

在这里插入图片描述

  1. 再来写插入一个字符串的insert,也是2种方法,思想和上面一样

在这里插入图片描述

  1. 写完了2个insert函数,就可以在push_back和append函数中复用insert了
    在这里插入图片描述

(E) erase/substr

a. erase

在这里插入图片描述

实现(1),要求:从pos位置开始,删除len个字符

Len的缺省值是npos,npos是string类中定义的一个static 变量,值为-1。因此我们也要在自主实现的string里加该static变量

在这里插入图片描述

在这里插入图片描述

问:下面代码有问题吗?
在这里插入图片描述

有,npos是-1,换成size_t类型的即为最大值,如果我们传的实参len==npos-1,pos>1,那么就会栈溢出风险,因此我们将len单独放在一边
在这里插入图片描述

b. substr

在这里插入图片描述

要求:返回子串,子串从pos位置开始,持续len个字符

在这里插入图片描述

(F) swap/find

a. swap

问:C++有提供swap模板,为什么string类又重新实现了一个swap?

在这里插入图片描述
在这里插入图片描述

因为调用C++库里的swap模板,要先调用一次拷贝构造,再经过2次赋值,最后再调用1次析构函数,太麻烦了。所以写了一个专门给string类的swap函数,这样就不需要调用上述的拷贝构造/赋值/析构函数,提高了效率

在这里插入图片描述

那如何避免string对象使用C++库的swap模板呢?在string类外再实现一个全局的swap函数,它在函数体里调用类里的swap(如下图)

在这里插入图片描述

那为什么对string对象用swap(s1,s2)调用的是我们写的全局swap,而不是C++库里的swap呢?因为对于string类来说,我们写的和C++库的都是全局的函数,我们写的swap函数的形参类型就是string,而C++库的是模板,有现成的就要现成的,因此会选择我们写的swap函数

b. find

在这里插入图片描述

我们写(2)(4)

在这里插入图片描述

在这里插入图片描述

(G) >>/<</getline

a. <<

在这里插入图片描述
在这里插入图片描述

b. >>

向string对象输入时,需要先清空对象的内容,再将输入的内容拷贝到对象中。注意:当遇到空格或是换行时,一次cin结束

先写清空对象的函数clear
在这里插入图片描述

再写>>重载

问:下面代码有问题吗?

在这里插入图片描述
在这里插入图片描述

有问题,不管我们输入空格还是换行都不能让程序停止,为什么?
因为cin>>会忽略空格和换行,也就是说cin>>根本不会取到空格和换行,因此会死循环。C++库中提供cin.get()来取得所有输入的字符,包括空格和换行
在这里插入图片描述

在这里插入图片描述

有人认为,像上图这样只要不是空格和换行就让s+=ch这条语句效率不高,因为如果输入的字符串比较大,那么s就可能要扩容许多次。因此有人相出了下面的这种方法,能让效率提高
在这里插入图片描述

c. getline

getline的思路和>>重载类似

在这里插入图片描述
在这里插入图片描述

(H) 现代写法vs传统写法

a. 拷贝构造

在这里插入图片描述

b. operator=

在这里插入图片描述

在这里插入图片描述


好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️

自己实现的字符串 class CMStringImp; class CMstring { public: explicit CMstring(void); ~CMstring(void); CMstring(LPCTSTR lpszstr); CMstring(const CMstring& lpszstr); CMstring& operator = (const CMstring& lpszstr); operator LPCTSTR() const; bool operator == (const CMstring&) const; bool operator != (const CMstring&) const; bool operator < (const CMstring&) const; TCHAR operator[] (int nIndex) const; TCHAR& operator[] (int nIndex); CMstring& operator += (LPCTSTR pStr); CMstring& operator += (TCHAR ch); friend CMstring operator+(const CMstring& str1, const CMstring& str2); friend CMstring operator+(const CMstring& str1, LPCTSTR lpszstr); friend CMstring operator+(const CMstring& str1, TCHAR ch); friend CMstring operator+(TCHAR ch, const CMstring& str1); friend CMstring operator+(LPCTSTR lpszstr, const CMstring& str1); // friend wostream operator <<(wostream &is;,const CMstring &str;); public: LPCTSTR GetData() const; bool IsEmpty() const; TCHAR GetAt(int) const; TCHAR& GetAt(int); int GetLength() const; int Compare(const CMstring&) const; int CompareNoCase(const CMstring&) const; int Find(TCHAR ch, int nStart = 0) const; int Find(LPCTSTR pStr, int nStart = 0) const; int ReverseFind(TCHAR ch) const; int ReverseFind(LPCTSTR pStr) const; CMstring Right(int nCount) const; CMstring Left(int nCount ) const; public: CMstring& MakeLower(); CMstring& MakeUpper(); CMstring& MakeReverse(); int Replace(TCHAR chOld, TCHAR chNew); int Replace(LPCTSTR pszOld, LPCTSTR pszNew); int Insert(int iIndex, TCHAR ch); void Format(LPCTSTR lpszFormat, ...); private: CMStringImp* m_pImp; };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值