初学C语言的人应该对strcpy和strncpy函数很熟悉。strcpy函数用于实现字符串的复制,打开msdn可以看到它的函数原型为char *strcpy( char *strDestination, const char *strSource);然而它的功能仅限于拷贝整个字符串,属于不受限制的字符串函数;那么如果我们想拷贝有限的字符该怎么办?strncpy函数刚好提供了此功能。下面我们就来模拟实现strncpy。
首先来看它的参数设置,经过上篇博客中对strcpy实现原理的讲解,很容易得出只需要在strcpy函数参数中再加入一个变量,用于控制拷贝字符的个数。那么strncpy的函数模型就显而易见,即为char *strncpy( char *dest, const char *src, size_t n );
再来看函数的实现部分,相比于strcpy的实现,strncpy的参数部分只是多了一个变量n,但原理类似;那么不难想到只需把strcpy函数中用while控制的循环条件稍作修改(strcpy中是以源字符串中的结束字符‘\0’为控制条件,而strncpy中利用n做循环的控制条件),然后每次拷贝,n递减即可。
有了初步思路,很容易地写下如下代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
char*my_strncpy(char*dest, const char*src, size_t n)
{
assert(dest != NULL);
assert(src != NULL);
char*ret = dest;
while (n)
{
*dest = *src;
src++;
dest++;
n--;
}
return ret;
}
int main()
{
char arr[20] = {0 };
char*p = "bit";
int n = 0;
printf("请输入拷贝的字符个数:\n");
scanf("%d", &n);
char*ret = my_strncpy(arr, p,n);
printf("%s\n", ret);
return 0;
}
接下来我们对这个程序进行测试,即分别令其拷贝字符的个数小于,等于,大于源串长度;测试所得结果如下:
情况(1)-->“小于”源串长度
情况(2)-->“等于”源串长度(注:我们这里所说的“长度”不是指strlen(str),而是把字符串尾部的‘\0’也算在内了,即为4)
情况(3)-->“大于”源串长度
可以发现,这三种情况下的字符拷贝看似没有什么问题,接下来我们对第二和第三种情况进行分析:
当拷贝的字符个数为4时,输出的是bit,这里为了可以看清楚数组中具体发生的变化,我们把数组的初始化内容稍作修改,即为char arr[20]={'a','b','c','d','e','f','g','h'};
然后F10逐步执行调用监视,查看数组中每个字符的替换情况,如下图:
可以看到前三个字符‘a’,'b','c'分别被替换为‘b’,'i','t';第四个字符‘d’被‘\0’替换掉,可见的确把字符串bit后边的‘\0’也拷贝了进去,而往后存储的字符并没有发生变化;
那么,当拷贝的字符个数大于源串长度(此处拷贝的字符个数为5)时,又是怎样的情况呢?同样,调用监视,结果如下:
很明显发现arr[4]出现了奇怪的值,说明程序本身逻辑就有问题,这时我们不妨用标准库里的strcncpy函数来测试第三种情况,结果如下
可以看到arr[4]的值为‘\0’,并没有出现上述情况,这里我们不妨打开msdn,仔细看一下它的实现原理,如下图:
可见当要拷贝的字符个数n大于源串长度时,进行拷贝时字符串的尾部会附加若干‘空字符’直到长度等于n;到这上述arr【4】=‘%’的出错点相信你应该很明白了:当n递减为2时,进入while循环内部,把‘\0’拷贝进去,然后src指针再向后移动,而此时指针就指向一块未知的空间;当n再递减为1时,拷贝的是未知空间里的那块内容,这就是为什么出现arr【4】=‘%’的原因,具体可看下图:
找到了问题出处,那么接下来我们就对本题的思路重新整合:在模拟实现strncpy中,不再像strcpy那样用*src等于‘\0’来作为循环的结束条件,而是分成了两种情况:
这里字符个数用count代替:
(1)当count小于src所指向的串时,由count决定拷贝什么时候停下来
(2)当count大于src所指向的串时,拷贝是否停下来由*src决定,如果*src=='\0',这时*src后面的内容将不再往dest中拷贝,那么接下来的任务就是往进去拷贝‘\0’,'\0'。。。
例如,源串只有三个字符,而count=6,这时先把这三个字符(包括‘\0’)拷贝进去,这时*src等于‘\0’停了下来,停下来后再进行后边内容的拷贝,也就是一直拷贝‘\0’直到长度等于6。讲到这里不难得出此处(即*src等于‘\0’停下来)的注意点:必须对count的值进行检查,如果大于0,才进行后续‘\0’的不断拷贝。
于是我们可以写出如下代码:
char*my_strncpy(char*dest, const char*src,int count)
{
assert(dest != NULL);
assert(src != NULL);
char*ret = dest;
while (count&&*src)
{
*dest = *src;
src++;
dest++;
count--;
}
if (count != 0)
{
while (count > 0)
{
*dest = '\0';
dest++;
count--;
}
}
return ret;
}
可以看出上面的代码虽然逻辑正确,但整体简洁度很差,其实可以把第一个while循环体部分加入判断条件中,第二个while循环也稍作改变,这样代码更简洁明了,最终的完整代码如下:
#include <stdio.h>
#include <string.h>
#include <assert.h>
char *my_strncpy(char *dest,const char *src, int count)
{
char *ret = dest;
assert(dest);
assert(src);
while(count && (*dest++ = *src++))
{
count--;
}
if(count > 0)
{
while(--count)
{
*dest++ = '\0';
}
}
return ret;
}
int main()
{
char arr[20] = { 'a', 'b', 'c', 'd', 'e', 'f','g','h' };
char*p = "bit";
int count = 0;
printf("请输入拷贝的字符个数:\n");
scanf("%d", &count);
char*ret = my_strncpy(arr, p,5);
printf("%s\n", ret);
return 0;
}
对之前所说的第三种情况进行测试,再来看数组arr中的字符变化情况,结果如下:
可以看到结果完全正确。
=======================================================================================================================
后记:第一次原创博客,难免有很多不足,如果你偶然看到了这篇文章,如发现有错误,请毫不吝啬地指出,也欢迎多多给予建议~