目录
string杂谈
类模板basic_string实例化出string类后,为什么还要实例化出u16string等其他类呢?
string类的构造函数
1.默认构造和拷贝构造
2.利用c语言的字符串构造string
3.截取str1的部分构造str2
4.利用str1的前n字节构造str2
5.用n个字符c构造str
6.利用迭代器范围构造str
string类的赋值函数
string类的用于获取元素的函数
1.operator【】
2.string::at
3.back和front
string类的迭代器类
关于迭代器的背景
string类迭代器的使用
1.正向迭代器
2.反向迭代器
3.关于cbegin等带c的迭代器接口
string类的操作函数
1.c_str和data
2.find和rfind
3.find_first_of
4.find_last_of
5.find_first_not_of和find_last_not_of
6.substr
string类的容量相关函数
1.reserve
2.resize
string类的修改函数
1.push_back
2.append
3.operator+=()
4.assign
5.insert
6.erase
7.replace
string类的非成员函数
1.operator<<()和operator>>()和getline()
2.operator+()
3.relational operators
头文件中的转换函数
VS中对string做了一些优化
如上图,string不在Containers中,这里我想说的是严格来说string并不属于STL库,因为string是早于STL出现的,但因为string的制作符合STL的规范,所以也就睁一只眼闭一只眼了。
string是类模板basic_string
既然string是字符的数组,那么避免不了谈论和字符强相关的字符集,字符集的功能是将内存中存储的01序列转换成字符,因为存在不同字符集导致编码也不同,比如UTF-8是一种变长字符集,所以可以表示多种语言,而UTF-16只能用2字节表示一个字符,UTF-32只能用4字节表示一个字符,所以他俩都无法表示英文。
背景介绍完毕后回归正题,char只有一个字节,对于需要两个甚至更多的字节才能表示的字符来说,字节的数量就不够用了,此时就诞生了u16string等等其他类,其中u16string就采用的UTF-16字符集表示字符,而u32string就采用UTF-32字符集表示字符。
默认构造和拷贝构造没啥可说的,但注意string类遵循C语言的字符串末尾带\0这一标准。
上图红框为最为常见的使用方式,值得一说的是:下图中两种方式是等价的,第二行实际上是隐式转换,原本应该是首先用hello world构造了一个临时对象,然后调用拷贝构造构造s2,但因为这两个构造操作在同一行执行,所以编译器实际上会把这两次构造优化成一次,即优化后直接使用C语言的字符串hello world构造string,这和第一行的操作是等价的。如果string的构造函数前加了关键字explicit,那么就不能发生隐式转换了,也就无法像第二行这样写了。
该方法不常见,意为顺序(连续)截取str1的一部分构造str2。截取部分从表示下标的参数pos开始,截取字节长度为 len字节。当len超出str1的范围时,直接截取到str1末尾,值得注意的是参数len带有缺省值npos,它是string类内部的一个静态无符号变量,值为-1,-1转换为有符号数是int的最大值42亿9千万,意为直接截取到str1末尾。
效果演示如下
不常用,不再赘述。
比如你想用100个‘x’构造出一个str,那就string str(100,‘x’);。
给一个迭代器区间,利用这个区间的string构造一个新的string。值得一说的是利用迭代器区间构造string时,这个构造函数是一个函数模板,即这个迭代器不一定是string类的迭代器,比如我也可以用vector的迭代器区间构造string对象。
不常见的第三种赋值重载表示利用字符c赋值给string。值得一提的是对于各个自定义类型来说,第一种为默认的赋值重载函数,不写也会生成,后两种不写则不会生成。
可以让string像数组一样遍历访问string内的元素。编译器会根据string类型去匹配该调用哪个operator【】,如果是const对象则调用第二个,如果是非const对象则调用第一个。注意返回值是引用类型,这里不是为了减少拷贝,而是想像数组一样,可以通过str【3】=‘a’修改原string。
功能和operator[]完全一致,但区别在于at越界失败后不是终止程序,而是抛异常,如下图红框处描述,而operator【】越界会报断言错误并终止程序。
用于返回第一个和最后一个元素。因为at或者operator[]的存在,所以back和front比较多余,因为operator【0】就等价于front。
迭代器是一个类,是为了给STL容器提供一种统一的、通用的遍历方式而存在的,在string这里不常用,因为operator[]比迭代器更好用,但对于树或者链表这样的底层数据不连续的类型来说,operator[]就不太合适了,所以迭代器的意义就在于此。
迭代器是容器的内嵌类型,一般要么是内部类【除了内部类是外部类的友元以外,它们之间没有任何关系】,要么是在类内给某个类型typedef过别名【注意typedef的功能是很强大的,即使某个类与容器完全无关,但只要将这个类取别名,那么这个别名就是容器的成员,即这个类也就是容器的成员了】。
迭代器可能就是重命名后的指针类型,也有可能不是指针类型,但用法和指针类型是一样的。
STL所有容器的end()接口,都是返回的指向最后一个元素的后一个位置的迭代器。
迭代器是一个左闭右开的区间,当有容器 container con 和迭代器 container ::iterator it 时,使用迭代器进行容器遍历最好为 it!= con.end(),而不是 it 这里简单介绍下begin()即可,其余接口类似。begin()用于返回 “指向” 首元素的迭代器。之所以带 “ ” 是因为迭代器的类型不一定是指针类型,但用法却和指针一样。值得注意的是,如果容器对象是const对象,那么使用begin()函数返回的首元素的迭代器也必须使用const_iterator类型的变量接收,如下图。想必你也能发现const_iterator类型和const指针很相似,注意这里所说的const指针的const在 * 左边(左定值,右定向)。 这里简单介绍下rbegin即可,其余接口类似。可以发现即使是反过来遍历,对于迭代器还是使用的是operator+()。和begin一样,const对象使用rbegin时,也得使用const_reverse_iterator变量接收rbegin返回的迭代器。 值得一提的是,rbegin接口用于返回指向最后一个元素的迭代器对象,rend()用于返回指向第一个元素的前一个位置的迭代器对象。 cbegin()用于返回const迭代器,所以事实上c就表示const的意思。这里有个问题,当const对象调用begin()时,不就已经能够返回const迭代器了吗?为什么C++11要更新cbegin或者crbegin这样的接口呢? 答案:因为begin()等接口不够直观,代码的可读性不高,当你调用begin()时,可能返回const迭代器,也可能返回普通迭代器,我得观察begin()是否是const对象调用的,以此辨别返回的迭代器类型是否为const迭代器。但如果使用cbegin()等接口,那就能够立马得出cbegin()这个接口是由const容器对象调用的,提高代码的可读性,同时因为代码的规范只能更新,不能删除,所以begin()等接口也就留下来了。所以如果是const对象,那我们未来编写代码时是期望我们调用cbegin()等接口的。但搞笑的是大部分人并不买账,依然觉得begin()等接口就挺方便,导致cbegin()等接口成为了C++的一种累赘。 作用为以string为原型,返回一个C语言字符串。有些接口是需要C语言字符串的,string无法使用,这时可以使用c_str(),使用场景如下图演示。data()接口和c_str()的含义是一模一样的,它们的关系类似size和lenth之间的关系。 如果没找到,返回npos(是size_t的-1,即MAX_INT);如果找到了,返回字符对应的下标。 接口1:在调用find函数的string中从下标pos开始找str,str也是string类型。 接口2:在调用find函数的string中从下标pos开始找str,str是C语言字符串。 接口3:在调用find函数的string中从下标pos开始,向后寻找n字节,看能否找到C语言字符串s。 接口4:在调用find函数的string中从下标pos开始找字符c。 接口1:在调用rfind函数的string中从下标pos开始,从后往前找str,str也是string类型。 接口2:在调用rfind函数的string中从下标pos开始,从后往前找s,s是C语言字符串。 接口3:在调用find函数的string中从下标pos开始,从后往前寻找n字节,看能否找到C语言字符串s 接口4:在调用find函数的string中从下标pos,从后往前开始找字符c。 此接口和find不同,find要求需要寻找的字串必须和调用find的string的部分完全匹配。如有string1为“hello world”,如果需要寻找的字串为”hellx“,则找不到并返回npos。 find_first_of则不同,只要 “hellx” 中有任意字符和“hello world”中匹配,则返回其下标。如 “hellx” 中有字符 ‘h’、‘e’、‘l’、‘l’,这些都能在“hello world”中找到,所以会返回对应元素的下标,每调用一次只能返回一个。 接口1:在调用find_first_of函数的string中从下标pos开始,从前往后找和str中任意一个字符匹配的字符的下标,并返回该下标,如果不存在则返回npos。 接口2:在调用find_first_of函数的string中从下标pos开始,从前往后找n字节,如有和str中任意一个字符匹配的字符,则返回该字符的下标,如果不存在则返回npos。 剩余接口类似,不再赘述。 和find_first_of完全一致,只不过从后往前找。 find_first_not_of的功能和find_first_of恰好相反,功能为:在调用find_first_not_of函数的string中从下标pos开始,从前往后找和str中任意一个字符都不匹配的字符的下标,并返回该下标,如果不存在则返回npos。 find_last_not_of和find_first_not_of功能完全一致,只不过是从后往前找。 如果我们知道大致要开多少空间,那么可以提前开辟n字节空间,避免string频繁扩容产生消耗,可以提高效率。注意这里开完空间后,string类内部的_capacity也会变成n,_size不变 resize用于改变string内部_size的大小,如果n小于等于_capacity,则将_size赋值为n;如果n大于_capacity,则容器大小扩容到n后再将_size也赋值成n。 resize和reserve不同,resize除了扩容,还会初始化,上图两个接口,第一个接口不带字符c,则默认全部初始化成\0(就是0),第二个接口全部初始化为c。 比较鸡肋,一次只能往string中添加一个char。 接口如上图,可以一次向原string中添加更多的字符。其接口含义和string构造函数的用法完全一致,仔细阅读,你一定能在上文找到答案。样例如下图。 对比append和push_back,+=更优,日常中被使用的也更频繁,但实际上+=就是通过append和push_back实现的,如果+=字符串,则底层调用append,如果+=单个字符,则底层调用push_back。从这里也能体现C++对比C语言的优势,如果用C语言的字符串,则需要自己考虑开辟字符串大小,太小不够,太大浪费,且不能动态扩容,用C语言在原字符串后追加字符串或者字符时,一般使用strcat等接口,该接口需要先找\0,之后其后追加字符串,并且strcat还需要考虑追加的字符串不能超过原字符串锁开辟空间的范围,不然报错,可以说是相当不方便了。但如果是string的operator+=(),则只需像下图使用即可。 作用和构造函数类似,都是给string赋值,而且接口的功能以及用法完全等同于构造函数。 接口不必多说,理解上文后,这里的接口也一看就懂。额外说一下inert头插的效率是非常低的,因为每次头插后,都得往后挪数据,string中有n个元素就得挪n次。当然尾插就不用挪动数据了,并且也不需要找尾,尾插的时间复杂度是O(1)的。 也可能需要挪动数据,导致效率低下。 第一行的接口都有缺省值,默认将整个string的有效元素全删除,即把_size置为0,_capacity不变。当然你也可以自己给值,从pos位置开始,删除len字节的元素。 第二行的接口用于删除迭代器 “指向” 的元素。 第三行接口用于删除位于迭代器区间的元素,左闭右开。 用于替换string内部的元素。 因为在全局里重载了operator<<等运算符,所以可以对string类使用如下图的运算符。注意对于C语言字符串即const char * 也重载过operator<<(),在输出string和const char*时,底层的原理是不同的,string输出内容的多少是看_size有多大,而const char*是输出到 \0 才会停止输出。 cin>>str这边有一个坑,比如你输入hello world时,str中只会录入hello,因为cin和scanf等接口都支持连续输入,而连续输入时是以换行符或者空格来进行间隔的,cin发现hello后带空格,则认为只需将hello给str即可,此时world会存储在用户层缓冲区里,后序可以通过cin将world赋给别人。如果想把完整的一句话,就比如hello world全部输入进str,则需要使用getline。 is一般传入cin即可,delim意为以什么字符作为结束输入的标志,默认为换行符\n,所以输入回车即可停止输入。如果如下图,使用第一个接口时给delim传入p,则之后输入以字符p判断输入结束。 不常使用,一般使用+=,因为+返回的是拷贝,但也不排除有些时候就是需要另拷一份,以免影响原数据。 字符串之间是支持比较大小的,比较方式为按照字符映射的编码大小比较。 这些函数不是string的成员函数,而是头文件中的全局函数。 为什么是28呢?按照我们的理解,string类的成员是_size和_capacity和char*_str,这三个成员组成的结构体大小应该只有12啊。 事实上这是VS对于string类做出的优化。VS认为,如果大量地创建string对象,但每个对象管理的字符串数据非常小,相当于在堆上开辟了很多个小块内存,这相当于有很多内存碎片,会影响动态开辟内存的性能,所以VS在string类中额外添加了一个char数组,如下图所示,这样当string对象管理的字符串数据很小的话就无需去堆上开辟内存,直接存进数组里即可,如果数组不够存,再去堆上开辟内存。 如下图,可以看到即使s0内什么也没有,capacity的大小都是15,更加证明了VS优化后的string内部是存在一个16字节的char数组的。 当string对象管理的字符串数据比较小时,该优化能提升不少性能,因为不必去堆上开辟内存。但VS的这个优化虽然提升了性能,也解决了堆上的内存碎片问题,但也有一个缺点,就是string对象变大了。 同时因为该优化的逻辑:小于15字节的数据存进数组,大于15字节的数据存在堆开辟的空间上,导致了string类内部大量的接口都可能要重新编写,为了方便,我们在模拟实现时不必仿照VS的这个优化版的string。string类迭代器的使用
1.正向迭代器
2.反向迭代器
3.关于cbegin等带c的迭代器接口
string类的操作函数
1.c_str和data
2.find和rfind
3.find_first_of
4.find_last_of
5.find_first_not_of和find_last_not_of
6.substr
substr
是一个常用的字符串处理函数,用于从一个字符串中提取子字符串。在C++中,substr
函数用于操作 std::string
类型的字符串。
pos
:子字符串的起始位置,即从哪个字符开始提取子串。位置的索引从 0 开始,表示字符串的第一个字符。len
:要提取的子字符串的总长度。substr
函数会返回从指定位置 pos
开始的总长度为 len
的子字符串。string类的容量相关函数
1.reserve
2.resize
string类的修改函数
1.push_back
2.append
3.operator+=()
4.assign
5.insert
6.erase
7.replace
string类的非成员函数
1.operator<<()和operator>>()和getline()
2.operator+()
3.relational operators
VS中对string做了一些优化