2、整潔的程式碼

程式1-1的三宗罪,分別是:程式碼冗長、容易出錯和重用效果差。當然羅,現在網路上面最流行找茬,別說三宗,n宗都可以找到。其實無論在網路上還是工作中,找茬都不是問題,問題是找到“茬”之後如何解決之。

【程式變】

應該說,程式1-1的“處理程式碼”段遵循如下規則:

    strncpy(域字串變數, szBuf+域起始位置, 域長度);

    域字串變數[長度]=0;

其中,“字串變數”和“域長度”在需求中已經確定了,而“域起始位置”則是由手工計算然後寫入,若是某一步計算失誤,則會引起後續一系列的錯誤。既然如此,為什麼不修改手工計算為程式計算,比如設定一個整型變數s來“域起始位置”(就是程式碼中的192739等數字),初始時:

s=0

每計算完一個域後,s向前移動到新的域起始位置,移動公式為:

s=s+長度;

如此就可以完全避免由於人工計算錯誤而造成的bug,程式碼1-1中“處理程式碼”部分變化如下:

    strncpy(域字串變數, szBuf + s, 域長度); //s代表了當前域在szBuf的起始位置

    域字串變數[域長度]=0;                                                            

    s = s + 域長度;                                    //更改後,s為下一個域在szBuf中的起始位置

依照上述思想更改程式,“處理程式碼”段如下:

 

  1. int s = 0;            //記錄每個資料域起始位置的變數   
  2. /* 以下為處理程式碼 */   
  3. strncpy(szAccno, szBuf+s, 19);  
  4. szAccno[19]=0;  
  5. s += 19;  
  6. strncpy(szName, szBuf+s, 8);  
  7. szName[8]=0;  
  8. s += 8;  
  9. strncpy(szAmt, szBuf+s, 12);  
  10. szAmt[12]=0;  
  11. s += 12;  
  12. strncpy(szDate, szBuf+s, 8);  
  13. szDate[8]=0;  
  14. s += 8;  
  15. strncpy(szLine, szBuf+s, 8);  
  16. szLine[8]=0;  
  17. s += 8;  
  18. strncpy(szStatus, szBuf+s, 4);  
  19. szStatus[4]=0;  
  20. s += 4;  
  21. strncpy(szBz, szBuf+s, 10);  
  22. szBz[10]=0;  
  23. s += 10; 

程式碼1-2

【程式設計浪子曰】

實現一個同樣的功能,程式的質量與程式碼行的多少並無直接聯絡。

正如文學寫作時“有言則長,無言則短”一般,篇幅的長度不是決定若貝爾文學獎的因素,程式的質量也一樣。對比程式碼1-1和程式碼1-2,顯然前者的程式碼行要比後者的少,質量卻不如後者,原因有二:

其一,程式1-1更容易犯錯,由於需要手工計算資料寫入程式碼中,倘若一步失誤則步步失誤。後者增加了一行程式碼自動實現這個計算過程,不存在失誤的可能性。

其二,程式1-1更難於維護。倘若資料域長度或者順序發生變化,則需要重新計算所有資料域的起始位置,而程式1-2中只需要更改程式碼行“s+=域長度”中的域長度即可。

【程式再變】

再次回顧程式碼1-2,“左看右看上看下看”還是都覺得不順眼,總覺得不應該為每一個資料域單獨編寫程式碼,這樣既帶來了工作量又容易出錯還不利於擴充套件,如果能夠使用一個迴圈來完成,那就太好不過了。

影響迴圈的攔路虎之一就是每個域長度無法統一,不能寫入迴圈程式碼中,難啊!

滄海橫流,方顯英雄本色!這個時間就需要陣列出馬了。

【程式設計浪子曰】

把一堆毫無關聯的資料組合到一個陣列中,就可以通過陣列名稱和下標以迴圈的方式來訪問了。

定義一個整型陣列len描述每一個資料域的長度,其中len[0]代表第一個域(賬號)的長度,len[1]代表第二個資料域姓名的長度,以此類推,則處理程式碼欄位可以抽象為:

//i次迴圈——len[i]是當前處理資料域的長度

    strncpy(域字串變數, szBuf + s, len[i]);      

    域字串變數[len[i]]=0;                                                        

    s = s + len[i];                      

其中,長度陣列len的定義如下:

int len[] = {19, 8, 12, 8, 8, 4, 10};

