1. 为什么有string类
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数(strcpy,strcat),但是这些库函数与字符串是分离开的,不太符合OOP(Object Oriented Programming面向对象编程)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
所以在C++中用string类统一管理字符串和其方法,简化编程。其实string并不是一个正经的STL库中的内容,它的出现比STL略早,内容也没有STL中的别的模块成熟。
2. 标准库中的string类
2.1 string类
官网资料:string - C++ Reference
string是一个管理char的类模板,是一个管理字符的顺序表,或者说是一个字符数组。
该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作,容器就是指栈、队列这些写好了可以装数据的东西。
在使用string类时,要包含它的头文件 <string>
2.2 string类的常用接口说明
2.2.1 string类的常见构造
官网资料:string::string - C++ Reference
98版中有7种构造方案其中1、2、4是比较常用的。
1号构造:就是默认构造,会构造一个长度为0的空字符串
2号、4号构造:拷贝构造,只不过参数不同,一个是string类型,一个是C字符串
3号构造:从pos位置拷贝len个字符去构造,如果字符串较短,就能拷几个就拷几个。npos是一个size_t类型的-1,也就是24亿多的一个数,用这样一个字符串不可能达到的长度作为len的缺省值,其意义就是在不传len参的时候拷贝到字符串结尾
5号构造:将一个C字符串的前n个字符拷贝构造
6号构造:用n个字符c构造
7号构造:用另一个string类对象的迭代区间构造
可能有的同学已经发现了我上面打印string的时候用的是流插入 << 操作符,当然是因为这个string类中为了方便我们进行打印写了友元重载函数。
2.2.2 string类对象的容量操作
2.2.2.1 size 与 length 与 capacity
size 返回字符串有效数据个数(有效字符长度),不包括 '\0'
length 返回字符串有效字符长度,不包括 '\0'
那为什么要设计两个功能一样的成员函数呢,实际上这就是设计上的失误,在之后我们学的各种容器中,都是只有size没有length,length的出现只是一个特例。为了保持一致,今后我们也只使用size
capacity 返回该string的容量,或者说string总空间的大小,因为string类中存了不止字符串本身的内容,所以其实际长度一定不等于字符串长度
不过不用担心string的容量不够,string会自己扩容的,换句话说,当这个capacity填满了,就会扩容到更大的capacity。
2.2.2.2 empty 与 clear
clear 清空有效字符
empty 检测字符串是否为空串,是空返回true,不是空返回false
2.2.2.3 reserve 与 resize
reserve是给string扩容的,直接改变capacity的大小,当我们知道一个string需要多大空间的时候就可以提前reserve,把这块空间开好。当然,reserve改变capacity的大小,因此也可以缩容,但是VS下不支持缩容操作。
注意,reserve(保留) reverse(逆转),这俩个函数要分清楚,逆转函数reverse是算法函数,其参数是两个迭代器,用来控制将容器中迭代器所划分出来的数据顺序反转,如果用在string身上的含义就是反转字符串。
resize 将有效字符的个数改成n个,多出的空间用选定的字符c填充,如果不指定字符c,编译器就会自动补上默认字符 '\0'
resize因为给字符串本身扩容了,所以它也会影响capacity
如果用resize强制缩短字符串长度,会使多出的字符串删掉,但是不会影响capacity的大小
2.2.2.4 shrink_to_fit
这个是正经的缩容接口,当我们把一个很长的字符串删除多次之后会出现很多空出来的空间,造成一定程度上的浪费,此时就用这个接口让 capacity 的大小更适应字符串长度 size
但是不要频繁缩容,因为缩容的本质是将原本大空间中的东西拷贝到小空间中去,这个过程会造成时间上的消耗。
2.2.3 string类对象的访问及遍历操作
2.2.3.1 operator[]
官网资料:https://legacy.cplusplus.com/reference/string/string/operator[]/
经过对于操作符 [ ] 的重载,string类对象也可以像C字符串那样访问和操作,这种遍历方案明显简单于迭代器,但缺点就是不通用,链表和树中无法用这种方案遍历。
我们看官网上对于这个运算符的重载声明:
首先,返回值是一个引用,这是为了可以对字符串中提取出来的某个字符进行修改(第一行的声明)。
第二,它重载了两个,一个是正常的,另一个带了const修饰,这是为了防止在使用 [ ] 操作一个const string类型的字符串时,用第一种重载就会出现权限放大的问题,所以用const修饰了this指针指向的内容,和重载函数的返回值。
PS: 范围for遍历(C++11)
说到遍历,我们前面还提过一种范围for的遍历方案,自动++,自动判断结束。
范围for遍历所有容器都可以用,因为范围for的底层就是迭代器,但范围for每次拿出来的这个e还是s1中一次一次正在遍历的字符的拷贝,如果 auto& 加上引用之后就可以修改s1了。
2.2.3.2 at访问
at访问与方括号[ ] 访问的区别在于,at越界会抛异常,而[ ]会断言。抛异常后面的章节会讲到,这个是可以更改的,但是断言报错就直接崩掉程序了。
2.2.3.3 begin end 与 迭代器
begin:任何容器中都是返回第一个数据位置的 iterator(迭代器)
end:任何容器中都是返回最后一个有效数据下一个位置的 iterator(迭代器)
iterator(迭代器):可以理解成一个类似指针的东西,由它来控制目前访问的是第几号数据。之前我们学过访问数组中的某号元素时,是用下标来控制,但是这种 下标引用操作符 [ ] 的使用是有限制的,只能作用于顺序表。但是迭代器就比较通用,可以作用于顺序表,也可以作用于链表或树。
不同容器的迭代器的实现是不尽相同的,所以 iterator 存在于各个容器的类域中,所以使用时要带上类域,否则全局范围内是找不到这个迭代器的。
因为 \0 不属于有效字符,算是一种标志,所以end返回的迭代器指向的位置就是 \0 而不是 \0 的下一个位置。
可以看到再使用迭代器的时候我们用 * 来修改迭代器指向的内容的,其实这里是重载了 * 操作符,使得迭代器用起来更像指针。其他容器也是这么用 * 修改迭代器内容的。
如果 s1 是const修饰的只读字符串,那么迭代器就要写成与其匹配的只读迭代器 const_iterator 防止权限放大。
2.2.3.4 rbegin rend 与 反向迭代器
这个就是把正向迭代器的所有用法反过来而已
rbegn:返回最后一个有效数据的迭代器
rend:返回第一个有效数据的前一位置的迭代器
reverse_iterator:反向迭代器,++向前走,反向迭代器也有const版本
PS: 算法排序 sort
讲到迭代器就要提及sort算法,它是用来给各种容器内的数据排序的,默认是从小到大排
官网资料:sort - C++ Reference
使用 sort() 要包含头文件 <algorithm> (算法),因为sort要给各类容器排序所以它是用模板写的,传的参数就是容器的迭代器,用两个迭代器来画出一块 左闭右开 的区域,此时sort就会在这块区域中排序了
2.2.4 string类对象的修改操作
2.2.4.1 push_back append 与 operator+=
push_back: string::push_back - C++ Reference
append: string::append - C++ Reference
operator+=: string::operator+= - C++ Reference
push_back() 一次只能在string类对象后面添加一个字符。append()可以添加字符串、另一个string的实例化、用下标圈出来或用迭代器圈出来的另一个string等等,总之构造时能实现的所有方案它都能实现,不过那个是构造,但append是添加。
但是上面两种都很少使用,operator+=操作符重载多方便啊,可以添加字符,字符串,或另一个string对象。
2.2.4.2 pop_back 与 erase
pop_back: string::pop_back - C++ Reference
erease: string::erase - C++ Reference
pop_back还是一次只能尾删一个字符,不太好用,它存在的意义就是和其他容器保持一致。
erease可以通过 下标 和 元素个数 来进行删除,也可以通过迭代器但是用的比较少,注意迭代区间永远是左闭右开
2.2.4.3 assign 重新赋值
官网资料:string::assign - C++ Reference
这个成员函数的功能就是将string对象中的原有内容都删掉然后放入新内容,参数用法和构造的用法完全一样,唯一的区别就是不能修改成空string,而构造可以构造空string。
2.2.4.4 insert 插入
官网资料:string::insert - C++ Reference
前面的 += 操作符重载为我们提供了给string实例尾插的方法,那么insert就提供了在string实例的任意位置插入的方法。
pos位置,用来选择在哪个位置插入,iterator迭代器也可以选择插入的位置。插入的内容可以是 char* 这种以 \0 结尾的C语言风格的字符串,也可以是C++的string类。
唯一要注意的是要插入一个字符的时候,用 5 号重载,插入一个字符时要给 n 传参为1,表示要插入 1 个字符。不能像传字符串那样,丢一串字符串就完事了,不去给字符数量。
2.2.4.5 replace 替换
官网资料:string::replace - C++ Reference
别看这玩意一大堆,其实就是让你指出string中的一块地方,可以把这块地方的内容替换成别的内容而已,只是给出的替换方案相当多。
2.2.5 string类的其他操作
2.2.5.1 c_str 获取字符串指针
可能之后写代码的时候C和C++混用,但是C没有string类,所以我们就获取字符转的底层指针,用来写C的代码段。
2.2.5.2 find 与 substr
官网资料:string::find - C++ Reference
find接口的功能是从 pos 位置向后找字符 c ,返回该字符在字符串中的下标
find接口一般与substr接口一起使用,用于取下来字符串中用分隔符分隔开的某串字符串
官网资料:string::substr - C++ Reference
substr接口从 pos 位置开始,向后截取 len 个字符,然后将其返回
2.2.5.3 operator+
之前的operator+=只能支持基础string在前,后面加上一个string、char*或char。
但是operator+的出现满足了基础string可以在后
2.2.5.3 getline
官网资料:getline (string) - C++ Reference
cin与scanf一样,遇到空格或者换行都会认为是结束标志,于是就停止提取了,那如何获取一句带有空格的一句话呢?
在C的时候我们会使用到 fgets 那么在C++的话我们就会用到getlin,同时getline还允许我们自己设置提取结束符 delim
可以看到getline不会像fgets那样从前向后覆盖字符串,而是先清空string再获取新字符串。
2.2.5.4 to_string 与 stoi
官网资料:to_string - C++ Reference
to_string可以将各种数值转化成string,其功能类似于C库中的 itoa
官网资料:stoi - C++ Reference
stoi可以将string转化成整形,其功能类似于C库中的 atoi