进一步探讨 string 类型(续)

/**
* 本节是作为《进一步探讨 string 类型》的详细补充,
* 
* 可直接跳过,也可以先粗略浏览。
* 
* 可以在编写程序时遇到有关 string 的这些操作时,才回来阅读其细节。
**/

[1. 构造 string 对象的其他方法]

string 类支持表 9.2 所列出的几乎所有构造函数,只有一个例外:

string 不支持带有单个容器长度作为参数的构造函数。

创建 string 对象时:不提供任何参数,则得到空的 string 对象;

也可将新对象初始化为另一个 string 对象的副本;

或用一对迭代器初始化:或者使用一个计数器和一个字符初始化:

string s1; // s1 is the empty string
string s2(5, 'a'); // s2 == "aaaaa"
string s3(s2); // s3 is a copy of s2
string s4(s3.begin(),
s3.begin() + s3.size() / 2); // s4 == "aa"

除了上述构造函数之外,string 类型还提供了三种其他的方式创建类对象(表 9.13)。

在前面的章节中,已经使用过只有一个指针参数的构造函数,

该指针指向以空字符结束的字符数组中的第一个元素。

另一种构造函数需要一个指向字符数组元素的指针和一个标记要复制多少个字符的计数器作参数。

由于该构造函数带有一个计数器,因此数组不必以空字符结束:

char *cp = "Hiya"; // null-terminated array
char c_array[] = "World!!!!"; // null-terminated
char no_null[] = {'H', 'i'}; // not null-terminated

string s1(cp); // s1 == "Hiya"
string s2(c_array, 5); // s2 == "World"
string s3(c_array + 5, 4); // s3 == "!!!!"
string s4(no_null); // runtime error: no_null not null-terminated
string s5(no_null, 2); // ok: s5 == "Hi"

使用只有一个指针参数的构造函数定义 s1,该指针指向以空字符结束的数组中的第一字符。

这个数组的所有字符,但不包括结束符 null,都被复制到新创建的 string 对象中。

而 s2 的初始化式则通过第二种构造函数实现,它的参数包括一个指针和一个计数器。

在这个例子中,从参数指针指向的那个字符开始,连续复制第二个参数指定数目的字符。

因此,s2 是 c_array 数组前 5 个字符的副本。

注意,将数组作为参数传递时,数组将自动转换为指向其第一个元素的指针。

当然,并没有限制非得传递指向数组起点的指针不可。

通过给 s3 的构造函数传递指向 c_array 数组中第一个感叹号字符的指针,

s3 被初始化为存储 4 个感叹号字符的 string 对象。

s4 和 s5 的初始化式并不是 C 风格字符串。其中,s4 的定义是错误的。

调用这种形式的初始化,其参数必须是以空字符结束的数组。

将不包含 null 的数组传递给构造函数将导致编译器无法检测的严重错误,

此类错误在运行时将支发生什么状况并未定义。

s5 的初始化则是正确的:

初始化式包含了一个计数器,以说明要复制多少个字符用于初始化。

该计数器的值必须小于数组的长度,此时,无论数组是否以空字符结束,都没什么关系。

//9.13. 构造 string 对象的其他方法
string s(cp, n)
  创建一个 string 对象,它被初始化为 cp 所指向数组的前 n 个元素的副本

string s(s2, pos2)
  创建一个 string 对象,它被初始化为一个已存在的 string 对象 s2 中从下标 pos2 开始的字符的副本

string s(s2, pos2, len2)
  创建一个 string 对象,它被初始化为 s2 中从下标 pos2 开始的 len2 个字符的副本。
  如果 pos2 > s2.size(),则该操作未定义,无论 len2 的值是多少,
  最多只能复制 s2.size() - pos2 个字符
  注意:n、len2 和 pos2 都是 unsigned 值

用子串做初始化式

另一种构造函数是程序员可以在创建 string 对象时将其初始化为另一个 string 对象的子串。

string s6(s1, 2); // s6 == "ya"
string s7(s1, 0, 2); // s7 == "Hi"
string s8(s1, 0, 8); // s8 == "Hiya"

第一个语句的两个参数指定了要复制的 string 对象及其复制的起点。

在两个参数的构造函数版本中,

复制 string 对象实参中从指定位置到其末尾的所有字符,用于初始化新创建的 string 对象。

