一起talk C栗子吧(第六十回:C语言实例--字符串复制)
各位看官们,大家好,上一回中咱们说的是字符串概述的例子,这一回咱们说的例子是:字符串复制。闲
话休提,言归正转。让我们一起talk C栗子吧!
看官们,在C语言的标准库中为我们提供了字符串复制函数,我们只需要包含string.h头文件就可以使用
字符串复制函数。标准库提供了四个字符串复制函数:strcpy,strncpy,memcpy,memmove。接下来我们
分别介绍它们的用法和使用时的注意事项。
strcpy函数原型:char * strcpy(char *s1, const char *s2)strcpy函数用法:它把s2中内容复制到s1中,并且返回s1.strcpy注意事项:如果s2中的内容太多,以至于超过s1的容量,那么会覆盖掉s1后面内存中的内容。
我们举一个实际的例子来说明,在程序中定义如下字符串:
char s0[SIZE] = string; char s1[SIZE]=str-1; char s2[SIZE+3]=str-2and123; char s3[SIZE] = {'A','B'};// other element of s3 is char s4[] = {'A','B'}; // there is loss of at the end of string
在执行strcpy前和strcpy后分别显示这些字符串的内存地址和字符串的内容,结果如下:
addr: 0xbfb08a39 | s0 : string addr: 0xbfb08a41 | s1 : str-1 addr: 0xbfb08a51 | s2 : str-2and123 addr: 0xbfb08a49 | s3 : AB addr: 0xbfb08a39 | s4 : ABstring ----- after running strcpy(s1,s3) ----- addr: 0xbfb08a39 | s0 : string addr: 0xbfb08a41 | s1 : AB addr: 0xbfb08a51 | s2 : str-2and123 addr: 0xbfb08a49 | s3 : AB addr: 0xbfb08a39 | s4 : ABstring ----- after running strcpy(s1,s2) ----- addr: 0xbfb08a39 | s0 : string addr: 0xbfb08a41 | s1 : str-2and123 addr: 0xbfb08a51 | s2 : str-2and123 addr: 0xbfb08a49 | s3 : 123 addr: 0xbfb08a39 | s4 : ABstring
从运行结果中,大家可以看到当使用strcpy(s1,s3)把s3的内容复制到s1中时,s1的内容变成了s3的
内容,其它几个字符串的内容没有改变。
当使用strcpy(s1,s2)把s2的内容复制到s1中时,s1的内容变成了s2的内容,而且s3的内容也被改变了。
我们再看看s1和s3的地址相邻很近,肯定是在复制过程中把s1后面的内存给“污染”了,所以s3的内容
被修改了,s3就这样中了躺枪。为了大家更加清楚地理解s3是如何中躺枪的,我们对内存中的地址进
行分析:
内存中的地址:0xbfb08a4 1 2 3 4 5 6 7 8 9 a b c d e f地址中的数值: s t r - 2 a n d 1 2 3
大家看看s1的地址从0xbfb08a41开始,占用8个字节,也就是到0xbfb08a48结束,但是执行strcpy操
作时复制了11个字符,这显然超过了s1占有的8个字节,因此它把后面3个字节的内容也强制占用了,但是
后面3个字节的空间是系统分配给s3的,s3虽然拥有这3个字节的空间,但是里面的内容已经被s1强制占用
时修改了。s3就这样中了躺枪。
strncpy函数原型:char * strcpy(char *s1, const char *s2,size_t n)strncpy函数用法:它把s2中的n个字符复制到s1中,并且返回s1.strncpy注意事项:如果s2中的字符数量大于n,那么只复制n个字符到s1中,而且不会在n个字符后面加上字符串的小尾巴。如果s2中的字符数量小于n,那么只复制s2中所有的字符,不足的用空字符:进行补充,直到n个字符为止。我们一般的经验是,在复制时设置n的值比s1的容量小于1,并且手动给s1加上小尾巴。如果n的值大于s1的容量就和strcpy一样了。引入strncpy就是为了通过n来控制复制的内容,避免类似strcpy复制时的缺陷。因此,我们要用好n这张牌。关键时候来个一招招制胜,哈哈。
我们举一个实际的例子来说明,程序中还使用刚才定义的字符串,在执行strncpy前和strncpy后分别显
示这些字符串的内存地址和字符串的内容,结果如下:
addr: 0xbfed56a9 | s0 : string //执行strncpy前各个字符串的值 addr: 0xbfed56b1 | s1 : str-1 addr: 0xbfed56c1 | s2 : str-2and123 addr: 0xbfed56b9 | s3 : AB addr: 0xbfed56a9 | s4 : ABstring ----- after running strncpy(s1,s3,SIZE) ----- addr: 0xbfed56a9 | s0 : string addr: 0xbfed56b1 | s1 : AB //s3中字符的数量小于SIZE,所以有小尾巴 addr: 0xbfed56c1 | s2 : str-2and123 addr: 0xbfed56b9 | s3 : AB addr: 0xbfed56a9 | s4 : ABstring ----- after running strncpy(s1,s2,SIZE) ----- addr: 0xbfed56a9 | s0 : string addr: 0xbfed56b1 | s1 : str-2andAB //从s2中复制了8个字符,但是没有小尾巴 addr: 0xbfed56c1 | s2 : str-2and123 addr: 0xbfed56b9 | s3 : AB addr: 0xbfed56a9 | s4 : ABstring ----- after running strncpy(s1,s2,SIZE-1) ----- addr: 0xbfed56a9 | s0 : string addr: 0xbfed56b1 | s1 : str-2an //从s2中复制了7个字符,手动加上小尾巴 addr: 0xbfed56c1 | s2 : str-2and123 addr: 0xbfed56b9 | s3 : AB addr: 0xbfed56a9 | s4 : ABstring ----- after running strncpy(s1,s2,SIZE+3) ----- addr: 0xbfed56a9 | s0 : string addr: 0xbfed56b1 | s1 : str-2and123 //复制的字符数量大于s1的容量,效果等于strcpy addr: 0xbfed56c1 | s2 : str-2and123 addr: 0xbfed56b9 | s3 : 123 addr: 0xbfed56a9 | s4 : ABstring
我们使用strncpy(s1,s2,n)进行了四次复制操作:
第一次复制时,s2中字符的数量小于n,s1和s2的内容相同,而且给s1中的内容加上了小尾巴。第二次复制时,s2中字符的数量大于n,s1和s2中前n个字符相同,因为s1中的内容没有小尾巴,所以它把s3中的内容也显示了出来,因为s1和s3的地址相邻,而且s3中的字符串有小尾巴。大家可以用我刚才分析内存中地址的方法来看,我就不详细介绍了。第三次复制时,s2中字符的数量大于n,s1和s2中前7个字符的内容相同,我们给s1手动加上了小尾巴,这是我们推荐的做法。第四次复制时n的值大于了s1的容量,s1和s2的内容虽然相同,但是s1污染了其它的内存空间,这和strcpy的做法一样,刚才已经分析过,这里就不再详细分析了。
接下我们说说memcpy函数:
memcpy函数原型:void * memcpy(char *s1, const char *s2,size_t n)memcpy函数用法:它把s2中的n个字符复制到s1中,并且返回s1.memcpy注意事项:和strncpy的相同,请参考上面strncpy的内容,哈哈!memcpy的用法和strncpy类似,所以咱们就不举例子说明了,大家可以参考上面strncpy的例子。
strcpy,strncpy和memcpy都有一个共同的缺点:如果s1和s2的内存空间有重叠的部分,那么使用这些
函数就会产生意想不到的后果,这会给程序埋下“地雷”,一旦爆发后果很严重。想挖掉这颗雷,没有一定的
经验,很难找出来。为此标准库提供了memmove。它可以避免这个缺点。
memmove函数原型:void * memmove(char *s1, const char *s2,size_t n)memmove函数用法:它把s2中的n个字符复制到s1中,并且返回s1.memmove注意事项:和strncpy的相同,请参考上面strncpy的内容,哈哈!如果s1和s2的内容有重叠的部分,那么它可以很好地处理。
我们举一个实际的例子来说明,程序中还使用刚才定义的字符串,执行这四个复制函数后分别显示这些字
符串的内存地址和字符串的内容,结果如下:
----- after running memmove(s1,s3,SIZE) ----- addr: 0xbff8aca9 | s0 : string addr: 0xbff8acb1 | s1 : AB //这个是正常的复制操作,没有任何问题 addr: 0xbff8acc1 | s2 : str-2and123 addr: 0xbff8acb9 | s3 : AB addr: 0xbff8aca9 | s4 : ABstring ----- after running strcpy(s2,s2+1) ----- addr: 0xbff8acc1 | s2 : tr-2and123 //复制时内存有重叠,结果异常 ----- after running strncpy(s2,s2+1,SIZE) ----- addr: 0xbff8acc1 | s2 : tr-2and1123 //复制时内存有重叠,结果正常 ----- after running memcpy(s2,s2+1,SIZE) ----- addr: 0xbff8acc1 | s2 : tr-2and1123 //复制时内存有重叠,结果正常 ----- after running memmove(s2,s2+1,SIZE) ----- addr: 0xbff8acc1 | s2 : tr-2and1123 //复制时内存有重叠,结果正常
对比程序运行结果,大家可以看到当复制字符串时,如果内存有重叠,那么只有strcpy复制的结果是异常
的,其它几个字符串复制函数的结果是正常的。其实,新版本的C库对strncpy和momcpy做了更新 ,使他
们也可以像memmove一样进行复制,只不过目前还没有官方的资料来说明,因此,我们按照老的标准来介
绍这四个字符串复制函数。
看官们,今天的内容有些多,我们在最后对这四个复制函数做一些总结:
strcpy复制时会把字符串后面的空字符,也就是我们说的小尾巴复制上,strncpy和memcpy则不会。strcpy通常对有小尾巴的字符串进行操作,strncpy和memcpy则不管字符串是否有小尾巴,它们都可以进行操作,操作时只参考n.str开头的复制函数,只能对字符串进行复制操作,但是mem开头的复制函数除了对字符串进行复制操作外,还可以对其它类型的数据进行复制操作,比如自己定义的结构体类型数据。因此,它的使用范围更加广一些。
看官们,经过我们对字符串复制函数的介绍,是不是觉得标准库也有些不可靠呢。其实,标准库还是很可
靠的,只是有历史方面的原因。打个比方,现在各种流行网络用语,字典中是没有的,流行起来后才收集
到字典中。标准库类似我们使用的字典。先有了函数,然后才收集到标准库中的,在收集的时候,虽然发
现了一些库函数有缺点,但是它们已经被广泛使用了,所以先收集起来,再提供一些改进后的库函数。
看官们,我把所有的例子整理成了一个文件,并且添加了详细的注释。正文中就不写代码了,以免显得乱,
详细的代码放到了我的资源中,大家可以点击这里下载使用。
各位看官,关于字符串复制的例子咱们就说到这里。欲知后面还有什么例子,且听下回分解。
推荐阅读
-
一起talk C栗子吧(第一百八十四回:C语言实例--在printf函数中设置输出宽度三)
-
一起talk C栗子吧(第一百九十六回:C语言实例--DIY less命令五 )
-
一起talk C栗子吧(第一百六十二回:C语言实例--套接字知识体系图)
-
一起talk C栗子吧( 第一百四十回:C语言实例--文件操作:基于文件描述符三)
-
一起talk C栗子吧(第一百四十三回:C语言实例--文件操作:基于文件指针三)
-
一起talk C栗子吧(第一百三十九回:C语言实例--文件操作:基于文件描述符二)
-
一起talk C栗子吧(第一百三十八回:C语言实例--文件操作:基于文件描述符一)
-
一起talk C栗子吧(第一百四十一回:C语言实例--文件操作:基于文件指针一)
-
一起talk C栗子吧(第一百四十二回:C语言实例--文件操作:基于文件指针二)
-
一起talk C栗子吧(第一百六十回:C语言实例--套接字通信模型二)