因为c++库中本身就存在string类,我们的模拟实现为了和原因的区分开,这里我定义一个名叫YY的命名空间,接下来我们的模拟实现全部在这个命名空间中进行。
构造函数
调试结果
我们自己实现的构造函数没有问题,s1被成功的初始化为hello world。
析构函数
析构之后,s1中的_str空间释放了,同时也被置成了空指针,_size和_capacity也被改成了0,所以这里的析构函数也是没有任何问题。
拷贝构造
默认拷贝构造
我们先用默认生成的拷贝构造试试看。
这里我们使用默认的拷贝构造出现了错误,这里就涉及到了深拷贝和浅拷贝的知识。
浅拷贝
看下图我们可以知道,s2确实以及被成功的初始化成了hello world,那么为什么会出错了,我们接着往下看。
当我们析构完s2时,可以发现这里s1成了一连串的奇怪字符,而问题也就出在这里。
这个错误就是因为我们使用的默认拷贝构造函数是浅拷贝,浅拷贝是按字节拷贝,拷贝完之后,s2和s1公用同一块空间,而当s2析构之后空间已经被释放,当s1再要去访问时就出错了。
自定义拷贝构造
深拷贝
为了解决浅拷贝发生的问题,所以这里我们自己实现一个深拷贝拷贝构造。
当加上深拷贝之后,我们就发现编译器现在已经不报错了。
深拷贝其实就是自己重新开辟一块空间,然后在这块空间中把内容复制过来。
拷贝构造现代写法
简化版(string类中只有char*str)
这里我们给_str初始化为nullptr的原因是,在调用swap时将nullptr给临时对象tmp,这样tmp在析构销毁时就不会出错,如果没有给_str初始化,那么_str就是一个随机值,给到tmp析构时会出错。
运算符重载
赋值运算符
通过调试我们可以看到s2已经成功赋值给了s1。
现代写法
现代写法(string类中只有char*str)
写法一:
方法二:
很妙
关系运算符重载
operator<
operator==
<=、>、>=、!=
这四个关系运算符重载全部复用上面两个关系运算符重载。
IO流运算符重载
operator<<
operator>>
c_str
这个函数返回的是一个字符串的指针,并且这个数组包含了\0。
size
返回字符串中的字符个数。
operator[]
如果需要检查是否越界我们可以加上assert(pos < _size)。
通过size和[],我们就可以遍历字符串和修改字符串了。
const operator[]
const[],我们使用这个带const的函数,那么就只能查看,不能修改。
使用场景如下,函数f的形参是有const修饰的,所以下面的s[0]会去调用const char& operator[](size_t pos) const这个函数。
迭代器
begin end
用迭代器遍历字符串s1。
用迭代器修改字符串s1。
const迭代器
范围for
范围for在编译时其实被替换成迭代器。
我们将begin修改成Begin,再用范围for就出错了。其实本质上范围for就是用的迭代器。
增容
在string类后增加一个字符
在string类后面增加一个字符串
这里的reserve函数和上面在string类后增加一个字符的reserve函数一样。
reserve
这个函数用于扩容。
resize
调整字符串的大小,将字符串调整为大小为n的字符串,如果n小于_size,那么就把_size的大小改为n,如果n大于_size,那么也把_size调整为n,后面扩大的位置上的字符初始化为ch。
operator+=
+=字符
在字符串后面再尾插一个字符。
+=字符串
在字符串后面再尾插一个字符串。
find
查找一个字符的下标位置,如果没找到则返回npos。
insert
在指定位置插入一个字符。
在指定位置插入一个字符串。
erase
从字符串的pos位置开始,删除len个数的字符。