上一节我们将到了长度不受限制的字符串处理函数,接下来我们学习长度受限的字符串长度。
这些函数接收一个显式的长度参数,用于限定进行复制或者比较的字符数。因此我们可以主动的选择字符串中的字符数量进行操作,防止难以预料的长字符串从它们的目标数组溢出。
一、strncpy库函数
1.1函数介绍:
strncpy函数含有三个参数:
第一个参数是指向目标空间的起始地址 char* dest
第二个参数是源头字符串的起始地址 char* src
第三个参数是一个无符号整型,size_t count
返回类型为 char* 即返回目标空间字符串的起始地址。
该函数的作用为:
strncpy字符串将源头字符串的内容复制到目标空间中,但是并不全部复制,而是写入的字符数目由count指定,即向目标空间拷贝count个字符。
如果源头字符串的数目小于count,那么目标空间中将会用额外的 '\0' 填充到count个数目长度,如果源头字符串的数目大于或等于count,那么将只有count个字符拷贝到目标空间,结果将不会以'\0' 结尾
使用案例:
我们用调试的方法对下面代码进行分析
当count为3时,说明我们要把arr2中xxx拷贝到arr1中,拷贝完是否为xxx\0defg\0
结果:
结果显然不是,strncoy函数只是将指定的3个字符拷贝过去,并没有在字符结尾放'\0'
当我们把count改为4呢?
结果:
此时我们发现出现了'\0',原因是strncpy需要将arr2中的4个字符拷贝过去,而arr2中的第四个字符为'\0'所以会将其拷贝过去。
如果count为6呢,大于字符串arr2的字符长度,是否会填充'\0'到6个字符呢?
结果:
源头字符串虽然只有4个字符,但是count为6,所以strncpy函数将自动把2个字符变为'\0'以满足复制6个字符的要求。
这也就说明了strncpy函数复制的长度完全取决于count的大小
还有一点,如果arr2字符串中间存在'\0',拷贝的时候遇到该'\0',strncpy也视为源头字符串到了结束标记,后面的内容将不会拷贝,而是补'\0'
拷贝后的字符串并不是"xxx\0xx",而是遇到'\0'后便不再拷贝,开始补'\0' 。
1.2模拟实现:
有了上面的分析,我们模拟实现起来便有了明确的思路。
大体逻辑还是和my_strcpy函数的模拟类似,用断言来避免传过来的空指针,加入const修饰来增强代码的鲁棒性,以及用ret指针来记录目标空间的起始地址,以便返回。
只是我们要注意到循环条件的终止应该取决于count的大小,而并非指针src指向字符的内容。
并且在src指向'\0'时,需要判断count是否为0,如果不是,我们要在目标空间补充'\0',是则跳出循环,返回起始指针。
因此代码的实现为下面的版本:
#include<assert.h>
char* my_strncpy(char* dest, const char* src, size_t n)
{
assert(dest && src);
char* ret = dest;//存放目标空间的起始地址
while (n--)
{
if (*src == '\0')//*src字符个数小于n,后面填充'\0'
{
*dest++ = '\0';
}
else//将n个字符复制过去
*dest++ = *src++;
}
return ret;
}
根据n的大小来执行复制,如果src指向的内容并非为'\0'则将内容赋值给dest,然后两者向后移动,如果src指向'\0'并且count没有为0,则dest指针指向的内容赋值为'\0',并且dest继续向后移动。
最终循环结束函数返回起始指针ret。
二、strncat库函数
2.1函数介绍:
strncat函数和strncpy函数的参数一样,也是含有三个参数。
第一个参数为需要连接的字符空间的起始地址 char* dest
第二个参数为连接字符的源头空间的起始地址 char* src
第三个参数为连接的字符个数count,也为 size_t 类型
返回类型依旧为char*类型,返回的是目标空间的起始地址
函数作用:
strncat函数的作用为将源头字符串src中的count个字符连接到dest字符串的末尾,最多复制的字符个数就为count。但是,strncat函数总是在结果字符串后面添加一个 '\0' ,而且不会像strncpy函数那样对目标数组用'\0'填充。
目标数组中原先的字符串并没有算在strncat的长度中。strncat最多向目标空间复制count个字符(再加一个结尾的'\0'),它才不管目标参数除去原先存在的字符串后空间够不够用,宛如一个愣头青。
使用案例:
使用strncat函数将arr2中的3个字符连接到arr1中,他会先找打arr1空间的'\0'作为连接的起始位置,然后将arr2中的三个字符从'\0'的位置开始存放,末尾会自动放上'\0'。
如果count超过arr2的字符数量呢,例如我们想把6个字符连接到arr1中,当然我们的arr1要保证足够大,否则将会溢出。
arr1[15]="xxxx\0xxxxxxxxx";
arr2[ ]="abcd"
我们发现strncat函数只是将arr2中存在的所有字符复制过去,并不会自动用'\0'补齐到6个字符。
2.2模拟实现:
有了上面的分析,模拟实现起来就容易多了。
首先,我们需要找到目标空间的'\0'字符,因为这是连接的起始位置。找到很容易,我们只需要用一个while循环便可以做到,就像在模拟实现strcat函数时那样。
然后,我们要开始进行字符串的复制,从源头src复制到目标dest,但是我们要注意,他并不是总是复制,当count为0时结束,当src指向'\0'时也会结束,而且这两个条件有一个满足就会停止。并且,他不会自己补齐'\0'。
最后,strncat函数总是会在末尾补上一个'\0',也就是说一旦循环停止,立刻将'\0'放到目标字符串后面。
实现代码:
char* my_strncat(char* dest, const char* src, size_t n)
{
assert(dest && src);
char* ret = dest;//存放目标空间的起始地址
while (*dest)//找到目标空间的'/0'
{
dest++;
}
while (n-- && *src)//*src为假说明源头字符串结束,n为假说明n个字符连接完毕
{
*dest++ = *src++;
}
*dest = '\0';//总是在最后放一个'\0'
return ret;
}
当然此时也有人问为何不把*dest++=*src++写到while判断那里。就像模拟strcpy那样实现呢?
如果如此书写代码,将会产生一个错误。
如果count数目小于src指向的字符字符串个数的时候会导致不会在结尾放一个'\0'
简单,加上就是了。
将代码写成这样:
char* my_strncat(char* dest, const char* src, size_t n)
{
assert(dest && src);
char* ret = dest;//存放目标空间的起始地址
while (*dest)//找到目标空间的'/0'
{
dest++;
}
while (n-- &&(* dest++ = *src++))
{
;//此代码只完成了连接,没有在后面放'/0'
}
*(dest) = '\0';//如果加上此句,将会产生多余的'/0'
return ret;
}
但是又会产生另一个错误,在count大于src指向的字符内容时,他的确会在末尾加一个'\0'。但是,他并不会理会前面是否存在'\0'了,即就算src字符末尾的'\0'拷贝过来了,他还是会在后方放一个'\0'。
因此,此代码不可取。
三、strncmp库函数
3.1函数介绍:
strncmp函数含有三个参数
str1和str2为两个待比较的字符串的起始地址。
count为比较的字符数,最多比较count个字符。
如果两个字符串在第count字符之前存在不相等的字符,该函数就会像strcmp一样停止比较,并返回结果;如果前count个字符相等,则返回0。
函数使用:
前5个字符相等,返回0。、
此时虽然要求比较前7个字符,但是,arr2并没有这么多的字符,在地6个字符时,arr2指向的是'\0',与arr1中的 'f' 进行比较,结果是arr1>arr2,返回大于0的数字。
3.2模拟实现:
该函数的模拟实现比较简单,就是在strcmp函数比较的基础上添加一个count字符的限制。
所以我们这样实现:
int my_strncmp(const char* s1, const char* s2, size_t n)
{
assert(s1 && s2);
while (n-- && *s1 == *s2)
{
if (n==0)
{
return 0;
}
s1++;
s2++;
}
return *s1 - *s2;
}