还可以为此类构造函数提供第三个参数,用于指定复制字符的个数。

在本例中,我们从指定位置开始复制指定数目(最多为 string 对象的长度)的字符数。

例如,创建 s7 时,从 s1 中下标为 0 的位置开始复制两个字符;

而创建 s8 时,只复制了 4 个字符,而并不是要求的 8 个字符。

无论要求复制多少个字符,标准库最多只能复制数目与 string 对象长度相等的字符。


[2. 修改 string 对象的其他方法]

string 类型支持的许多容器操作在操作时都以迭代器为基础。

例如,erase 操作需要一个迭代器或一段迭代器范围作其参数,用于指定从容器中删除的元素。

类似地,所有版本的 insert 函数的第一参数都是一个指向插入位置之后的迭代器,

而新插入的元素值则由其他参数指定。

尽管 string 类型支持这些基于迭代器的操作,它同样也提供以下标为基础的操作。

下标用于指定 erase 操作的起始元素,或在其前面 insert 适当值的元素。

表 9.14 列出了 string 类型和容器类型共有的操作;而表 9.15 则列出了 string 类型特有的操作。

// 表 9.14 与容器共有的 string 操作
s.insert(p, t)      在迭代器 p 指向的元素之前插入一个值为 t 的新元素。
            返回指向新插入元素的迭代器

s.insert(p, n, t)    在迭代器 p 指向的元素之前插入 n 个值为 t 的新元素。
            返回 void

s.insert(p, b, e)    在迭代器 p 指向的元素之前插入迭代器 b 和 e 标记范围内所有的元素。
            返回 void

s.assign(b, e)      在迭代器 b 和 e 标记范围内的元素替换 s。
            对于 string 类型,该操作返回 s;对于容器类型,则返回 void

s.assign(n, t)    用值为 t 的 n 个副本替换 s。
            对于 string 类型,该操作返回 s;对于容器类型,则返回 void

s.erase(p)      删除迭代器 p 指向的元素。
            返回一个迭代器,指向被删除元素后面的元素

s.erase(b, e)    删除迭代器 b 和 e 标记范围内所有的元素。
            返回一个迭代器,指向被删除元素段后面的第一个元素
// 表 9.15 string 类型特有的版本
s.insert(pos, n, c)      在下标为 pos 的元素之前插入 n 个字符 c

s.insert(pos, s2)         在下标为 pos 的元素之前插入 string 对象 s2 的副本

s.insert(pos, s2, pos2, len) 在下标为 pos 的元素之前插入 s2 中从下标 pos2 开始的 len 个字符

s.insert(pos, cp, len)      在下标为 pos 打元素之前插入 cp 所指向数组的前 len 个字符

s.insert(pos, cp)         在下标为 pos 的元素之前插入 cp 所指向的以空字符结束的字符串副本

s.assign(s2)            用 s2 的副本替换 s

s.assign(s2, pos2, len)      用 s2 中从下标 pos2 开始的 len 个字符副本替换 s

s.assign(cp, len)         用 cp 所指向数组的前 len 个字符副本替换 s

s.assign(cp)            用 cp 所指向的以空字符结束的字符串副本替换 s

s.erase(pos, len)         删除从下标 pos 开始的 len 个字符

注意:除非特殊声明,上述所有操作都返回 s 的引用

2.1 基于位置的实参

string 类型为这些操作提供本类型特有的版本,它们接受的实参类似于在前一节介绍的补充构造函数。

程序员可通过这些操作基于位置处理 string 对象,以及使用指向字符数组的指针而不是 string 对象作实参。

例如,所有容器都允许程序员指定一对迭代器,用于标记删除(erase)的元素范围。

对于 string 类型,还允许通过为 erase 函数传递一个起点位置和删除元素的数目,来指定删除的范围。

假设 s 至少有 5 个元素,下面的语句用于删除 s 的最后 5 个字符:

s.erase(s.size() - 5, 5); // erase last five characters from s

类似地,对于容器类型,可在迭代器指向的元素之前插入(insert)指定数目的新值。

而对于 string 类型,系统还允许使用下标而不是迭代器指定插入位置:

s.insert(s.size(), 5, '!'); // insert five exclamation points at end of s

2.2 指定新的内容

