C++之string类
本节目标
1. string类概览
1.1 string的由来
1.2 string函数列表
2.string常用接口
1. 初始化
2. string::npos
3. c_str()
4. 获取长度(length、size)
5. 容量(size、capacity)
6. 插入(insert)
7. 替换(replace)
8. 添加(append、push_back、+=)
9. 赋值(assign)
10. 删除与判空(erase、clear、empty)
11. 剪切(substr)
12. 比较(compare)
13. 交换(swap)
14. 反转(reverse)
15. 迭代器(iterator)
15.1 正向迭代器
15.2 反向迭代器
15.3 const迭代器
16. 搜索与查找(find等函数)
16.1 find()函数
16.2 rfind函数
16.3 find_xxx_of()函数(功能强大,但不常用)
3. string类的应用
3.1 三种遍历方式
3.2 替换空格
3.3 通过find取后缀
3.4 getline的应用
4. string总结
本节目标
熟练掌握各种string类的函数并将其应用。
注:本文参考以下两篇优秀文章,将其结合并加上额外的知识用自己的理解进行描述:
1.string类概览
1.1 string的由来
之所以抛弃char*的字符串而选用C++标准程序库中的string类,是因为他和前者比较起来,不必担心内存是否足够、字符串长度等等,而且作为一个泛型类出现,他集成的操作函数足以完成我们大多数情况下(甚至是100%)的需要。我们可以用 = 进行赋值操作,== 进行比较,+ 做串联(是不是很简单?)。我们尽可以把它看成是C++的基本数据类型。
C++中对于string的定义为:typedef basic_string string; 也就是说C++中的string类是一个泛型类,由模板而实例化的一个标准类,本质上不是一个标准数据类型。
在我们的程序中使用string类型,我们必须包含头文件 。如下:
#include using namespace std; 此语句必不可少,否则有的编译器无法识别
1.2string函数列表
begin 得到指向字符串开头的Iterator
end 得到指向字符串结尾的Iterator
rbegin 得到指向反向字符串开头的Iterator
rend 得到指向反向字符串结尾的Iterator
size 得到字符串的大小
length 和size函数功能相同
max_size 字符串可能的最大大小
capacity 在不重新分配内存的情况下,字符串可能的大小
empty 判断是否为空
operator[] 取第几个元素,相当于数组
c_str 取得C风格的const char* 字符串
data 取得字符串内容地址
operator= 赋值操作符
reserve 预留空间
swap 交换函数
insert 插入字符
append 追加字符
push_back 追加字符
operator+= += 操作符
erase 删除字符串
clear 清空字符容器中所有内容
resize 重新分配空间
assign 和赋值操作符一样
replace 替代
copy 字符串到空间
find 查找
rfind 反向查找
find_first_of 查找包含子串中的任何字符,返回第一个位置
find_first_not_of 查找不包含子串中的任何字符,返回第一个位置
find_last_of 查找包含子串中的任何字符,返回最后一个位置
find_last_not_of 查找不包含子串中的任何字符,返回最后一个位置
substr 得到字串
compare 比较字符串
operator+ 字符串链接
operator== 判断是否相等
operator!= 判断是否不等于
operator< 判断是否小于
operator>> 从输入流中读入字符串
operator<< 字符串写入输出流
getline 从输入流中读入一行
2.string常用接口
对于这些用法来说,下面传入的参数个数在同一个用法中都有所差异,这是因为每一个成员函数都支持了重载。
在介绍下述接口之前,需要知道的是,由于每一个函数都有重载,因此有的有const的重载,有的没有,其实这是以函数的需求从而判断其有无const类型的重载函数,这里提前进行总结:
只读功能函数 const版本
只写功能函数 非const版本
读写功能的函数 const+非const版本
1.初始化
初始化有两种方式,其中使用等号的是拷贝初始化,不使用等号的是直接初始化。(注释后面是打印的结果)
但对于使用等号的和str(str1),即一个变量通过另一变量初始化的,都是拷贝构造。(深拷贝
string str1 = "hello world"; // str1 = "hello world" string str2("hello world"); // str2 = "hello world" string str3 = str1; // str3 = "hello world" string str4(str2); // str4 = "hello world" string str5(10,'h'); // str5 = "hhhhhhhhhh" string str6 = string(10,'h'); // str6 = "hhhhhhhhhh" string str7(str1,6); // str7 = "world" 从字符串str1第6个字符开始到结束,拷贝到str7中 string str_7(str1,6,3); // str_7 = "wor" 从字符串str1第6个字符开始的三个字符,拷贝到str7中 string str8 = string(str1,6); // str8 = "world" string str9(str1,0,5); // str9 = "hello" 从字符串str1第0个字符开始,拷贝5个字符到str9中 string str10 = string(str1,0,5); // str10 = "hello" char c[] = "hello world"; string str11(c,5); // str11 = "hello" 将字符数组c的前5个字符拷贝到str11中 string str12 = string(c,5); // str12 = "hello"
2.string::npos
我们观察一下上面str7与str_7的区别,想必大家已经看出,这里的str7和str_7是同一个重载函数,并且这个函数具有缺省值,当我们不传入最后一个参数时,其就会一直拷贝到字符串的末尾为止。那这个缺省参数是什么呢?我们查阅文档得知,是npos:
我们发现,npos的值规定为-1,但实际上因为是size_t类型,所以这是一个无符号的数字,即此-1并不是十进制的-1,而是:4294967295
因此,此位置重载在不输入数值时默认为此值,也就能够遍历到字符串的末尾了。
此外,对于内置的string类,是支持运算符重载的,因此同样的也支持流的相关重载,即cin、cout:
string str1 = "hello world"; cout << "str1 = " << str1; //输出: str1 = hello world
3. c_str()
对于string类来说,其内部有这么一个成员变量,c_str,正如此图,c_str本身和指向的值均不能改变,返回值是char*
实际上返回的就是string类中的内容的地址,也就是字符串的地址。
那c_str有什么作用呢?事实上对于一些线程,网络,Linux内核等都是通过C实现的,因此c_str很好的充当了一个C++中string与C之间的互通,因为我们知道,对于string定义的变量名,不是内部字符串的地址,因此就出现了c_str()返回内容的地址,从而解决这个问题。
演示:
结果:
这样就将其内容正确的打开了。
4.获取长度(length、size)
length()函数与size()函数均可获取字符串长度。但除了string,其他类型就只有size()。
string str = "hello world"; cout << str.length() << str.size(); // 11 11
当str.length()与其他类型比较时,建议先强制转换为该类型,否则会意想之外的错误。
比如:-1 > str.length() 返回 true。
容量(size、capacity)
对于size和capacity来说,大家在学了顺序表之后并不陌生,size是实际长度,而capacity代表着容量的大小,对于string类来说,其也具有这样的成员变量(对应值C语言顺序表中结构体内部的的size、capacity),而这里的扩容规则在每一个平台也是不一样的,比如我的linux和vs2019二者之间就有很大的区别,不过我们并不需要关心他,因为string作为内部类,其扩容的机制已经被写在该类之中。
string str; str += "hello world hello world";
可以看出,其大概是是扩容了二倍的大小。
此外,还有其他函数也属于容量的范畴:
resize可以改变成员的size()的大小。
reserve可以改变成员的capacity()的大小。
而对于max_size(),这里我们只打印结果就知道其具体的含义:
6. 插入(insert)
string str = "hello world"; string str2 = "hard "; string str3 = "it is so happy wow"; //s.insert(pos,n,ch) 在字符串s的pos位置上面插入n个字符ch str.insert(6,4,'z'); // str = "hello zzzzworld" //s.insert(pos,str) 在字符串s的pos位置插入字符串str str.insert(6,str2); // str = "hello hard world" //s.insert(pos,str,a,n) 在字符串s的pos位置插入字符串str中位置a到后面的n个字符 str.insert(6,str3,6,9); // str = "hello so happy world" //s.insert(pos,cstr,n) 在字符串s的pos位置插入字符数组cstr从开始到后面的n个字符 //此处不可将"it is so happy wow"替换为str3 str.insert(6,"it is so happy wow",6); // str = "hello it is world"
虽然有这样的接口,但是我们知道对于类似于顺序表的结构来说,这样的插入,实际上底层都会将后面的数据进行挪动,因此效率难免会低一些。
7.替换(replace)
替换与插入对应,对比理解更为简单。
string str = "hello world"; string str2 = "hard "; string str3 = "it is so happy wow"; //s.replace(p0,n0,n,ch) 删除p0开始的n0个字符,然后在p0处插入n个字符ch str.replace(0,6,4,'z'); // str = "zzzzworld" //s.replace(p0,n0,str) 删除从p0开始的n0个字符,然后在p0处插入字符串str str.replace(0,6,str2); // str = "hard world" //s.replace(p0,n0,str,pos,n) 删除p0开始的n0个字符,然后在p0处插入字符串str中从pos开始的n个字符 str.replace(0,6,str3,6,9); // str = "so happy world" //s.replace(p0,n0,cstr,n) 删除p0开始的n0个字符,然后在p0处插入字符数组cstr的前n个字符 //此处不可将"it is so happy wow"替换为str3 str.replace(0,6,"it is so happy wow",6); // str = "it is world"
8. 添加(append、push_back、+=)
append函数用在字符串的末尾添加字符和字符串。(同样与插入、替换对应理解)而push_back只适用于添加单个字符,此外,对于添加来说,如果是在末尾添加字符或者字符串我们仍然可以像初始化中的拷贝构造一样,即通过+=进行添加。
string str = "hello world"; string str2 = "hard "; string str3 = "it is so happy wow"; string str4 = "hello world" //s.append(n,ch) 在当前字符串结尾添加n个字符c str.append(4,'z'); // str = "hello worldzzzz" //s.append(str) 把字符串str连接到当前字符串的结尾 str.append(str2); // str = "hello worldhard " //s.append(str,pos,n) 把字符串str中从pos开始的n个字符连接到当前字符串的结尾 str.append(str3,6,9); // str = "hello worldso happy " //append(cstr,int n) 把字符数组cstr的前n个字符连接到当前字符串结尾 //此处不可将"it is so happy wow"替换为str3 str.append("it is so happy wow",6); // str = "hello worldit is " str4.push_back('c'); // str4 = "hello worldc" str4 += 'c'; // str4 = "hello worldcc" st4 += "cdef"; // str4 = "hello worldcccdef"
9. 赋值(assign)
赋值也是一种初始化方法,与插入、替换、添加对应理解较为简单。
string str; string temp = "welcome to my blog"; //s.assign(n,ch) 将n个ch字符赋值给字符串s str.assign(10,'h'); // str = "hhhhhhhhhh" //s.assign(str) 将字符串str赋值给字符串s str.assign(temp); // str = "welcome to my blog" //s.assign(str,pos,n) 将字符串str从pos开始的n个字符赋值给字符串s str.assign(temp,3,7); // str = "come to" //s.assaign(cstr,n) 将字符数组cstr的前n个字符赋值给字符串s //此处不可将"it is so happy wow"替换为temp str.assign("welcome to my blog",7); // str = "welcome"
10.删除与判空(erase、clear、empty)
对于clear,其具有清空的功能,也就是从任意的字符串使用clear,都会将其清空至空字符串;对于erase来说,其可以指定的删除,也就是说可以删除一部分,也可以删除全部。
string str = "welcome to my blog"; //s.erase(pos,n) 把字符串s从pos开始的n个字符删除 str.erase(11,3); // str = "welcome to blog" str.clear(); // str = "" str.empty(); // 返回1
clear()实际上不会将capacity的空间也清除掉,即size会改变,但capacity并不会改变。
对于empty来说,实际上是判断是否为空的函数,是空就返回1,不是空就返回0,因此empty是根据size()是否为0判断的。