关于strcpy函数形参类型的解析和指针作为输入型输出型参数的不同
在C语言中,字符串一直都是热点,关于strcpy函数大家都很熟悉,但是真正了解的很少,一旦用到总会报一大堆莫名其妙错误,今天我就来给大家详细剖析一下strcpy函数。
虽然不能看到strcpy的内部实现,但是我们通过查阅<string.h>可以看到strcpy函数的声明。
char * __cdecl strcpy(char *, const char *);
那个_cdecl是一个函数调用约定,暂且不讨论,我们今天就来说一下strcpy指针形参加const与不加的区别,帮助大家更好使用这个函数
首先我们要理解这两种语句有何不同
1 char *p="abcd"; 2 char str[5]="abcd";
这两条语句都是存储abcd字符串,但是经过编译链接后,会产生不同的结果
语句1,常量字符串会保存在程序的常量区,编译时会将该字符串在常量区的起始地址拷贝过来存在指针p中,
语句2 常量字符串也会保存在程序的常量区,但是在字符数组str初始化时i,会将字符串拷贝到str中,即数组中存储字符串副本
即str[0]='a';str[1]='b';str[2]='c';str[3]='d';str[4]='\0';
如图所示
我们看到在地址0x0042201C 处存储的是字符串常量abcd,而语句1汇编指令mov dword ptr ds:[ebp-4], 0xoo42201C,作用就是把常量字符串地址存到[ebp-4]这个内存空间(即变量名为p的内存地址)
语句2则是将该常量值一个个拷贝到数组中,即字符串存储在数据段中
那么这样区分之后,会产生一个是否允许修改的差异,我们都知道常量区中的内容不允许修改,而数据区的内容是可读可写的,因此,如果我们这样写
1 p[0]='w'; 2 str[0]='q';
会发现 虽然语句1编译链接都通过,但是运行时会报错,这是因为语句1试图非法修改常量区的值,而常量区是允许修改,只能读取
而语句2则可以使程序正常运行,因为str数组只是常量字符串的一份副本,这份副本存在数据区,可以修改,而且不会影响到常量区字符串的值
明白了关于指向字符串常量的指针和存储字符串常量的数组之间的差异后,我们接下来讨论指针形参输出型和输入型问题
对于形参是指针类型的,我们都知道是传址调用,也明白函数内形参的改变会影响到形参,这就涉及一个实参是否允许修改的问题
char * strcpy(char * strDest,const char * strSrc);
这个函数形参,一个是不加const修饰,一个是加上了const修饰,有何区别呢?这个函数是将strSrc指向的字符串复制到strDest中,(连同字符串结束符'\0'一起复制),
那么就是说,strDest指向的值是可以修改的,而strSRC指向的值在函数中是不允许修改的,我们把加上const修饰的参数称为输入型参数,即只允许读取,不允许写入,把不加const修饰的参数称为输出型参数,即可以在函数内部进行读写改变,从而在主调函数中看到改变。
通过刚才的探讨,我们可以很容易知道如下四条语句哪些会使程序运行时出错
char *p="1234"; char *q="abcd"; char str1[5]="6789"; char str2[5]="hijk"; strcpy(p,q);① strcpy(p,str1);② strcpy(str1,p);③ strcpy(str1,str2);④
很明显,标号①②的语句都会运行时出错,因为strcpy的第一个形参要求是可以写入的,而p,q都是指向了常量区字符串的首地址,不可写入
标号③④都是可以正常运行,但是推荐写法③,因为写法④设计一个隐式转换问题,将str2转换成了常指针了。
下面给出一个strcpy函数的实现
char * strcpy(char *strDest,const char *strSrc) { assert((strDest!=NULL)&&(strSrc!=NULL)); //断言两个指针都不是空指针 char *address=strDest; //函数要返回复制后的字符串首地址 while((*(strDest++)=*(strSrc++))!='\0');//连同结束符一起复制 return address; //返回复制后的字符串首地址 }
总结
1 char *p="1234";指针p指向常量区,不允许修改内容及p[0]='a'非法
2 char str[5]="abcd" 字符数组str复制了常量区字符串“abcd”的值,而字符数组是在数据段,可以进行修改及str[0]='1'合法
3 对于strcpy函数的第一个形参,是输出型形参,因此只能是数组名,而不能是字符指针变量
4 对strcpy的第二个形参,是输入型形参,可以是数组名或者是字符指针变量,但最好是字符指针变量