因为字符串是固定长度,不好进行操作,因此c++就用类将字符串进行了封装让其变得方便实用。
要深刻了解string,我们必须要熟练掌握类的使用,如果还有疑问可以看这一篇博客:c++ 类 (要学习类这一篇就够了 )
string
#include<string>
string是需要引用头文件<string>的,同时使用官方命名空间std。
string的函数
首先就要了解string构造相关的函数:
默认,传参,拷贝,赋值,析构
对于迭代器版本我在下面再统一介绍
默认构造
默认构造就是构造一个"",里面只包含了'\0',但是它的大小是0,说明'\0'不作为它的长度。
拷贝构造
第一个拷贝构造就是传统的(里面有隐式类型转换,创造一个string的临时对象来进行拷贝,但是编译器把它进行优化了,相当于string a("123456")),第二个就是加了一个限定范围(从它的哪个地方开始拷贝,拷贝多长)。其实两个拷贝构造能合成一个缺省参数的。string(const string&str,size_t pos=0,size_t len=npos)
传参构造
这三个构造函数都可以归类于传参构造,接下来我们来举例一下:
第一个就是用一个字符串来进行构造,第二个是用字符串的前n个字符来进行构造,第三个是用n个c字符进行构造
其他构造方法
迭代器构造
具体在下面迭代器块详细介绍:
初始值设定项列表
这种是c++11出来的东西,主要是这样用:
赋值
赋值也很简单:
值得注意的就是赋值和拷贝都是深拷贝,需要重新申请堆区的内存。
析构
析构需要释放堆区的内存,这是重点。
迭代器
看这里需要知道迭代器是什么,可以看看这一篇:迭代器的介绍
里面详细介绍了string的迭代器,这里大致再复习一下。
迭代器有iterator(正向迭代器)、const_iterator(不可修改值的正向迭代器)reverse_iterator (反向迭代器) const_reverse_iterator(不可修改值的反向迭代器)
这里可以就看成指向数组内容的指针,只是typedef了一个别名而已。
我们可以通过解引用来访问值,通过调用begin(),end()来调用正向迭代器的开头和结尾,同时我们可以用rbegin(),rend()来调用反向迭代器的开头和结尾。
其实就把它成连续内存的特殊指针就行了,如果是反向迭代器就是反着走是正方向。
string的其他成员函数
我们分块来讲:
首先开头的迭代器成员函数我们已经了解到了
Capacity
了解函数前我们要大概了解string的成员变量是什么:
string的结构其实和顺序表是差不多的,用一个指针只想堆区内存的成员,然后是一个size记录当前字符串的长度,然后是用capacity记录申请内存的长度,即当前最大能存储字符的长度,超过这个长度就要扩容。
然后是;
size/length
这些是关于空间的函数,第一个就是获取当前的字符串实际长度。第二个和第一个是同义,为什么会有两个相同功能的函数是因为原先只有length的函数,但是并不适用于其他容器,其他容器空间不是线性的,所以用的size更加的宽泛,然后string为了和后面的统一就又加了size函数。
max_size
这里的max_size就是能够装多大的长度,一般是长度为len,size_t len=-1。因为类型转换会变成1111111111,就是size_t的最大储存长度。
resize
re一般在英语里面表示"再"的意思,这里就是再次定义空间意思,这里的空间不是capacity的空间重新申请,而是增加或减少size的长度。
首先是缩短:
void resize(size_t n)一般这个函数是用来缩减长度的。
缩短就是直接减少长度。
增长:
resize(size_t n,char c=0)
这里的n不是要增加或者减少多少长度,而是改变后的长度。第二个参量是增长要的字符,一般是缺省值'\0'
增长不会改变原有的内容,而是从末尾处用字符c开始增长。
这里我们发现它和字符串的打印还略有不同,不是到'\0'终止,而是继续打印('\0'是打印不出来东西的),这里是根据size来打印。
capacity
这个就是返回最大储存长度的值
如果size和capacity相等了就会扩容,那么扩容会有不同扩的方法,在vs上面是1.5倍扩:
这里看上去不是标准的二倍是没有吧'\0'算进去。
这里我们还发现在开始第一次是二倍扩的,这是因为我们开始的16长度不是在堆区申请的内存,而是在栈区的内存,如果满了就再申请堆区的。
如果在Linux下,就是2倍扩容。而且初始不会给内存。
reserve
这个就是重新定义capacity的长度,那么也有缩短和增长的问题,在VS和linux下面是略有不同的:
在vs上:
首先是增长,增长会根据1.5倍扩容来reserve ,假如你现在是64长度的capacity,那么你要扩到80,他会给你扩到96
然后是缩短:vs是不会进行缩短的,因为缩短也会进行重新内存申请,然后把原来的数据拷贝过去。可能考虑到这一点所以不缩。
在Linux上:
Linux上面就比较自由,你要它扩多少它就扩到多少,如果是缩短它也会缩,如果你让它缩的长度小于size会所到等于size为止。
clear
clear就是清除数据,只留一个'\0'
empty
这个函数是判断string是否为空
shrink_to_fit
这个函数就是让capacity缩到和size一样的长度。和linux的reserve的缩短一样的。
Element access
operator[]
重载[]方便像数组一样进行下标访问。
如果超出范围会assertv
at
这个和opertator[]的作用是一样的,唯一不同就是超出范围会抛异常不是assert
back
根据类是否const来返回最后一个数据的引用
front
同上面一样,除了这里返回第一个
Modifiers:
operator+=
这个就是在原串后面增加一个串的操作服重载:
前面三个好说,就是加string、数组、字符。
第四个是c++11的:
用一个括号括起来里面放数据的做法就是initializer_list类型
append
和+=重载是一样的,往后追加内容,但是有更多的操作:
使用是和string的构造函数一样的,只是一个是用指定内容构造一个是用指定内容追加在末尾。
push_back
这个就是往后面压入一个字符
assgin
assgin就是重新给string内容的意思,和resize不同的是,它是会将之前的值全面覆盖。
这里的8个重载也和构造函数的几个重载和append的7个重载一样的。(里面的右值引用我不讲,因为我还没学到,后面学到了再来补充)
insert
插入内容的意思,涉及的重载和上面的assgin、append、构造函数一样的。
支持只插入一个字符或者字符串
下面是举例:
erase
和insert函数是对应的
第一个参数就是传开始删除的位置,可以传迭代器,也可以传下标。第二个是终止的位置不传一般是删到末尾。
replace
顾名思义,是替换内容的意思,这里前两个参数
这些其实都是一个逻辑用法,前两个参数传要替换的区间,可以传下标区间,可以传迭代器区间;
后面参数其实和构造函数、append、insert传参是一个模式。
swap
swap函数是交换两个string的内容,但是它不是用的官方的普通swap。
我们用下面的代码模拟一下
红框的就是官方的常用swap,构造一个临时变量,然后再赋值,这样的操作给string将会有一次拷贝构造两次赋值重载,会有很大的时间损耗,每次都要开新空间。所以类里面实现是交换每个参量的值。为了防止有人用默认的swap,所以又写了一个全局的string专用swap函数(如果有现成的函数,和它同类型的模板是不会去实例化的,会直接用现成的)。
pop_back
就是删除最后一个元素。
String operations:
c_str
返回c类型的字符串。
data
跟c_str是差不多的,返回字符串
get_allocator
获取内存池,这里内存池是指先从堆区申请一大块内存,然后要内存申请就在内存池里面申请,增加申请的速度。
copy
将从pos下标开始的,长度为len的内容拷贝到s字符串上面去。
find
寻找对应的字符或者字符串,返回第一个字符的下标
rfind
rfind就是反向查找
find_first_of
这个函数就是find的加强版,输入一个字符串或字符,只要是里面其中之一的字符,就会返回。
举例:
要把所有aeiou的字符屏蔽掉。
find_last_of
这个就是反着开始进行find_first_of
find_first_not_of
这个就是找出了那几个字符其他的字符。
find_last_not_of
这个就是反着进行find_first_not_of
substr
取出string里面从pos下标点,长度为n的数据并返回字符串
举例:
compare
进行比较,和strcmp的返回值一样的。
用的不是很多,因为可以用运算符重载
Member constants
这个和max_size是一个东西,返回可以创建string的最大长度
它其实是一个成员变量 是 const static size_t npos=-1。
Non-member function overloads(全局函数)
operator+
实现两个串的相加
逻辑运算符
这个很简单就不说了,这个比compare好用的多。但是这个没有compare的局部比较
operator<<和operator>>
这个就不用多说了,流插入和流提取。
getline
operator>>的流插入是默认空格和换行停止读取的,所以针对于"I love you"这种有空格的字符串就不能很好的运行。所以就有了getline
这里的delim就是遇到什么停止读取,默认是'\n'停止读取。
到这里所有和string相关的函数就说完了,大家可以理解性记忆。
string的实现
较为完善的string实现
首先我们写的string类是写在.h文件里的,因为在类里面的函数是内联函数,所以就直接在类里面写,如果遇到函数行数比较多的,就可以在.cpp里面写
我们可以自己创建一个命名空间来写我们的string类,与标准库里面的string类进行区分。
string我只实现一个简单版本的,没有内存池,没有vs里面的初始16长度的栈区内存,那么成员变量就有:char*_str size_t _size size_t _capacity const static size_t npos=-1;
先是构造函数:
这里我们不将'\0'计入capacity中,所以我们申请内存要额外再给‘\0’申请一字节的空间。即new char[_capacity+1]
然后是一些其他的成员函数:
类外函数我们就只实现这三个,然后是迭代器的typedef。
一些比较长的函数就在另一个文件里面:
那么一个相对完整的string就模拟出来了。
面试里面简单string的实现
面试肯定不会让你实现一个这么长的string,只会让你将一些基础的功能实现出来。
那么我参考了陈皓的文章C++面试中string类的一种正确写法 | 酷 壳 - CoolShell来进行如下讲解:
首先我们可以不需要capacity和size这两个成员变量,只要进行简单的构造,赋值,交换,返回大小这些操作即可: