字符串函数【2】(strncpy、strncat、strncmp)
全文目录
引言
之前我们已经了解过没有限制的字符串函数,并且提到了这些字符串函数在使用时是有着许多的局限性的。我们先来详细说明一下strcpy、strcmp、strcat这些函数在使用时可能会受到的限制:
无限制的字符串函数的局限性
对于strcmp。如果我们想比较两个字符串中的某一部分内容:
char s1[] = "abcdef";
char s2[] = "abfcde";
我们想要比较s1中的第3到第5这三个元素与s2中的第4到第6这三个元素是否相等。当我们用strcmp函数并传入s1+2与s2+3这两个字符指针时,strcmp只能比较从s1+2指向的元素到’\0’与从s2+3指向的元素到’\0’的这两个子串,即子串"cdef"与子串"cde"。很显然,我们的想法不能被实现。
对于strcpy。同样的,如果我们想将一个字符串中的一部分内容拷贝到另一串的特定位置:
char s1[] = "abcdef";
char s2[] = "abfcde";
我们想要将s2中的子串"abf"拷贝到s1的"cde"的位置上去。但是当我们使用strcpy函数并将s1+2与s2作为参数的时候,strcpy会将从s2指向的字符到’\0’拷贝到s1+2指向的字符及后面的位置,即将"abfcde"拷贝到"cde"上。不但没有实现我们的目的,还造成了数组越界访问。
对于strcat。如果我们想要将一个字符串追加到他本身后面:
char s1[] = "abcdef";
我们想要在s1末尾追加一个s1本身,s1中的元素有’a’、‘b’、‘c’、‘d’、‘e’、‘f’、‘\0’七个。在用strcat追加并将s1与s1作为两个参数时,strcat会首先将’a’拷贝到’\0’的位置。此时结束标志被替换,strcat函数就会一直追加下去。这样程序可能就会挂掉。
所以,有了有限制条件的字符串函数可以解决这些问题:
strncpy
strncpy用来拷贝指定长度的字符串:
函数声明
我们可以查询到这个函数的声明:
strncpy函数被声明在头文件string.h中。
这个函数有三个参数:char*接收拷贝目的地的起始地址;const char*接收源头的起始地址(由于进行字符串拷贝时源头串不会发生变化,所以用const修饰会更安全),这两个参数是与strcpy函数相同的。增加了第三个参数,参数类型是size_t(无符号整型),表示从源头子串的首元素向后拷贝num个元素到目的地子串。
返回值是char*型的,返回拷贝结束的目的地子串的首元素地址。
这个函数在拷贝结束后,在末尾不加’\0’以确保部分拷贝结束后的源头字符串依旧可以访问拷贝部分后面的内容。
需要注意的是,这个函数在处理有重叠内容的情况时(例如将一个字符串的第1到4个元素拷贝到第2到5个元素的位置上)会受到限制,或者出问题。这时候就要使用memmove函数(下一篇文章就介绍,本篇暂不提)。
函数使用
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char s1[] = "abcdef";
char s2[] = "abfcde";
printf("%s\n", strncpy(s1+2, s2, 4));
return 0;
}
以s1串的第三个元素地址、s2的首元素地址以及需要拷贝3个元素作为strncpy的参数。
这个函数使我们能够成功将s2的子串"abf"拷贝到s1的"cde"位置。并且由于拷贝完成后不加’\0’使得我们可以打印出abff而不是abf。
my_strncpy的实现
在了解了strncpy的使用之后,我们就可以尝试去实现一个my_strncpy:
char* my_strncpy(char* str1, const char* str2, int num)
{
assert(str1&&str2);
while (num--)
{
*(str1 + num) = *(str2 + num);
}
return str1;
}
在这段代码中:
首先,assert断言字符指针str1与str2的有效性。
while循环,判断条件为num–,表示循环num次。循环体里面我们可以直接用str2+num指向的字符给str1+num指向的字符赋值。因为num在判断部分是每次循环自减的,所以str1与str2+num就可以实实现从后面遍历以str1、str2起始,长度为num的子串。并实现赋值。
接下来我们可以使用以下这个my_strcnpy函数:
#include<stdio.h>
#include<assert.h>
char* my_strncpy(char* str1, const char* str2, int num)
{
assert(str1&&str2);
while (num--)
{
*(str1 + num) = *(str2 + num);
}
return str1;
}
int main()
{
char s1[] = "abcdef";
char s2[] = "abfcde";
printf("%s\n", my_strncpy(s1+2, s2, 3));
return 0;
}
显然,是可以实现我们的目的的。
strncat
strncat用来追加指定长度的字符串:
函数声明
我们可以查询到这个函数的函数声明:
这个函数在头文件string,h中声明。
strncat函数可以实现将从str2开始的前num个元素追加到str1末尾。
这个函数有三个参数:char*接收拷贝目的地的起始地址;const char*接收源头的起始地址(由于进行字符串追加时源头串不会发生变化,所以用const修饰会更安全),这两个参数是与strcat函数相同的。增加了第三个参数,参数类型是size_t(无符号整型),表示将从str2开始的前num个元素追加到str1末尾。
返回值是char*型的,返回拷贝结束的目的地子串的首元素地址。
当num大于字符串str2的长度,那就只追加str2中’\0’前面的部分。
需要注意的是,当使用这个函数时,需要确保目的地串的长度足够接收追加,否则会造成数组越界访问的问题。
函数使用
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char s1[20] = "abcdef";
char s2[] = "abfcde";
printf("%s\n", strncat(s1, s2, 3));
return 0;
}
这段代码中:
将s1与s2这两个首元素地址以及常数3作为参数,表示将从s2指向的字符开始前3个字符追加到str1的末尾。即将子串"abf"追加到字符串"abcdef"的末尾。
返回目的地即s1的地址,用%s打印。结果就是abcdefabf。
当然,我们也可以验证一下,当s2的长度小于num的情况:此时只追加s2中’\0’前面的部分:
#include<stdio.h>
#include<string.h>
int main()
{
char s1[20] = "abcdef";
char s2[] = "ab";
printf("%s\n", strncat(s1, s2, 3));
return 0;
}
对于引言中提到的自己给自己追加的情况,我们也可以用strncat函数来实现:
两个参数都是s1,追加的字符个数是字符串s1的长度:
#include<stdio.h>
#include<string.h>
int main()
{
char s1[20] = "abcdef";
printf("%s\n", strncat(s1, s1, strlen(s1)));
return 0;
}
my_strncat的实现
在了解了strncat的使用之后,我们就可以尝试实现my_strncat函数:
char* my_strncat(char* str1, const char* str2, int num)
{
assert(str1&&str2);
char* ret = str1;
int i = 0;
while (*(str1++))
{
;
}
str1--;
while (i<num)
{
*(str1 + i) = *(str2 + i);
if (*(str2 + num) == 0)
{
break;
}
i++;
}
return ret;
}
在这段代码中:
首先assert断言字符指针s1与s2的有效性。
由于后面需要对str1自增,所以先创建一个字符指针ret,赋值为str1,方便返回目的地子串的首地址。
接下来要将str2追加在str1的末尾,就需要先找到字符串str1的结束标志。while循环遍历,str1自增,直到str1指向’\0’时终止循环。但是由于是后置++,所以在跳出循环后str1指向的是’\0’的下一个字符,所以需要再将str1自减再进行追加。
然后再循环,由i=0开始循环num次,*(str1 + i) = *(str2 + i)
就表示将从str2指向的元素开始的num个字符赋值给str1+i指向的元素(循环开始时str1+i指向的是’\0’)。循环过程中,赋值结束后如果判断发现str2+i指向的元素为’\0’,说明虽然没有追加足num个元素,但是str2串已经被全部追加,这时直接跳出循环,返回字符指针ret。
当追加足够num个元素后,循环结束,返回字符指针ret。
使用如下:
#include<stdio.h>
#include<assert.h>
char* my_strncat(char* str1, const char* str2, int num)
{
assert(str1&&str2);
char* ret = str1;
int i = 0;
while (*(str1++))
{
;
}
str1--;
while (i<num)
{
*(str1 + i) = *(str2 + i);
if (*(str2 + num) == 0)
{
break;
}
i++;
}
return ret;
}
int main()
{
char s1[20] = "abcdef";
printf("%s\n", my_strncat(s1, s1, strlen(s1)));
return 0;
}
这里实现了字符串自己追加自己,显然实现了我们的目的。
strncmp
strncmp用来比较两个指定长度的字符串大小。
函数声明
我们可以查询到这个函数的声明:
这个函数在头文件string,h中声明。
有3个参数,其中两个参数的类型都是const char*(由于进行字符串比较时两个字符串不会发生变化,所以用const修饰会更安全),这两个参数是与strcmp函数相同的。增加了第三个参数,参数类型是size_t(无符号整型),是想要追加的字符串长度。表示将从str1指向的字符开始的num个字符与从str2指向的字符开始的num个字符进行比较。
返回值是int,与strcmp函数相同:当比较结果第一个字符串大于第二个字符串时返回一个大于0的数;等于是返回0;小于时返回一个小于0的数(vs环境下返回-1、0、1)。
函数使用
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char s1[] = "abcdef";
char s2[] = "abczxc";
printf("%d\n", strncmp(s1, s2, 3));
return 0;
}
这点代码中:
将首元素地址s1与s2以及常数3作为strncmp的参数。表示比较从s1指向的字符开始的3个字符与从s2指向的字符开始的3个字符。均为"abc",所以返回0;
但是,当我们给形参num传一个常数4时,就是比较从s1指向的字符开始的4个字符与从s2指向的字符开始的4个字符。即比较"abcd"与"abcz",这时候应该返回一个小于0的数(vs环境下返回-1):
#include<stdio.h>
#include<string.h>
int main()
{
char s1[] = "abcdef";
char s2[] = "abczxc";
printf("%d\n", strncmp(s1, s2, 4));
return 0;
}
my_strncmp的实现
在了解了strncmp的使用之后,我们就可以尝试实现一个my_strncmp函数:
int my_strncmp(const char* str1, const char* str2, int num)
{
assert(str1&&str2);
int i = 0;
while (*str1 == *str2&&i < num)
{
i++;
str1++;
str2++;
}
if (i == num)
{
return 0;
}
else
{
return *str1 - *str2;
}
}
在这段代码中:
首先assert断言字符指针s1与s2的有效性。
接着while循环,变量i用于控制循环的次数,i<num表示循环最多进行num次。当str1指向的元素等于str2指向的元素,并且i<num时,继续循环。
所以当跳出循环时有两种情况:1、str1与str2指向的元素不相等;2、i=num。
第二种情况下能够说明从str1指向的字符开始的num个字符与从str2指向的字符开始的num个字符都是相等的。于是我们在while循环后进行判断:
当满足第二种情况时返回0,其他情况返回*str1-*str2。
使用如下:
#include<stdio.h>
#include<assert.h>
int my_strncmp(const char* str1, const char* str2, int num)
{
assert(str1&&str2);
int i = 0;
while (*str1 == *str2&&i < num)
{
i++;
str1++;
str2++;
}
if (i == num)
{
return 0;
}
else
{
return *str1 - *str2;
}
}
int main()
{
char s1[] = "abcdef";
char s2[] = "abczxc";
printf("%d\n", strncmp(s1, s2, 4));
return 0;
}
#include<stdio.h>
#include<assert.h>
int my_strncmp(const char* str1, const char* str2, int num)
{
assert(str1&&str2);
int i = 0;
while (*str1 == *str2&&i < num)
{
i++;
str1++;
str2++;
}
if (i == num)
{
return 0;
}
else
{
return *str1 - *str2;
}
}
int main()
{
char s1[] = "abcdef";
char s2[] = "abczxc";
printf("%d\n", strncmp(s1, s2, 3));
return 0;
}
显然,当传给形参num的值是3或4时都是合适的。
总结
这篇文章中,我们了解了strncat、strncmp、strncpy的使用以及实现。这样,在对字符串进行操作时就会更加的得心应手。
但是,这些函数仅能对字符串进行操作,如果我们想要对整型数组、浮点型数组甚至结构体数组进行操作时就没有办法了吗?
当然不是,下一篇文章就会详细介绍内存函数。
如果对本文有任何问题,欢迎在评论区进行讨论哦
希望与大家共同进步哦