首页 > 代码库 > C风格字符串
C风格字符串
尽管C++支持C风格字符串,但在C++程序中最好还是不要使用它们。这是因为C风格字符串不仅使用起来不太方便,而且极易引发程序漏洞,是诸多安全问题的根本原因。
字符串字面值是一种通用结构的实例,这种结构即是C++由C继承而来的C风格字符串。C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。按此习惯书写的字符串存放在字符数组中并以空字符串结束。以空字符结束的意思是在字符串最后一个字符后面跟着一个空字符(‘\0‘)。一般利用指针来操作这些字符串。
C标准库String函数
下表列出了C语言标准库提供的一组函数,这些函数可用于操作C风格字符串,它们定义在出string头文件中,出string是C语言头文件string.h的C++版本。
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 |
传入此类函数的指针必须指向以空字符作为结束的数组:
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) //未定义的:试图比较两个无关地址
谨记之前介绍过的,当使用数组的时候其实真正用的是指向数组首元素的指针。因此,上面的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后面
混用string对象和C风格字符串
允许使用字符串字面值来初始化string对象:
string s("Hello World!"); //s的内容是Hello World
更一般的情况是,任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代:
- 允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值。
- 在string对象的加法运算中允许使用以空字符结束的字符数组作为其中一个运算对象(不是两个运算对象都是):在string对象的复合赋值运算中允许使用以空字符数组作为右侧的运算对象。
上述性质反过来就不成立了:如果程序的某处需要一个C风格字符串,无法直接用string对象来代替它。例如,不能用string对象直接初始化指向字符的指针。为了完成该功能,string专门提供了一个名为c_str的成员函数:
char *str=s;//错误:不能用string对象初始化char*
const char *str=s.c_str(); //正确
顾名思义,c_str函数的返回值是一个C 风格的字符串。也就是说,函数的返回结果是一个指针,该指针指向一个以空字符结束的字符数组,而这个数组所存的数据恰好与那个string对象的一样。结果指针的类型是const char*,从而确保我们不会改变字符数组的内容。
我们无法保证c_str函数返回的数组一直有效,事实上,如果后续的操作改变了s的值就可能让之前返回的数组失去效用。
使用数组初始化vector对象
前面介绍过不允许使用一个数组为另一个内置类型的数组赋初始值,也不允许使用vector对象初始化数组。相反的,允许使用数组类初始化vector对象。要实现这一目的,只需指明要拷贝区域的首元素地址和尾后元素地址就可以了:
int int_arr[]={0,1,2,3,4,5};
//ivec有6个元素,分别是int_arr中对应元素的副本
vector<int> ivec(begin(int_arr),end(int_arr));
在上述代码中,用于创建ivec的两个指针实际上指明了用来初始化的值在数组int_arr中的位置,其中第二个指针应指向待拷贝区域尾元素的下一个位置。此例中,使用标准库函数begin和end来分别计算int_arr的首指针和尾后指针,在最后的结果中,ivec将包含6个元素,它们的次序和值都与数组int_arr完全一样。