以上更改似乎大功告成,很多紙上談兵者也是這麼認為的,其實不然,因為雖然slen[i]可以出現在迴圈程式碼中,但是每個資料域的“域字串變數”卻不一樣,最終程式碼只能以如下的方式含恨收場:

 

  1. strncpy(szAccno, szBuf+s, len[0]);  
  2. szAccno[len[0]]=0;  
  3. s += len[0];  
  4. strncpy(szName, szBuf+s, len[1]);  
  5. szName[len[1]]=0;  
  6. s += len[1]; 

思路到了這裡,算是卡住了,很多人可能打算就此收工,殊不知成功是無功而返之間也許只隔了辦毫米——據說萊斯研究的電話時因為一顆螺絲少擰了半毫米,結果被貝爾捷足先登——是該想辦法完成這最後的半毫米。

在上個世紀的中國點子為王,誰有一個好點子就可以成功。但現在不行了,這裡有了一顆把資料域長度集合到一個陣列中的點子,但貌似程式化簡仍然沒有成功。這是因為現今思想大解放,好點子太多了,一個好點子不一定能夠成功——至少需要兩個。

回到程式,為了解決“域字串變數”不一致的問題,完全可以這樣:

【點子】

另外定義一個字串陣列varData[][20],其每個元素代表了一個資料域,當處理第i+1個資料域時,只需將資料拷貝入varData [i]即可。

處理程式碼欄位可以抽象為:

 

  1. //第i次迴圈  
  2.     strncpy(varData[i], szBuf + s, len[i]);     
  3.     varData[i][len[i]]=0;                           
  4.     s = s + len[i];    

 

於是,程式可以通過迴圈方式化簡如下:

 

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.  
  4. #define LEN 7  
  5. int main(int argc, char *argv[])  
  6. {  
  7.     char szBuf[]="9559901010008888888木鴻飛  600.00      20110630063001230000測試一次  ";  
  8.     char varData[LEN][20];//記載所有資料域的陣列   
  9.     int len[LEN] = {19, 8, 12, 8, 8, 4, 10};  
  10.     int s = 0, i;  
  11.  
  12.     /* 以下為處理程式碼 */   
  13.     for (i=0; i<LEN; i++)  
  14.     {  
  15.         strncpy(varData[i], szBuf + s, len[i]);  
  16.         varData[i][len[i]]=0;  
  17.         s += len[i];  
  18.     }      
  19.          /* 以下為列印程式碼 */ 
  20.     for (i=0; i<LEN; i++)  
  21.     {  
  22.         printf("第%d號域, 【%s】 ",  i+1, varData[i]);  
  23.     }      
  24.     system("PAUSE");    
  25.     return 0;  
  26. }  
  27.  

程式碼1-3

【技巧】

其一、上述程式中使用了巨集“#define LEN 7”,利用LEN來代替數字7,這種做法是為了便於程式擴充套件。假設不使用巨集LEN,則程式碼中必定多次出現數字7(本處為4次),而一旦需求變更,資料域數量增加或是減少,就必須更改程式碼中的每一次,一旦漏掉了某處,都將給程式程式碼不可估量的損失。使用巨集LEN後,只需一次更改即可。

其二、數字元素本身可以作為陣列的下標,比如表示式varData[i][len[i]]中,陣列元素len[i]成為了二維陣列varData的下標。

【疑問】

需求中要求將各個域資料存入szAccnoszName等陣列中,而程式碼13似乎太過於大膽了,居然私自更改需求,將之儲存與另外的字串陣列中,如此行為,是可忍熟不可忍。

【程式設計浪子曰】

使用者的需求是可以引導的。

很多時候,使用者並不是特別清楚自己的需求,尤其在某些細節方便。比如本處,開發者完全可以引導客戶,比如說:“資料域的儲存位置無所謂,只要能夠拆分出來就可以了。”

【程式又變】

但是,有的時候,有的客戶是很執著的,他認定的需求就是不能更改,此時需求再難也必須不折不扣的完成。

再次回到程式的處理核心,如下:

    strncpy(域字串變數, szBuf + s, len[i]);      

    域字串變數[len[i]]=0;                                                        

    s = s + len[i];                      

其實,前面已經有了成功方案,就是將一群完全不關聯的資料域長度集合到一個整型陣列中。既然可以集合整數,當然也可以集合“字串變數”,要知道字串變數本質上就是指標,那麼完全可以將之集合到一個指標陣列之中。

【點子】

