1 字符串字面值与C风格字符串
在C++中,双引号括起来的零个或多个字符构成字符串字面值,如
“Hello World”
字符串字面值的类型实际上是由常量构成的数组,编译器在每个字符串的结尾处添加一个空字符(’\0’),因此字符串字面值的实际长度要比它的内容多1。
字符串字面值是一种通用结构的实例,这种结构即是C++由C继承而来的C风格字符串。C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。按此习惯书写的字符串存放在字符数组中,并以空字符结束。
2 字符数组
字符数组的详细解释请参考《C++中字符数组》。可以使用字符串字面值对字符数组进行初始化。
char a[] = “C++”;
在“1字符串字面值与C风格字符串”中提到,字符串字面值的结尾还包含一个空字符,此时数组a的维度是4。
3 操作C风格字符串的函数
处理C风格字符串的函数有strlen()、strcmp()、strcat()以及strcpy()等。这些函数在cstring头文件中定义,cstring是C语言头文件string.h的C++b版本,因此,在使用这些函数时,应该包含cstring头文件。
#include <cstring>
4 实战
4.1 问题提出
在CSDN论坛上有朋友提到如下问题:
char *src = "Danny"; const char *addition = "Andy"; strcat(src, addition);
程序报错,而如果将程序改为
char src[] = "Danny"; const char *addition = "Andy"; strcat(src, addition);
则不报错。
4.2 问题分析
4.2.1 内存结构
C++程序在内存中主要分为五个数据段,分别是代码段、数据段、BBS段、堆和栈。其中,数据段中保存的是全局变量和静态变量,字符串字面值也保存在数据段中;而栈中保存的是程序临时创建的局部变量。
4.1.2 处理数据段中的数据
对于当strcat()函数的两个参数都是字符指针时的情况,此时两个字符指针均指向保存在数据段中的字符串字面值,如图1所示。
图1 src和addition的内容
从图1中可以看出,字符串字面值”Danny”保存在0x00347838中,而字符串字面值”Andy”保存在0x00347830中。之后通过strcat()函数将”Andy”附加到”Danny”之后,实际上是对程序的数据段进行操作,而程序的数据端是不允许写入的,因此报错信息是“写入位置时发生访问冲突”,如图2所示。
图2 报错信息
4.2.2 处理栈中的数据
如果将src定义为字符数组,则此时src保存在栈中,程序允许对栈中的数据进行修改。src的地址如图3所示。
图3 src和addition的内容
从图3中可以看出,src的地址是在0x007bf7ec处,该地址位于程序的栈中;而addition的地址是在0x002f7830处,该地址位于程序的数据段中。
此时,虽然strcat()函数不再报错,但是当main()函数返回时,还会弹出栈被破坏的信息,如图4所示。
图4 栈被破坏信息
正如“2 字符数组”中提到,数组src的维数是6,使用strcat()函数,将长度为5的字符串字面值“Andy”附加到长度为6的src的后面,那么肯定覆盖了程序堆中的其他数据,因此会弹出堆被破坏的信息。
4.3 问题解决
4.3.1 加大strcat()源维数
如果使用处理字符串字面值的函数strcat(),则需要将源即src的数据维数变大,使其大于等于“DannyAndy” 的长度,即不小于10。例如
char src[11] = "Danny";
4.3.2 使用C++的string类
标准库类型string表示可变长度的字符序列,使用string类型必须首先包含string头文件。
#include <string> string src = "Danny"; string addition = "Andy"; src += addition;