附录F string模板类

本附录的技术性较高,但如果读者指向了解模板类string的功能,可以将重点放在对各种string类方法的描述上。

string类是基于下述模板定义的:

image750_1

其中,chatT是存储在字符串中的类型;traits参数是一个类,它定义了类型要被表示为字符串时,所必须具备的特征。例如,它必须有length()方法,该方法返回被表示为charT数组的字符串的长度。这种数组结尾用charT (0)值(广义的空值字符)表示(表达式charT (0)将0转换为charT类型。它可以像类型为char时那样为零,也可以是charT的一个构造函数创建的对象)。这个类还包含用于对值进行比较等操作的方法。Allocator参数是用于处理字符串内存分配的类。默认的allocator<char>模板按标准方式使用new和delete。

有两种预定义的具体化:

image750_2

而上述具体化又使用下面的具体化:

image750_3

除char和wchar_t外,还可以通过定义traits类和使用basic_string模板来为其他一些类型创建一个string类。

F.1 13种类型和一个常量

basic_string模板定义了几种类型,供以后定义方法时使用:

image750_4

traits是一个将对应于某个具体类型(如char_traits<char>)的模板参数;traits_type将成为该具体类型的typedef。下述表示法:

typedef typename traits: : char_type value_type;

意味着char_type是traits表示的类中定义的一个类型名。关键字typename告诉编译器:表达式trait:: char_type是一种类型。例如,对于字符串具体化,value_type为char。

size_type与size_of的用法相似,只是它根据存储的类型返回字符串的长度。对于string具体化,将根据char返回字符串的长度,在这种情况下,size_type与size_of等效。size_type是一种无符号类型。

Differrence_type用于度量字符串中两个元素之间的距离(单位为元素的长度)。通常,它是底层类型size_type有符号版本。

对于char具体化来说,pointer的类型为char*,而reference的类型为char &类型。不过,如果要为自己设计的类型创建具体化,则这些类型(pointer和reference)可以指向类,与基本指针和引用有相同的特征。

为将标准模板库(STL)算法用于字符串,该模板定义了一些迭代器类型:

image751_1

该模板还定义了一个静态常量:

static const size_type npos = -1;

由于size_type是无符号的,因此将-1赋给npos相当于将最大的无符号值赋给它,这个值比可能的最大数组索引大1。

F.2 数据信息、构造函数及其他

可以根据其效果来描述构造函数。由于类的私有部分可能依赖于实现,因此可根据公用接口中可用的数据来描述这些效果。表F.1列出了一些方法,它们的返回值可用来描述构造函数和其他方法的效果。注意,其中的大部分术语来自STL。

表F.1 一些string数据方法

方  法 返 回 值
begin() 指向字符串第一个字符的迭代器(在const版本中也可用,在这种情况下,将返回一个const迭代器)
end() 为超尾值的迭代器(在const版本中也可用)
rbegin() 为超尾值的反转迭代器(在const版本中也可用)
rend() 引用第一个字符的反转迭代器(在const版本中也可用)
size() 字符串中的元素数,等于begin()到end()之间的距离
length() 与size()相同
capacity() 给字符串分配的元素数。这可能大于实际的字符数,capacity() - size()的值表示在字符串末尾附加多少字 符后需要分配更多的内存
max_size() 字符串的最大长度
data() 一个指向数组第一个元素的const charT指针,其第一个size()元素等于this控制的字符串中对应的元素, 当string对象本身被修改后,该指针可能无效
c_str() 一个指向数组第一个元素的const charT指针,其第一个size()元素等于this控制的字符串中对应的元素, 其下一个元素是charT类型的charT (0)字符(字符串尾标识)。当string对象本身被修改后,该指针可 能无效
get-allocator() 用于为字符串object分配内存的allocator对象的副本

请注意begin()、rend()、data()和c_str()之间的差别。它们都与字符串的第一个字符相关,但相关的方式不同。begin()和rend()方法返回一个迭代器,正如第16章讨论的,这是一种广义指针。具体地说,begin()返回一个正向迭代器模型,而rend()返回反转迭代器的一个副本。这两种方法都引用了string对象管理的字符串(由于string类使用动态内存分配,因此实际的string内容不一定位于对象中,因此,我们使用术语“管理”来描述对象和字符串之间的关系)。可以将返回迭代器的方法用于基于迭代器的STL算法中。例如,可以使用STL reverse()函数来反转字符串的内容:

image752_1

而data()和c_str()方法返回常规指针。另外,返回的指针将指向存储字符串字符的数组的第一个元素。该数组可能(但不一定)是string对象管理的字符串的副本(string对象采用的内部表示可以是数组,但不一定非得是数组)。由于返回的指针可能指向原始数据,而原始数据是const,因此不能用它们来修改数据。另外,当字符串被修改后,将不能保证这些指针是有效的,这表明它们可能指向原始数据。data()和c_str()的区别在于,c_str()指向的数组以空值字符(或与之等价的其他字符)结束,而data()只是确保实际的字符串字符是存在的。因此,c_str()方法期望接受一个C-风格字符串参数:

image752_2

同样,data()和size()可用作这种函数的参数,即接受指向数组元素的指针和表示要处理的元素数目的值:

image752_3

C++实现可能将string对象的字符串表示为动态分配的C-风格字符串,并使用char*指针来实现正向迭代器。在这种情况下,实现可能让begin()、data()和c_str()都返回同样的指针,但返回指向3个不同的数据对象的引用也是合法的(虽然更复杂)。

下面是basic_string模板类的6个构造函数和1个析构函数:

image752_4

这6个构造函数都有1个下面这样的参数:

const Allocator& a = Allocator()

Allocator是用于管理内存的allocator类的模板参数名;Allocator()是这个类的默认构造函数。因此,在默认情况下,构造函数将使用allocator对象的默认版本,但它们使得能够选择使用allocator对象的其他版本。下面分别介绍这些构造函数。

F.2.1 默认构造函数

默认构造函数的原型如下:

explicit basic_string (const Allocator& a = Allocator ());

通常,接受allocator类的默认参数,并使用构造函数来创建空字符串:

image752_5

该构造函数被调用后,将存在下面的关系:

● data()方法返回一个非空指针,可以将该指针加上0。

● size()方法返回0。

● capacity()的返回值是不确定的。

将data()返回的值赋值给指针str后,第一个条件意味着str+O是有效的。

F.2.2 使用数组的构造函数

使用数组的构造函数让您能够将string对象初始化为一个C-风格字符串;从更普遍的意义来看,它使得能够将charT具体化初始化为一个charT数组:

basic_string (const charT* s, const Allocator& a = Allocator ());

为确定要复制的字符数,该构造函数将traits::length()方法用于s指向的数组(指针s不能为空值)。例如,下面的语句:

string toast ("Here's looking at you, kid.");

使用指定的字符串来初始化toast对象。char类型的traits::length()方法将使用空值字符来确定要复制多少个字符。

该构造函数被调用后,将存在下面的关系:

● data()方法返回一个指针,该指针指向数组s的一个副本的第一个元素。

● size()方法返回的值等于trains::length()的值。

● capacity()方法返回一个至少等于size()的值。

F.2.3 使用部分数组的构造函数

使用部分数组的构造函数让您能够使用C-风格字符串的一部分来初始化string对象;从更广泛的意义上说,该构造函数使得能够使用charT数组的一部分来初始化charT具体化:

basic_string (const charT* s, size_type n, const Allocator& a = Allocator ());

该构造函数将s指向的数组中的n个字符复制到构造的对象中。请注意,如果s包含的字符数少于n,则复制过程将不会停止。如果n大于s的长度,该构造函数将把字符串后面的内存内容解释为charT类型的数据。

该构造函数要求s不能是空值指针,同时n<npos (npos是一个静态类常量,它是字符串可能包含的最大元素数目)。如果n等于npos,该构造函数将引发一个out_of_range异常(由于n的类型为size_type,而npos是size_type的最大值,因此n不能大于npos);否则,在该构造函数被调用后,将存在下面的关系:

data()方法返回一个指针,该指针指向数组s的副本的第一个元素。

size()方法返回n

capacity()方法返回一个至少等于size()的值。

F.2.4 复制构造函数

复制构造函数提供了多个有默认值的参数:

image753_1

调用复制构造函数时如果只提供一个basic_string参数,新对象将被初始化为该string参数:

image753_2

其中,ida将是mel管理的字符串副本。

第二个可选参数pos指定了源字符串中的位置,将从这个位置开始进行复制:

image753_3

位置编号从0开始,因此,位置4是字符p。所以,et被初始化为“Telephone home”。

第3个可选参数n指定要复制的最大字符数目,因此下面的语句:

image753_4

将pt初始化为字符串“Telephone”。不过,该构造函数不能跨越源字符串的结尾,例如,下面的语句:

string pt (att, 4, 200)

将在复制句点后停止。因此,该构造函数实际复制的字符数量等于n和str.size()-pos中较小的一个。

该构造函数要求pos不大于str.size(),也就是说,被复制的初始位置必须位于源字符串中。如果情况并非如此,该构造函数将引发out_of_range异常;否则,该构造函数被调用后,copy_len将是n和str.size()-pos中较小的一个,并存在下面的关系:

