第16章 STL string类
标准模板库(STL)向程序员提供了一个用于字符串操作的容器类。string类不仅能够根据应用程序的需求动态调整大小,还提供了很有用的辅助函数(方法),可帮助操作字符串,这让程序员能够在应用程序中使用标准的、经过测试的可移植功能,并将其主要精力放在开发应用程序的重要功能上。
在本章中,您将学习:
• 为何需要字符串操作类;
• 如何使用STL string类;
• STL如何帮助您轻松地执行拼接、附加、查找以及其他字符串操作;
• 如何使用基于模板的STL string实现。
16.1 为何需要字符串操作类
在C++中,字符串是一个字符数组。第4章介绍过,最简单的字符数组可这样定义:
该语句声明了一个名为 staticName 的字符数组(也叫字符串),其长度是固定的(静态的),包含20个元素。可以看到,这个缓冲区可存储一个长度有限的字符串,如果试图存储的字符数超出限制将溢出。不能调整静态数组的长度,为避开这种限制,C++支持动态分配内存,因此可以如下定义更动态的字符数组:
这定义了一个动态分配的字符数组,其长度由变量ArrayLength的值指定,而这种值是在运行阶段确定的,因此该数组的长度是可变的。然而,如果要在运行阶段改变数组的长度,必须首先释放以前分配给它的内存,再重新分配内存来存储数据。
如果将char*用作类的成员属性,情况将更复杂。将对象赋给另一个对象时,如果编写正确的复制构造函数和赋值运算符,两个对象将包含同一个指针的拷贝,该指针指向相同的缓冲区。其结果是,两个对象的字符串指针存储的地址相同,指向同一个内存单元。其中一个对象被销毁时,另一个对象中的指针将非法,让应用程序面临崩溃的危险。
字符串类帮您解决了这些问题。STL 字符串类std::string和std::wstring分别模拟了普通字符串和宽字符串,可提供如下帮助:
• 减少了程序员在创建和操作字符串方面需要做的工作;
• 在内部管理内存分配细节,从而提高了应用程序的稳定性;
• 提供了复制构造函数和赋值运算符,可确保成员字符串得以正确复制;
• 提供了帮助执行复制、截短、查找和删除等操作的实用函数;
• 提供了用于比较的运算符;
• 让程序员能够将精力放在应用程序的主要需求而不是字符串操作细节上。
std::string和std::wstring实际上是同一个模板类(std::basic_string<T>)的具体化,即分别针对类型char和wchar的具体化。学习使用其中一个后,就能使用这些方法和运算符用于另一个。
稍后将以std::string为例,介绍STL字符串类提供的一些辅助函数。
16.2 使用STL string类
最常用的字符串函数包括:
• 复制;
• 连接;
• 查找字符和子字符串;
• 截短;
• 使用标准模板库提供的算法实现字符串反转和大小写转换。
要使用STL string类,必须包含头文件<string>。
string类提供了很多重载的构造函数,因此可以多种方式进行实例化和初始化。例如,可使用常量字符串初始化STL string对象或将常量字符串赋给STL string对象:
或:
上述代码与下面的代码类似:
显然,实例化并初始化 string对象时,无需关心字符串长度和内存分配细节。STL string类的构造函数将自动完成这些工作。
同样,可使用一个string对象来初始化另一个:
可让string的构造函数只接受输入字符串的前n个字符:
还可这样初始化string对象,即使其包含指定数量的特定字符:
程序清单 16.1演示了一些实例化和复制STL string的常见方法。
程序清单16.1 实例化和复制STL string的方法
输出:
分析:
上述示例代码演示了如何实例化 STL string对象,还演示了如何将STL string对象初始化为另一个字符串、字符串的一部分或多个相同的字符。ConstCStyleString 是一个包含示例值的 C 风格字符串,它是第6行初始化的。从第9行可知,使用std::string的构造函数进行复制非常简单。第12行将另一个常量字符串复制给std::string对象str2,第13行演示了如何使用std::string的另一个重载构造函数来复制std::string对象,从而获得str2Copy。第17行演示了如何进行部分复制。第21行演示如何实例化一个std::string对象,并将其初始化为包含多个相同的字符。这段代码只是一个小型演示程序,演示了通过使用std::string及其众多复制构造函数,创建、复制和显示字符串很容易。
如果要将一个C风格字符串复制到另一个C风格字符串中,程序清单16.1的第9行代码将为:
可以看到,这需要更多的代码,导致错误的可能性也更大,同时程序员还需要负责管理内存的分配与释放。这些工作STL string都能完成,还能完成其他工作!
要访问STL string的字符内容,可使用迭代器,也可采用类似于数组的语法并使用下标运算符([])提供偏移量。要获得 string对象的C风格表示,可使用成员函数 c_str (),如程序清单 16.2所示。
程序清单16.2 两种访问STL string字符元素的方式:运算符[ ]和迭代器
输出:
分析:
上述代码演示了访问string内容的多种方式。迭代器很重要,因为很多string成员函数都以迭代器的方式返回其结果。第12~18行使用std::string类实现的下标运算符([])以类似数组的语法显示string中的字符。注意,这个运算符要求提供偏移量,如第17行所示。因此,确保不超出string的边界很重要,即读取字符时,提供的偏移量不能大于string的长度。第25~31行也逐字符显示string的内容,但使用的是迭代器。
要拼接字符串,可使用运算符+=,也可使用成员函数append:
程序清单16.3演示了这两种方式。
程序清单16.3 使用加法赋值运算符(+=)或方法append()拼接字符串
输出:
分析:
第 11、15和 19行演示了各种拼接STL string的方法。请注意运算符+=的用法以及 append函数的功能。append函数有多个重载版本,能够接受另一个string对象(如第15行所示),还能接受一个C风格字符串。
STL string类提供了成员函数 find,该函数有多个重载版本,可在给定 string对象中查找字符或子字符串。
程序清单16.4演示了std::string::find的用法。
程序清单16.4 使用string::find()查找子字符串或字符
输出:
分析:
第12~18行演示了find函数的最简单用法,它判断在string中是否找到了特定子字符串。这是通过将find操作的结果与std::string::npos(实际值为−1)进行比较实现的,std::string::npos表明没有找到要搜索的元素。如果find函数没有返回npos,它将返回一个偏移量,指出子字符串或字符在string中的位置。
这段代码演示了如何在while循环中使用 find函数在STL string查找指定字符或子字符串的所有实例。这里使用的find函数的重载版本接受两个参数:要搜索的子字符串或字符,以及命令find从哪里开始搜索的偏移量。
STL string 还有一些与 find()类似的函数,如 find_first_of()、find_first_not_of()、find_last_of()和find_last_not_of(),这些函数可满足程序员的其他编程需求。
STL string类提供了 erase函数,可用于:
• 在给定偏移位置和字符数时删除指定数目的字符;
• 在给定指向字符的迭代器时删除该字符;
• 在给定由两个迭代器指定的范围时删除该范围内的字符。
程序清单16.5的示例演示了string::erase()函数的各种重载版本的用途。
程序清单16.5 使用string::erase从指定偏移位置或迭代器指定的位置开始截短字符串
输出:
分析:
该程序清单演示erase函数的3个版本。其中一个版本在给定偏移位置和字符数的情况下删除指定数目的字符,如第 14 行所示;另一个版本在给定指向字符的迭代器的情况下删除指定的字符,如第24 行所示;最后一个版本在给定由两个迭代器指定的范围的情况下删除该范围内的字符,如第 30 行所示。在这里,范围是由string的成员函数begin()和end()指定的,它包含字符串的所有内容,因此对该范围调用erase()将清除string对象的全部内容。注意,string类还提供了clear()函数,该函数清除全部内容并重置string对象。
C++11
使用auto简化冗长的迭代器声明
对于程序清单16.5中冗长的迭代器声明,C++11可帮助简化:
为此,可使用第3章介绍的关键字auto:
编译器将根据std::find的返回类型自动推断变量iCharS的类型。
有时需要反转字符串的内容。假设要判断用户输入的字符串是否为回文,方法之一是将其反转,再与原来的字符串进行比较。反转STL string很容易,只需使用泛型算法 std::reverse:
程序清单16.6演示了如何将算法std::reverse用于std::string。
程序清单16.6 使用 std::reverse反转STL string
输出:
分析:
第12行的std::reverse算法根据两个输入参数指定的边界反转边界内的内容。在这里,两个边界分别是 string 对象的开头和末尾,因此整个字符串都被反转。只要提供合适的输入参数,也可将字符串的一部分反转。注意,边界不能超过end()。
要对字符串进行大小写转换,可使用算法 std::transform,它对集合中的每个元素执行一个用户指定的函数。在这里,集合是string对象本身。程序清单16.7演示了如何对string中的字符进行大小写转换。
程序清单16.7 使用 std::transform将STL string转换为大写
输出:
分析:
第 15行和第 19行演示了如何使用 std::transform来改变STL string的大小写。
16.3 基于模板的STL string实现
前面说过,std::string类实际上是 STL模板类 std::basic_string <T>的具体化。容器类 basic_string的模板声明如下:
在该模板定义中,最重要的参数是第一个:_Elem,它指定了basic_string对象将存储的数据类型。因此,std::string使用_Elem=char具体化模板 basic_string的结果,而wstring使用_Elem= wchar具体化模板basic_string的结果。
换句话说,STL string类的定义如下:
而STL wstring类的定义如下:
因此,前面介绍的所有 string功能和函数实际上都是 basic_string提供的,它们也适用于 STL wstring类。
如果编写的应用程序需要更好地支持非拉丁字符,如中文和日文,应使用std::wstring。
16.4 总结
本章介绍了 STL string类,它是标准模板库提供的一个容器,可满足程序员众多的字符串操作需求。使用这个类的优点是,原本由程序员负责的内存管理、字符串比较和字符串操作都将由STL框架提供的一个容器类完成。
16.5 问与答
问:我要使用std::reverse来反转一个字符串,要使用这个函数,需要包含哪个头文件?
答:要使用std::reverse ,需要包含头文件<algorithm>。
问:使用tolower()函数将字符串转换为小写时,std::transform的作用是什么?
答:std::transform对string对象中指定边界内的每个字符调用tolower()函数。
问:为什么std::wstring和std::string的行为和成员函数完全相同?
答:因为它们都是具体化模板类std::basic_string的结果。
问:STL string类的比较运算符<在执行比较时是否区分大小写?
答:区分大小写。
16.6 作业
作业包括测验和练习,前者帮助读者加深对所学知识的理解,后者提供了使用新学知识的机会。请尽量先完成测验和练习题,然后再对照附录 D 的答案。在继续学习下一章前,请务必弄懂这些答案。
1.std::string具体化了哪个STL模板类?
2.如果要对两个字符串进行区分大小写的比较,该如何做?
3.STL string与C风格字符串是否类似?
1.编写一个程序检查用户输入的单词是否为回文。例如,ATOYOTA是回文,因为该单词反转后与原来相同。
2.编写一个程序,告诉用户输入的句子包含多少个元音字母。
3.将字符串的字符交替地转换为大写。
4.编写一个程序,将4个string对象分别初始化为I、Love、STL和String,然后在这些字符串之间添加空格,再显示整个句子。