C语言中对于字符和字符串的处理很是频繁,所以出现了许多的作用于字符和字符串的函数,今天,我就对这些函数的其中几个的使用进行说明,并且对每个函数进行模拟实现。
strlen函数
由msdn查询可以得知,strlen函数的程序设计为:size_t strlen(const char* str),返回类型为:size_t(无符号整形),参数为const char*,我们接着向下查找,观察strlen函数的返回值和作用。
观察返回类型可知,strlen函数返回字符串中的字符个数,不包含’\0’,没有保留返回值为错误。在这里,我还要提出几个strlen函数的需要特别注意的点:
1.字符串中把’\0’作为结束标志,strlen函数返回的是在字符串中’\0’前面出现的字符个数(不包含’\0’)。
2.参数指向的字符中必须要以’\0’结束,如果字符串中没有以’\0’作为结束标志的话,strlen函数就会越界访问,直到找到’\0’,这时就无法计算字符串中的字符个数了,返回的是一个随机值。
3.注意函数的返回值为size_t,是无符号整形。
strlen函数的例子:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdef";
int len = strlen(arr);
printf("%d\n",len);
return 0;
}
由运行结果可以得知,strlen函数计算出了字符数组arr的字符个数,并且没有包含‘\0’。
接下来,我来模拟实现strlen函数。
#include<stdio.h>
#include<string.h>
#include<assert.h>
size_t my_strlen(const char* str)
{
int len = 0;
//检验str不是非空指针
assert(str != NULL);
//str不是'\0',len就自增1
while(*str != '\0')
{
len++;
str++;
}
return len;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
由运行结果可知,该strlen函数的模拟实现代码可以满足strlen函数功能要求。另外,在模拟函数的过程中,应当保证返回类型和参数与函数底层的实现相同,这才是模拟实现。
接下来,我将对strlen函数的返回类型为无符号整形作进一步的解释:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abc";
if(strlen(arr2) - strlen(arr1) >0)
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
大家肯定认为该代码实现后会打印 < 吧,因为arr2的长度是小于arr1的长度的,所以strlen函数的返回值是arr1大于arr2,arr2的strlen函数返回值减去arr1的strlen函数返回值肯定是小于0,那第一个if判断语句不成立,自然就运行第二条语句,打印 < ,让我们看看运行结果。
运行结果就是这样的出人意外,运行结果为 > 表明if判断语句为真,那么前面我们的分析是哪一步出错了呢,其实我们前面的分析没有错误,只是分析少了一步,我们忽略了strlen函数的返回类型是size_t,arr2的strlen函数返回值是无符号整形,arr1的strlen函数返回值也是无符号整形,无符号整形减去无符号整形依然是无符号整形,在无符号整形下所有的数字都是大于0的,所以两者相减大于0,if判断语句是成立的。
strcpy函数
strcpy函数的程序设计为:char* strcpy(char* destination, const char* source),返回类型为char*,两个参数分别是char和const char,接下来,我们来了解一下strcpy函数的返回值:
strcpy函数返回destination字符串的地址,没有保存返回值为错误。
接下来,我提出几个关于strcpy函数的注意的点:
1.将source的字符数组拷贝进destination的字符数组,包括’\0’。
2.源字符串也就是source字符串,必须以’\0’结束。(因为strcpy将’\0’拷贝过去才会停止)
3.目标空间也就是destination的字符串的空间要足够大,确保能够存放source的字符串。
strcpy函数的例子:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = {0};
char arr2[] = "abcdef";
printf("%s\n",strcpy(arr1,arr2));
return 0;
}
由前面对strcpy函数的介绍可以得知,strcpy函数返回目标字符的地址,在这里就是arr1的地址,所以打印的就是arr1的内容,而arr1为由原来的全’0’字符数组,变成了有abcdef的字符数组,说明arr2的内容拷贝到了arr1里面。
接下来,我来验证strcpy函数连’\0’也拷贝过去。
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "xxxxxxxxxxxxxxxxxxx";
char arr2[] = "abcdef";
printf("%s\n",strcpy(arr1,arr2));
return 0;
}
调试可以发现arr1中存放着19个x字符和一个’\0’,当我们运行完strcpy函数后,arr1会存放什么呢?
由图片可以知道,arr1存放着abcdef’\0’和几个x字符,由该程序就可以证明strcpy函数连源字符串的’\0’也拷贝到目标字符串中。
在使用strcpy函数的过程中,我们必须记住几个要点。
1.源字符串中必须要以’\0’结束,不然strcpy函数在拷贝的过程中,会往源字符串后面越界访问,并且拷贝到目标字符串,直到找到’\0’。
2.目标字符串的空间必须要足够放下源字符串。
3.目标字符串不能是常量字符串,因为常量字符串不能修改,如果有内容拷贝到常量字符串,会导致程序报错。
接下来,我来模拟实现strcpy函数。
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest,const char* src)
{
char* ret = dest;
assert(dest != NULL);
assert(src != NULL);
while(*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "abc";
char arr2[] = "hello bit";
printf("%s\n",my_strcpy(arr1,arr2));
return 0;
}
arr1被覆盖前:
arr1被覆盖后:
运行后结果:
由此可见,这个strcpy函数的模拟实现符合了我们的要求。
strcat函数
strcat函数是字符追加函数,它的返回值和参数类型的设置是:
char* strcat (char* destination,const char* source);
在使用strcat函数的过程中,有几个需要注意的点
1.源字符串和目标字符串必须以’\0’结束。
2.目标字符串必须足够大,能容纳下源字符串的内容。
3.目标字符串必须可修改。
strcat函数使用的例子:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
strcat(arr1,arr2);
printf("%s\n",arr1);
return 0;
}
运行结果如下:
由该程序可以得知,strcat函数是将源字符串的内容拷贝到目标字符串内容的后面,那么,源字符串的’\0’是否会被覆盖呢?我利用个小程序来解释一下:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "hello \0xxxxxxxxx";
char arr2[] = "world";
strcat(arr1,arr2);
printf("%s\n",arr1);
return 0;
}
在这里的小程序中,我将arr1后面加上了’\0’和几个’x’,通过调试来发现strcat函数拷贝的过程中,最开始覆盖源字符串函数的哪个位置。
这是arr1最开始的内容:
我们需要格外的注意arr1的’\0’的变化。
这是拷贝后的arr1的内容:
我们注意到strcat函数拷贝到arr1中,是从下标为6的元素开始覆盖的,那么在未拷贝前,arr1中下标为6的元素是什么呢?我们往文章上面翻找,可以发现arr1在未拷贝前,下标为6的元素是’\0’。(要注意的是,在strcat函数未拷贝前,监视窗口中arr1的下标为6的元素是0,我们需要知道’\0’的阿斯玛值是0,也就是监视窗口显示0,那么该元素就是’\0’)
在这里,我们就可以解释为什么目标字符串中也要有’\0’。
strcat函数是追加字符串的函数,它是通过找到目标字符串的’\0’,来判断该位置是目标字符串的末尾,再从’\0’开始覆盖目标字符串。
源字符串中要有’\0’是为了找到源字符串的末位置,方便拷贝停止,与strcpy函数的原因相同。
接下来,我来模拟实现strcat函数:
#include<assert.h>
#include<stdio.h>
char* my_strcat(char* dest,const char* src)
{
char* ret = dest;
assert(dest != NULL);
assert(src != NULL);
//在源字符串中找到'\0'的位置
while(*dest != '\0')
{
dest++;
}
//从'\0'开始拷贝
while(*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
my_strcat(arr1,arr2);
printf("%s\n",arr1);
return 0;
}
运行结果如下:
这个strcat函数的模拟实现达到了我们的要求。
接下来,我提出一个问题,字符串给自己追加后如何?如下:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "abcdef";
strcat(arr1,arr1);
return 0;
}
在VS调试的过程中,程序报出了错误。
接下来,我来解释为什么会报错。
这是使用strcat函数最开始传参时的样子,dest和src都指向字符数组的首元素。
接下来,dest开始往后面走,直到找到’\0’,最后就是下面的结果。
然后,dest开始被src覆盖,并且覆盖一次,src和dest就往后走一步。(为了方便观看,我将覆盖后的元素写在原来的元素下面)
'\0’被a所覆盖,接下来,src和dest各自往后面走一步。
按这样的过程持续下去,会出现这样的结果。
在原来的strcat函数的设置中,如果将’\0’拷贝到过去以后,strcat函数就会停止拷贝,换句话说,如果strcat函数没有拷贝’\0’过去,那么该函数就永远不会停止拷贝。
由图片可以得知,src已经到达了’\0’的位置,那么strcat函数将src中的’\0’拷贝到dest就可以停止运行了,但遗憾的是’\0’早已被’a’字符所替代,所以拷贝过去的是’a’字符,就这样,程序一直找不到’\0’,进入死循环的拷贝,最终,程序报错。
strcmp函数
strcmp函数的返回值和参数类型的设置是:
int strcmp(const char* str1,const char* str2)
该strcmp函数的返回值有标准规定:
标准规定:
1.第一个字符串小于第二个字符串,返回小于0的数字。
2.第一个字符串等于第二个字符串,返回等于0的数字。
3.第一个字符串大于第二个字符串,返回大于0的数字。
在msdn查询之后,我们对strcmp函数的了解就进一步加深了,我再提出一个问题,该函数是怎么判断两个字符串的大小关系的呢?
别急,接下来我来给大家讲解。
在使用strcmp函数的过程中,会将字符串的首个字符串的地址传过去,如上图,strcmp函数就会解引用str1和str2,比较两者的阿斯玛值的大小关系,如果两个阿斯玛值相等,str1和str2就会往后走一步,为下次比较做准备。
那么就会有以下五种情况。
1.str1所在地址的的字符的阿斯玛值与str2所在地址的字符的阿斯玛值相等,那么str1和str2就会向后走一步。
2.str1所在地址的字符的阿斯玛值比str2所在地址的字符的阿斯玛值大,直接返回大于0的数字,strcmp函数的调用结束。
3.str1所在地址的字符的阿斯玛值比str2所在地址的字符的阿斯玛值小,直接返回小于0的数字,strcmp函数的调用结束。
4.str1和str2两个同时到达’\0’的地址,两个字符串都比较完了也没有出现字符不相等的情况,那么返回等于0的数字,strcmp函数的调用结束。
5.str1和str2的其中一个先到达’\0’,这里要注意的是,'\0’的阿斯玛值是0,所以依然可以拿来比较,返回值同上面2,3规则。
接下来,我举个strcmp函数的使用例子。
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcde";
int ret = strcmp(arr1, arr2); //arr1大于arr2,返回大于0的数字
printf("%d\n", ret);
char arr3[] = "abcdef";
char arr4[] = "abcdefg";
ret = strcmp(arr3, arr4); //arr3小于arr4,返回小于0的数字
printf("%d\n", ret);
char arr5[] = "abcdef";
char arr6[] = "abcdef";
ret = strcmp(arr5, arr6); //arr4等于arr5,返回等于0的数字
printf("%d\n", ret);
return 0;
}
接下来,我来模拟实现strcmp函数。
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 != NULL);
assert(str2 != NULL);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
if (*str1 > *str2)
{
return 1;
}
else
{
return -1;
}
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcde";
int ret = my_strcmp(arr1, arr2); //arr1大于arr2,返回大于0的数字
printf("%d\n", ret);
char arr3[] = "abcdef";
char arr4[] = "abcdefg";
ret = my_strcmp(arr3, arr4); //arr3小于arr4,返回小于0的数字
printf("%d\n", ret);
char arr5[] = "abcdef";
char arr6[] = "abcdef";
ret = my_strcmp(arr5, arr6); //arr4大于arr5,返回大于0的数字
printf("%d\n", ret);
return 0;
}
在C语言中,strcpy、strcat、strcmp函数统称为长度不受限制的字符串函数,在下一篇的文章中,我会介绍几个长度受限制的字符串函数,关注点一点,下期更精彩。