RSS 2.0 Feed
行医记
摘要:我是一个中医的支持者。中医虽然被认为不科学,但往往能解决西医不能解决的事情。这其实不奇怪,中医的起点高,他立足于提高人体免疫力,激发人体自然力量去对付入侵者,而不是西医那样头疼医头。我承认大多数情况下,西医是立竿见影药到病除的,不过有谁通过常看西医打针吃药开刀体质好起来的么?越看身体越差的倒是不少。我认为我们的终极目标是强健身体即免疫力的增强,而不是治好某几种特定的病,因此满足于立竿见影药到病除是不够的。 大象无形,泛泛而谈的道理,一般具有普适性。 软件开发中,何尝没有类似的情况! 非常多的程序员在面临问题的时候,本能的反应是--看书学习,希望有那么一本书的一个章节正好讲述了自己的问题;或者求助于人希望他人解决过自己的问题。一般情况下,这是可行的,是"立竿见影药到病除的",甚至那位"西医式"的程序员还为自己又学到一个技巧而高兴,然后暗暗把这个技巧记在心里。然而,一个程序员在其程序生涯中,遇到的问题数会是一个天文数字。每个问题的时候都要靠这样"学一个技巧"来解决的话,我们就可以一直听到"做程序员很要不停地学习不然就落后了,太累了"这样的感叹了。 我想那些"好学"的程序员们的终极目的,是成为所谓的"高手"吧。然而什么是"高手"?是会很多语言,用过很多工具,记着很多技巧的人么?如果那样,迪杰克斯特拉(数据结构课本里有他,经典无向图中两点最短路径算法的提出者)在世的话一定成为不了"高手",99%的社区上的提问他是回答不了的。 可是他却是无可争议的令人高山仰止的高手,因为他的思维深邃且严谨,科学且神奇,而思维的力量才是真正的力量,决定人技术水平高下的最关键因素。令人庆幸的是,这种力量不会随着技术发展而衰弱,永远不会过时。所以他始终是高手,而我们虽然渺小,但只要向着他的方向努力,以提高思考能力为宗旨并贯彻到日常的工作中去,成为那样的大师的可能性固然可以忽略不计,但至少可以做一个不"紧跟时代"也永不"落伍过时"的中医。 所以我要写行医记来和大家切磋思维。行医记里面提及的问题,一般不会有直接的应用,但是却是很好的思维锻炼场。行医记的行文以医生和病人的对话为主。其中病人的话,是我个人认为程序员通过独立思考能推理出的结果;医生的话则偏向概念,引导和总结。希望这些故事能对提高读者的思维有所帮助,最终可以把"做程序员很要不停地学习不然就落后了,太累了"这样的话送进故纸堆去。 不过我个人水平有限,也许某些病人的话的力度不够,这意味着我的思考强度还不够,能推理出的东西我却判断为需要学习才能得知,如果大家发现这样的地方,请一定要告诉我因为我也很想加强自己的思维!...[阅读全文]

posted @ | Feedback (0) |

摘要:病人:医生,局部变量超出作用域之后会发生什么事?我为此头疼了很久。 中医:哦,它们不能被访问了,消亡了。你的病不会这么简单吧,到底什么问题,详细描述一下。 病人:我想知道的是,指针所指的局部变量,超出作用域之后,那个指针的行为。比如这段程序 #include "stdio.h" int main(int argc, char* argv[]) { int i=10; int *piToTest=&i; printf("%d\n",*piToTest); { int iGone=20; piToTest=&iGone; // <---咔咔 } printf("%d\n",*piToTest); // <---啊啊 return 0; } //////////////////////////////////////////////////////////////// //// Figure 1. //////////////////////////////////////////////////////////////// 运行结果是打出“20”,正是*piToTest生前的值,难道iGone其实没有消亡,永远活在我们心中? 中医:嗯,你要这么说的话,iGone“生前”究竟活在什么地方呢?用术语来说,其storage在何处? 病人:哦哦,不知道。 中医:这就对了,你的问题的本质正是局部变量的storage问题,所以你会头疼。现在这个问题先搁置一下,你也不知道函数传递参数的机制对么? 病人:对。 中医:我们得从这里着手。每次函数调用,都要有一块内存存放其参数(不妨假设所有函数都有一些参数), 而编译时刻无从知道某个函数究竟会被(递归)调用几次,要为他留多少份参数的空间,对不对? 病人:。。。 对,即使是有一个main函数,也不妨碍他调用自己n次m层。放参数的地方,必定是一个很“动态”的地方。 中医:这个很动态的地方,术语叫堆栈(简称“栈”)。其运作机制和数据结构中的堆栈一样,都是先进后出所有操作都在栈顶发生,不过这里的堆栈是由CPU和OS来实现的。 函数调用的时候,①把其参数push进堆栈,②把返回地址push进堆栈,③跳转到函数入口地址。 void func1(int a,int b); ... func1(10,20); ... ┌--------------------┐ │返回地址 │ ├--------------------┤ │左面的参数 -- 10 │ ├--------------------┤ │最右面的参数 -- 20 │ ├--------------------┤ │之前的堆栈 │ //////////////////////////////////////////////////////////////// //// Figure 2. 进入func1时刻的堆栈情况(假定堆栈向上生长) //// 参数自右向左入栈,最后是返回地址 //////////////////////////////////////////////////////////////// 现在,你说说函数返回的时候,应该发生什么事。 病人:我猜是调用的逆序列吧。(1)取得返回地址跳转回去,(2)堆栈恢复成“之前的堆栈” 中医:很好,请记住(1)是callee做的事,而(2)是caller的责任。看完病之后,你再想一下printf之类不定个数参数函数的机制来理解这样做的必要性。不过现在,我们得整理一下函数调用的过程。 ①caller 把其参数push进堆栈 ②caller 把返回地址push进堆栈 ③跳转到函数入口地址。 ④callee的函数体被执行 ⑤callee取得返回地址跳转回去 ⑥caller把堆栈恢复成“之前的堆栈” //////////////////////////////////////////////////////////////// //// Figure 3. 函数调用的过程 //////////////////////////////////////////////////////////////// 病人:嗯,这些我理解了。这和我的病根“局部变量的storage问题”之间的关系是--? 中医:真的理解了么?这里面还隐含了一个前提,callee必定可以取得返回地址。 病人:这还用推,不就在栈顶么? 中医:嘿嘿,你这句话也隐含了一个前提,函数体内,除了调用函数这样的“堆栈平衡的操作”,堆栈不生长,不然返回地址不会在栈顶让你唾手可得。 病人:难道不是? 。。。 啊!......[阅读全文]

posted @ | Feedback (6) |

摘要:病人:医生,我折腾了许久,无法理解函数指针。中医:能意识到自己不理解,不错。那么你说说你目前的理解。函数指针是什么?病人:函数指针是指向函数的指针。 中医:那么函数是什么?病人:函数就是函数。中医:不是指向函数的指针?病人:自然不是。 中医:那函数怎么可以赋值给函数指针?难道int可以赋值给int* ?病人:这个。。。。。。 中医:逻辑不通了吧?病人:是啊,怎么回事哩? 中医:这个问题先搁置一下,我问你,什么是指针?病人:是放地址的变量。中医:函数指针里面放的什么?病人:函数入口地址。中医:那么函数指针就是放函数入口地址的变量?病人:  (小心地)我同意。中医:函数是放函数入口地址的常量。病人:哇!这样一来就好解释了!函数赋值给函数指针就像把常量赋值给同类型变量! 中医:还有问题吗?病人:有,"函数是放函数入口地址的常量。"这句话不通啊。 函数是放自己入口地址的东西?中医:孺字可教。这里"函数入口地址"是一个词,不能拆。真正的函数,无非是一块代码, C/C++中没有描述"一块代码"的东西, 只有描述"一块代码"的入口地址的东西,函数及函数指针。病人:我懂了,"函数指针是指向函数(1)的指针"和"函数(2)不是指向函数(3)的指针"的矛盾, 出自"函数(1)"的是你刚才说的"真正的函数", 函数(2)(3)指的C/C++语法意义上的"函数",两码事! 中医:嗯,有道理。那么还有问题吗?     病人:"函数指针是指向函数的指针。"这句话固然误导人,不过C/C++的语法,也起到了推波助澜的作用。 //////////////////////////////////#include typedef int (*FN_HAHA)();int real_haha(){ return printf("haha\n");} void main(int argc, char* argv[]){ FN_HAHA haha=real_haha; real_haha(); (*haha)(); }////////////////////////////////// 既然  haha 和 real_haha是一个层次上的东西,那么调用的时候为什么 一个 "real_haha();",一个(*haha)()哩?很明显是在搞分化,搞脑子。 医生:连C/C++语法你都敢批评,强的!    正如你所说,这不是好的语法,所以现在的编译器,比如VC和gcc,    都允许用 haha();来代替传统的(*haha)();你44就知道了。      至于书上都写(*haha)();我只能说,       这个问题我自己也被书害了很久,最后扔了书自己想通的。病人:我的病好了,我回去也把书扔了。  ...[阅读全文]

posted @ | Feedback (36) |