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中的起始位置
依照上述思想更改程序,“處理代碼”段如下:
- int s = 0; //記錄每個數據域起始位置的變量
- /* 以下為處理代碼 */
- strncpy(szAccno, szBuf+s, 19);
- szAccno[19]=0;
- s += 19;
- strncpy(szName, szBuf+s, 8);
- szName[8]=0;
- s += 8;
- strncpy(szAmt, szBuf+s, 12);
- szAmt[12]=0;
- s += 12;
- strncpy(szDate, szBuf+s, 8);
- szDate[8]=0;
- s += 8;
- strncpy(szLine, szBuf+s, 8);
- szLine[8]=0;
- s += 8;
- strncpy(szStatus, szBuf+s, 4);
- szStatus[4]=0;
- s += 4;
- strncpy(szBz, szBuf+s, 10);
- szBz[10]=0;
- 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]可以出現在循環代碼中,但是每個數據域的“域字符串變量”卻不一樣,最終代碼只能以如下的方式含恨收場:
- strncpy(szAccno, szBuf+s, len[0]);
- szAccno[len[0]]=0;
- s += len[0];
- strncpy(szName, szBuf+s, len[1]);
- szName[len[1]]=0;
- s += len[1];
思路到了這裡,算是卡住了,很多人可能打算就此收工,殊不知成功是無功而返之間也許只隔了辦毫米——據說萊斯研究的電話時因為一顆螺絲少擰了半毫米,結果被貝爾捷足先登——是該想辦法完成這最後的半毫米。
在上個世紀的中國點子為王,誰有一個好點子就可以成功。但現在不行了,這裡有了一顆把數據域長度集合到一個數組中的點子,但貌似程序化簡仍然沒有成功。這是因為現今思想大解放,好點子太多了,一個好點子不一定能夠成功——至少需要兩個。
回到程序,為了解決“域字符串變量”不一致的問題,完全可以這樣:
點子】
另外定義一個字符串數組varData[][20],其每個元素代表了一個數據域,當處理第i+1個數據域時,只需將數據拷貝入varData [i]即可。
處理代碼字段可以抽象為:
- //第i次循環
- strncpy(varData[i], szBuf + s, len[i]);
- varData[i][len[i]]=0;
- s = s + len[i];
於是,程序可以通過循環方式化簡如下:
- #include <stdio.h>
- #include <stdlib.h>
- #define LEN 7
- int main(int argc, char *argv[])
- {
- char szBuf[]="9559901010008888888木鴻飛 600.00 20110630063001230000測試一次 ";
- char varData[LEN][20];//記載所有數據域的數組
- int len[LEN] = {19, 8, 12, 8, 8, 4, 10};
- int s = 0, i;
- /* 以下為處理代碼 */
- for (i=0; i<LEN; i++)
- {
- strncpy(varData[i], szBuf + s, len[i]);
- varData[i][len[i]]=0;
- s += len[i];
- }
- /* 以下為打印代碼 */
- for (i=0; i<LEN; i++)
- {
- printf("第%d號域, %s】\n", i+1, varData[i]);
- }
- system("PAUSE");
- return 0;
- }
代碼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};
有關指針數組的詳細介紹,等到本書“指針與數組”一章中會有詳細介紹,這裡還是先看程序的改變結果,如下:
- #include <stdio.h>
- #include <stdlib.h>
- #define LEN 7
- int main(int argc, char *argv[])
- {
- char szBuf[]="9559901010008888888木鴻飛 600.00 20110630063001230000測試一次 ";
- char szAccno[20]; //代表"賬戶"
- char szName[9]; //代表"姓名"
- char szAmt[13]; //代表"交易金額"
- char szDate[9]; //代表"交易日期"
- char szLine[9]; //代表"交易流水號"
- char szStatus[5]; //代表"交易狀態"
- char szBz[11]; //代表"交易說明"
- char varData[LEN][20];//記載所有數據域的數組
- int len[LEN] = {19, 8, 12, 8, 8, 4, 10};
- char * varP[LEN] = {szAccno, szName, szAmt, szDate, szLine, szStatus, szBz};
- /* 以下為處理代碼 */
- int s = 0, i;
- for (i=0; i<LEN; i++)
- {
- strncpy(varP[i], szBuf + s, len[i]);
- (varP[i])[len[i]]=0;
- s += len[i];
- }
- /* 以下為打印代碼 */
- for (i=0; i<LEN; i++)
- {
- printf("第%d號域, %s】\n", i+1, varP[i]);
- }
- system("PAUSE");
- return 0;
- }
代碼1-3
總結】
數組的妙用之一就在於其能化繁雜為簡單,將一系列毫無聯系的內容集合在一個數組中,就可以通過循環的方式處理之,從而大大的簡化程序代碼。
作業3:
上述程序在設計好之後需求發生變更,報文格式變更如下:
字符串第1位~8位代表了“交易日期”; //位置提前
字符串第9位~20位代表了“交易流水號”; //位置提前,長度加長
字符串第21位~39位代表了“賬戶”;
字符串第40位~47位代表了“姓名”;
字符串第48位~63位代表了“交易金額”; //長度加長
字符串第64位~71位代表了“傳票號”; //新增域
字符串第72位~75位代表了“交易狀態”;
//取消了“備注”域。
字符串實例和變量情況如下:
- char szBuf[]="201106300630123456789559901010008888888木鴻飛 600.00 999912340000";
- char szAccno[20]; //代表“賬戶”
- char szName[9]; //代表“姓名”
- char szAmt[17]; //代表“交易金額”
- char szDate[9]; //代表“交易日期”
- char szLine[13]; //代表“交易流水號”
- char szStatus[5]; //代表“交易狀態”
- char szBill[9]; //代表“傳票”
作業4:
上述程序在設計好之後需求發生變更,運行結果要求打印每號域的域說明,比如:
第1號域,賬號,9559901010008888888】
第2號域,戶名,木鴻飛 】
前一篇 目錄 後一篇
PS1:歡迎跟帖,寫下自己的作業心得。
PS2:征求名稱
本書將講述數組相關的知識與應用,適用語言:C語言。
描述顯示:每次通過一個案例來說明。比如當前為字符串報文解析程序,接下來馬上使用音樂演奏程序。
目前考慮的名稱有:
1)數組達人成長之路。
2)我愛數組
3)別告訴我你懂數組
4)數組玩轉趣味程序
你覺得那個名稱更加吸取眼球,或者你有什麼好的建議,歡迎跟帖。
本文出自 “編程浪子朱雲翔” 博客,轉載請與作者聯系!