在 string 对象中 insert 或 assign 的字符可来自于字符数组或另一个 string 对象。

例如,以空字符结束的字符数组可以用作 insert 或 assign 到 string 对象的内容:

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

类似地,可如下所示将一个 string 对象的副本插入到另一个 string 对象中:

s = "some string";
s2 = "some other string";
// 3 equivalent ways to insert all the characters from s2 at beginning of s
s.insert(0, s2);     // insert copy of s2 before position 0 in s
s.insert(0, s2, 0, s2.size());    // insert s2.size() characters from s2 starting at s2[0] before s[0]
s.insert(s.begin(), s2.begin(), s2.end());    // insert iterator range before s.begin()

[3. 只适用于 string 类型的操作]

string 类型也提供了其他几种操作,而这些是容器类型不支持的,如表 9.16 所示:

// 表 9.16 子串操作
s.substr(pos, n)     返回一个 string 类型的字符串,它包含 s 中从下标 pos 开始的 n 个字符 
s.substr(pos)     返回一个 string 类型的字符串,它包含从下标 pos 开始到 s 末尾的所有字符
s.substr()     返回 s 的副本

3.1 substr 操作

使用 substr 操作可在指定 string 对象中需要检索的子串。

我们可以给 substr 函数传递查找的起点和一个计数器。

该函数将生成一个新的 string 对象,包含原目标 string 对象从指定位置开始的若干个字符

(字符数目由计数器决定,但最多只能到原 string 对象的最后一个字符):

string s("hello world");
// return substring of 5 characters starting at position 6
string s2 = s.substr(6, 5); // s2 = world

可选择另一种方法实现相同的功能:

// return substring from position 6 to the end of s
string s3 = s.substr(6); // s3 = world

3.2 append 和 replace 函数

string 类型提供了 6 个 append 重载函数版本和 10 个 replace 版本(见表 9.17)。

// 表 9.17 修改 string 对象的操作(args 在表 9.18 中定义)
s.append( args)          将 args 串接在 s 后面。
                 返回 s 引用

s.replace(pos, len, args)    删除 s 中从下标 pos 开始的 len 个字符,用 args 指定的字符替换之。
                 返回 s 的引用
                 在这个版本中,args 不能为 b2,e2

s.replace(b, e, args)       删除迭代器 b 和 e 标记范围内所有的字符,用 args 替换之。
                 返回 s 的引用
                 在这个版本中,args 不能为 s2,pos2,len2

append 和 replace 函数使用了相同的参数集合实现重载。

这些参数如表 9.18 所示,用于指定在 string 对象中添加的字符。

对于 append 操作,字符将添加在 string 对象的末尾。

而 replace 函数则将这些字符插入到指定位置,从而替换 string 对象中一段已存在的字符。

string s("C++ Primer"); // initialize s to "C++ Primer"
s.append(" 3rd Ed."); // s == "C++ Primer 3rd Ed."
// equivalent to s.append(" 3rd Ed.")
s.insert(s.size(), " 3rd Ed.");

string 类型为 replace 操作提供了 10 个不同版本,

其差别在于以不同的方式指定要删除的字符和要插入的新字符。

前两个参数应指定删除的元素范围,可用迭代器对实现,也可用一个下标和一个计数器实现。

其他的参数则用于指定插入的新字符。

可将 replace 视为删除一些字符然后在同一位置插入其他内容的捷径:

// starting at position 11, erase 3 characters and then insert "4th"
s.replace(11, 3, "4th"); // s == "C++ Primer 4th Ed."
// equivalent way to replace "3rd" by "4th"
s.erase(11, 3); // s == "C++ Primer Ed."
s.insert(11, "4th"); // s == "C++ Primer 4th Ed."

append 操作提供了在字符串尾部插入的捷径:

replace 操作用于删除一段指定范围的字符,然后在删除位置插入一组新字符,

等效于调用 erase 和 insert 函数。

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

在这个例子中,删除了 3 个字符,但在同一个位置却插入了 6 个字符。

// 表 9.18 append 和 replace 操作的参数:args
s2           string 类型的字符串 s2

s2, pos2, len2   字符串 s2 中从下标 pos2 开始的 len2 个字符

cp           指针 cp 指向的以空字符结束的数组

cp, len2        cp 指向的以空字符结束的数组中前 len2 个字符