定義一個陣列varP,其元素的型別是字串指標(char *),其元素分別記載了各個儲存資料域的的儲存位置。

大家千萬不要一聽說“指標陣列”就覺得一個頭有兩個大,其實很簡單,就是一個陣列,這陣列的每個元素都是一個指標,其定義方式如下:

char *varP[LEN];

倘若還不明白,就看看它的賦值語句:

varP[0] = szAccno;

varP[1] = szName;

……

varP[6] = szBz;

當然,上述的賦值過程還是過於複製,至少需要n條語句,其實是可以化簡的,如下所示:

char * varP[LEN] = {szAccno, szName, szAmt, szDate, szLine, szStatus, szBz};

有關指標陣列的詳細介紹,等到本書“指標與陣列”一章中會有詳細介紹,這裡還是先看程式的改變結果,如下:

 

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.  
  4. #define LEN 7  
  5. int main(int argc, char *argv[])  
  6. {  
  7.     char szBuf[]="9559901010008888888木鴻飛  600.00      20110630063001230000測試一次  ";  
  8.     char szAccno[20]; //代表"賬戶"  
  9.     char szName[9];      //代表"姓名"  
  10.     char szAmt[13];      //代表"交易金額"  
  11.     char szDate[9];      //代表"交易日期"  
  12.     char szLine[9];      //代表"交易流水號"  
  13.     char szStatus[5]; //代表"交易狀態"  
  14.     char szBz[11];       //代表"交易說明"  
  15.     char varData[LEN][20];//記載所有資料域的陣列   
  16.     int len[LEN] = {19, 8, 12, 8, 8, 4, 10};  
  17.     char * varP[LEN] = {szAccno, szName, szAmt, szDate, szLine, szStatus, szBz};  
  18.     /* 以下為處理程式碼 */   
  19.     int s = 0, i;  
  20.     for (i=0; i<LEN; i++)  
  21.     {  
  22.         strncpy(varP[i], szBuf + s, len[i]);  
  23.         (varP[i])[len[i]]=0;  
  24.         s += len[i];  
  25.     }      
  26.     /* 以下為列印程式碼 */ 
  27.     for (i=0; i<LEN; i++)  
  28.     {  
  29.         printf("第%d號域, 【%s】 ",  i+1, varP[i]);  
  30.     }      
  31.  
  32.     system("PAUSE");    
  33.     return 0;  
  34. }  
  35.  

 

程式碼1-3

【總結】

陣列的妙用之一就在於其能化繁雜為簡單,將一系列毫無聯絡的內容集合在一個陣列中,就可以通過迴圈的方式處理之,從而大大的簡化程式程式碼。

作業3

上述程式在設計好之後需求發生變更,報文格式變更如下:

字串第1位~8位代表了“交易日期”;        //位置提前

字串第9位~20位代表了“交易流水號”;     //位置提前,長度加長

字串第21位~39位代表了“賬戶”;

字串第40位~47位代表了“姓名”;

字串第48位~63位代表了“交易金額”;      //長度加長

字串第64位~71位代表了“傳票號       //新增域

字串第72位~75位代表了“交易狀態”;

//取消了“備註”域。

字串例項和變數情況如下:

 

  1. char szBuf[]="201106300630123456789559901010008888888木鴻飛  600.00          999912340000";  
  2. char szAccno[20];          //代表“賬戶”  
  3. char szName[9];          //代表“姓名”  
  4. char szAmt[17];           //代表“交易金額”  
  5. char szDate[9];            //代表“交易日期”  
  6. char szLine[13];          //代表“交易流水號”  
  7. char szStatus[5];         //代表“交易狀態”  
  8. char szBill[9];           //代表“傳票” 

作業4

上述程式在設計好之後需求發生變更,執行結果要求列印每號域的域說明,比如:

1號域,賬號,【9559901010008888888

2號域,戶名,【木鴻飛  

 前一篇   目錄   後一篇

PS1:歡迎跟帖,寫下自己的作業心得。

PS2:徵求名稱

本書將講述陣列相關的知識與應用,適用語言:C語言。

描述顯示:每次通過一個案例來說明。比如當前為字串報文解析程式,接下來馬上使用音樂演奏程式。

目前考慮的名稱有:

(1)陣列達人成長之路。

(2)我愛陣列

(3)別告訴我你懂陣列

(4)陣列玩轉趣味程式

你覺得那個名稱更加吸取眼球,或者你有什麼好的建議,歡迎跟帖。