● data()方法返回一个指向字符串str的指针,该字符串包含copy_len个元素,这些元素是从str的pos位置开始复制而得到的。

● size()方法返回copy_len。

● capacity()方法返回一个不小于size()的值。

F.2.5 使用一个字符的n个副本的构造函数

使用一个字符的n个副本的构造函数创建一个由nc组成的string对象:

basic_string (size_type n, charT c, const Allocator& a = Allocator ());

该构造函数要求n<npos。如果n等于npos,该构造函数将引发out_of_range异常;否则,该构造函数被调用后,将存在下面的关系:

● data()方法返回一个指向字符串第一个元素的指针,该字符串由n个元素组成,其中每个元素的值都为c

● size()方法返回n

● capacity()方法返回不小于size()的值。

F.2.6 使用区间的构造函数

使用区间的构造函数使用一个用迭代器定义的、STL-风格的区间:

image754_1

begin迭代器指向源字符串中要复制的第一个元素,end指向要复制的最后一个元素的后面。

这种构造函数可用于数组、字符串或STL容器:

image754_2

在第一种用法中,Inputlterator的类型为const char *;在第二种用法中,Inputlterator的类型为vector<char>:: iterator。

调用该构造函数后,将存在下面的关系:

● data()方法返回一个指向字符串的第一个元素的指针,该字符串是通过复制区间[begin,end)中的元素得到的。

● size()方法返回begin到end之间的距离(度量距离时,使用的单位为对迭代器解除引用得到的数据类型的长度)。

● capacity()方法返回一个不小于size()的值。

F.2.7 内存杂记

一些方法用于处理内存,如清除内存的内容、调整字符串长度或容量。表F.2列出了一些与内存相关的方法。

表F.2 一些与内存有关的方法

方  法 作  用
void resize (size_type n) 如果n>npos,将引发out_of_range异常;否则,将字符串的长度改为n;如果n<size(), 则截短字符串;如果n>size(),则使用charT (0)中的字符填充字符串
void resize (size_type n, charT c) 如果n>npos,将引发out_of_range异常;否则,将字符串长度改为n;如果n<size(), 则截短字符串;如果n>size(),则使用字符c填充字符串
void reserve (size_type res_arg = 0) 将capacity()设置为大于或等于res_arg。由于这将重新分配字符串,因此以前的引用、 迭代器和指针将无效
void clear() 删除字符串中所有的字符
bool empty () const 如果size()==0,则返回true
F.3 字符串存取

有4种方法可以访问各个字符,其中两种方法使用[]操作符,另外两种方法使用at()方法:

image755_1

第一个operator方法使得能够使用数组表示法来访问字符串的元素,可用于检索或更改值。第二个operator方法可用于const对象,只能用于检索值:

image755_2

at()方法提供了相似的访问功能,只是索引是通过函数参数提供的:

image755_3

差别在于(除句法差别外):at()方法执行边界检查,如果pos>=size(),将引发out_of_range异常。pos的类型为size_type,是无符号的,因此pos的值不能为负;而operator方法不进行边界检查,因此,如果pos>=size(),则其行为将是不确定的(如果pos= =size(),const版本将返回空值字符的等价物)。

因此,可以在安全性(使用at(),检测异常)和执行速度(使用数组表示)之间进行选择。

还有一个这样的函数,它返回原始字符串的子字符串:

basic_string substr (size_type pos = 0, size_type n = npos) const;

它返回一个字符串——这是从pos开始,复制n字符(或到字符串尾部)得到的。例如,下面的代码将pet初始化为“donkey”:

image755_4

F.4 基本赋值

有3种重载的赋值方法:

image755_5

第一种方法将一个string对象赋给另一个;第二种方法将C风格的字符串赋给string对象;第三种方法将一个字符赋给string对象。因此,下面的操作都是可能的:

image755_6

image756_1

F.5 字符串搜索

string类提供了6种搜索函数,其中每个函数都有4个原型。下面简要地介绍它们。

F.5.1 find()系列

find()的原型如下:

image756_2

第一个返回str在调用对象中第一次出现时的起始位置。搜索从pos开始,如果没有找到子字符串,将返回npos。

下面是在一个字符串中查找字符串“hat”的位置的代码:

image756_3

由于第二条搜索语句从位置2开始(That中的a),因此它找到的第一个hat位于字符串尾部。要测试是否失败,可使用string::npos值:

image756_4

第二个方法完成同样的工作,不过它使用字符数组而不是string对象作为子字符串:

