前言
在C语言中,字符串是以'\0'
结尾的字符数组,为了操作方便,C语言还提供了和字符串相关的函数放在string.h
中,但是在C语言中,函数和字符串类型是分割的,不符合面向对象编程的思想,并且在操作字符串的过程中还需要注意防止越界。
一.标准库中的string类
1. 字符串是表示字符序列的类
2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作 单字节字符字符串的设计特性。
3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信 息,请参阅basic_string)。
4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits 和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个 类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
总结:
1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;
4. 不能操作多字节或者变长字符的序列。 在使用string类时,必须包含#include头文件以及using namespace std;
在使用string类时,必须包含#include头文件以及using namespace std;
二. string类的常用接口说明
1. string类对象的常见构造
2. string类对象的容量操作
size()
函数与length()
函数
对于计算字符串有效字符长度的函数来说不会计算'\0'
,并且size()
和length()
的效果相同
示例如下:
capacity()
函数
示例如下:
需注意,string中的size和capacity含义和作用并不相同,前者表示当前字符串的长度,后者表示此时可以容纳的字符串长度。
capacity的扩容方式
在VS环境下面,运行下面的代码可以大致估算得到capacity的扩容倍数。
计算capacity
时不会计算\0
所存在的空间
在上面的代码中,因为并没有计算\0所在的空间,所以实际结果为:
观察到,除了第一次和第二次以外,其余均为大致1.5倍增长,但是第一次和第二次的增长并不是2倍,而是因为VS将第一次存储字符串的位置分成了两部分,第一个部分是_buf[16]数组,第二个部分是*ptr,指向超过16个字符时存储的动态开辟的内存空间
对于下面的代码中,有一个11个字符(不包括\0)的字符串:
此时hello world\0
存储的位置在_buf[16]
数组中
如果字符串的长度大于16时,则不会存储到_buf[16]
中,而是存储到ptr
指针指向的内存空间
在设计string
类时,底层的成员变量有下面几种:
所以在VS下,第一次和第二次之间是两倍的关系并不是扩容两倍,而是改变了存储位置
reserve()
函数
使用reserve()
函数可以为字符串预留指定大小的空间,此时当指定大小远大于前面扩容的大小时一般不会再进行扩容。
在VS下,使用reserve()函数指定的大小一般会被编译器扩大一小部分,但是在GCC下指定多少就是多少,所以在上面的代码中,虽然指定的大小为100,但是实际的大小为111
但是不论时哪种编译平台,都要至少保证开辟的大小不能小于指定大小
使用reserve()函数时,如果已经有空间时,对原始空间进行扩容时不会改变有效字符长度size值,只会改变capacity的值,从而达到扩容的效果
注意,若扩容的值小于原始空间时,那么此时reserve()函数不起任何效果
示例如下:
在扩容之后,尽管原始字符串的大小小于_buf[16]
数组的长度,也会被移动到ptr
指向的空间,如图所示:
执行reserve()
函数前:
执行reserve()
函数后:
resize()
函数
区别于reserve函数,resize不仅可以改变string的capacity,还会将size的值转变为resize函数的参数值,并且将size的剩余部分全部初始化为\0, 当指定为某一个字符后,则将初始化对应字符.
示例如下:
也可以指定某一个字符
注意:
尽管使用resize()
函数后改变了size
的大小,但是对于capacity
来说,一旦在开始确定好了大小,如果使用resize()
改变size
的值使其小于已经设定的值,此时不会改变capacity
的大小,因为缩容是非法的。
3. string类对象的访问及遍历操作
注意迭代器中的end
为最后一个字符的下一个位置,一般指向\0
,形成左闭右开区间
示例如下:
在上面的代码中是,使用了三种遍历字符串的方式,但是实际上常用的方式为下标访问的方式,并且范围for的本质也是迭代器,auto ptr本质就是调用s.begin()函数给string : iterator ptr,s的本质即为s.end(),通过汇编代码可以验证这一点
直接使用迭代器:
范围for
:
operator[]
与at()
函数
对于string类来说,可以使用下标进行访问:
operator[]
和at()
函数均可以使用下标的形式访问字符串,但是at()
函数可以在对字符串进行越界访问时抛出异常,而不是像operator[]
越界访问一样断言终止程序。
operator[]
越界访问断言报错:
4.string类对象的修改操作
此处展示部分可能用得到的成员函数,全部成员函数参考文档:string - C++ Reference (cplusplus.com)
push_back()
函数
使用push_back()
函数可以在字符串后追加字符。
示例如下:
append()
函数
使用append()
函数可以在字符串后追加字符串。
示例如下:
operator+=()
函数(+=
运算符重载函数)
使用operator+=()
函数可以达到append()
和push_back()
的效果,既可以追加字符也可以追加字符串。
示例如下:
operator+()
函数(+运算符重载函数)
使用operator+()
函数可以将两个字符串相加(即合并但不改变原来的字符串)
示例如下:
assign()
函数
使用assign()
函数可以将一个对象或者字符串赋值给另一个对象
示例如下:
insert()
函数
使用insert()
函数可以指定插入某个内容到字符串指定位置中
在插入过程中会涉及挪动数据以及扩容问题,所以不建议大量使用
注意插入字符串时不会覆盖原来pos
位置存在的字符,原来pos
位置及以后的字符向后移动。
示例如下:
erase()
函数
使用erase()
函数可以删除指定对象中的内容
因为删除字符会涉及到挪动数据,所以不推荐大量使用erase()
函数
示例如下:
如果剩余字符串的长度小于len,则会将剩余部分直接全部删除。
replace()函数
使用replace()函数可以替换调用对象的字符串中的字符
因为替换的字符串可能比被替换的字符串长,所以可能涉及到空间的扩容和数据的移动,不推荐大量使用replace()函数。
示例如下
find()
函数
使用fine()
函数可以在string对象中从前往后找某一个字符或者字符串
示例如下:
swap()函数
使用swap()函数可以交换调用对象的字符串和另一个对象中的字符串。
对于任意类型,标准库中还有个全局函数swap(),对比于string类中的swap()来说,类string中的swap()函数执行效率会被全局函数swap()效率更高,因为全局的swap()函数在拷贝过程中会调用对应对象类的拷贝构造函数,此时会有额外的空间和时间消耗
全局函数swap()定义:
在前面推测过字符串存储的位置可能是数组或者指针,但是不论是那种,实际上都是地址,只要改变指向两个字符串的指针的指向即可完成交换,所以效率会比全局函数的swap()
高。
c_str()
函数
使用c_str()
函数可以按照C语言字符串的形式返回调用对象中的字符串
substr()
函数
使用substr()
函数可以截取字符串中的某一部分字符串作为子字符串返回。
rfind()
函数
使用rfinde()
函数可以从字符串的最后一个字符向前找指定字符或字符串
示例如下:
find_first_of()
函数
使用find_first_of()
函数可以在对象字符串中匹配指定字符串中的内容。
示例如下:
同样的函数还有:
find_last_of()函数:在字符串中匹配指定字符串的字符最后一次在原始字符串中的位置
find_first_not_of()函数:在字符串中找出指定字符串中不存在的字符第一次出现的位置
find_last_not_of()函数:在字符串中找出指定字符串中不存在的字符最后一次在原始字符串中的位置。
getline()
函数
使用getline()函数可以获取到指定字符(默认'\n'
)结尾之前的字符串。
不同于cin
,cin
遇到空白字符时就会停止读入,getline()
函数遇到'\n'
(或者指定字符)才结束。
5.string类迭代器遍历操作
正向遍历
使用begin
和end
可以从前往后遍历字符串,使用迭代器iterator
示例如下:
在上面的代码中,使用了const
类型的迭代器,但是注意该迭代器对于指针来说只是修饰指针指向的内容不可以修改,但是指针可以改变指向。
逆向遍历
使用rbegin
和rend
可以从后往前遍历字符串,此时使用的迭代器为reverse_iterator
示例如下:
注意此时ptr1并不是--,而是++,因为此时的rbegin为字符串最后一个有效字符的前一个位置,而rend为字符串第一个有效字符的前一个位置,形成左闭右开
当使用迭代器的对象为const修饰时,迭代器需要同样使用const修饰的迭代器。