别告诉我你懂数组(2)

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】\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的下标。

【疑问】

需求中要求将各个域数据存入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】\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)数组玩转趣味程序

你觉得那个名称更加吸取眼球,或者你有什么好的建议,欢迎跟帖。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值