本期视频链接:点击这里
有人说过:“程序源代码其实是跟人阅读的,只是恰好机器可以编译而已”。编程初学者常常会有这样一个观念,就是我的程序只要编译通过了,运行没有问题那就万事大吉了。至于代码的编写规不规范,完全就是无关紧要的小事情。如果是处于学习阶段,比如为了完成在学校的C语言课的作业,那么花心思在代码规范上的确没有特别的必要,因为这些代码基本不会进入实用工程,也不会被很多人阅读到。
但是,如果应用到了工程领域,比如在软件/互联网企业的技术研发部门,或者Github等平台上的开源工程,那么编程的规范性将变得无比重要。因为在这些场合,你写的代码将被许多人阅读,并且可能会成为许多人进行后续开发的基础。此时,差劲的代码风格将严重拉低其他开发人员的工作效率。因此,我们推荐从一开始学习便养成一个良好的编程习惯,维持一个合理的代码风格,这样对未来的工作大有裨益。
C语言编程风格的内容相当庞大,这里只挑选一部分相对常用而且比较重要的内容作为参考,主要分为5个部分,包括排版、注释、命名、变量/结构、函数等。
1、排版
程序排版使得代码的结构更加清晰明了,而且有助于理解上下文的逻辑关系。
(1)程序块应根据上下文关系采用缩进风格,缩进的长度根据具体标准规定;
(2)独立的程序块之间、变量说明之后必须加空行;比如:
int fun()
{
int nVal1 = 0, nVal2 = 5, nSum;
{
nSum = nVal1 + nVal2;
}
printf("Sum is %d\n", nSum);
}
(3)一条语句占一行,不允许将两条语句写在一行中;
(4)对于存在判断、循环的代码,像if/for/do/while/case/swith/default等部分独占一行,且无论执行部分有多少条语句,都必须使用大括号{ };
(5)包裹代码块的大括号{ }必须另起一行,不要跟随上一行代码的末尾;且大括号也要符合代码缩进规则;
2、注释
注释虽然不影响程序的运行,但仍然是代码的重要组成部分。完善的代码注释对快速理解代码的功能具有重要意义,相反如果代码逻辑复杂且没有注释,或注释不完整、不科学,那么旁人很难理解这段代码究竟是做什么的。需注意一点,别人无法理解的程序即使运行良好,也永远都是垃圾代码。
添加注释需要注意,注释应简洁、有效,有助于提升对代码的理解。所以添加注释应注意不要添加一些完全无意义或者错误的信息。通常,我们认为一套代码按照优劣分为4个等级:
第一等级:不需要注释,通过优秀的代码风格、标识符命名和代码的上下文关系就可以达到高可读性的代码;
第二等级:代码的命名和组织规范、风格稍显不足,但有完善的注释;
第三等级:代码风格和注释都不够完善,但是组织了较为完善的文档在一定程度弥补了这一缺陷;
第四等级:代码风格、注释和文档都不足,这种就属于其他人难以理解的垃圾代码。
函数头部的注释:
在函数头部应添加注释,说明函数的功能、参数、返回值等信息。下面的注释格式比较完善,不一定要局限与此,但建议保留其中的大部分信息:
/*************************************************
Function: // 函数名称
Description: // 函数功能、性能等的描述
Calls: // 被本函数调用的函数清单
Called By: // 调用本函数的函数清单
Input: // 输入参数说明,包括每个参数的作
// 用、取值说明及参数间关系。
Output: // 对输出参数的说明。
Return: // 函数返回值的说明
Others: // 其它说明
*************************************************/
代码中的注释:
语句的注释应在被注释语句的正上方或右方。如果是在上方的话,除非十分必要否则不要再代码和注释之间插入空格。
对于具有物理含义的常量和变量,以及数据结构,除非命名本身是充分注释的,在声明时必须加以注释。
全局变量要有详细的注释,包括对其功能、取值范围、使用的函数以及存取时的注意事项等。
注释与上方的代码用一行空格间隔。
对选择、循环语句应当添加注释,说明分支、循环体的意义。
在程序块结束的大括号右方添加注释,说明匹配的程序块开始位置。
如以下代码:
if (...)
{
program code
while (index < MAX_INDEX)
{
program code
} /* end of while (index < MAX_INDEX) */ // 指明该条while语句结束
} /* end of if (...)*/ // 指明是哪条if语句结束
3、标识符命名
标识符命名是代码风格中的重要组成部分,甚至直接决定了代码可读性的高低。最常用的标识符无非就是常量名、变量/结构体名、函数名、宏定义、标签名等。对不同的标识符类型一般适用不同的要求,但有一些基本要求是一致的:标识符的命名必须清晰明了,含义明确,尽量少地使用缩写;严禁使用无意义的单个字母如a, b, i, m, n或者func1, fun等无意义的单词或缩写用于命名;
(1)常量、变量、函数命名:
对于变量名和函数名,通常比较常用的有两种命名法:驼峰命名法和下划线命名法,这两种方法的根本区别在于通过怎样的方式来分隔标识符命名中的逻辑断点。
驼峰命名法:通过大小写字母的变化进行分隔,如:int imgWidth = 0; char *studentName = “Jerry”;
下划线命名法:通过下划线进行分隔,如:double earth_moon_distance;
对于函数名和变量名,一般可以使用不同的命名方法,但是需要注意的是只要选定的命名规范就要从头到尾保持不变。通常,我个人的习惯是,变量名和结构体名使用驼峰命名法,变量名用小写开头,结构体用大写开头;函数名使用下划线命名法,公有API以大写字母开头,私有函数以小写开头并声明为static类型。
另外,对于常量、结构体成员变量、全局变量,还可以参考匈牙利命名法的原则,在变量名前加入前缀c_、m_和g_。
(2)宏定义命名
对于宏定义的命名,一律全部使用大写字母,逻辑断点采用下划线分隔,如
#define MAX_ARRAY_LENGTH 256
对于头文件保护作用的宏定义,则以头文件的文件名命名,逻辑断点和扩展名前的点全部用下划线替代,并且在首位各添加一个下划线,如
//ImageProcessing.h
#ifndef _IMAGE_PROCESSING_H_
#define _IMAGE_PROCESSING_H_
/*code*/
#endif
(3)标签名
通常标签名配合goto语句一起使用。由于goto本来就是比较冷门的语句,标签也不是很常用。如果用到,则全部使用小写字母,并且在结尾加_label,如:
void test()
{
/*code*/
goto end_label;
/*code*/
end_label:
/*code*/
}
4、变量和结构使用规范
变量和结构的使用是编程中最为频繁的动作,如果能正确规范变量的使用,那么对整体的编程风格的提升大有帮助。
(1)变量定义之后立刻初始化。通常数值型变量定以后可以立刻初始化为0、某个负数或其他无意义的数值,指针变量定义后立刻初始化为NULL。这样在后面使用变量时可以更方便地判断变量是否已经被正确地处理,防止无意中使用了未经初始化的值。
(2)除非特别必要,否则尽量减少全局变量的使用,对于跨文件使用的全局变量更要慎重。全局变量是造成代码之间耦合的重要因素,通常使用全局变量越多,代码就越难以维护。
(3)对于数值完全不应当改变的量,一律定义为常量,防止被误修改。
(4)定义一个结构体的功能应当越具体越好,不应定义一个实现多种功能的结构。另一个体现是,不要定义规模过于庞大的结构,这样不但在运行时浪费系统资源,而且逻辑上难以理解。
(5)除非特别必要,尽量减少变量类型之间的强制转换。因为强制转换实际上也是需要计算机额外操作的,过多的强制转换对系统资源也是一种浪费。
(6)定义结构体时注意优化成员之间的顺序,尽量减少因为字节对齐导致的存储空间浪费。
5、函数使用规范
- 对于可能出现执行错误(如打开文件失败等)的函数,一律通过返回值返回错误码,且错误码用宏定义预先定义好。
- 除非专门用来设计输出随机信息的函数,所有的函数都应当是可预测的,即相同的输入永远产生相同的输出。
- 一个函数只完成一个较小的功能,避免出现一个完成大量不相关功能的超长函数。
- 严格区分输入和输出参数,对于函数体中不应该改变的参数全部声明为const类型。
- 在函数正式开始进行处理之前,检查输入参数以及其他用到的外部的有效性。
- 避免使用过长的参数表,可以把相关的参数封装成一个结构体并以该结构体作为参数。