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】 ", 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】 ", 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)陣列玩轉趣味程式
你覺得那個名稱更加吸取眼球,或者你有什麼好的建議,歡迎跟帖。