程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 別告訴我你懂數組(2)

別告訴我你懂數組(2)

編輯:關於C語言

2、整潔的代碼

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

程序變】

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

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

    域字符串變量[長度]=0;

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

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};

以上更改似乎大功告成,很多紙上談兵者也是這麼認為的,其實不然,因為雖然s和len[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】\n",  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的下標。

疑問】

需求中要求將各個域數據存入szAccno、szName等數組中,而代碼1-3似乎太過於大膽了,居然私自更改需求,將之存儲與另外的字符串數組中,如此行為,是可忍熟不可忍。

編程浪子曰】

用戶的需求是可以引導的。

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

程序又變】

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

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

    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】\n",  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)數組玩轉趣味程序

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

 

 

本文出自 “編程浪子朱雲翔” 博客,轉載請與作者聯系!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved