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