第十一章的习题7,刚开始觉得简单,可是实际思考时发现要考虑很多东西,然后脑子就一片混沌了,然后问题一拖一个月,然而时间并没有帮我解决掉问题,问题还是要自己去努力接触才能发展的呀。
有参考答案,上网也找到了答案,昨天验证了下,是对的。自己就是眼高手低,还犟,想凭借自己的“智慧”想出来(别人的我看了觉得费劲,而且很难理解他的思想,他到底是怎么想到的呢)。不过不能自负,遇到问题想不出来很正常,就该学习自己想不到的思路,改变自己的思维方式。应该向前人学习,可以先读懂,理解一点是一点,接触多了自己才能彻底理解,否则一直止步不前会更痛苦。
第一套方案,网上下载的CPrimerPlus的答案,贴上源码,注释上,再谈谈自己的理解。
char *string_in(char *p1, char *p2) { char *p1_save = p1, *p2_save = p2; //每个字符串分别有两个指针指着 if(*p1 == '\0' || *p2 == '\0') //两个字符串都是空的话就没得比了,返回空指针 return NULL; while(1) //循环是必须的吧,一直找,直到符合跳出的标准,所以循环中要分清跳出循环的情况 { if(*p1 == *p2) //如果指向的母字符串和子字符串首元素相同 { if(*++p2 == '\0') return p1_save; //指针指向子字符串下一个元素,并且判断是否为\0:是的话就比完啦,且得到明确结论,母字符串里找到了子字符串,返回相应指针;不是的话,子字符串还没到达尾部呢(回头还得继续判断,所以用循环),运行下一步 if(*++p1 == '\0') return NULL; //指针指向母字符串下一个元素,并且判断是否为\0:是的话也比完啦,且得到明确结论,就是子字符串还没到尾部母字符串就到尾部了,即母字符串里是找不出子字符串的(子字符串比母字符串还长呢),返回空指针;不是的话,那我也不能得出明确的结论,所以要继续分析,走下一步吧 } else //母字符串中被指向的元素和子字符串首元素不同 { if(*++p1 == '\0') return NULL; //指针指向母字符串下一个元素,并且判断是否为\0:是的话也没得玩儿了,结论是母字符串里找不到子字符串(母字符串到尾都没能找到子字符串的首元素,真是惨);不是的话,母字符串还没结束呢,后面还有希望哦,继续判断 p1_save = p1; //p1_save记住已母字符串已经往后移一位的元素位置,接下来要比较母字符串中这个元素和子字符串的首元素,如果找到了完整的子字符串,就要回头输出这个地址,所以要记住了 p2 = p2_save; //p2_save记住的就是子字符串的首元素的地址,这就是让指针重新指向子字符串的首元素,接下来重头与母字符串进行比较 } } }
从代码反过来去注释,去推作者的思路,然后用语言总结他解决这个问题的办法。虽然比较笨,但是自己想不到就要问别人,这个别人不在身边没法告诉你他是怎么想的,只能自己去带入代码中去换位思考,看看人家牛人是怎么想到的啦。接下来我来总结下这个牛人是怎么想的,先去吃饭,O(∩_∩)O哈哈~
用子字符串首元素和母字符串首元素比较,假设不同,指向母字符串的指针往后移一位,再和子字符串首元素比较,当找到和子字符串首元素相同的母字符串中的元素时,停下来,我想想,恩,这时应该同时将指向母字符串和子字符串的指针都往后移一位,进行比较(假设子母字符串都是不止一个元素),假设相同,再将两个指针同时后移一位,再比较,假设还相同,再后移再比较,直到指向子字符串的指针已经指向尾部\0,也就是说,此前都相同的话,说明母字符串中找到了子字符串了,返回母字符串中相应的地址。
这是只针对比较顺利的情况来描述的思路,要考虑每一个“假设”并不是那么顺利的情况。针对每一个假设,我们分别考虑周全些。假设子字符串和母字符串的首元素就相同呢?那么早就该采取子母字符串的指针同时后移再做比较的步骤了。假设母字符串中找遍了都没有子字符串首元素呢?应该返回NULL。假设子母字符串后移后(子母字符串前面相同后面又不同了)再比较发现母字符串元素和子字符串元素不同了呢?这时应该从母字符串中这个不同元素开始,和子字符串的首元素继续比较。假设子母字符串同时后移再比较都相同再后移却发现母字符串先达到尾部呢?这时说明找不到,应该返回NULL。
再说说程序设计时,while(1)的循环中,应该每次只处理或者判断一个字符,以母字符串为基础从头开始挨个进行处理。
试着设计一个伪代码(其实还是抄袭的原来的代码^_^,再结合着理解一遍的话,会更透彻一些吧):
while(1) //循环着开始 挨个处理 “字符们” { if (p1 == p2) //子字符和母字符相同的话,子母字符都往后移一位,并且要进行判断 { p2++; if(p2 == \0) return p1; //先判断子字符串(通常会短一些),是否到达结尾了,若是的话,也就是子字符串结尾前的字符都和母字符串中的字符能对上,也就是在母字符串中找到了子字符串,返回相应指针 p1++; if(p1 == \0) return NULL; //再判断子字符串在没有到达结尾的情况下(能运行这一步,其实也是在上一步的if不成立的条件下,即子字符没结束),母字符串是否达到结尾了。若是的话,则子字符串比母字符串长,所以返回NULL,即母字符串中找不到对应的子字符串;若不是的话,继续返回循环开始,比较移位后的字母字符。 } else //子字符和母字符不相同的话,母字符后移一位,再和子字符串比较(比较之前,要判断一下母字符串是否结束了。若是结束了,则说明母字符串到结束前没有对应上子字符串首元素,即母字符串中找不到子字符串,返回NULL;若是没结束,继续返回循环开始,比较移位后的母字符和 子字符。) { ++p1; if(p1 == \0) return NULL } }
好了,到此为止,算是理解了这个程序吧。只能赞美作者的思路清晰,办法巧妙,自己是想不到的啊,膜拜之余,我体会到了:a)自己想不到的不要硬想,清晰的思路和逻辑一方面需要静下来思考,另外一方面是需要灵感的,所以要多多体会和理解大神们的杰作,因为很多经验或者模板是很重要的;b)从程序倒推思路虽然看似“笨”,但是也是考验一个人学习能力的,多看多琢磨多体会,思路肯定会越来越清晰的,自己要静下来努力做。最后,要达到不看原来的程序能自己实现代码的程度吧,哪怕看起来非常像也不要紧,能加强理解。
附上自己复现的代码:
char *string_in(char *p1, char *p2) { char * p2_save = p2; while(1) //这个可不能少啊,开始居然忘了写这个 { if(*p1 == *p2) { p2++; if(*p2 == '\0') return (p1 - strlen(p2_save) + 1); //返回(p1指向的位置在往前退“子字符串长度”个单位) p1++; if(*p1 == '\0') return NULL; } else { p1++; if(*p1 == '\0') return NULL; } }
总的程序如下(仅供参考,倒没什么好看的):
#include <stdio.h> #include <string.h> char *string_in(char *p1, char *p2); int main(void) { char str1[81]; char str2[21]; char *p; do { puts("input range string:"); gets(str1); puts("input match string:"); gets(str2); p = string_in(str1, str2); if ( p ) { puts("Find!"); puts(p); } else puts("Can't find!"); puts("input any char except q to go on."); gets(str1); } while(*str1 != 'q'); puts("Quit."); return 0; } char *string_in(char *p1, char *p2) { char * p2_save = p2; while(1) //这个可不能少啊,开始居然忘了写这个 { if(*p1 == *p2) { p2++; if(*p2 == '\0') return (p1 - strlen(p2_save) + 1); //返回(p1指向的位置在往前退“子字符串长度”个单位) p1++; if(*p1 == '\0') return NULL; } else { p1++; if(*p1 == '\0') return NULL; }
} }
调试结果如下: