題目要求“寫一個函數,輸入一行字符,將此字符串中最長的單詞輸出”,可是無論alphabetic()還是longest()函數都沒有實現“輸入一行字符,將此字符串中最長的單詞輸出”這個功能要求。疑惑很久,發現實現這個功能的函數居然是main()。這就難免讓人貽笑大方了。因為按照常規的慣例,要求寫一個函數實現某個功能,從來不是要求寫main(),盡管不能說main()不是“一個函數”。然而如果是要求main()完成的事情,通常是作為一個完整的問題提出的,不會提出“寫一個函數”這樣的要求。如果硬要狡辯“寫一個函數”也不排除是寫main(),就牽強的近乎強詞奪理了。不過設若真的有人如此嘴硬,你還真拿他沒什么辦法。
在代碼中一眼瞄見了flag這個變量。經驗表明,凡是有這個flag變量的代碼,80%以上都是垃圾代碼。道理很簡單:首先,多數問題根本不需要設置這個別別扭扭標志變量,只有那些善于把自己的思維扭曲得如同爛麻花一樣的人才喜歡時不時地祭出flag這個破爛的法寶。其次,即使需要設置標準變量,優秀的代碼作者也不會使用這個含義模糊不清的名字作為標志變量名,而會用一個更貼切、意義更明確恰當更適合描述問題的名字。所以,一般來說,flag往往反映了代碼的垃圾度。
對于垃圾代碼,沒必要進行過于細致的分析,只要指出錯誤即可。不要試圖了解這種代碼的思路,因為這種代碼的思路本來就是錯亂不堪的,就如同不要試圖理解瘋子的胡言亂語一樣。不要試圖修繕一座胡亂搭建起來的破爛不堪的危房,推倒重來才是明智的選擇。
然而,找出程序的漏洞或錯誤,往往比完成程序要難得多。而且越是垃圾的代碼越難查錯,因為垃圾代碼往往也不具備良好的可測試性。
但是對付這種可測試性極差的垃圾代碼,有一些簡單的辦法往往非常容易奏效,比如邊界檢查。訓練有素的程序員通常都特別注意邊界,無論是寫代碼時還是檢查代碼時。因為他們知道這里非常容易出錯,而且往往失之毫厘謬之千里。但垃圾代碼的作者,由于代碼是東拼西補、胡亂拼湊而成的,所以往往顧不上或考慮不到這些,因此垃圾代碼很容易被“邊界檢查”這把小刀輕而易舉地戳破。以alphabetic()函數為例,只要簡單地考察一下其中if語句所要求的表達式——(c>='a'&&c<='z')||(c>='A'&&c<='z'),就不難發現c<='z'這個子表達式是c<='Z'之誤。這樣就充分說明原代碼中存在著BUG。
順便說一下,alphabetic()函數中的if-else語句用得非常愚蠢,因為(c>='a'&&c<='z') || (c>='A'&&c<='Z')這個表達式的值本身就只能為0或1,所以直接返回這個表達式的值就可以了。壓根用不著脫褲子放屁地寫一個if-else語句。
或許,有人會認為這是一個簡單的筆誤或印刷錯誤,修正了這個錯誤原來的代碼是正確的。那么好吧,下面改正這個錯誤后再來運用一次簡單的邊界測試。
由于問題要求輸出一行字符中最長的單詞,而一行字符中可能有0個單詞、1個單詞、2個單詞……。注意,這里0個單詞的情況就是一種邊界情況,運行這個程序并輸入0個單詞(輸入一行不含任何字母的字符,因為代碼作者把連續的若干字母字符作為一個單詞),后果居然是——運行時程序崩潰了。這個結果絕對可以充分說明原來的代碼是錯誤的。
這個結果是如何產生的呢?只要在紙上走查一遍,就不難發現,輸入一行不含任何字母的字符時,longest()函數中嵌套在for語句內部的if語句將毫無意義地反復執行
{flag=1; if(len>=length) {length=len; place=point; len=0; } } |
部分,而其中的賦值給place的point卻居然是一個不確定的垃圾值。
應該如何正確地給出這個問題的代碼呢?正確解決問題的前提是正確地提出問題。原來問題的提法本身就有很多不正確或不嚴謹的地方。例如,“將此字符串中最長的單詞輸出”,這個要求本身就是似是而非很不明確的。比如,字符串中有兩個單詞長度相同且都長于其他單詞,究竟應該輸出這兩個單詞中的任何一個還是需要同時輸出這兩個單詞?再有,要求函數“輸入一行字符”也非常無聊。為了能正確地解決問題,有必要對原問題的錯誤要求進行如下更正:
寫一個函數,輸出字符串中的任一長度最長的單詞。這里所謂的單詞,是指不含空白字符的連續字符序列。
#include <stdio.h> void print_a_longestword ( const char [] ) ; int be_white ( const char ) ; int find_begin( char const [] , unsigned ) ; int find_end ( char const [] , unsigned ) ; void output ( char const [] , unsigned , unsigned ) ; int main( void ) { printf("%s中一最長單詞為:",""); //測試"" print_a_longestword(""); printf("%s中一最長單詞為:"," \n\t "); //測試" \n\t " print_a_longestword(" \n\t "); printf("%s中一最長單詞為:"," abc "); //測試" abc " print_a_longestword(" abc "); printf("%s中一最長單詞為:"," abc \tabcd "); //測試" abc \tabcd " print_a_longestword(" abc \tabcd "); return 0; } void output( char const str[] , unsigned from , unsigned to ) { while(from < to) putchar(str[from ++]); putchar('\n'); } int find_end ( const char str[] , unsigned from ) { while( str[from]!='\0' && ! be_white( str[from] ) ) from ++ ; return from ; } int find_begin ( const char str[] , unsigned from ) { while( be_white( str[from] ) ) from ++ ; return from ; } int be_white( const char c ) { return c == ' ' || c == '\t' || c == '\n' ; } void print_a_longestword ( char const line[] ) { unsigned site = 0U ; unsigned begin_longest , end_longest ; begin_longest = end_longest = site ; do{ int this_begin , this_end ; site = this_begin = find_begin ( line , site ) ;//單詞開頭 site = this_end = find_end ( line , site ) ;//單詞結尾 if( ( this_end - this_begin ) > ( end_longest - begin_longest ) ){ begin_longest = this_begin ; end_longest = this_end ; } }while( line[ site ] != '\0') ; output( line , begin_longest , end_longest ); } |