附录F string模板类
本附录的技术性较高,但如果读者指向了解模板类string的功能,可以将重点放在对各种string类方法的描述上。
string类是基于下述模板定义的:
其中,chatT是存储在字符串中的类型;traits参数是一个类,它定义了类型要被表示为字符串时,所必须具备的特征。例如,它必须有length()方法,该方法返回被表示为charT数组的字符串的长度。这种数组结尾用charT (0)值(广义的空值字符)表示(表达式charT (0)将0转换为charT类型。它可以像类型为char时那样为零,也可以是charT的一个构造函数创建的对象)。这个类还包含用于对值进行比较等操作的方法。Allocator参数是用于处理字符串内存分配的类。默认的allocator<char>模板按标准方式使用new和delete。
有两种预定义的具体化:
而上述具体化又使用下面的具体化:
除char和wchar_t外,还可以通过定义traits类和使用basic_string模板来为其他一些类型创建一个string类。
F.1 13种类型和一个常量
basic_string模板定义了几种类型,供以后定义方法时使用:
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)算法用于字符串,该模板定义了一些迭代器类型:
该模板还定义了一个静态常量:
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()函数来反转字符串的内容:
而data()和c_str()方法返回常规指针。另外,返回的指针将指向存储字符串字符的数组的第一个元素。该数组可能(但不一定)是string对象管理的字符串的副本(string对象采用的内部表示可以是数组,但不一定非得是数组)。由于返回的指针可能指向原始数据,而原始数据是const,因此不能用它们来修改数据。另外,当字符串被修改后,将不能保证这些指针是有效的,这表明它们可能指向原始数据。data()和c_str()的区别在于,c_str()指向的数组以空值字符(或与之等价的其他字符)结束,而data()只是确保实际的字符串字符是存在的。因此,c_str()方法期望接受一个C-风格字符串参数:
同样,data()和size()可用作这种函数的参数,即接受指向数组元素的指针和表示要处理的元素数目的值:
C++实现可能将string对象的字符串表示为动态分配的C-风格字符串,并使用char*指针来实现正向迭代器。在这种情况下,实现可能让begin()、data()和c_str()都返回同样的指针,但返回指向3个不同的数据对象的引用也是合法的(虽然更复杂)。
下面是basic_string模板类的6个构造函数和1个析构函数:
这6个构造函数都有1个下面这样的参数:
const Allocator& a = Allocator()
Allocator是用于管理内存的allocator类的模板参数名;Allocator()是这个类的默认构造函数。因此,在默认情况下,构造函数将使用allocator对象的默认版本,但它们使得能够选择使用allocator对象的其他版本。下面分别介绍这些构造函数。
F.2.1 默认构造函数
默认构造函数的原型如下:
explicit basic_string (const Allocator& a = Allocator ());
通常,接受allocator类的默认参数,并使用构造函数来创建空字符串:
该构造函数被调用后,将存在下面的关系:
● 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 复制构造函数
复制构造函数提供了多个有默认值的参数:
调用复制构造函数时如果只提供一个basic_string参数,新对象将被初始化为该string参数:
其中,ida将是mel管理的字符串副本。
第二个可选参数pos指定了源字符串中的位置,将从这个位置开始进行复制:
位置编号从0开始,因此,位置4是字符p。所以,et被初始化为“Telephone home”。
第3个可选参数n指定要复制的最大字符数目,因此下面的语句:
将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个副本的构造函数创建一个由n个c组成的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-风格的区间:
begin迭代器指向源字符串中要复制的第一个元素,end指向要复制的最后一个元素的后面。
这种构造函数可用于数组、字符串或STL容器:
在第一种用法中,Inputlterator的类型为const char *;在第二种用法中,Inputlterator的类型为vector<char>:: iterator。
调用该构造函数后,将存在下面的关系:
● data()方法返回一个指向字符串的第一个元素的指针,该字符串是通过复制区间[begin,end)中的元素得到的。
● size()方法返回begin到end之间的距离(度量距离时,使用的单位为对迭代器解除引用得到的数据类型的长度)。
● capacity()方法返回一个不小于size()的值。
F.2.7 内存杂记
一些方法用于处理内存,如清除内存的内容、调整字符串长度或容量。表F.2列出了一些与内存相关的方法。
表F.2 一些与内存有关的方法
F.3 字符串存取
有4种方法可以访问各个字符,其中两种方法使用[]操作符,另外两种方法使用at()方法:
第一个operator方法使得能够使用数组表示法来访问字符串的元素,可用于检索或更改值。第二个operator方法可用于const对象,只能用于检索值:
at()方法提供了相似的访问功能,只是索引是通过函数参数提供的:
差别在于(除句法差别外):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”:
F.4 基本赋值
有3种重载的赋值方法:
第一种方法将一个string对象赋给另一个;第二种方法将C风格的字符串赋给string对象;第三种方法将一个字符赋给string对象。因此,下面的操作都是可能的:
F.5 字符串搜索
string类提供了6种搜索函数,其中每个函数都有4个原型。下面简要地介绍它们。
F.5.1 find()系列
find()的原型如下:
第一个返回str在调用对象中第一次出现时的起始位置。搜索从pos开始,如果没有找到子字符串,将返回npos。
下面是在一个字符串中查找字符串“hat”的位置的代码:
由于第二条搜索语句从位置2开始(That中的a),因此它找到的第一个hat位于字符串尾部。要测试是否失败,可使用string::npos值:
第二个方法完成同样的工作,不过它使用字符数组而不是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()方法的原型如下:
这些方法与相应find()方法的工作方式相似,不过它们搜索字符串最后一次出现的位置,该位置位于pos之前(包括pos)。如果没有找到,该方法将返回npos。
下面的代码从字符串末尾开始查找子字符串“hat”的位置:
F.5.3 find_first_of()系列
find_first_of()方法的原型如下:
size_type find_first_of (const basic_string& str, size_type pos = 0) const;
这些方法与对应find()方法的工作方式相似,不过它们不是搜索整个子字符串,而是搜索子字符串中的字符首次出现的位置。
在longer中,首次出现的fluke中的字符是funny中的f,而首次出现的fat中的字符是That中的a。
F.5.4 find_last_of()系列
find_last_of()方法的原型如下:
这些方法与对应rfind()方法的工作方式相似,不过它们不是搜索整个子字符串,而是搜索子字符串中的字符出现的最后位置。
下面的代码在一个字符串中查找字符串“hat”和“any”中字母最后出现的位置:
在longer中,最后出现的hat中的字符是hat中的t,而最后出现的any中的字符是hat中的a。
F.5.5 find_first_not_of()系列
find_first_not_of()方法的原型如下:
这些方法与对应find_first_of()方法的工作方式相似,不过它们搜索第一个不位于子字符串中的字符。
下面的代码在字符串中查找第一个没有出现在“This”和“Thatch”中的字母:
在longer中,That中的a是第一个在This中没有出现的字符,而字符串longer中的第一个空格是第一个没有在Thatch中出现的字符。
F.5.6 find_last_not_of()系列
find last not of()方法的原型如下:
这些方法与对应find_last_of()方法的工作方式相似,不过它们搜索的是最后一个没有在子字符串中出现的字符。
下面的代码在字符串中查找最后一个没有出现在“That”中的字符:
在longer中,最后的空格是最后一个没有出现在shorter中的字符,而longer字符串中的f是搜索到位置10时,最后一个没有出现在shorter中的字符。
F.6 比较方法和函数
string类提供了用于比较2个字符串的方法和函数。下面是方法的原型:
这些方法使用traits::compare()方法,后者是为用于字符串的字符类型定义的。如果根据traits:: compare()提供的顺序,第一个字符串位于第二个字符串之前,则第一个方法将返回一个小于0的值;如果这两个字符串相同,则它将返回0;如果第一个字符串位于第二个字符串的后面,则它将返回一个大于0的值。如果较长的字符串的前半部分与较短的字符串相同,则较短的字符串将位于较长的字符串之前。
第二个方法与第一个方法相似,不过它进行比较时,只使用第一个字符串中从位置pos1开始的n1个字符。
下面的范例将字符串sl的前4个字符同字符串s2进行比较:
第三个方法与第一个方法相似,不过它使用第一个字符串中从posl位置开始的n1个字符和第二个字符串中从pos2位置开始的n2个字符进行比较。例如,下面的语句将对stout中的out和about中的out进行比较:
第四个方法与第一个方法相似,不过它将一个字符串数组而不是string对象作为第二个字符串。
第五个方法与第三个方法相似,不过它将一个字符串数组而不是string对象作为第二个字符串。
非成员比较函数是重载的关系操作符:
每一个操作符都被重载,使之将string对象与string对象进行比较、将string对象与字符串数组进行比较、将字符串数组与string对象进行比较。它们都是根据compare()方法定义的,因此提供了一种在表示方面更为方便的比较方式。
F.7 字符串修改方法
string类提供了多个用于修改字符串的方法,其中绝大多数都拥有大量的重载版本,因此可用于string对象、字符串数组、单个字符和迭代器区间。
F.7.1 用于追加和相加的方法
可以使用重载的+=操作符或append()方法将一个字符串追加到另一个字符串的后面。如果得到的字符串长于最大字符串长度,将引发length_error异常。+=操作符使得能够将string对象、字符串数组或单个字符追加到string对象的后面:
append()方法也使得能够将string对象、字符串数组或单个字符追加到string对象的后面。此外,通过指定初始位置和追加的字符数,或者通过指定区间,还可以追加string对象的一部分。通过指定要使用字符串中的多少个字符,可以追加字符串的一部分。追加字符的版本使得能够指定要复制该字符的多少个实例。下面是各种append()方法的原型:
下面是几个范例:
operator+()函数被重载,以便能够拼接字符串。该重载函数不修改字符串,而是创建一个新的字符串,该字符串是通过将第二个字符串追加到第一个字符串后面得到的。加法函数不是成员函数,它们使得能够将string对象和string对象、string对象和字符串数组、字符串数组和string对象、string对象和字符以及字符和string对象相加。下面是一些例子:
F.7.2 其他赋值方法
除了基本的赋值操作符外,string类还提供了assign()方法,该方法使得能够将整个字符串、字符串的一部分或由相同字符组成的字符序列赋给string对象。下面是各种assign()方法的原型:
F.7.3 插入方法
insert()方法使得能够将string对象、字符串数组或几个字符插入到string对象中。这个方法与append()方法相似,不过它还接受另一个指定插入位置的参数,该参数可以是位置,也可以是迭代器。数据将被插入到插入点的前面。有几种方法返回一个指向得到的字符串的引用。如果pos1超过了目标字符串结尾,或者pos2超过了要插入的字符串结尾,该方法将引发out_of_range异常。如果得到的字符串长于最大长度,该方法将引发length_error异常。下面是各种insert()方法的原型:
例如,下面的代码将字符串“former”字符串插入到“The banker.”中b的前面:
而下面的代码将字符串“waltzed”(不包括!,这是第9个字符)插入到“The former banker.”结尾的句号之前:
st3.insert (st3.size()- 1, " waltzed! ", 8);
F.7.4 清除方法
erase()方法从字符串中删除字符,其原型如下:
第一种格式将从pos位置开始,删除n个字符或删除到字符串尾。第二种格式删除迭代器位置引用的字符,并返回指向下一个元素的迭代器;如果后面没有其他元素,则返回end()。第三种格式删除区间[first,last)中的字符,即从first(包括)到last(不包括)之间的字符;它返回最后一个迭代器,该迭代器指向最后一个被删除的元素后面的一个元素。
F.7.5 替换方法
各种replace()方法都指定了要替换的字符串部分和用于替换的内容。可以使用初始位置和字符数目或迭代器区间来指定要替换的部分。替换内容可以是string对象、字符串数组,也可以是特定字符的多个实例。对于用于替换的string对象和数组,可以通过指定特定部分(使用位置和计数或只使用计数)或迭代器区间做进一步的修改。下面是各种replace()方法的原型:
下面是一个例子:
注意,您可以使用find()来找出要在replace()中使用的位置:
上述代码将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对象的引用,因此可以拼接输出:
string类重载了>>操作符,使得能够将输入读入到字符串中:
到达文件尾、读取字符串允许的最大字符数或遇到空白字符后,输入将终止(空白的定义取决于字符集以及charT表示的类型)。
有两个getline()函数,第一个的原型如下:
这个函数将输入流is中的字符读入到字符串str中,直到遇到定界字符delim、到达文件尾或者到达字符串的最大长度。delim字符将被读取(从输入流中删除),但不被存储。第二个版本没有第三个参数,同时使用换行符(或其广义形式),而不是delim: