过马路,左右看
小时候,大人们总是教导小孩子“过马路,左右看”。我年纪小不懂得为什么,但是由于大人们唠叨得遍数太多,以至于都深深印在脑海里,成为了潜意识。每每过马路的时候,不由自主得左右看看。长大以后,渐渐的喜欢考虑问题,凡事问个为什么:为什么是“过马路,左右看”而不是“过马路,右左看”?有朋友告诉我说那是因为中国话的习惯就是左右、男女、老幼之类的说,就像“决一雌雄”不叫作“决一公母”一样只是个习惯而已。可是,我觉得习惯是养成的,如果天天说“过马路,右左看”说多了,反倒这么说比较习惯——这也说不准那~ :)
其实,仔细想想,“过马路,左右看”是个很有道理的话。中国的车辆都是靠右行驶,所以过马路的时候前一半的过程(就是从路始边走到路中间的过程)总是车辆从我们左边开过来,而后一半的过程(从路中间到路右边的过程)总是车辆从右边开向左边。
于是,这个俗语就教导我们先看左边再看右边,甚至可以说走前一半的时候可以只看左边,走后一半的时候可以只看右边。如果把口诀反过来做,就做了无用功;如果没有这个口诀,没准过马路时头会摇的跟一个波浪鼓似的。(注1)。
中国人做事很讲究总结一个口诀,内功讲究心法,练刀有刀谱,练剑有剑诀,甚至书圣王曦之还写了个什么书诀(我忘了多少招了,那个兄弟提示下)。再比如《西游记》中说孙悟空过火焰山的时候把毛都烤糊了,于是左手捏了个避火诀冲了下去。这说明口诀很有用,本领大如孙大圣都用的上:虽然你不知道他是怎么来的,但是照着口诀做就没错——老祖宗已经验证过了才总结成口诀传下来。
其实软件开发的时候也有很多口诀,即便是不明白道理,但是照着做,总没错。如果不照着做,没准还真就捅了篓子,或者在代码中埋下了一时难以出现的隐患。下面就举一个C中的应用口诀的例子(C++我不懂,就不献丑拉。~)
比如,有个口诀叫做“定义宏常量或宏公式的时候,一定要加圆括号”,有很多人就不理解,于是他们就不加,结果就出了类似于如下的问题:
#define NUM_A 100#define NUM_B 25 * 2#define int_div(a, b) a / bfloat c;c = int_div(NUM_B, NUM_A); //作者原意是 50/100,实际结果是 25 * 2 / 100, 结果没错c = int_div(NUM_A, NUM_B); //作者原意是 100/50,实际结果是 100 / 25 * 2,结果错了。试想,如果我们就按照口诀来操作:#define NUM_A (100) #define NUM_B (25 * 2)#define int_div(a, b) ( (a) / (b) )
又怎么会出错呢? 再举个例子,比如有个口诀叫做“头文件里面只声明不定义”。如果不照这个口诀操作,也会出问题。比如:
//a.hint max(int a, int b){ return a>b?a:b;}//main.c#include "a.h"#include "a.h" //include 两遍后就会重复定义max(),结果出重复定义的错。int main(){return 0;}
你肯能会说,我在.h里面加点料儿,保证它不重复定义,比如:
//a.h#ifndef _A_H_#define _A_H_int max(int a, int b){ return a>b?a:b;}#endif //_A_H_
Okey,现在上面那个case确实搞定了(注2)。但是下面这个就又不行了:
//a.h#ifndef _A_H_#define _A_H_int max(int a, int b){ return a>b?a:b;}#endif //_A_H_//b.h#ifndef _B_H_#define _B_H_void foo();#endif //_B_H_//b.c#include "a.h"void foo(){ max(100, 200);}//c.h#ifndef _C_H_#define _C_H_void bar();#endif //_C_H_//c.c#include "a.h"void bar (){ max(300, 400);}//main.c#include "b.h"#include "c.h"int main(){ foo(); bar();return 0;}
分开编译b.c、c.c和main.c都还可以通过,但是最后一link就玩儿完,因为max()被定义了两遍。所以如果max()的定义放在a.c文件里,而在a.h中只写max()的声明就不会有这个问题了。(注3)
综上所述,我想说的是:如果能通透理解口诀的含义那是最好(达到了心中有剑、见招拆招的境界,每招虽无口诀的形但是却暗含了口诀的神),如果不能通透理解,不妨先照着口诀做,而不是总跟前人总结口诀对着干,真是“不听老人言,吃亏在眼前”。做的多了,也就慢慢悟了,可以细细琢磨口诀的真正含义,总结口诀应用的场景,从而达到避开“听了老人言,吃亏在后面”的境界了!:p 这就是否定之否定么?我瞎说的。^_^
- 注1:后来我想到:英国的车是靠左边开的,会不会他们教小孩子的口诀是:
when going across the road, look right then left! :p - 注2:这里用了“头文件里面要加guard宏”的口诀,所以避免了一个问题!:p
- 注3:后来释雪提出来,可以在.h中定义的函数加个inline来搞定。这个例子举的不好,我只是觉得函数定义和声明的概念比变量定义和声明的概念容易描述清楚才举这个例子。其实对于出“函数定义写在.h中”的错还真不多见;不过把数组定义和初始化写在.h文件中可真就屡见不鲜了,有时候还真是让人头疼的狠。