【C++基础】第五十四课:[顺序容器]额外的string操作

substr,append,replace,insert,erase,assign,find,rfind,find_first_of,find_last_of,find_first_not_of,find_last_not_of,compare,to_string,stoi,stol,stoul,stoll,stoull,stof,stod,stold

Posted by x-jeff on November 4, 2022

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.额外的string操作

除了顺序容器共同的操作之外,string类型还提供了一些额外的操作。这些操作中的大部分要么是提供string类和C风格字符数组之间的相互转换,要么是增加了允许我们用下标代替迭代器的版本。

2.构造string的其他方法

除了我们在【C++基础】第十四课:标准库类型string中第2部分已经介绍过的构造函数,以及与其他顺序容器相同的构造函数(见【C++基础】第五十一课:[顺序容器]容器库概览中第5部分的表9.3)外,string类型还支持另外三个构造函数,如下表所示:

1
2
3
4
5
6
7
8
9
10
const char *cp = "Hello World!!!"; //以空字符结束的数组
char noNull[] = {'H', 'i'}; //不是以空字符结束
string s1(cp); //拷贝cp中的字符直到遇到空字符;s1 == "Hello World!!!"
string s2(noNull,2); //从noNull拷贝两个字符;s2 == "Hi"
string s3(noNull); //未定义:noNull不是以空字符结束
string s4(cp + 6, 5); //从cp[6]开始拷贝5个字符;s4 == "World"
string s5(s1, 6, 5); //从s1[6]开始拷贝5个字符;s5 == "World"
string s6(s1, 6); //从s1[6]开始拷贝,直至s1末尾;s6 == "World!!!"
string s7(s1, 6, 20); //正确,只拷贝到s1末尾;s7 == "World!!!"
string s8(s1, 16); //抛出一个out_of_range异常

在本地尝试了下:

1
2
3
4
const char *cp = "Hello World!!!"; //以空字符结束的数组
char noNull[] = {'H', 'i'}; //不是以空字符结束
string ss1(noNull); //ss1为"Hi@?",后面的字符确实是未定义的
string ss2(cp,2,3); //这个也可以,ss2为"llo",不知道是否是编译器优化过的

通常当我们从一个const char*创建string时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止。如果我们还传递给构造函数一个计数值,数组就不必以空字符结尾。如果我们未传递计数值且数组也未以空字符结尾,或者给定计数值大于数组大小,则构造函数的行为是未定义的。

当从一个string拷贝字符时,我们可以提供一个可选的开始位置和一个计数值。开始位置必须小于或等于给定的string的大小。如果位置大于size,则构造函数抛出一个out_of_range异常。如果我们传递了一个计数值,则从给定位置开始拷贝这么多个字符。不管我们要求拷贝多少个字符,标准库最多拷贝到string结尾,不会更多。

2.1.substr操作

substr操作返回一个string,它是原始string的一部分或全部的拷贝。可以传递给substr一个可选的开始位置和计数值:

1
2
3
4
5
string s("hello world");
string s2 = s.substr(0, 5); //s2 = hello
string s3 = s.substr(6); //s3 = world
string s4 = s.substr(6, 11); //s4 = world
string s5 = s.substr(12); //抛出一个out_of_range异常

如果开始位置超过了string的大小,则substr函数抛出一个out_of_range异常。如果开始位置加上计数值大于string的大小,则substr会调整计数值,只拷贝到string的末尾。

3.改变string的其他方法

string类型支持顺序容器的赋值运算符以及assigninserterase操作。除此之外,它还定义了额外的insert和erase版本。

除了接受迭代器的insert和erase版本外,string还提供了接受下标的版本。下标指出了开始删除的位置,或是insert到给定值之前的位置:

1
2
s.insert(s.size(), 5, '!'); //在s末尾插入5个感叹号
s.erase(s.size() - 5, 5); //从s删除最后5个字符

标准库string类型还提供了接受C风格字符数组的insert和assign版本。例如,我们可以将以空字符结尾的字符数组insert到或assign给一个string:

1
2
3
const char *cp = "Stately, plump Buck";
s.assign(cp, 7); // s == "Stately"
s.insert(s.size(), cp + 7); // s == "Stately, plump Buck"

我们也可以指定将来自其他string或子字符串的字符插入到当前string中或赋予当前string:

1
2
3
4
string s = "some string", s2 = "some other string";
s.insert(0, s2); //在s中位置0之前插入s2的拷贝,s为"some other stringsome string"
//在s[0]之前插入s2中s2[0]开始的s2.size()个字符
s.insert(0, s2, 0, s2.size()); //s为"some other stringsome other stringsome string"

3.1.append和replace函数

string类定义了两个额外的成员函数:append和replace,这两个函数可以改变string的内容(详见表9.13)。append操作是在string末尾进行插入操作的一种简写形式:

1
2
3
string s("C++ Primer"), s2 = s; //将s和s2初始化为"C++ Primer"
s.insert(s.size(), " 4th Ed."); //s == "C++ Primer 4th Ed."
s2.append(" 4th Ed."); //等价方法:将" 4th Ed."追加到s2;s == s2

replace操作是调用erase和insert的一种简写形式:

1
2
3
4
5
//将"4th"替换为"5th"的等价方法
s.erase(11, 3); // s == "C++ Primer Ed."
s.insert(11, "5th"); // s == "C++ Primer 5th Ed."
//从位置11开始,删除3个字符并插入"5th"
s2.replace(11, 3, "5th"); //等价方法:s == s2

此例中调用replace时,插入的文本恰好与删除的文本一样长。这不是必须的,可以插入一个更长或更短的string:

1
s.replace(11, 3, "Fifth"); // s == "C++ Primer Fifth Ed."

在此调用中,删除了3个字符,但在其位置插入了5个新字符。

4.string搜索操作

string类提供了6个不同的搜索函数,每个函数都有4个重载版本。见表9.14。每个搜索操作都返回一个string::size_type值,表示匹配发生位置的下标。如果搜索失败,则返回一个名为string::npos的static成员。标准库将npos定义为一个const string::size_type类型,并初始化为值-1。由于npos是一个unsigned类型,此初始值意味着npos等于任何string最大的可能大小。

string搜索函数返回string::size_type值,该类型是一个unsigned类型。因此,用一个int或其他带符号类型来保存这些函数的返回值不是一个好主意。

find函数完成最简单的搜索。它查找参数指定的字符串,若找到,则返回第一个匹配位置的下标,否则返回npos:

1
2
string name("AnnaBelle");
auto pos1 = name.find("Anna"); // pos1 == 0

这段程序返回0,即子字符串”Anna”在”AnnaBelle”中第一次出现的下标。

搜索(以及其他string操作)是大小写敏感的。当在string中查找子字符串时,要注意大小写:

1
2
string lowercase("annabelle");
pos1 = lowercase.find("Anna"); // pos1 == npos

一个更复杂一些的问题是查找与给定字符串中任何一个字符匹配的位置。例如,下面代码定位name中的第一个数字:

1
2
3
string numbers("0123456789"), name("r2d2");
//返回1,即,name中第一个数字的下标
auto pos = name.find_first_of(numbers);

如果是要搜索第一个不在参数中的字符,我们应该调用find_first_not_of。例如,为了搜索一个string中第一个非数字字符,可以这样做:

1
2
3
string dept("03714p3");
//返回5,即字符'p'的下标
auto pos = dept.find_first_not_of(numbers);

4.1.指定在哪里开始搜索

我们可以传递给find操作一个可选的开始位置。这个可选的参数指出从哪个位置开始进行搜索。默认情况下,此位置被置为0。比如:

1
2
3
4
5
6
7
string::size_type pos = 0;
//每步循环查找name中下一个数
while ((pos = name.find_first_of(numbers, pos)) != string::npos)
{
	cout << "found number at index: " << pos << " element is " << name[pos] << endl;
	++pos; //移动到下一个字符
}

4.2.逆向搜索

到现在为止,我们已经用过的find操作都是由左至右搜索。标准库还提供了类似的,但由右至左搜索的操作。rfind成员函数搜索最后一个匹配,即子字符串最靠右的出现位置:

1
2
3
string river("Mississippi");
auto first_pos = river.find("is"); //返回1
auto last_pos = river.rfind("is"); //返回4

find返回下标1,表示第一个”is”的位置,而rfind返回下标4,表示最后一个”is”的位置。

类似的,find_last函数的功能与find_first函数相似,只是它们返回最后一个而不是第一个匹配:

  • find_last_of搜索与给定string中任何一个字符匹配的最后一个字符。
  • find_last_not_of搜索最后一个不出现在给定string中的字符。

每个操作都接受一个可选的第二参数,可用来指出从什么位置开始搜索。

5.compare函数

除了关系运算符外,标准库string类型还提供了一组compare函数,这些函数与C标准库的strcmp函数很相似。类似strcmp,根据s是等于、大于还是小于参数指定的字符串,s.compare返回0、正数或负数。

如表9.15所示,compare有6个版本。根据我们是要比较两个string还是一个string与一个字符数组,参数各有不同。在这两种情况下,都可以比较整个或一部分字符串。

6.数值转换

字符串中常常包含表示数值的字符。例如,我们用两个字符的string表示数值15,即字符’1’后跟字符’5’。一般情况,一个数的字符表示不同于其数值。数值15如果保存为16位的short类型,则其二进制位模式为0000000000001111,而字符串”15”存为两个Latin-1编码的char,二进制位模式为0011000100110101。第一个字节表示字符’1’,其八进制值为061,第二个字节表示’5’,其Latin-1编码为八进制值065。

C++11新标准引入了多个函数,可以实现数值数据与标准库string之间的转换:

1
2
3
int i = 42;
string s = to_string(i); //将整数i转换为字符表示形式
double d = stod(s); //将字符串s转换为浮点数

要转换为数值的string中第一个非空白符必须是符号(+或-)或数字。它可以以0x或0X开头来表示十六进制数。对那些将字符串转换为浮点值的函数,string参数也可以以小数点(.)开头,并可以包含e或E来表示指数部分。对于那些将字符串转换为整型值的函数,根据基数不同,string参数可以包含字母字符,对应大于数字9的数。

1
2
3
4
5
6
7
8
string s1 = "23a";
int i1 = stoi(s1); //i1为23
string s2 = "23a23";
int i2 = stoi(s2); //i2为23
string s3 = "a23a23";
int i3 = stoi(s3); //抛出invalid_argument异常
string s4 = ".2e3";
double d1 = stod(s4); //d1为200

如果string不能转换为一个数值,这些函数抛出一个invalid_argument异常。如果转换得到的数值无法用任何类型来表示,则抛出一个out_of_range异常。

1
2
3
4
5
size_t p;
string s = "23a45";
int i = stoi(s,&p,10); //10表示进制
cout << i << endl; //i为23
cout << p << endl; //p为2,即第一个非数值字符'a'的下标