这篇我们自己来简单实现一下string类中的各个接口,来帮助我们更好地理解string类接口的底层原理。
1.构造函数和析构函数
对于构造函数我们要写两种情况:空字符串和非空字符串

因为我们要自己实现string类,所以就不能用std命名空间,这里我们要自己创建一个命名空间,在里面实现string类。
我们之前也讲过string类底层就是一个数组,所以我们在实现时也按数组来实现,并定义数组中数据的大小和容量,就和我们实现顺序表一样。
这里面我先实现了c_str接口,因为我们现在是自定义类型,所以不能直接输出变量,所以实现c_str接口便于我们输出我们实现的字符串。
我们首先讲空字符串的情况:
这里可能有人会疑惑我为什么这样初始化?
可能觉得既然是空字符串,直接定义为null不就好了。我想大多数人的第一想法也是这样,但其实这样写是有问题的。
当我们初始化一个空字符串时,我们调用c_str接口时,返回值是char*类型,而此时字符串是空指针,乍一看没问题,感觉那就输出空指针嘛,没设么没问题啊。
但是char*这个类型比较特殊,它默认是当字符串输出的,所以它会解引用,此时就出问题了,对空指针解引用是很危险的事,所以不能初始化为null。
而字符串在底层保存时,其实会多开辟一块空间,来存储'\0',所以我们创建一个空间来存放'\0',_size和_capacity初始化为0即可。
下面讲非空字符串:
给一个默认空字符串也是为了调用但不传参数这种情况,而""这种在底层就是'\0'。
而是为了模拟真实的string类,所以我们创建空间时多创建一个空间来存放'\0'。
再把传入的参数拷贝到我们创建的空间即可。
最后是析构函数:
析构函数就和之前讲的一样,new出来的空间delete就行,再把_str置为null即可。
2.遍历字符串
在遍历字符串中我们要实现的就是size接口,[]符号重载以及迭代器的实现。
首先讲size:
这个就很简单,直接返回_size即可。

下面讲[]符号重载:
\]符号重载我们要实现两种,const类型和非const类型。

实现起来也简单,直接返回是相应位置的值即可。const类型和非const类型区别就是能否通过解引用来改变字符串的内容。
最后是迭代器的实现:


迭代器我们就只实现begin和end,并且每种都实现const类型和非const类型。
迭代器不一定是指针,但在这里实现时我们就用指针来实现,毕竟和指针的作用一样。
实现起来也简单,begin就是返回第一个元素的地址,end就是返回最后一个数据的下一个位置的地址。
3.修改字符串
修改字符串要实现的比较多,我就一个一个讲:
3.1reserve

reserve虽然能扩容也能缩容,但是缩容是分编译器的,并且实现起来较为麻烦,涉及当前未学的知识,所以这里我只实现扩容功能。
1.创建一个临时变量来接收我们扩容后的空间
2.讲当前数组的内容拷贝到新空间中
3.讲究空间销毁,并将_str指向新空间
4._capacity记录新的容量
3.2push_back

push_back就是尾插,注意的点就是要首先判断数组是否满了,满了就要扩容,最后在原_size的位置处插入数据,_size++,再将新_size位置置为'\\0'。
3.3append

实现append时要先计算字符串的长度,并判断我们之前的扩容方式扩容后与_size+len谁更大,取更大的作为我们的扩容方式。
接下来就是把字符串拷贝进数组,这里我用memcpy进行拷贝,避免字符串中中间就含有'\\0'导致拷贝终止。
注意:拷贝时要拷贝len+1个字符,所以最后一步可写可不写,或者拷贝len个字符,这样就要写上最后一步。
3.4+=运算符重载

实现了push_back和append后,实现+=运算符重载就简单了很多,唯一需要注意的就是我们要返回的是字符串本身,所以要注意返回值是string。
3.5insert
3.5.1插入一个字符

1.assert判断pos是否在有效范围内
2.判断是否需要扩容
3.因为底层数组,所以在中间位置插入一个数据就要将pos位置以后的数据全部向后移一位
4.将pos位置处的值置为要插入的数据
5._size++
至于为什么要把pos强转成int,这是因为我们这个循环的结束条件是i\