预防错误的做法是采取积极的预防措施。本节将给出一些有益的建议。
最简单的预防措施莫过于使用正确的书写格式,这既可以避免错误,又能给查错提供方便。为了提高可读性,应增加必要的注释。其实,注释既能帮助理解程序,也能为查错提供方便,所以不要小看它的作用。
13.1.1 书写格式和注意事项
C语言的书写格式对于充分理解这种语言非常重要。一个格式适当的程序和一个格式不适当的程序就像一封写得很漂亮的信和一封写得非常凌乱的信,给人的印象是大不一样的。书写程序时,应该使源代码易于理解,特别是容易被输入这些程序的程序员所理解,这有助于复杂程序的调试及以前输入代码的修改。
教科书为了减少页码,都尽可能地减少空行。再加上每行字数有限,为了在一行排下一条长的语句,也尽可能不用空格(包括作者本人,也是这样处理的,这都是无奈之举)。程序员在写程序时,一定不要再受书的影响。常常听同学讲:“我是按照xxx书的格式写的”,或“我是按照xxx人的格式写的”,这些思想都是要不得的,应该根据实际情况来处理这些问题,不能生搬硬套。尤其是不要拿不正确的做法作为自己的借口。
下面说的书写风格,是指在编程环境里编写程序的风格。其实,编程环境会根据语句自动处理对齐和缩进。例如碰到if语句,换行时就会自动缩进。
提倡使用缩进式和必要的空行的书写风格,这样可使源代码具有层次性和逻辑性,增加程序的可读性和可操作性。
一般来讲,每次缩进5个字符的位置,并按程序特性设置空行。读者在编写程序时,应注意养成良好的书写风格。
1.建议的书写格式
在书写程序语句时,一般应注意如下规则。
(1)括号紧跟在函数名的后面,但在for和while后面,应用一个空格与左括号隔开以增加可读性。
(2)数学运算符的左右各留一个空格以与表达式区别。
(3)在声明多个参数时,逗号后面留一个空格。
(4)在if、for、do…while和while语句中,合理使用缩进、一对花括号和空行。
(5)在if…else之类的语句及其嵌套语句中,注意书写格式要易于排错并提高可读性。
(6)在碰到if和for等语句的组合,或for嵌套时,不仅要缩进,而且一定要留有空行,突出它们的层次。为了避免混淆,可以增加必要的注释。
(7)函数名和“(”之间留一空格,参数之间不要挤在一起,各个参数之间至少在“,”号后面留一空格。为了易于理解,函数原型声明时,可以包括参数类型和参数名。
(8)在带有运算符的表达式中,可在运算符两侧留一个空格。对像“++”一类的敏感运算符,一定要表达清楚自己的意图。
(9)对复杂的表达式,合理使用括号以免计算顺序出错。
第25章的部分程序将遵循这些格式。
2.注意集成环境对字体的显示颜色
各个编译环境对关键字和注释都配以特定颜色,所以在书写时要注意它们的颜色是否与规定的相符。拼错关键字或者混有中文空格,都会使它们的颜色变为普通颜色。注释前面混有中文空格,也会变为普通颜色。
3.书写程序的参考建议
采用一些书写方式可以避免某些错误,这里给出几点建议。
(1)书写程序时,输入西文要在西文状态下进行,尤其是“,”和“;”号,“=”和“!”之类的运算符也要注意。
(2)程序中的“{”和“}”是配对出现的。一般在if、for、while等复合语句中,如果需要使用括号,可以在书写时就把一对括号写出来,以避免漏掉右括号。
(3)switch语句要求一对括号,使用时就先把一对括号写出。
(4)主函数是int类型,一开始就写出包含语句及它的主体部分,即
#include <stdio.h>int main(void){ return 0;}
(5)函数定义也采取与主函数相同的方法,一定要把函数类型和参数定义正确。
(6)结构等的定义不要漏掉右括号外边的分号,最好在定义时先把括号和分号写出。先写出框架,然后再往里面添内容。例如:
struct student {};
(7)对可能存在歧义的地方要采取措施。例如“x=-5”,为避免一些版本误解为“x=x-5”,可以写成“x=(-5)”或空格多一点,如“x=-5”。
(8)在编写程序时,对少量吃不准的地方,可以先使用“//”或“/**/”注释掉,通过调试进行取舍。对大量需要修改的部分,可以复制备份,用“#if 0--#endif”的方式先注释起来,以免修改后不满意,还要恢复到原状。
如果书写程序时就能避免一些错误,将会起到事半功倍的效果。
13.1.2 命名注意事项
在程序中要为常量和变量起个合适的名字,函数名也是如此。同理,教科书都是采取很简单的命名方式,在编程中不要学习这种做法。正确地命名有助于程序的查错和理解。
在C语言中,大小写字母具有不同的含义,如name和NAME就代表不同的标识符。原来的C语言中虽然规定标识符的长度不限,但只有前8个字符有效,所以对下面两个变量是无法区别的。
dwNumberRadiodwNumberTV
现在流行的32位操作系统配备的C编译器已经能识别长文件名,不再受8位的限制。另外,在取名时不仅要保证正确性,还要考虑容易区分,不易混淆。例如,数字1和字母i在一起,就不易辨认。取名时应使名字有很清楚的含义。例如,使用area作为求面积函数的名字,area的英文含义就是面积,就很容易从名字猜出函数的功能。对一个可读性好的程序,必须选择恰当的标识符,取名应统一规范,使读者一目了然。
1.常量命名
使用的常量都有具体的含义,所以不要用a、b、c之类的单个字母(除非是公认的常量符号,例如物理学中的比例系数k),应该能从名字知道它的含义,例如圆周率pi。名字可以长一些,以便能很容易地知道它们的用途,方便理解和维护。
为了和变量区别,常量有时用大写字母表示,如圆周率PI。常量命名可以用汉语拼音的字头,也可以用英文的缩写,还可以参考下面介绍的变量命名方法,只是命名时无需考虑字节和字等标记。
2.变量和函数的命名
变量命名可以参考Windows API编程推荐的匈牙利命名法。这种命名法通过在数据和函数名中加入额外的信息,既可增进程序员对程序的理解,也方便查错。例如:
char ch; //所有的字符变量均以ch开始byte b; //所有的字节变量均以b开始long l; //所有的长字变量均以l开始
用前缀p作为定义指针的标记,则有:
char *pch; //指向字符变量的指针以pch开始 byte *pb; //指向字节变量的指针以pb开始 long *pl; //指向长字变量的指针以pl开始char **ppch; //指向字符指针的指针以ppch开始byte **ppb; //指向字节指针的指针以ppb开始
函数、变量及数组的命名与此同理。下面的含义就非常清楚:
ch = chLastKeyPressed; //由变量得到一个字符ch = chInputBuffer[i]; //由数组得到一个字符ch = chReadKeyboard( ); //由键盘函数读入一个字符
用下面的变量可以清楚地理解它们的含义:
dTVPrice //电视机价格—double型dRadioPrice //收音机价格—double型iBoyNumber //男孩的人数—整型
当看到某个函数里有名为pchText的变量时,不用查看声明,就可以知道它是指向字符的指针。如果在程序中看到向变量bOne赋值45645.65,就能判断这是错误的(bOne是字节变量)。
在内部名字中至少前31个字符是有效的,所以应该采用直观的名字。一般可以遵循如下简单规律:
(1)使用能代表数据类型的前缀。
(2)名称尽量接近变量的作用。
(3)如果名称由多个英文单词组成,每个单词的第1个字母大写。
(4)由于库函数通常使用下划线开头的名字,因此不要将这类名字用作变量名。
(5)局部变量使用比较短的名字,尤其是循环控制变量(又称循环位标)。
(6)外部变量使用比较长且贴近所代表变量的含义的名字。
(7)函数名字使用动词,如Get_char(void)。变量使用名词,如iMen_Number。
13.1.3 程序注释
程序注释不是愈多愈好,而是重在说明算法以有助于理解和使用。根据使用情况,可以把它分为几种类型。
1.程序作者和版权说明
这放在程序的起始部分,说明软件的作用、软件的版本和作者的信息。对于公司的软件文档,这是不可缺少的,但作为自写自用,又作别论。
2.函数的注释
对于自己编写的函数,应该在函数前以注释的形式给予说明。一般包括函数功能、参数和返回值。下面是一个简单的例子。
/******************************************//*函数int max(int number1, int number2)*//*功能:求两个整数中的大者 *//*参数number1:整数 *//* number2: 整数 *//*返回值:整数 *//******************************************/
当然,对于容易理解的函数,也可以简单地给予解释。例如:
//函数max用于求两个整数中的大者
甚至在函数原型声明时予以注释。例如:
int max(int number1, int number2); //求两个整数中的大者
可以根据情况选择,甚至不予注释(例如max函数的名字已经显示出它的作用),但对较为复杂的函数,建议予以详细注释。
3.变量和常量的注释
尽管遵循匈牙利命名法可以使人容易理解名字的含义,但不能全部寄托在别人的理解上,所以对一些关键常量和变量,仍然需要给予注释。例如:
#define MaxSize 64 //定义一维数组的最大长度const int length = 2; //步长为2int mul_total = 1; //存放乘积结果,初始值为1
4.程序语句的注释
为了便于维护,需要对一些语句的作用进行注释,包括初始值和归零等语句。尤其是一个变量被另外一个程序段使用时,更应该交代清楚其来龙去脉,以免误解。
sum = 2+ count ++; //将计数器加1后,再与2相加并计入总数count = 0; //清零,准备重新计数
5.算法段的注释
对重要的算法段进行注释,不仅是为了提高可读性,也是为了验证算法的正确性。有时,通过对它们进行注释,会找到优化或改进的办法。
在编程时,有时甚至是先写出详细注释,再来编程实现。有时会通过反复修改注释,找到最优的实现方法。例如:
//state=0 时,等待input=1,置state=1else if((state==0)&&(input==1)){ state=1; //input=1,state从0变到1 p=&buf[i]; //单词起点}
本书将在第25章用实例说明这一问题。
6.头文件的注释
头文件涉及变量和函数等的声明,忌讳罗列一堆常量、变量和函数原型,让人不知道它们是干什么的。应该给予必要的注释,既增进可读性,也方便在需要时能很快找到相应文件去查询。下面是一个头文件的部分示例。
/****************************** 建立头文件 ******************************/#if !defined(RING_H)#define RING_Hstruct person //参加人的数据结构{ char name[10]; //参加人的名字或代号 struct person *next;};…… //省略void Countx(int m); //数间隔数void Dispx(); //显示出局者void Clsx(); //删除出局的结点void SetRing(int n); //建立循环链表void Find(); //求解void Initial(); //接收游戏的人数和间隔数void FindOut(); //找出并输出出局者void PrintLeft(); //输出存活者#endif