大家好,我是Duoni!
开始前言
博主介绍:一位不愿透露姓名的艺术生跨界分子
学习阶段:C语言进阶
信念支撑:业精于勤,只要足够肝,世间就没有跨不了的界!
阅前请求:博主自愧没有任何计算机基础,之所以接触于此,是因为本台电脑频频掉链子,奈何本人骨子里不愿服输的一根筋气质,我励志将自己弄坏的自己修的精神贯彻到底!至此偶然间就接触到了程序,从此便一发不可收拾,日夜沉迷。
我所分享的博文可能没有大佬们优化到极致的最优解,或是妙不可言的神码。但我可以确定的是,我的博文绝对是通俗易懂的,哪怕是小白。我希望在这里记录下我成长的脚印,同时也渴望得到各位大佬们的建议和斧正,就让我们一起前进吧!
介绍完啦!那么接下来我们就......
发车!
目录
寄语
此篇博客是对字符串操作知识点的一个总结。字符串是一个特殊的存在,因为它不属于任何数据类型,因此,很多人在初次接触字符串相关操作的题目时,都不由得会无从下手。
为了确保知识点的完整性和具有实践性,我将会对每个知识点进行图演,与特殊情况解析,以便小伙伴们理解!
那么接下来,就让我们保持着必胜的姿态,去征服“它”吧!
一、字符串拷贝操作
在各大笔试题目中,我们总能看到字符串的身影,而字符串的拷贝操作,则是考察我们对字符串特性熟悉与否的一个基本考点。
在第一节中,我们的目标是要弄清strcpy函数与strncpy函数的区别,以及各自的特性,并且能够实现他们,利用此知识点去解决例题!
各个环节紧扣,伙伴们抓紧上车啦!
(一)、strcpy函数 · (无限制)字符串拷贝
首先,我们得大体弄清strcpy函数是干嘛的:
strcpy函数是负责将源字符串中的元素分毫不差的拷贝到目标字符串中的一个函数
在这一过程里,源字符串内容并不会被修改,而目标字符串则会被源字符串内容所覆盖。这也是括号中(无限制)的含义。
(1)strcpy函数原型解析
char *strcpy( char *strDestination, const char *strSource );
这个函数将参数strSource字符元素拷贝到strDestination字符串参数中,由于在此过程strSource参数将会被持续的访问元素并将其拷贝给strDestination。
因此它必须是字符数组或者是指向动态内存分配内存的数组的指针,不能使用字符串常量,不然会发生意想不到的的错误,未定义。
strcpy函数是以(源字符串)strSource 末尾的NULL('\0')为结束标志,并且将NULL也拷贝至目标字符串中。
(2)strcpy函数的使用
在探索strcpy函数的使用中,我们同时也思考着这两种情境:
1、如果目标字符串比源字符串长时,strcpy函数怎么处理?
2、如果目标字符串比源字符串短时,strcpy函数怎么处理?
情境一:
如果目标字符串比源字符串长时,strcpy函数怎么处理?
int main()
{
char vate1_char[] = "Sorrowful life";
char vate2_char[] = "Happiness";
char* ret = strcpy(vate1_char, vate2_char);
printf("%s\n", ret);
return 0;
}
结果:
情境二:
如果目标字符串比源字符串短时,strcpy函数怎么处理?
int main()
{
char vate1_char[] = "Happiness";
char vate2_char[] = "Sorrowful life";
char* ret = strcpy(vate1_char, vate2_char);
perror("strcpy:");
printf("输出:%s\n", ret);
return 0;
}
结果:
情境一分析:
问题1:如果目标字符串比源字符串长时,strcpy函数怎么处理?
char *strcpy( char *strDestination, const char *strSource );
目标参数中的内容将被覆盖丢失,即使源字符串参数比目标字符串参数要短,但它的元素末尾是包含着NULL字节结尾的,所以在拷贝的过程中,在读取到源字符串中的NULL时,目标字符串后面的内容都会被有效的删除。
情境二分析:
问题2:如果目标字符串比源字符串短时,strcpy函数怎么处理?
char *strcpy( char *strDestination, const char *strSource );
在目标字符串参数短于源字符串参数的情况下,strcpy函数仍会继续拷贝,进而越界修改使用未知内存的参数,虽然它会将源字符串元素拷贝进目标字符串中,但这种操作是违法的。strcpy函数无法有效的解决这个问题,因为它无法判断目标字符串起始空间的大小。
但这种错误是有办法规避的,那就是需要各位伙伴们在使用前细心的检查一遍目标空间大小是否足够。不要小看这一步,它可以为我们节省去很多调试排错的时间!
(3)strcpy函数的模拟实现
抓住核心:
当源字符串中的'\0'被拷贝至目标字符串中时,即停止拷贝!
模拟实现:
char* my_strcpy(char* vate1_char, const char* vate2_char)
{
assert(vate1_char && vate2_char);
char* temp = vate1_char;
while (*vate1_char = *vate2_char)//当vate1_char为NULL时停止拷贝
{
vate1_char++;
vate2_char++;
}
return temp;
}
int main()
{
char vate1_char[20] = "我爱编程";
char vate2_char[] = "编程爱我!";
char* ret = my_strcpy(vate1_char, vate2_char);
printf("%s", ret);
return 0;
}
结果:
愿所有的努力都不会被辜负,加油吧伙伴们!
(4)strcpy函数的缺陷
1、拷贝函数一旦运行,它就会自主的将所有的源字符串元素拷贝至目标字符串,缺乏调控性,局限了使用范围。
2、容易造成越界操作。
(二)、strncpy函数 · (有限制)字符串拷贝
strncpy函数与strcpy函数不能说毫无差别,只能说功能一模一样。唯一不同的是加入了:限制数。
这也使得strcpy函数的弊端得到了根本优化!
(1)strncpy函数原型解析
char *strcpy( char *strDestination, const char *strSource ,size_t len);
size_t len的出现,就意味着这个函数将不再“失控”。虽然功能与strcpy不尽相同,但实现的步骤却有了不同:size_t len提供给拷贝任务运行限制,如果符合限制条件,那么拷贝任务继续。如果触及限制条件,那么拷贝停止。
注意!它的结果将不再以源字符串中的NULL字节结尾。
(2)strncpy函数的使用
同样的,在开始进行函数使用前,我们思考这三个情境:
1、如果限制数len的数比源字符串的元素位数大,结果是如何?
2、如果源字符串的元素位数比限制数len的数大,结果是如何?
3、如果限制数len的数比目标字符串的元素位数大,结果是如何?
情境一:
如果限制数len的数比源字符串的元素位数大,结果是如何?
//1、如果限制数len的数比源字符串的元素位数大,结果是如何?
int main()
{
char vate1_char[15] = "wwwwwwwwwwwwww";
char vate2_char[] = "ssssss";
int len = 10;
char* ret = strncpy(vate1_char, vate2_char, len);//10为限制数
printf("%s", ret);
return 0;
}
结果:
情境二:
如果源字符串的元素位数比限制数len的数大,结果是如何?
//2、如果源字符串的元素位数比限制数len的数大,结果是如何?
int main()
{
char vate1_char[15] = "wwwwwwwwwwwwww";
char vate2_char[] = "ssssss";
int len = 3;
char* ret = strncpy(vate1_char, vate2_char, len);
printf("%s", ret);
return 0;
}
结果:
情境三:
如果限制数len比目标字符串的元素位数大,结果是如何?
int main()
{
char vate1_char[15] = "wwwwwwwwwwwwww";
char vate2_char[] = "ssssss";
int len = 20;
char* ret = strncpy(vate1_char, vate2_char, len);
printf("%s", ret);
return 0;
}
结果:
情境一分析:
问题1:如果限制数len的数比源字符串的元素位数大,结果是如何?
char *strcpy( char *strDestination, const char *strSource ,size_t len);
如果限制数len的大小比strlen(strSource)->(源字符串)大,那么strncpy函数会将源字符串中的元素拷贝到目标字符串中,而len限制数未运行的拷贝数,也将会由等量的NULL来填充。
这是函数拷贝进行时vate1_char字符串内部的变化,而当调用结果以字符指针被传回时,在输出的过程中,只能读取到源字符串结尾的'\0',所以后面的字节都会被覆盖。
vate1_char数组内部元素摆放:
情境二分析:
问题2:如果源字符串的元素位数比限制数len的数大,结果是如何?
char *strcpy( char *strDestination, const char *strSource ,size_t len);
如果strlen(strSource)->(源字符串位数)比限制数len大时。那么,函数将依据len的大小,只拷贝源字符串中的len个数位到目标字符串中。而在结束时,不以NULL字节作为补齐,因为源字符串数位满足len限制数的调用需求,故结果将以目标字符串中的NULL为结束标志。
但发生源字符串大于len限制数时,那么只会拷贝源字符串中的len位,而原目标字符串中的其他数位也不会被覆盖,因为返回的结果是以原目标字符串中的NULL为结束标志。
vate1_char数组内部元素摆放:
情境三分析:
问题3:如果限制数len的数比目标字符串的元素位数大,结果是如何?
char *strcpy( char *strDestination, const char *strSource ,size_t len);
当限制数len大于strlen(trDestination)目标字符串时,不论源字符串的数位大小满足限制数len的条件与否,都会造成最后的内存溢出与非法修改操作。最后strncpy函数会完成它本职的拷贝工作,但是你根本不知道它将溢出的数位拷贝到了哪一块内存中!
现在再来回头看看其中的错误,其实大部分的错误都是可以被避免的,比如我们在使用拷贝函数前,先将目标字符串的大小进行合理的预留,那么bug就将减少%50!
猜想:那么为什么要用NULL来填充呢?
因为,strncpy函数限制数的加入,使得函数的使用变得更灵动化。但有时候,由于错误的限制数设定,导致strncpy函数的调用结果可能并不是一个字符串。原因在于:strncpy(有限制)函数不会像strcpy函数那样,会将源字符串中的NULL也拷入进目标字符串来作为停止标记。
所以,strncpy函数为了避免错误的发生,它会在限制数大于源字符串数的时候触发这个保护机制,即:不够的拷贝位由NULL来补齐,以确保结果是一个能用格式化输出printf->%s来打印出。
(3)strncpy函数的模拟实现
strncpy函数的特性:
具有限制性!
上代码:
char* my_strncpy(char* v_1, const char* v_2, int len)
{
assert(v_1 && v_2);//断言
char* ret = v_1;//将v_1首元素复制给*ret,用于返回值
while (len--)//交换一次限制数就--
{
*v_1++ = *v_2++;//拷贝
}
return ret;
}
int main()
{
char vate_1[25] = "我爱熬夜!也爱学习!";
char vate_2[] = "我爱生活!";
int len = 0;
puts("请输入拷贝数:");
scanf("%d", &len);
char* ret = my_strncpy(vate_1, vate_2, len);
printf("%s\n", ret);
return 0;
}
结果:
将源字符串中的5个位数拷贝至目标字符串中,可能有人会疑惑了:
为什么五个元素你却输入9个限制数,那岂不是拷贝了9次?
其实,在最初,char类型是不允许汉字的参入的,但由于计算机语言的迅速发展,亚太地区汉语、日语、韩语等没有办法在计算机中得到参入,严重影响了功能的实现,故添加了文字。但文字与字母不同,一个汉字等同于两个字母。所以在拷贝中,需要消耗两个拷贝数才能实现一个汉字的拷贝。
故:2+2+2+2+1=9.
二、字符串追加操作
字符串的追加操作,指的是在字符串的末尾插入另一个字符串,对目标字符串进行元素的增加。
(一)、strcat函数·(无限制)字符串追加
strcat函数是一个无限制的字符串追加函数。
即:给定一个目标字符串与源字符串,那么它就会将源字符串元素全部插入目标字符串。
这个函数的顺利进行,必须要依托'\0',且是两个字符串参数都具有NULL字节结尾。可以这么理解:
目标字符串中的'\0',是插入元素(源字符串)首元素的地址(入口),而源字符串的'\0'则是出口,即结束标志。
图演:
以上是strcat函数的核心作用,但这这是为了让伙伴们先简要理解以下它是干什么的,好菜还在下面呢!
(1)strcat函数原型解析
char *strcat( char *strDestination, const char *strSource );
strcat函数的两个参数必须是以'\0'为结尾的字符序,其返回的值是一个字符指针。
或许有人好奇:那如果两个参数都不为'\0'结尾的字符序结果会是如何?
那么接下来就让我们实验出真理吧!
(2)strcat函数的使用
老样子,开始前,我们需要怀揣着几个问题思考:
1、如果strcat函数参数部分双方不为字符串,结果是如何?
2、strcat函数可以自己(参数)调用自己(参数)吗?
好戏开始!
情境一:
如果strcat函数参数部分双方不为字符串('\0'结尾),结果是如何?
上代码:
int main()
{
char vate_1[20] = { 'h','a','p','p','y'};//字符数组
char vate_2[] = { 'l','i','f','e'};
char* ret = strcat(vate_1, vate_2);
printf("%s\n", ret);
return 0;
}
结果:
持续闪烁,最后程序挂掉。
如果给这连个非NULL结尾的字符序各自加上NULL,结果会不会有所改变 呢?
上代码:
int main()
{
char vate_1[20] = { 'h','a','p','p','y','\0'};//在字符数组尾部添加'\0'
char vate_2[] = { 'l','i','f','e','\0'};
char* ret = strcat(vate_1, vate_2);
printf("%s\n", ret);
return 0;
}
结果:
最后确实达到了我们的预期,但这是为什么呢?
情境二:
strcat函数可以自己(参数)调用自己(参数)吗?
上代码:
int main()
{
char vate_1[20] = "happy";
char vate_2[] = "life";
char* ret = strcat(vate_1, vate_1);
printf("%s\n", ret);
return 0;
}
结果:
接下来开始解析,小伙伴们,能看到这已经很棒啦!但请继续坚持,我保证最后你将会有所收获!
情境1解析:
问题:如果strcat函数参数部分双方不为字符串('\0'结尾),结果是如何?
如果stact函数的两个参数都不以NULL字节结束,那么,可怜的strcat函数根本就找不到进行的入口,更不要想出口在哪里,所以它一直在寻找目标字符串中的NULL字节,等到最后确实找不到了,那么这个函数也就挂了!
尽管幸运找到那两个入口与出口,那么所返回的值也必定是非法的!
而上述最后所作的修改:是在目标字符序与源字符序中各添加“NULL”字节,有了入口与出口,strcat函数就满血复活啦!
情境2解析:
strcat函数可以自己(参数)调用自己(参数)吗?
这里出现了一个有趣的例子,也是对strcpy函数的挑战。strcat函数能够对目标字符串进行直接的改动,而在情境二中:strcat函数首先找到vate_1的NULL字节,将其修改为源字符串的首元素:'h',注意!strcat函数能够对目标元素进行直接的修改,这就意味着作为参数的源字符串vate_1中的NULL字节也被修改为了:'h',
之前我们提到:目标字符串中的NULL字节,相当于strcat函数的一个切入点,即:“入口”,而源字符串中的NULL字节则相当于一个结束标志,即:“出口”。而上述情况则相当于函数有了入口,却没了出口!
那会发生什么情况呢?答案一定是内存的越界操作,可怜的strcat函数在苦苦的寻找那个出口,但久久未能找到,最后导致程序崩溃!
依据strcat函数的运行原理,它无法完成对同一字符序的自我追加操作,但strncat函数则对此进行了修善,待完成strcat函数的模拟实现后,我们就见分晓!
(3)strcat函数的模拟实现
char *strcat( char *strDestination, const char *strSource );
我们举了两个strcat函数的使用情境,对此,从中我们可以总结出:
strcat函数在目标字符串结束符“NULL”上进行插入,将源字符串的首元素覆盖掉NULL字节,并逐个向后访问,直到找到源字符串尾部的NULL字节,并将其插入至目标字符串尾部后,函数才会停止。
接下来,我们就以此总结为实现目标,完成对strcat函数的模拟实现:
上代码:
//strcat模拟实现
char* my_strcat(char* value1_char, const char* value2_char)
{
assert(value1_char && value2_char);
char* temp = value1_char;//储存目标字符串的首地址
while (*value1_char)//寻找目标字符串'\0'
{
value1_char++;
}
while (*value1_char++ = *value2_char++)//在'\0'位置进行源字符串的拷贝
{
;
}
return temp;//返回
}
int main()
{
char value1_char[50] = "我热爱生活!也热爱学习!";
char value2_char[] = "愿世间充满欢乐!";
char* temp = my_strcat(value1_char, value2_char);
printf("%s", temp);
return 0;
}
//理解及思路
//1、保证目标字符串及源字符串的结尾都具备有‘\0’。
//2、目标字符串空间须是可修改的,且是足够大的。
//3、第一步需要找到目标字符串的‘\0’处。
//4、第二步,在目标字符串‘\0’处进行进行拷贝,将源字符串拷贝到目标字符串之后,直到'\0'结束。
//5、需要对目标字符串进行存地址,否则贸然返回目标字符串,所得到的将是尾部'\0\的地址,从而导致空白打印。
结果:
在这喧嚣内卷的世界中,我愿拼尽全力,去创那三分自留地,护一家长幼安详,与她安稳相守。也祝你们顺利!
(5)strcat函数的缺陷
要说strcat的缺陷,其实只要有两点:
1、不能作用与同一个参数的调用。
2、不具备灵活调控性。
(二)、strncat函数·(有限制)字符串追加
strncat函数也加入了限制数,因为限制数的加入,使得strcat函数不再像是一匹充满野性的野马,不受控制,反之它将被我们很好的使用。
(1)strncat函数原型解析
char *strncat( char *strDest, const char *strSource, size_t count );
与上方所探索的strncpy很相似,它是控制拷贝数。而strncat函数的限制数,则是用来控制追加数的,知识点可以互通,相信看到这里伙伴们早已经有了思路!
接下来就让我们实验出真理吧!
(2)strncat函数的使用
带着这两个问题进入以下环节:
1、strncat函数可以作用于同一个参数吗?
2、如果限制数比原字符串参数数位:“大了”或者“小了”会如何?
情境一:
strncat函数可以作用于同一个参数吗?
上代码:
//strncat函数可以作用于同一个参数吗?
int main()
{
char vate_1[20] = "joy";
char vate_2[] = "and sadness";
int count = 0;
puts("请输入追加数:");
scanf("%d", &count);//限制数输入:4
char* ret = strncat(vate_1, vate_1, count);
printf("%s\n", ret);
return 0;
}
结果:
情境二:
如果限制数比原字符串参数数位:“大了”或者“小了”会如何?
(限制数大于源字符串位数)上代码:
//限制数大于源字符串位数,会发生什么?
int main()
{
char vate_1[20] = "joy";
char vate_2[] = " and sadness";
int count = 0;
puts("请输入追加数:");
scanf("%d", &count);//限制数输入值:50
char* ret = strncat(vate_1, vate_2, count);
printf("%s\n", ret);
return 0;
}
结果:
(限制数小于源字符串位数)上代码:
//限制数小于源字符串位数,会发生什么?
int main()
{
char vate_1[20] = "joy";
char vate_2[] = " and sadness";
int count = 0;
puts("请输入追加数:");
scanf("%d", &count);//限制数输入值:3
char* ret = strncat(vate_1, vate_2, count);
printf("%s\n", ret);
return 0;
}
结果:
情境一解析:
strncat函数可以作用于同一个参数吗?
这是一个很刁钻的角度,因为如果按照strcat函数的运作来说,这无疑将是一个无法解决的难题。但strncat对此进行了优化。
在两个参数都为同一字符串的情况下,追加无疑会改变参数本身元素,而缺少了一个源字符串末尾NULL字节的出口,无疑又会面临着死循环的问题。
所以,当strncat函数中的限制数为0时,它就会给追加目标数据的末尾加上一个NULL字节。
情境二解析:
如果限制数比原字符串参数数位:“大了”或者“小了”会如何?
面对这种情况,我们可以很轻松的去理解它:
1、当限制数大于源字符串时:其会先将源字符串全部追加至目标字符串中,但源字符串末尾蕴含NULL字节,所以会停止。
2、但限制数小于源字符串时:其会追加限制数要求的几个数位,若限制数为0,那么将为目标字符串末尾追加NULL字节。
(3)strncat函数的模拟实现
上代码:
//strncat函数模拟实现
char* my_strncat(char* v_1, const char* v_2, size_t count)
{
assert(v_1 && v_2);//断言判断是否为空指针
char* ret = v_1;//将目标字符串首地址赋值给字符指针:ret用作返回值
while (*v_1)
{
v_1++;//找到目标字符串参数中的'\0'(找到:入口)
}
while ((count--))//限制数为0时停止
{
if ((*v_1++ = *v_2++) == '\0')
//当v_1为'\0'时返回指针。
//此情况是在两个参数都为NULL字节结束的情况下发生
{
return ret;
}
}
*v_1 = '\0';
//而在末尾补NULL字节是在源字符序结尾不为NULL字节时,或同一参数追加时进行。
return ret;
}
int main()
{
char vate_1[25] = "道阻且长,";
char vate_2[] = "行则将至!";
char* ret = my_strncat(vate_1, vate_2, 100);
printf("%s\n", ret);
return 0;
}
结果:
三、字符串比较操作
字符串比较是字符串操作常用到的一种解法,它用于比较两个字符串之间(无限制)或两个字符串中指定元素(有限制)的大小。
(一)、strcmp函数·(无限制)字符串比较
strcmp函数用于比较两个字符串之间的大小,至于是如何进行比较的、及依据什么去判断的?我们接下来开始探索:
(1)strcmp函数原型解析
int strcmp( const char *string1, const char *string2 );
strcmp函数的两个参数是字符指针,它所返回的值是:int(整形)。在函数内部规定了以下标准:
1、当(目标字符串)string_1 > (源字符串)string_2 时, 返回大于0的数。
2、当(目标字符串)string_1 < (源字符串)string_2 时, 返回小于0的数。
3、当(目标字符串)string_1 ==(源字符串)string_2 时, 返回0。
那么它究竟是怎么判断的呢?请看图演:
而字符比较大小的依据,则取决于ASCII码值,逐个进行大小的比对。
(2)strcmp函数的使用
int strcmp( const char *string1, const char *string2 );
接下来我们就来使用strcmp函数,而在此函数中,博主只有一个问题:
strcmp函数能对汉字进行大小的比较吗?
我们实践出真理吧!
上代码:
比较:张三与李三
//strcmp函数能否比对汉字的大小?
//张三与李三
int main()
{
char v_1[] = "张三";
char v_2[] = "李三";
int ret = strcmp(v_1, v_2);
printf("%d\n", ret);
return 0;
}
结果:
比较:李三与张三
//strcmp函数能否比对汉字的大小?
//李三与张三
int main()
{
char v_1[] = "李三";
char v_2[] = "张三";
int ret = strcmp(v_1, v_2);
printf("%d\n", ret);
return 0;
}
结果:
比较:张三与张三
//strcmp函数能否比对汉字的大小?
//张三与张三
int main()
{
char v_1[] = "张三";
char v_2[] = "张三";
int ret = strcmp(v_1, v_2);
printf("%d\n", ret);
return 0;
}
结果:
猜想:我无法相信strcmp函数能够依据汉字的拼音首字母进行解读,毕竟中华文化博大精深。但我也还未清楚它是如何做到的。但我觉得它可能是以汉字的二进制,来作为判断标准进行比较。
希望知道的大佬能够在评论区解答!
(3)strcmp函数的模拟实现
上代码:
//strcmp比较字符串
int my_strcmp(const char* value1_char, const char* value2_char)
{
assert(value1_char && value2_char);//断言
while (*value1_char == *value2_char)//当两位数比较不同时退出
{
if (*value1_char == '\0' && *value2_char == '\0')//如果俩字符串同时找到\0,那么就相等
{
return 0;//返回0
}
value1_char++;//如果每一位相等,那么各自向后移动一位,直到找到\0,或不同
value2_char++;
}
return *value1_char - *value2_char;//如果不同则返回目标位-源字符串位,依据结果判断大小
}
int main()
{
char value1_char[] = "good";
char value2_char[] = "good";
int temp = my_strcmp(value1_char, value2_char);
if (temp > 0)//判断
{
puts(">");
}
else if (temp < 0)
{
puts("<");
}
else
{
puts("==");
}
printf("%d", temp);
return 0;
}
结果:
如何实现都在代码注释里啦!
(4)strcmp函数的缺陷
strcmp函数在运行过程中,无需进行任何的数据修改,只要进行两个字符串之间的比较,所以,内存上的常出bug不会出现,如果一定要揪出来问题,那只有这个了:
缺乏灵活性
(二)、strncmp函数·(有限制)字符串比较
同样的,有限制就意味着“限制数”的存在。即:我们需要对字符串元素进行比较的数位。
(1)strncmp函数原型解析
int strncmp( const char *string1, const char *string2, size_t count );
这里要注意的是:count指的是将要进行比较的元素数位,即从首位到count位。
(2)strncmp函数的使用
在开始前,有一个问题:
如果在对比中,在count的范围内有多处错误,函数该怎么判断先后?
为了更具有视觉辩证性,我将不采用库函数中的strncmp进行演示,因为库中此函数总是将:
1、大于的返回值为:1
2、小于的返回值为:-1
3、等于的返回值为:0
这样无法弄清,函数是怎么判断错误的先后性,所以,这里就不作库函数的使用演示了,通过以上的个例大家早已经融会贯通,那么接下来就让我们开始 吧!
(3)strncmp函数的模拟实现
问题:
在对比中,count的范围内有多处错误,函数该怎么判断先后呢?
测试:count = 1~6,且字符串比较中存在三个错误
错误点:
1、第三个元素:‘f'与'c'
2、第四个元素:'d'与'e'
3、第五个元素:’e'与'f'
上代码:
//strncmp函数的模拟实现
int my_strncmp(const char* v_1, const char* v_2, int temp)
{
assert(v_1 && v_2);//判断两个参数是否为空指针
while ((--temp) && (*v_1 == *v_2))//如果temp为0 并且 *v_1 != *v_2时退出循环
{
if (*v_1 == '\0' && *v_2 == '\0')//如果*v_1 && *v_2同时等于/0则代表相等
{
return 0;//返回0
}
v_1++;//逐个向后
v_2++;
}
return *v_1 - *v_2;//如果某元素不相等则直接返回,*v_1-*v_2
}
int main()
{
char vate1_char[] = "abfdef";
char vate2_char[] = "abceff";
int temp = my_strncmp(vate1_char, vate2_char, 6);//测试1~6之间
if (temp > 0)//判断部分
{
puts("vate1_char > vate2_char");
}
else if (temp < 0)
{
puts("vate1_char < vate2_char");
}
else
{
puts("vate1_char == vate2_char");
}
printf("%d", temp);
return 0;
}
结果:
count = 1
vate1_char(‘a') == vate2_char(‘a')
count = 2
vate1_char(‘b') == vate2_char(‘b')
count = 3
vate1_char(‘f') > vate2_char(‘c')
('f'(102) - 'c'(99)) = 3
count = 4
vate1_char(‘d') < vate2_char(‘e')
('d'(100) - 'e'(101)) = -1
count = 5
vate1_char(‘e') < vate2_char(‘f')
('e'(101) - 'f'(102)) = -1
count = 6
vate1_char(‘f') == vate2_char(‘f')
('f'(102) - 'f'(102)) = 0
总结:count = 3时,是第一个错误点。但在之后,不论限制数怎么增加,错误总是停留在上方的第三个元素位。所以,我们得出结论:
strncmp函数在遇到第一个错误点时就会停止并返回!
四、子串判断
(strstr函数)子串判断也可以理解为:子集判断。说到子集大家再熟悉不过了,在此,我以一张图演与大家共同温习一下:
(1)strstr函数原型解析
char *strstr( const char *string, const char *strCharSet );
函数所返回的指针是指向目标字符串中包含源字符串子集直到NULL字节的一块区域,当然这是找到子集后的情况。反之,如果未找到子集,那么它所返回的是一个空指针。
(2)strstr函数的使用
回想第一次接触这一函数时,它的实现原理曾让我为难不已,而现在有了一定的理解,在此写下我的认识。
首先,在学习它的实现原理前,我们先要弄清楚它是做什么的,再者它是怎么用的,最后再深挖它是怎么实现的。现在我们已经来到了第二步:它该怎么用?
上代码:
//strstr库函数的使用
int main()
{
char vate_1[] = "assefussacusr";
char vate_2[] = "ssac";
char* ret = strstr(vate_1, vate_2);
if (NULL == ret)//判断是否为空指针
{
puts("找不到子集!");
}
else
{
puts("找到子集了!");
printf("%s\n", ret);
}
return 0;
}
结果:
封装后的库函数使用起来总是如此的简易、省心,但strstr函数的实现却不是那么简单。
接下来就让我们来试着实现它吧!
(3)strstr函数的模拟实现
上代码:
//strstr函数的模拟实现
char* my_strstr(const char* value1_char, const char* value2_char)
//查找源字符串是否为目标字符串中的子集,无需修改故加const确保源数据不被修改,保证数据安全性
{
assert(value1_char && value2_char);//断言,确保指针不为空
const char* value1 = value1_char;//创建指针保存目标字符串首元素地址,用于以下操作(添加const保证与目标字符串类型一致)
const char* value2 = value2_char;//创建指针保存源字符串元素首元素地址,用于找不到时的回位操作
const char* temp = value1_char;//保证越过不含可能性的元素地址
while (*temp)
//temp为'\0'时停止。
//在此,以temp作为循环条件有两项原因:
//1、起到每一位的遍历作用。在此期间会产生两个通道:
//(1)、遍历目标元素(value)过程未找到源字符串(value)中的'/0',却找到目标元素中'\0',那么返回空指针,表示未找到value2包含在value1中的子集。
//(2)、中途value2找到字符末尾'\0',那么返回value1中子集的起始位置。
//2、筛选作用
{
value1 = temp;
value2 = value2_char;
while (*value1 && *value2 && (*value1 == *value2))
//循环条件解析:
//(1)、如果value1为'\0',那么退出第二层循环进入第一次循环,temp将会不断++,赋值给value1,直到temp找到'\0'时退出返回空指针。
//(2)、如果value2(源字符串)为'\0',那么将退出第二层循环进入分支语句,返回temp在value1中所标记的子集的首元素地址,表示找到子集。
//(3)、如果*value1 == *value2则表示二者元素目前相等,那么*value1与*value2共同推进,如果二者不同则退出进行调整环节。
{
value1++;
value2++;
}
if (*value2 == '\0')
{
return (char*)temp;//强转原const修饰的字符指针常变量为字符指针,确保返回与接收类型的一致。
}
temp++;
//temp发生++,则说明发生了value1 != value2的情况,那么temp此时将所记录的二者相等元素的地址进行推进,保证下一次的value1与value2对比时不是从原匹配位置再次进行比对。避免了死循环的发生,增加了找到子集的可能;
return NULL;
}
int main()
{
char value1_char[] = "vboogoosff";
char value2_char[] = "oos";
char* ret = my_strstr(value1_char, value2_char);//字符指针接收
if (NULL == ret)//判断
{
puts("找不到子串");
}
else
{
printf("%s", ret);
}
return 0;
}
函数实现的思路阐述包含在了注释之中,为了方便大家的理解,我在下面附上一张图演:
感觉有一点思路了,伙伴们就动起手来,敲个一两遍,理解会更加的深入,加油伙伴们!
基础打牢,何惧地动山摇?
五、字符串分割操作
这个函数比较有趣,但我还没有深入理解清它的底层实现,所以在这里就不向大家演示模拟实现了,感兴趣的伙伴们可以私下探索探索,大佬们在评论区也可以讨论讨论呀!我们一起进步!
首先,我们需要先认识到它是做什么的:
一个字符串常常包含几个单独的部分,它们彼此被分隔开来。每次为了处理这些部分,都必须把它们从字符串中抽取出来。
这个任务正是strtok函数所实现的。它将字符串中的分隔符作为标记部分,然后将其与元素进行分割,随后丢弃分隔符。
(1)strtok函数原型解析
char *strtok( char *strToken, const char *strDelimit );
strToken所指的是待分割字符串,strDelimit 指的是由分隔符组成的字符串集合。strtok找到strToken中的标记符,并将其更改为NULL字节结尾,然后返回一个指向该标记符首元素的指针。
注意:在执行时,strtok函数会对目标字符串进行修改。如果不希望被更改,最好拷贝一份,将其作为参数。
(2)strtok函数的使用
上代码:
//strtok函数的使用
int main()
{
char vate_1[] = "life/is.w onderful!";
char vate_2[] = "/. ";
char* ret = NULL;
for (ret = strtok(vate_1, vate_2); ret != NULL; ret = strtok(NULL, vate_2))
{
printf("%s\n", ret);
}
return 0;
}
结果:
详解:
1、如果strtok函数的第一个参数表示NULL,那么函数将向后找到字符串中的第一个标记符,同时也保存它在字符串中的位置。
2、如果strtok函数的第一个参数是NULL,函数将会在上次同一字符串中继续寻找第一个标记后的标记符。
3、如果目标字符串中不存在标记符了,那么函数就会返回一个NULL指针,宣布任务结束!
六、预警函数
当调用某些函数时,请求操作系统完成某样任务,如果此时发生了某些错误,操作系统会在特定的日志中存放整形变量errno进行错误代码报告。
预警函数的作用在于,它能够对函数进行监控,如发生错误,它会报出警告。
(1)strerror函数
char *strerror( int errnum );
在发生错误时,strerror函数会把其中一个错误代码作为参数,并且返回一个用于描述错误的字符指针。
(2)perror函数
void perror( const char *string );
perror函数的作用在于打印错误信息,它的参数没有实质性的要求,可以是任何类型,任何符号,容易内容,都不会影响它的作用!
七、最后
如果你看到这里了,那么我要跟你说声谢谢!
业精于勤荒于嬉,行成于思毁于随。虽说我非科班出身,但我却无限憧憬成长后的自我,我愿压上我全部的精力去赌一瞬化简成蝶!亲爱的朋友们,我们一起前进吧!
在接下来的时间中,我会以每周两篇的数量进行更新,分享我的知识与感悟,如果喜欢博主,就毫不犹豫的关注我吧!我们一起共进!!