n, c          字符 c 的 n 个副本

b2, e2         迭代器 b2 和 e2 标记的范围内所有字符

[4. string 类型的查找操作]

string 类提供了 6 种查找函数(表 9.19),每种函数以不同形式的 find 命名。

// 表9.19. string Search Operations (Arguments in Table 9.20)
s.find( args)           在 s 中查找 args 的第一次出现

s.rfind( args)           在 s 中查找 args 的最后一次出现

s.find_first_of( args)      在 s 中查找 args 的任意字符的第一次出现

s.find_last_of( args)       在 s 中查找 args 的任意字符的最后一次出现

s.find_first_not_of( args)   在 s 中查找第一个不属于 args 的字符

s.find_last_not_of( args)    在 s 中查找最后一个不属于 args 的字符

这些操作全都返回 string::size_type 类型的值,以下标形式标记查找匹配所发生的位置;

或者返回一个名为 string::npos 的特殊值,说明查找没有匹配。

string 类将 npos 定义为保证大于任何有效下标的值。

每种查找操作都有 4 个重载版本,每个版本使用不同的参数集合。

表 9.20 列出了查找操作使用的不同参数形式。

// 表 9.20. string 类型提供的 find 操作的参数
c, pos      在 s 中,从下标 pos 标记的位置开始,
        查找字符 c。pos 的默认值为 0

s2, pos     在 s 中,从下标 pos 标记的位置开始,
        查找 string 对象 s2。pos 的默认值为 0
 
cp, pos     在 s 中,从下标 pos 标记的位置形参,
        查找指针 cp 所指向的 C 风格的以空字符结束的字符串。pos 的默认值为 0

cp, pos, n    在 s 中,从下标 pos 标记的位置开始,
        查找指针 cp 所指向数组的前 n 个字符。pos 和 n 都没有默认值

基本上这些操作的不同之处在于,

查找的到底是单个字符、另一个 string 字符串、C 风格的以空字符结束的字符串,

还是用字符数组给出的特定数目的字符集合。

4.1 精确匹配的查找

最简单的查找操作是 find 函数,用于寻找实参指定的内容。

如果找到的话,则返回第一次匹配的下标值;如果找不到,则返回 npos:

string name("AnnaBelle");
string::size_type pos1 = name.find("Anna"); // pos1 == 0

返回 0,这是子串“Anna”位于字符串“AnnaBelle”中的下标。

默认情况下,find 操作以及其他处理字符的 string 操作,

使用内置操作符比较 string 字符串中的字符。

因此,这些操作(以及其他 string 操作)都区分字母的大小写。

以下程序寻找 string 对象中的某个值,字母的大小写影响了程序结果:

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

这段代码使 pos1 的值为 npos —— 字符串 Anna 与 anna 不匹配。

find 操作的返回类型是 string::size_type,请使用该类型的对象存储 find 的返回值。

4.2 查找任意字符

如果在查找字符串时希望匹配任意指定的字符,则实现起来稍微复杂一点。

例如,下面的程序要在 name 中寻找并定位第一个数字:

string numerics("0123456789");
string name("r2d2");
string::size_type pos = name.find_first_of(numerics); // pos == 1
cout << "found number at index: " << pos
   << " element is " << name[pos] << endl;

4.3 指定查找的起点

程序员可以给 find 操作传递一个可选的起点位置实参,

用于指定开始查找的下标位置,该位置实参的默认值为 0。

通常的编程模式是使用这个可选的实参循环查找 string 对象中所有的匹配。

下面的程序重写了查找“r2d2”的程序,以便找出 name 字符串中出现的所有数字:

string::size_type pos = 0;
// each trip reset pos to the next instance in name
while ((pos = name.find_first_of(numerics, pos))
      != string::npos) {
  cout << "found number at index: " << pos
  << " element is " << name[pos] << endl;
  ++pos; // move to the next character
}

如果漏掉了循环体末尾让 pos 加 1 的语句,那么循环永远都不会结束。

考虑没有该操作时,会发生什么情况?

第二次循环时,从 pos 标记的位置开始查找,

而此时 pos 标记的就是一个数字,于是 find_first_of 函数将(不断重复地)返回同一个 pos 值。

4.4 寻找不匹配点

除了寻找匹配的位置外,还可以调用 find_first_not_of 函数查找第一个与实参不匹配的位置。

