3.5.4 C风格字符串
尽管C++(www.cppentry.com)支持C风格字符串,但在C++(www.cppentry.com)程序中最好还是不要使用它们。这是因为C风格字符串不仅使用起来不太方便,而且极易引发程序漏洞,是诸多安全问题的根本原因。
字符串字面值是一种通用结构的实例,这种结构即是C++(www.cppentry.com)由C继承而来的C风格字符串(C-style character string)。C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。按此习惯书写的字符串存放在字符数组中并以空字符结束(null terminated)。以空字符结束的意思是在字符串最后一个字符后面跟着一个空字符('\0')。一般利用指针来操作这些字符串。
C标准库String函数
表3.8列举了C语言标准库提供的一组函数,这些函数可用于操作C风格字符串,它们定义在cstring头文件中,cstring是C语言头文件string.h的C++(www.cppentry.com)版本。
表3.8:C风格字符串的函数
|
表3.8:C风格字符串的函数
|
|
strlen(p)
|
返回p的长度,空字符不计算在内
|
|
strcmp(p1, p2)
|
比较p1和p2的相等性。如果p1==p2,
返回0;如果p1>p2,返回一个正值;
如果p1<p2,返回一个负值
|
|
strcat(p1, p2)
|
将p2附加到p1之后,返回p1
|
|
strcpy(p1, p2)
|
将p2拷贝给p1,返回p1
|
表3.8所列的函数不负责验证其字符串参数。
传入此类函数的指针必须指向以空字符作为结束的数组:
- char ca[] = {'C', '+', '+'}; // 不以空字符结束
- cout << strlen(ca) << endl; // 严重错误:ca没有以空字符结束
此例中,ca虽然也是一个字符数组但它不是以空字符作为结束的,因此上述程序将产生未定义的结果。strlen函数将有可能沿着ca在内存中的位置不断向前寻找,直到遇到空字符才停下来。
比较字符串
比较两个C风格字符串的方法和之前学习过的比较标准库string对象的方法大相径庭。比较标准库string对象的时候,用的是普通的关系运算符和相等性运算符:
- string s1 = "A string example";
- string s2 = "A different string";
- if (s1 < s2) // false:s2小于s1
如果把这些运算符用在两个C风格字符串上,实际比较的将是指针而非字符串本身:
- const char ca1[] = "A string example";
- const char ca2[] = "A different string";
- if (ca1 < ca2) // 未定义的:试图比较两个无关地址
谨记之前介绍过的,当使用数组的时候其实真正用的是指向数组首元素的指针(参见3.5.3节,第117页)。因此,上面的if条件实际上比较的是两个const char*的值。这两个指针指向的并非同一对象,所以将得到未定义的结果。
要想比较两个C风格字符串需要调用strcmp函数,此时比较的就不再是指针了。如果两个字符串相等,strcmp返回0;如果前面的字符串较大,返回正值;如果后面的字符串较大,返回负值:
- if (strcmp(ca1, ca2) < 0) // 和两个string对象的比较 s1 < s2效果一样
目标字符串的大小由调用者指定
连接或拷贝C风格字符串也与标准库string对象的同类操作差别很大。例如,要想把刚刚定义的那两个string对象s1和s2连接起来,可以直接写成下面的形式:
- // 将largeStr初始化成s1、一个空格和s2的连接
- string largeStr = s1 + " " + s2;
同样的操作如果放到ca1和ca2这两个数组身上就会产生错误了。表达式ca1 + ca2试图将两个指针相加,显然这样的操作没什么意义,也肯定是非法的。
正确的方法是使用strcat函数和strcpy函数。不过要想使用这两个函数,还必须提供一个用于存放结果字符串的数组,该数组必须足够大以便容纳下结果字符串及末尾的空字符。下面的代码虽然很常见,但是充满了安全风险,极易引发严重错误:
- // 如果我们计算错了largeStr的大小将引发严重错误
- strcpy(largeStr, ca1); // 把ca1拷贝给largeStr
- strcat(largeStr, " "); // 在largeStr的末尾加上一个空格
- strcat(largeStr, ca2); // 把ca2连接到largeStr后面
一个潜在的问题是,我们在估算largeStr所需的空间时不容易估准,而且largeStr所存的内容一旦改变,就必须重新检查其空间是否足够。不幸的是,这样的代码到处都是,程序员根本没法照顾周全。这类代码充满了风险而且经常导致严重的安全泄漏。
对大多数应用来说,使用标准库string要比使用C风格字符串更安全、更高效。
3.5.4节练习
练习3.37:下面的程序是何含义,程序的输出结果是什么?
- const char ca[] = {’h’, ’e’, ’l’, ’l’, ’o’};
- const char *cp = ca;
- while (*cp) {
- cout << *cp << endl;
- ++cp;
- }
练习3.38:在本节中我们提到,将两个指针相加不但是非法的,而且也没什么意义。请问为什么两个指针相加没什么意义?
练习3.39:编写一段程序,比较两个string对象。再编写一段程序,比较两个C风格字符串的内容。
练习3.40:编写一段程序,定义两个字符数组并用字符串字面值初始化它们;接着再定义一个字符数组存放前两个数组连接后的结果。使用strcpy和strcat把前两个数组的内容拷贝到第三个数组中。