size_type loc3 = longer.find ("is");      //sets loc3 to 5

第三个方法完成相同的工作,不过它只使用字符串s的前n个字符。这与使用basic_string (const charT *s,size_type n)构造函数,然后将得到的对象用作第一种格式的find()的string参数的效果完全相同。例如,下面的代码搜索子字符串“fun”:

size_type loc4 = longer.find ("funds", 3);   //sets loc4 to 10

第四个方法的功能与第一个相同,不过它使用一个字符而不是string对象作为子字符串:

size_type loc5 = longer.find ('a');     //sets loc5 to 2

F.5.2 rfind()系列

rfind()方法的原型如下:

image756_5

这些方法与相应find()方法的工作方式相似,不过它们搜索字符串最后一次出现的位置,该位置位于pos之前(包括pos)。如果没有找到,该方法将返回npos。

下面的代码从字符串末尾开始查找子字符串“hat”的位置:

image756_6

F.5.3 find_first_of()系列

find_first_of()方法的原型如下:

size_type find_first_of (const basic_string& str, size_type pos = 0) const;

image757_1

这些方法与对应find()方法的工作方式相似,不过它们不是搜索整个子字符串,而是搜索子字符串中的字符首次出现的位置。

image757_2

在longer中,首次出现的fluke中的字符是funny中的f,而首次出现的fat中的字符是That中的a

F.5.4 find_last_of()系列

find_last_of()方法的原型如下:

image757_3

这些方法与对应rfind()方法的工作方式相似,不过它们不是搜索整个子字符串,而是搜索子字符串中的字符出现的最后位置。

下面的代码在一个字符串中查找字符串“hat”和“any”中字母最后出现的位置:

image757_4

在longer中,最后出现的hat中的字符是hat中的t,而最后出现的any中的字符是hat中的a

F.5.5 find_first_not_of()系列

find_first_not_of()方法的原型如下:

image757_5

这些方法与对应find_first_of()方法的工作方式相似,不过它们搜索第一个不位于子字符串中的字符。

下面的代码在字符串中查找第一个没有出现在“This”和“Thatch”中的字母:

image757_6

在longer中,That中的a是第一个在This中没有出现的字符,而字符串longer中的第一个空格是第一个没有在Thatch中出现的字符。

F.5.6 find_last_not_of()系列

find last not of()方法的原型如下:

image757_7

这些方法与对应find_last_of()方法的工作方式相似,不过它们搜索的是最后一个没有在子字符串中出现的字符。

下面的代码在字符串中查找最后一个没有出现在“That”中的字符:

image758_1

在longer中,最后的空格是最后一个没有出现在shorter中的字符,而longer字符串中的f是搜索到位置10时,最后一个没有出现在shorter中的字符。

F.6 比较方法和函数

string类提供了用于比较2个字符串的方法和函数。下面是方法的原型:

image758_2

这些方法使用traits::compare()方法,后者是为用于字符串的字符类型定义的。如果根据traits:: compare()提供的顺序,第一个字符串位于第二个字符串之前,则第一个方法将返回一个小于0的值;如果这两个字符串相同,则它将返回0;如果第一个字符串位于第二个字符串的后面,则它将返回一个大于0的值。如果较长的字符串的前半部分与较短的字符串相同,则较短的字符串将位于较长的字符串之前。

image758_3

第二个方法与第一个方法相似,不过它进行比较时,只使用第一个字符串中从位置pos1开始的n1个字符。

下面的范例将字符串sl的前4个字符同字符串s2进行比较:

image758_4

第三个方法与第一个方法相似,不过它使用第一个字符串中从posl位置开始的n1个字符和第二个字符串中从pos2位置开始的n2个字符进行比较。例如,下面的语句将对stout中的out和about中的out进行比较:

image758_5

第四个方法与第一个方法相似,不过它将一个字符串数组而不是string对象作为第二个字符串。

第五个方法与第三个方法相似,不过它将一个字符串数组而不是string对象作为第二个字符串。

非成员比较函数是重载的关系操作符:

image758_6

每一个操作符都被重载,使之将string对象与string对象进行比较、将string对象与字符串数组进行比较、将字符串数组与string对象进行比较。它们都是根据compare()方法定义的,因此提供了一种在表示方面更为方便的比较方式。

F.7 字符串修改方法

string类提供了多个用于修改字符串的方法,其中绝大多数都拥有大量的重载版本,因此可用于string对象、字符串数组、单个字符和迭代器区间。

F.7.1 用于追加和相加的方法

