程序设计课第七周课件里推荐的阅读材料,秉持着“翻译是理解外语学习资料的最佳方式”的理念,自己做了一个翻译。
原文地址:http://www.cs.swarthmore.edu/~newhall/unixhelp/c_codestyle.html
写在开头:对于一个刚进入软工专业不到两个月的新生而言,里面的一些指导是不必要的(比如还没学函数,那只能把“低级的细节”都放在main函数里)。里面一些建议跟我实际编程时的方案也不太一样,比如对于if-else结构,我通常是这样写的
if { stmt1; } else { stmt2; }
而不是
if{ stmt1; } else { stmt2; }
这也是我们的教材C How To Program使用的风格。不过,虽然前一种方法需要的行数更多,但是形式上更对齐,就我个人而言感觉是更好看的。反正只要同一个程序里同类的结构都用一样的风格,看起来整齐一点就行了。
后记:昨天上了实验课才知道,第一种其实是ANSI C风格,第二种则是K&R C风格,两种均对
这个指南里很多指导确实非常有用,即使一些相关功能还没学过,但提前了解这些功能用什么风格展现更好也是很有好处的。
C代码风格指南
· 进行谨慎的模块化设计:在开始编写代码之前,请先对你将创建的函数及数据结构三思。
· 仔细地检查错误,并正确地加以处理:不断检查函数的返回值,并对出现的错误进行适当的处理。(容忍段错误并非正确的错误处理方式!)
(译注:段错误,原文为SEGFAULT,即segmentation fault的缩写,中文为内存区段错误,简称段错误,在不正确地访问内存时出现,通常由数组越界、向受系统保护的内存地址写入数据、内存溢出等问题引起。)
· 每次对内存进行动态分配(例如调用malloc函数)时,应当在你的程序内某处编写代码释放这块内存。
· 一般而言,任何函数长度都不应超过一页。例外当然可以有,但请先确保这些例外是必要的。
· 对变量、函数及常量进行描述性的命名。没人希望看到又臭又长的函数和变量名,但它们必须能够描述自己的性质(例:对一个返回值为圆的半径的函数,用"getRadius" 或 "get_radius"(注:意为求半径) 命名,而不要用"foo"这种词不达意的名字)。此外,请坚持使用C风格的命名惯例(例:用i或j来命名作为循环计数器的变量)。
· 在为函数、局部变量和全局变量命名时,选定一种标注单词首字母的方式,并且坚持用到底。比如,在为一个函数命名时,你可以用"square_the_biggest" ,或"squareTheBiggest" ,或 "SquareTheBiggest".
(译注:这一条的意思是在一个程序里不要混着用标注单词首字母的方式,比如打算用下划线分隔单词,那么整个程序里的标识符就都用下划线分隔里面的单词,如果打算把整个标识符开头的第一个字母大写,那么其他的标识符也应该大写首字母。)
· 不要在程序里直接使用数值,而是先对其定义常量,然后再使用。常量能够让你的代码更容易阅读,也更容易修改(如果你想把常量MAX从50改成100,只要修改常量定义就能搞定,而不用找到你程序里用过的所有50,然后纠结里面有多少个对应这个MAX的50需要修改)
这么干: 别这么干: #define MAX 50 int buf[MAX]; if( i< MAX) { ... } int buf[50]; if( i < 50) { ... }· 尽量别用全局变量,作为替代方案,你可以在那些需要改变全局变量值的函数里传引用这些变量。
· main函数里不应有低级的细节,它应该用于高度概括你的解法(请牢记自顶向下的设计方法)。
· 使用正确的缩进方式(用TAB,别用空格),函数体、循环体和if体、else体等都应该缩进,具有同结构体级的语句缩进的量应当是一样的,比如:int blah(int x, int y) { stmt1; stmt2; while (...) { stmt3; stmt4; if(...) { stmt5; stmt6; stmt7; } else { stmt8; } stmt9; } stmt10; }· 行长度:你的源代码里每一行的长度都不应该超过80个字符。如果有一行超过了这个长度,把它分割成多行。
下面是一个如何将较长的布尔表达式分割成三行的例子:if ( ((blah[i] < 0 ) && (grr[j] > 234)) || ((blah[j] == 3456) && (grr[i] <= 4444)) || ((blah[i] > 10) && (grr[j] == 3333)) ) { stmt1; stmt2; } else { ...下面是一个如何将较长的注释分割成多行的例子:
x = foo(x); /* 计算下一个比x大的 */ /* 素数的值 */ /* (用foo来命名一个函数是很糟糕的)*/下面是两种分割较长的字符串常量的方法:
printf("这是一个超长的字符串,里面还带了一个int值 %d", x); printf(" 我想把它输出到标准输出设备\n");或者
printf("这是一个超长的字符串,里面还带了一个int值 %d" " 我想把它输出到标准输出设备\n", x);如果要保证每一行代码都不超过80个字符,最简单的方法就是在一个宽度刚好能容下80个字符的窗口里编写代码。当某一行代码延续到了下一行的时候,就说明它太长了。
·注释你的代码!
文件注释:每一个.h和.c文件都应该在开头做一个最高等级的注释,用于描述文件内容,里面还应该包括你的名字和代码的编写日期。
函数注释:每一个函数(不管它是在.h还是.c文件里)都应该有注释,用来描述:
1.这个函数是用来做什么的
2.它的参数值都是什么
3.它返回的值都是什么(如果一个函数一般只返回一个类型的值,而另一个值用来表示出错,那么你的注释应当把这两种返回值都描述一下)
在头文件里,函数注释是给接口用户看的;在源代码文件里,函数注释是给用这些函数的人看的。因此,在C源代码文件里的函数注释通常还要说明这个函数的功能是怎么实现的,尤其是对于一个内部算法相当复杂的函数,它的注释应当包括这个算法的主要步骤。
我对于注释函数的建议:先写注释,再写具体的函数代码。对于那些比较复杂的函数,先注释好每一步的算法是很有用的。
注释的时候要坚持一定的样式,比如:/* * 函数: approx_pi * -------------------- * 用以下方法计算圆周率的近似值: * pi/6 = 1/2 + (1/2 x 3/4) 1/5 (1/2)^3 + (1/2 x 3/4 x 5/6) 1/7 (1/2)^5 + * * n: 需要求和的级数里的项数 * * 返回值: 通过对上面的级数求前n项和得到的 * 圆周率的近似值 * 如果出错(当n不是正数的时候),返回0 */ double approx_pi(int n) { ...(注:对于这个函数,我会使用行内注释来描述自己怎样计算这个级数里下一项的每一部分)
/* * 函数: square_the_biggest * ---------------------------- * 返回输入的两个值里最大的数的平方 * * n1: 一个实数值 * n2: 另一个实数值 * * 返回值: n1和n2中较大的那个数的平方 */ double square_the_biggest(float n1, float n2) { ...行内注释:函数体内每一种复杂的、高难度的、丑陋的代码序列都应该带有行内注释,用来描述它的功能(这就是为什么使用易懂的函数和变量名能够帮你节省注释)
对于代码里那些复杂的部分而言,注释确实很重要,但没必要钻牛角尖。过多的注释可不比过少的注释好到哪里去。避免注释那些一看就懂的代码。如果你的函数和变量命名得当,你的大部分代码应该是很容易读懂的。例如,下面的注释就是不必要的,因为这行C代码本身已经把它的意思表达得很明白了,而这行没用的注释很可能会抢走你的代码里那些真正重要的注释的风头。x = x + 1; /* x的值加1 */