例如,如果要在 string 对象中寻找第一个非数字字符,可以如下编写程序:

string numbers("0123456789");
string dept("03714p3");
// returns 5, which is the index to the character 'p'
string::size_type pos = dept.find_first_not_of(numbers);

4.5 反向查找

迄今为止,我们使用的所有 find 操作都是从左向右查找的。

除此之外,标准库还提供了一组类似的从右向左查找 string 对象的操作。

rfind 成员函数用于寻找最后一个(即,最右边的)指定子串出现的位置:

string river("Mississippi");
string::size_type first_pos = river.find("is"); // returns 1
string::size_type last_pos = river.rfind("is"); // returns 4

find 函数返回下标 1,标记 river 字符串中第一个“is”的出现位置;

而 rfind 函数返回最后一个匹配的位置,而并不是第一个。

find_last_of 函数查找与目标字符串的任意字符匹配的最后一个字符。

find_last_not_of 函数查找最后一个不能跟目标字符串的任何字符匹配的字符。

这两个操作都提供第 2 个可选参数,用于指定在 string 对象中开始查找的位置。


[5. string 对象的比较]

正如在之前所了解的,string 类型定义了所有关系操作符,

使程序员可以比较两个 string 对象是否相等(==)、不等(!=),

以及实现小于或大于(<、<=、>、>=)运算。

string 对象采用字典顺序比较,即,string 对象的比较与大小写敏感的字典顺序比较相同:

string cobol_program_crash("abend");
string cplus_program_crash("abort");


除了关系操作符,string 类型还提供了一组 compare 操作(表 9.21),用于实现字典顺序的比较。

//表 9.21 string 类型 compare 操作
s.compare(s2)               比较 s 和 s2

s.compare(pos1, n1, s2)         让 s 中从 pos 下标位置开始的 n1 个字符与 s2 做比较

s.compare(pos1, n1, s2, pos2, n2)  让 s 中从 pos1 下标位置开始的 n1 个字符
                     与 s2 中从 pos2 下标位置开始的 n2 个字符做比较

s.compare(cp)               比较 s 和 cp 所指向的以空字符结束的字符串

s.compare(pos1, n1, cp)         让 s 中从 pos1 下标位置开始的 n1 个字符
                     与 cp 所指向的字符串做比较

s.compare(pos1, n1, cp, n2)       让 s 中从 pos1 下标位置开始的 n1 个字符
                    与 cp 所指向的字符串的前 n2 个字符做比较

这些操作的结果类似于 C 语言中的库函数 strcmp。

假设有语句:s1.compare (args);

compare 函数返回下面列出的三种可能值之一:

正数,此时 s1 大于 args 所代表的 string 对象。

负数,此时 s1 小于 args 所代表的 string 对象。

0,此时 s1 恰好等于 args 所代表的 string 对象。

// returns a negative value
cobol_program_crash.compare(cplus_program_crash);
// returns a positive value
cplus_program_crash.compare(cobol_program_crash);

compare 操作提供了 6 种重载函数版本,

以方便程序员实现一个或两个 string 对象的子串的比较,

以及 string 对象与字符数组或其中某一部分的比较:

char second_ed[] = "C++ Primer, 2nd Edition";
string third_ed("C++ Primer, 3rd Edition");
string fourth_ed("C++ Primer, 4th Edition");

// compares C++ library string to C-style string
fourth_ed.compare(second_ed); // ok, second_ed is null-terminated

// compare substrings of fourth_ed and third_ed
fourth_ed.compare(fourth_ed.find("4th"), 3,
third_ed, third_ed.find("3rd"), 3);

我们对第 2 个 compare 函数的调用更感兴趣。

这个调用使用了具有 5 个参数的 compare 函数版本。

先调用 find 函数找到子串“4th”的起点。

让从此起点开始的 3 个字符与 third_ed 的子串做比较。

而 third_ed 的子串起始于 find 函数找到的“3rd”的起点,同样取其随后的 3 个字符参加比较。

可见这个语句本质上比较的是 "4th" 和 "3rd" 。

原文链接: https://www.cnblogs.com/yshl-dragon/archive/2013/06/14/3135953.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/92174

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月10日 上午1:32
下一篇 2023年2月10日 上午1:33

相关推荐