3.2.3 处理string对象中的字符(1)
我们经常需要单独处理string对象中的字符,比如检查一个string对象是否包含空白,或者把string对象中的字母改成小写,再或者查看某个特定的字符是否出现等。
这类处理的一个关键问题是如何获取字符本身。有时需要处理string对象中的每一个字符,另外一些时候则只需处理某个特定的字符,还有些时候遇到某个条件处理就要停下来。以往的经验告诉我们,处理这些情况常常要涉及语言和库的很多方面。
另一个关键问题是要知道能改变某个字符的特性。处理在cctype头文件中定义了一组标准库函数处理,表3.3列出了主要的函数名及其含义。
表3.3:cctype头文件中的函数
|
表3.3:cctype头文件中的函数< xml:namespace prefix = o /> |
|
isalnum(c) |
当c是字母或数字时为真 |
|
isalpha(c) |
当c是字母时为真 |
|
iscntrl(c) |
当c是控制字符时为真 |
|
isdigit(c) |
当c是数字时为真 |
|
isgraph(c) |
当c不是空格但可打印时为真 |
|
islower(c) |
当c是小写字母时为真 |
|
isprint(c) |
当c是可打印字符时为真(即c是空格或c具有可视形式) |
|
ispunct(c) |
当c是标点符号时为真(即c不是控制字符、
数字、字母、可打印空白中的一种) |
|
isspace(c) |
当c是空白时为真(即c是空格、横向制表符、
纵向制表符、回车符、换行符、进纸符中的一种) |
|
isupper(c) |
当c是大写字母时为真 |
|
isxdigit(c) |
当c是十六进制数字时为真 |
|
tolower(c) |
如果c是大写字母,输出对应的小写字母;否则原样输出c |
|
toupper(c) |
如果c是小写字母,输出对应的大写字母;否则原样输出c |
建议:使用C++(www.cppentry.com)版本的C标准库头文件
C++(www.cppentry.com)标准库中除了定义C++(www.cppentry.com)语言特有的功能外,也兼容了C语言的标准库。C语言的头文件形如name.h,C++(www.cppentry.com)则将这些文件命名为cname。也就是去掉了.h后缀,而在文件名name之前添加了字母c,这里的c表示这是一个属于C语言标准库的头文件。
因此,cctype头文件和ctype.h头文件的内容是一样的,只不过从命名规范上来讲更符合C++(www.cppentry.com)语言的要求。特别的,在名为cname的头文件中定义的名字从属于命名空间std,而定义在名为.h的头文件中的则不然。
一般来说,C++(www.cppentry.com)程序应该使用名为cname的头文件而不使用name.h的形式,标准库中的名字总能在命名空间std中找到。如果使用.h形式的头文件,程序员就不得不时刻牢记哪些是从C语言那儿继承过来的,哪些又是C++(www.cppentry.com)语言所独有的。
处理每个字符?使用基于范围的for语句
如果想对string对象中的每个字符做点儿什么操作,目前最好的办法是使用C++(www.cppentry.com)11新标准提供的一种语句:范围for(range for)语句。这种语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作,其语法形式是:
- for (declaration : expression)
- statement
其中,expression部分是一个对象,用于表示一个序列。declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素。每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值。
一个string对象表示一个字符的序列,因此string对象可以作为范围for语句中的expression部分。举一个简单的例子,我们可以使用范围for语句把string对象中的字符每行一个输出出来:
- string str("some string");
- // 每行输出str中的一个字符。
- for (auto c : str) // 对于str中的每个字符
- cout << c << endl; // 输出当前字符,后面紧跟一个换行符
for循环把变量c和str联系了起来,其中我们定义循环控制变量的方式与定义任意一个普通变量是一样的。此例中,通过使用auto关键字(参见2.5.2节,第68页)让编译器来决定变量c的类型,这里c的类型是char。每次迭代,str的下一个字符被拷贝给c,因此该循环可以读作"对于字符串str中的每个字符c,"执行某某操作。此例中的"某某操作"即输出一个字符,然后换行。
举个稍微复杂一点的例子,使用范围for语句和ispunct函数来统计string对象中标点符号的个数:
- string s("Hello World!!!");
- // punct_cnt的类型和s.size的返回类型一样;参见2.5.3节(第70页)
- decltype(s.size()) punct_cnt = 0;
- //统计s中标点符号的数量
- for (auto c : s) //对于s中的每个字符
- if (ispunct(c)) //如果该字符是标点符号
- ++punct_cnt; //将标点符号的计数值加1
- cout << punct_cnt
- << " punctuation characters in " << s << endl;
程序的输出结果将是:
- 3 punctuation characters in Hello World!!!
这里我们使用decltype关键字(参见2.5.3节,第70页)声明计数变量punct_cnt,它的类型是s.size函数返回值的类型,也就是string::size_type。使用范围for语句处理string对象中的每个字符并检查其是否是标点符号。如果是,使用递增运算符(参见1.4.1节,第12页)给计数变量加1。最后,待范围for语句结束后输出统计结果。
使用范围for语句改变字符串中的字符
如果想要改变string对象中字符的值,必须把循环变量定义成引用类型(参见2.3.1节,第50页)。记住,所谓引用只是给定对象的一个别名,因此当使用引用作为循环控制变量时,这个变量实际上被依次绑定到了序列的每个元素上。使用这个引用,我们就能改变它绑定的字符。
新的例子不再是统计标点符号的个数了,假设我们想要把字符串改写为大写字母的形式。为了做到这一点可以使用标准库函数toupper,该函数接收一个字符,然后输出其对应的大写形式。这样,为了把整个string对象转换成大写,只要对其中的每个字符调用toupper函数并将结果再赋给原字符就可以了:
- string s("Hello World!!!");
- // 转换成大写形式。
- for (auto &c : s) // 对于s中的每个字符(注意:c是引用)
- c = toupper(c); // c是一个引用,因此赋值语句将改变s中字符的值
- cout << s << endl;