C风格字符串

小心:尽管C++支持C风格字符串,但不应该在C++程序中使用这个类型.C风格字符串常常带来许多错误,是导致大量安全问题的根源

在前面我们使用过字符串字面值,并了解字符串字面值的类型是字符常量的数组, 现在可以更明确地认识到:字符串字面值的类型就是const char类型的数组.C++从C语言继承下来的一种通用结构是C风格字符串(C-style character string),而字符串字面值就是该类型的实例.实际上,C风格字符串既不能确切地归结为C语言的类型,也不能归结为C++语言的类型,而是以空字符null结束的字符数组:

char ca1[]={'c','+','+'};  //no null,not C-style string
char ca2[]={'c','+','+','/0'};  //explict null
char ca3[]="C++";  //null terminator added automatically
const char *cp="C++"; //null terminator added automatically
char *cp1=ca1;  //points to first element of a array,but not C-style string
char *cp2=ca2;  //points to first element of a null-terminated char array

ca1和cp1都不是C风格字符串:ca1十一个不带结束符null的字符数组,而指针cp1指向ca1,因此,它指向的并不是以null结束的数组.其他的声明则都是C风格字符串,数组的名字即是指向该数组第一个元素的指针.于是,ca2和ca3分别是指向各自数组第一个元素的指针.

1.C风格字符串的使用

C++语言通过(const)char*类型的指针来操纵C风格字符串.一般来说,我们是用指针的算术操作来遍历C风格字符串,每次对指针进行测试并递增1,直到到达结束符null为止:

const char *cp="some value";
while(*cp){
 //do something to *cp
 ++cp
}
while语句的循环条件是对const char*类型的指针cp进行解引用,并判断cp当前指向的字符是true值还是false值.真值表明这是除null外的任意字符,则继续循环直到cp指向结束字符数组的null时,循环结束.while循环体做完必要的处理后,cp加1,向下移动指针指向数组中的下一个字符.

-------------------------------------------------------我是华丽的分割线-------------------------------------------------

小心:如果cp所指向的字符数组没有null结束符,则此循环将会失败.这时,循环会从cp指向的位置开始读数,直到遇到内存中某处null结束符为止.

------------------------------------------------------我还是华丽的分割线-----------------------------------------------

2.C风格字符串的标准库函数

下面列出了C语言标准库提供的一系列处理C风格字符串的库函数.要使用这些标准库函数,必须包含相应的C头文件:

#include<cstring>

cstring是string.h头文件的C++版本,而string.h则是C语言提供的标准库

strlen(s)  返回s的长度,不包括字符串结束符null
strcmp(s1,s2)   比较两个字符串s1和s2是否相同.若s1与s2相等,返回0;若s1大于s2,返回正数;若s1小于s2,则返回负数
strcat(s1,s2)   将字符串s2连接到s1后,并返回s1
strcpy(s1,s2)   将s2复制给s1,并返回s1
strncat(s1,s2,n)  将s2的前n个字符连接到s1后面,并返回s1
strncpy(s1,s2,n)  将s2的前n个字符复制给s1,并返回s1

-----------------------------------------------------------我是华丽的分割线------------------------------------------------

 

小心:这些标准库函数不会检查其字符串参数.

---------------------------------------------------------我依旧是华丽的分割线----------------------------------------------

 

传递给这些标准库函数例程的指针必须具有非零值,并且指向以null结束的字符数组中的第一个元素.其中一些标准库函数会修改传递给它的字符串,这些函数将假定它们所修改的字符串具有足够大的空间接收本函数新生成的字符,程序员必须确保目标字符串必须足够大.

C++语言提供普通的关系操作符实现标准库类型string的对象的比较.这些操作符也可用于比较指向C风格字符串的指针,但效果却很不相同:实际上,此时比较的是指针上存放的地址值,而并非它们所指向的字符串:

if(cp1<cp2)   //compares addresses,not the values pointed to

如果cp1和cp2指向同一数组中的元素(或该数组的溢出位置),上述表达式等效于比较在cp1和cp2中存放的地址;如果这两个指针指向不同的数组,则该表达式实现的比较没有定义.红色的语句什么意思??????????????

字符串的比较和比较结果的解释都须使用标准库函数strcmp进行:

const char *cp1="A string example";
const char *cp2="A different string";
int i=strcmp(cp1,cp2);  //i is positive
i=strcmp(cp2,cp1);  //i is negative
i=strcmp(cp1,cp1);  //i is zero

标准库函数strcmp有3种可能的返回值:若两个字符串相等,则返回0值;若第一个字符串大于第二个字符串,则返回正数,否则返回负数.

3.永远不要忘记字符产结束符null

在使用处理C风格字符串的标准库函数时,牢记字符串必须以结束符null结束:

char ca[]={'c','+','+'};  //not null-terminated
cout<<strlen(ca)<<endl;   //disaster: ca isn't null-terminated

在这个例题中,ca是一个没有null结束符的字符数组,则计算的结果不可预料.标准库函数strlen总是假定其参数字符串以null字符结束,当调用该标准库函数时,系统将会从实参ca指向的内存空间开始一直搜索结束符,直到恰好遇到null为止.strlen返回这一段内存空间中总共有多少个字符,无论如何这个数值不可能是正确的.

4.调用者必须确保目标字符串具有足够的大小

传递给标准库函数strcat和strcpy的第一个实参数组必须具有足够大的空间存放新生成的字符串.以下代码虽然演示了一种通常的用法,但是却有潜在的严重错误:

//Dangerous:What happens if we miscalculate the size of largeStr?
char largeStr[16+18+2];  //will hold cp1 a space and cp2
strcpy(largeStr,cp1);    //copies cp1 into largeStr
strcat(largeStr," ");    //adds a space at end of largeStr
strcat(latgeStr,cp2);    //concatenates cp2 to largeStr
//prints A string example A different string
cout<<latgeStr<<endl;

问题在于我们经常会算错largeStr需要的大小.同样地,如果cp1或cp2所指向的字符串大小发生了变化,largeStr所需要的大小则会计算错误.不幸的是,类似于上述代码的程序应用非常广泛,这类程序往往容易出错,并导致严重的安全漏洞.多问一句,那我们如何才能恰当的定义大小?.

5.使用strn函数处理C风格字符串

如果必须使用C风格字符串,则使用标准库函数strncat和strncpy比strcat和strcpy函数更安全:

char largeStr[16+18+2];  //to hold cp1 a space and cp2
strncpy(largeStr,cp1,17); //size to copy includes the null
strncat(largeStr," ",2);  //pedantic,but a good habit
strncat(largeStr,cp2,19); //adds at most 18 characters,plus a null

使用标准库函数strncat和strncpy的诀窍在于可以适当地控制复制字符的个数.特别是在复制和串联字符串时,一定要时刻记住算上结束符null.在定义字符串时要切记预留存放null字符的空间,因为每次调用标准库函数后都必须以此结束字符串largeStr.让我们详细分析一下这些标准库函数的调用:

调用strncpy时,要求复制17个字符:字符串cp1中所有字符,加上结束符null.留下存储结束符null的空间是必要的,这样largeStr才可以正确地结束.调用strncpy后,字符串largeStr的长度strlen值是16.记住:标准库函数strlen用于计算C风格字符串中的字符个数,不把扩null结束符.

调用strncat时,要求复制2个字符:一个空格和结束该字符串字面值的null.调用结束后,字符串largeStr的长度是17,原来用于结束largeStr的null被新添加的空格覆盖了,然后在空格后面写入新的结束符null.

第二次调用strncat串接cp2时,要求赋值cp2中所有字符,包括字符串结束符null.调用结束后,字符串largeStr的长度是35:cp1的16个字符和cp2的18个字符,再加上分隔这两个字符串的一个空格.

整个过程中,存储largeStr的数组大小始终保持为36(包括结束符).

只要则可以正确计算出size实参的值,使用strn版本要比没有size参数的简化版本更安全.但是,如果要向目标数组复制或串接比其size更多的字符,数组溢出的现象仍然会发生.如果要复制或串接的字符串比实际要复制或串接的size大,我们会不经意地把新生成的字符串截短了.截短字符串比数组溢出要安全,但,这仍是错误的.

6.尽可能使用标准库类型string

如果使用C++标准库类型string,则不存在上述问题:

string largeStr=cp1;  //initialize largeStr as a copy of cp1
largeStr+=" ";        //add space at end of largeStr
largeStr+=cp2;        //concatenate cp2 onto end of largeStr

这是我们之前学过的字符串连接操作,此时,标准库负责处理所有的内存管理问题,我们不必再担心每一次修改字符串时涉及到的大小问题.

-----------------------------------------------------我是华丽的分割线------------------------------------------------------

注解:对大部分的应用而言,使用标准库类型string,除了增强安全性外,效率也提高了,因此应该尽量避免使用C风格字符串.

-------------------------------------------------我还是华丽的分割线----------------------------------------------------

习题4.22   解释下列两个while循环的差别;

const char *cp="hello";
int cnt;
while(cp){  ++cnt;++cp;}
while(*cp){  ++cnt;++cp;}
两个while循环的差别为:前者的循环结束条件是cp为0值(即指针cp为0值);后者的循环结束条件是cp所指向的字符为-值(即cp所指向的字符为字符串结束符null('即/n')).因此后者能正确地计算出字符串"hello"中有效字符的数目(放在cnt中),而前者的执行是不确定的.题目还有点小问题,即cnt没有初始化为0.   - -

4.23   下列程序实现什么功能?

const char ca[]={'h','e','l','l','o'};
const char *cp=ca;
while(*cp){
 cout<<*cp<<endl;
 ++cp;
}


该程序段从数组ca的起始地址(即字符'h'的存储地址)开始,输出一段内存中存放的字符,每行输出一个字符,直至存放0值(null)的字节为止.注意,输出的内容一般来说要多于5个字符,因为字符数组ca中没有null结束符.

4.24   解释strcpy和strncpy的差别在哪里,各自的优缺点是什么?

strcpy和strncpy的差别在于:前者复制整个指定的字符串,后者只复制指向字符串中指定数目的字符.
strcpy比较简单,而使用strncpy可以适当地控制复制字符的数目,因此比strcpy更为安全.

4.25   编写程序比较两个string类型的字符串,然后编写另一个程序比较两个C风格字符串的值.

//比较两个string类型的字符串
#include<iostream>
#include<string>
using namespace std;
int main()
{
 string str1,str2;
 //输入两个字符串
 cout<<"Enter two strings:"<<endl;
 cin>>str1>>str2;
 //比较两个字符串
 if(str1>str2)
  cout<<"/""<<str1<<"/""<<"is bigger than "
      <<"/""<<str2<<"/""<<endl;
 else if(str1<str2)
  cout<<"/""<<str2<<"/""<<"is bigger than "
      <<"/""<<str1<<"/""<<endl;
 else
  cout<<"They are equal"<<endl;
 return 0;
}

注:  /'是单引号,/"是双引号.

//比较两个C风格字符串的值
#include<iostream>
#include<string>
using namespace std;
int main()
{
 //char *str1="string1",*str2="string2"
 const int str_size=80;
 char *str1,*str2;
 //为两个字符串分配内存
 str1=new char[str_size];
 str2=new char[str_size];
 if(str1==NULL||str2==NULL){
  cout<<"No enough memory!"<<endl;
  return -1;
 }
 //输入两个字符串
 cout<<"Enter two strings:"<<endl;
 cin>>str1>>str2;
 //比较两个字符串
 int result;
 result=strcmp(str1,str2);
 if(result>0)
  cout<<"/""<<str1<<"/""<<"is bigger than "
      <<"/""<<str2<<"/""<<endl;
 else if(result<0)
  cout<<"/""<<str2<<"/""<<"is bigger than "
      <<"/""<<str1<<"/""<<endl;
 else
  cout<<"They are equal"<<endl;
 //释放字符串所占用的内存
 delete[]str1;
 delete[]str2;
 return 0;
}

此程序中使用了内存的动态分配与释放.如果不用内存的动态分配与释放,可将主函数中第2、3行代码、有关内存分配与释放的代码以及输入字符串的代码注释掉,再将主函数中第一行代码
//char *str1="string1",*str2="string2";前的双斜线去掉即可.

两者有何不同?释放字符串所占用的内存方法?

4.26  编写程序从标准输入设备读入一个string类型的字符串.考虑如何编程实现从标准输入设备读入一个C风格字符串.

从标准输入设备读入一个string类型字符串的程序段
string str;
cin>>str;
从标准输入设备读入一个C风格字符串可如下实现:
const int str_size=80;
char str[str_size];
cin>>str;

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值