软件开发环境2良好的程序风格良好的程序风格是程序构架的基础2.1常量的运用常量是运行中不可改变的数据,比如字符串常量就是存储在不可改变的常量数据段。在C语言中我们用宏定义来申明常量。使用常量使程序易读,易修改和维护。常量Inc语言中#definess12#defineaString“hello”//aStringishello12是一个常数,SS代表常数12,我们在程序中把SS作为12inti;i=ss;printf(“%d\n”,i);printf(“%s\n”,aString);Theresultis12hello在pascal语言中Constss=12;aString=‘hello’;12是一个常量,SS代表常量12,当引用SS是就引用的是12i:Integer;i=ss;WriteLn(IntToStr(I));WriteLn(aString);Theresultis:12hello数值型的值可以用十六进制表示Inclanguagex=0xFF;//uppercaseorlowercaseisequaly=0x1B234f;Inpascallanguagex=$ff;y=$1b234f;为什么要用常量?案例分析.设想编写一个处理单词的程序,其功能为:如果单词是Hello,我们能按我们的格式改变它,例如,当选择大写按钮时,hello变成大写,选择小写按钮时,它变成小写,按红色按钮时,它变成红色。假若有三种源文件,file1,file2,file3,每一个文件都包括一个函数完成单词格式化的功能。在file1中:Word:=getAword(ReadALn(Fileread));IfWord=‘hello’then//ifwordishello,changeitto//uppercaseWord:=UpperCase(Word);…..Infile2Word:=getAword(ReadALn(Fileread));IfWord=‘hello’then//ifwordishello,changeitto//lowercaseWord=LowerCase(Word);…..Infile3Word:=getAword(ReadALn(Fileread));IfWord=‘hello’then//ifwordishello,changeitscolor//Word=ChangeColor(Word);…..问题:一切OK了,但如果我们不处理HELLO而处理LOVE,问题就出现了。我们不得不在三个文件中找到HELLO,并把它改为LOVE,这仅是改动三个文件可以达到目的,如果有成百上千个文件,这将是一个巨大的工作量,如果少一个地方没有改到,将引起错误。正确的方法:用常量如果定义了常量,就容易解决了,用一个file4声明如下:const_Keyword=‘hello’;File1,file2,andfile3用了file4的声明在file1Usesfile4;Word:=getAword(ReadALn(Fileread));IfWord=_KeywordthenWord=UpperCase(Word);…Infile2Usesfile4;Word:=getAword(ReadALn(Fileread));IfWord=_KeywordthenWord=lowerCase(Word);Infile3UsesFile4;Word:=getAword(ReadALn(Fileread));IfWord=_KeywordthenWord=ChangeColor(Word);…..//如果像这样把HELLO变成LOVE,我们只处理File4中的一行就可以了。其他文件根本不改动!将_Keyword=‘hello’改为_Keyword=‘love’;因为其它三个文件不用改动,程序变得易于维护.同时我们发现使用常量后程序比较容易理解:试比较以下两种风格的代码:(1)Switch(flag){case1:….case2:….case3:….}(2)#defineUPPERCASE1#defineLOWERCASE2#defineREDCOLOR3Switch(flag){caseUPPERCASE:…caseLOWERCASE:…caseREACOLOR:…}常量能防止大意的错误:正如上面的例子,直接用字符串,即使你把HELLO打成了HELLO1,你也能正确编译它,只是在逻辑上有误。要是我们把_Keyword打成了_Keyword1,在没有把它作为常量的情况下,程序不能正确编译,报错为notdefine_Keyword1。这说明了常量能防止大意的错误2.2避免深层次的条件语句嵌套复杂的条件语句是代码维护的障碍。switch语句:if–else–if是相当复杂的结构,很难理解,可以用另一种形式,达到相同的效果这就是switch语句.Inclanguageswitch(expression){caseconstant1:statements1;break;caseconstant2:statements2;break;caseconstant3:statements3;break;…default:statementsN;}同样数目的多层次的If-else语句和Switch语句哪个执行速度更快?为什么?为什么Switch语句必须是整数而不能是浮点或字符串。从Switch中得到的启示是什么?switch效率高,因为这种情况下它一般是用跳转表实现的比如,switch(i){case1:case2:case3:case4:...case10:default:}会被编译成一个地址指针表addr_table[1..10],分别装入case1..case10的地址然后编译成下列伪代码:ifi1thengotodefaultelseifi10thengotodefaultelsegotoaddr_table[i]注意CASE后的值必须是CHAR或INTER因此对字符串的判断需要一些技巧Example:必须转换字符串,把字符串转换为字符类型。转换逻辑像这样:用户输入-h,我们打印hello.,用户输入-f我们打印fly,用户输入-b,我们打印bye还记得一个相似的例子吗?下面的例子说明了如何使用netstat.c:\netstat–a–n为什么在控制台上运行的许多程序用了相似的命令选项格式:-字符因为我们很容易复制命令选项的第二字母,这第二个字母可以用switch语句处理#includestdio.h#includestring.hintmain(intArgCount,char*ArgValue[]){if(ArgCount!=2){printf(pleaseinputaparameter);return1;}else{if(strlen(ArgValue[1])!=2){printf(formatiserror);return1;}switch(ArgValue[1][1]){case'h':printf(okitishello);break;case'f':printf(okitisfly);break;case'b':printf(okitisbye);break;default:printf(argumentiserror);}};getchar();}进一步消除嵌套的If或Case语句(1)查表法可以定义一个结构StructAction{intflag;函数指针ToDo;}申明一个数组存放这个结构,每一个Action的Flag和函数指针不同。只有作一个遍历的过程就可以了Actionactions[4];intflag;//初始化Actions,对每个元素赋值不同的Flag和指针//获取flag的值for(i=0;i4;i++)if(actions[i].flag==flag)actions[i].ToDo();进一步消除If或Case语句查表法的改进将Flag位的值尽量设计成连续的值比如:100,101,102,103;我们可以省略If语句,可以从Flag直接得到Index。100的Index是100-100=0;101的Index是101-100=1;。。。。103的Index是103-100=3。Actionactions[4];intflag;//初始化Actions,对每个元素赋值不同的Flag和指针//获取flag的值//直接访问函数指针而不需要If语句比较。actions[flag-100].ToDo();(2)在面向对象中可以消除If语句或Case这需要用到State设计模式2.3分割函数函数是程序的基本单位,不要写一个高大全的函数,而是尽量将它们分割为含义清晰,可重用的子函数。最简单的方式是,发现多次拷贝的代码,就是子函数的前身。(a)可以被其他的函数调用.(b)使程序很容易理解(函数名应该容易被理解)2.4数组的边界问题和数据大小的问题对C语言无边界检查:当你要访问的元素超出了数组边界,编译时不会报错,只有在运行时才会出错ints[3];for(i=0;i30;i++)s[i]=i;//数组只有三个元素,但循环时访问了三十个元素。这是错误的红色的标志的内存,它不属于数组,但是程序要写数据,这是很危险的01234…s解决方法:可以用常量定义数组的边界,然后用这个常量去作为循环的边界#define_MaxCount3ints[_MaxCount];for(i=0;i_MaxCount;i++)s[i]=i;多维数组容易发生堆栈溢出类型var_name[size1][size2]…[sizen];多维数组的元素个数是issize1*size2*..sizen.例子intsample[2][3][4].//该数组有2*3*4=24个元素var_name占用typesize*size1*size2*…*sizen.字节sample占用4*2*3*4=96字节,int占用4字节.注意:用多维数组很容易发生内存溢出,局部变量存有栈中,Windows下线程的缺省栈为1M,数组的占用空间很容易大于这个空间#includestdio.hvoidmain(intargc,char*argv[]){inti1,i2;//thelocalvariablesampleneedallocatememoryonstackcharsample[1024][1024];//thearrayis1Mbytes=1024*1024*1for(i1=0;i11024;i1++)for(i2=0;i21024;i2++)sample[i1][i2]='c';getchar();}Sample空间只有1M字节,当运行时很容易发生内存溢出#includestdio.h//theglobalvariablesampleneedallocatememoryondatasegment,sotheprogramok.charsample[1024][1024];//thearrayis1Mbytes=1024*1024*1voidmain(intargc,char*argv[]){inti1,i2;for(i1=0;i11024;i1++)for(i2=0;i21024;i2++)sample[i1][i2]='c';getchar();}数组sample是一个全局变量它在数据段中动态分配内存空间,因此不会出现问题在pascal语言中提示:最好用0..size-1定义数组,这种风格使你容易阅读C程序,并不容易犯错误,因为有时你必须在两种语言间不断转换边界检查Pascal有边界检查,这是比C语言好的一点,但是数组的下标是一个变量时编译程序就不会检查它varsample:array[0..2]ofInteger;I:Integer;beginforI:=0to4dosample[I]:=I;//超出了边界,编译能通过sample[3]:=2;