小時候,大人們總是教導小孩子“過馬路,左右看”。我年紀小不懂得為什麼 ,但是由於大人們唠叨得遍數太多,以至於都深深印在腦海裡,成為了潛意識。每每過馬路 的時候,不由自主得左右看看。長大以後,漸漸的喜歡考慮問題,凡事問個為什麼:為什麼 是“過馬路,左右看”而不是“過馬路,右左看”?有朋友告訴我說 那是因為中國話的習慣就是左右、男女、老幼之類的說,就像“決一雌雄”不叫 作“決一公母”一樣只是個習慣而已。可是,我覺得習慣是養成的,如果天天說 “過馬路,右左看”說多了,反倒這麼說比較習慣——這也說不准那 ~ :)
其實,仔細想想,“過馬路,左右看”是個很有道理的話。中國的 車輛都是靠右行駛,所以過馬路的時候前一半的過程(就是從路始邊走到路中間的過程)總 是車輛從我們左邊開過來,而後一半的過程(從路中間到路右邊的過程)總是車輛從右邊開 向左邊。
於是,這個俗語就教導我們先看左邊再看右邊,甚至可以說走前一半的時候可以只看左 邊,走後一半的時候可以只看右邊。如果把口訣反過來做,就做了無用功;如果沒有這個口 訣,沒准過馬路時頭會搖的跟一個波浪鼓似的。(注1)。
中國人做事很講究總結一 個口訣,內功講究心法,練刀有刀譜,練劍有劍訣,甚至書聖王曦之還寫了個什麼書訣(我 忘了多少招了,那個兄弟提示下)。再比如《西游記》中說孫悟空過火焰山的時候把毛都烤 糊了,於是左手捏了個避火訣沖了下去。這說明口訣很有用,本領大如孫大聖都用的上:雖 然你不知道他是怎麼來的,但是照著口訣做就沒錯——老祖宗已經驗證過了才總 結成口訣傳下來。
其實軟件開發的時候也有很多口訣,即便是不明白道理,但是照著 做,總沒錯。如果不照著做,沒准還真就捅了簍子,或者在代碼中埋下了一時難以出現的隱 患。下面就舉一個C中的應用口訣的例子(C++我不懂,就不獻丑拉。~)
比如,有個 口訣叫做“定義宏常量或宏公式的時候,一定要加圓括號”,有很多人就不理解 ,於是他們就不加,結果就出了類似於如下的問題:
#define NUM_A 100
#define NUM_B 25 * 2
#define int_div(a, b) a / b
float 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.h
int 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文件中可真就屢見 不鮮了,有時候還真是讓人頭疼的狠。