可以使用重载的+=操作符或append()方法将一个字符串追加到另一个字符串的后面。如果得到的字符串长于最大字符串长度,将引发length_error异常。+=操作符使得能够将string对象、字符串数组或单个字符追加到string对象的后面:

image759_1

append()方法也使得能够将string对象、字符串数组或单个字符追加到string对象的后面。此外,通过指定初始位置和追加的字符数,或者通过指定区间,还可以追加string对象的一部分。通过指定要使用字符串中的多少个字符,可以追加字符串的一部分。追加字符的版本使得能够指定要复制该字符的多少个实例。下面是各种append()方法的原型:

image759_2

下面是几个范例:

image759_3

operator+()函数被重载,以便能够拼接字符串。该重载函数不修改字符串,而是创建一个新的字符串,该字符串是通过将第二个字符串追加到第一个字符串后面得到的。加法函数不是成员函数,它们使得能够将string对象和string对象、string对象和字符串数组、字符串数组和string对象、string对象和字符以及字符和string对象相加。下面是一些例子:

image759_4

F.7.2 其他赋值方法

除了基本的赋值操作符外,string类还提供了assign()方法,该方法使得能够将整个字符串、字符串的一部分或由相同字符组成的字符序列赋给string对象。下面是各种assign()方法的原型:

image759_5

下面是几个例子:

image760_1

F.7.3 插入方法

insert()方法使得能够将string对象、字符串数组或几个字符插入到string对象中。这个方法与append()方法相似,不过它还接受另一个指定插入位置的参数,该参数可以是位置,也可以是迭代器。数据将被插入到插入点的前面。有几种方法返回一个指向得到的字符串的引用。如果pos1超过了目标字符串结尾,或者pos2超过了要插入的字符串结尾,该方法将引发out_of_range异常。如果得到的字符串长于最大长度,该方法将引发length_error异常。下面是各种insert()方法的原型:

image760_2

例如,下面的代码将字符串“former”字符串插入到“The banker.”中b的前面:

image760_3

而下面的代码将字符串“waltzed”(不包括!,这是第9个字符)插入到“The former banker.”结尾的句号之前:

st3.insert (st3.size()- 1, " waltzed! ", 8);

F.7.4 清除方法

erase()方法从字符串中删除字符,其原型如下:

image760_4

第一种格式将从pos位置开始,删除n个字符或删除到字符串尾。第二种格式删除迭代器位置引用的字符,并返回指向下一个元素的迭代器;如果后面没有其他元素,则返回end()。第三种格式删除区间[first,last)中的字符,即从first(包括)到last(不包括)之间的字符;它返回最后一个迭代器,该迭代器指向最后一个被删除的元素后面的一个元素。

F.7.5 替换方法

各种replace()方法都指定了要替换的字符串部分和用于替换的内容。可以使用初始位置和字符数目或迭代器区间来指定要替换的部分。替换内容可以是string对象、字符串数组,也可以是特定字符的多个实例。对于用于替换的string对象和数组,可以通过指定特定部分(使用位置和计数或只使用计数)或迭代器区间做进一步的修改。下面是各种replace()方法的原型:

image760_5

image761_1

下面是一个例子:

image761_2

注意,您可以使用find()来找出要在replace()中使用的位置:

image761_3

上述代码将old替换为mature。

F.7.6 其他修改方法:copy()和swap()

copy()方法将string对象或其中的一部分复制到指定的字符串数组中:

size_type copy (charT* s, size_type n, size_type pos = 0) const;

其中,s指向目标数组,n是要复制的字符数,pos指出从string对象的什么位置开始复制。复制将一直进行下去,直到复制了n个字符或到达string对象的最后一个字符。函数返回复制的字符数,该方法不追加空值字符,同时由程序员负责检查数组的长度是否足够存储复制的内容。

警告:copy()方法不追加空值字符,也不检查目标数组的长度是否足够。

swap()方法使用一个恒定时间的算法来交换两个string对象的内容:

void swap (basic_string<charT, traits, Allocator>&);

F.8 输出和输入

string类重载了<<操作符来显示string对象,该操作符返回istream对象的引用,因此可以拼接输出:

image761_4

string类重载了>>操作符,使得能够将输入读入到字符串中:

image761_5

到达文件尾、读取字符串允许的最大字符数或遇到空白字符后,输入将终止(空白的定义取决于字符集以及charT表示的类型)。

有两个getline()函数,第一个的原型如下:

image761_6

这个函数将输入流is中的字符读入到字符串str中,直到遇到定界字符delim、到达文件尾或者到达字符串的最大长度。delim字符将被读取(从输入流中删除),但不被存储。第二个版本没有第三个参数,同时使用换行符(或其广义形式),而不是delim:

image761_7