文件命名
- 文件及目录的命名规定可用的字符集是[A-Z;a-z;0-9;._-]。
- 源文件名后缀用小写字母 .c 和.h。
- 文件的命名要准确清晰地表达其内容,同时文件名应该精练,防止文件名过长而造成使用不便。在文件名中可以适当地使用缩写。
- 对于文件的长度没有非常严格的要求,但应尽量避免文件过长。一般来说,文件长度应尽量保持在 1000 行之内。
头文件引用
- 一个软件包或一个逻辑组件的所有头文件和源文件建议放在一个单独的目录下,这样有利于查找并使用相关的文件,有利于简化一些编译工具的设置,比如一般分为Application、Drivers、Platform三层
- 对于整个项目需要的公共头文件,应存放在一个单独的目录下(例如:myProject/include)下,可避免其他编写人引用时目录太过分散的问题,比如STM32cubemx生成的公共头文件都被main.h引用。
- 在引用头文件时,不要使用绝对路径。如果使用绝对路径,当需要移动目录时,必须修改所有相关代码,繁琐且不安全;使用相对路径,当需要移动目录时,只需修改编译器的某个选项即可。
例如:
#include “/project/inc/hello.h” /* 不应使用绝对路径 */
#include “../inc/hello.h” /* 可以使用相对路径 */
- 在引用头文件时,使用 <> 来引用预定义或者特定目录的头文件,使用 “” 来引用当前目录或者路径相对于当前目录的头文件。
#include <stdio.h> /* 标准头文件 */
#include <projdefs.h> /* 工程指定目录头文件 */
#include “global.h” /* 当前目录头文件 */
#include “inc/config.h” /* 路径相对于当前目录的头文件 */
- 为了防止头文件被重复引用,应当用 ifndef/define/endif 结构产生预处理块。
#ifndef __DISP_H /* 文件名前名加两个下划线“__”,后面加 “_H”
#define __DISP_H
...
...
#endif
排版
- 程序块要采用缩进风格编写,缩进的空格数为 4 个。
- 相对独立的程序块之间以及变量说明之后都必须加空行。
void DemoFunc(void)
{
uint8_t i;
<---- 局部变量和语句间空一行
/* 功能块 1 */
for (i = 0; i < 10; i++)
{
...
}
<---- 不同的功能块间空一行
/* 功能块 2 */
for (i = 0; i < 20; i++)
{
...
}
}
- 作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读。
if ((ucParam1 == 0) && (ucParam2 == 0) && (ucParam3 == 0)
|| (ucParam4 == 0)) <---- 长表达式需要换行书写
{
......
}
- 不允许把多个短语句写在一行中,即一行只写一条语句。
rect.length = 0; rect.width = 0; <---- 不正确的写法
rect.length = 0; <---- 正确的写法
rect.width = 0;
- 对齐使用 TAB 键,1 个 TAB 对应 4 个字符位。
- 函数或过程的开始、结构的定义及循环、判断等语句中的代码都要采用缩进风格,case 语句下的情况处理语句也要遵从语句缩进要求。可以多用格式化。
- 程序块的分界符(如大括号‘{’和‘}’ )应各独占一行并且位于同一列,同时与引用它们的语句左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以及 if、for、do、while、switch、case 语句中的程序都要采用如上的缩进方式。对于与规则不一致的现存代码,应优先保证同一模块中的风格一致性。
for (...) { <---- 不规范的写法(但若是其他人这么写了,你需要保证同一模块下的一致性)
... /* program code */
}
for (...)
{ <---- 规范的写法
... /* program code */
}
if (...)
{ <---- 不规范的写法
... /* program code */
}
if (...)
{ <---- 规范的写法
... /* program code */
}
- 在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。
(1)、逗号、分号只在后面加空格。
int_32 a, b, c;
(2)比较操作符,赋值操作符"=“、 “+=”,算术操作符”+“、”%“,逻辑操作符”&&“、”&“,位域操作符”<<“、”^"等双目操作符的前后加空格。
if (current_time >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b ^ 2;
(3) “!”、“~”、“++”、“–”、“&”(地址运算符)等单目操作符前后不加空格。
*p = 'a'; /* 内容操作"*"与内容之间 */
flag = !isEmpty; /* 非操作"!"与内容之间 */
p = &mem; /* 地址操作"&" 与内容之间 */
i++; /* "++","--"与内容之间 */
(4) “->”、"."前后不加空格。
p->id = pid; /* "->"指针前后不加空格 */
(5) if、for、while、switch 等与后面的括号间应加空格,使 if 等关键字更为突出、明显,函数名与其后的括号之间不加空格,以与保留字区别开。
if (a >= b && c > d)
注释
- 注释的原则是有助于对程序的阅读理解,在该加的地方都加,注释不宜太多也不能太少,注释语言必须准确、易懂、简洁。
- 个人要求必须全英文,且不对函数进行版权说明和过多的注释,个人认为一个好的函数命名不需要注释既可明白其含义,过多的注释反而成为了累赘。
- 边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
- 对于单行的注释用//,多行用/**/,但不管是多行还是单行都放在句子上方。
void example_fun(void)
{
/* one
example */
CodeBlock One
// code two comments
CodeBlock Two
}
可读性
- 注意运算符的优先级,并用括号明确表达式的操作顺序,避免使用默认优先级。
正确的写法:
word = (high << 8) | low;
if ((a | b) && (a & c))
if ((a | b) < (c & d))
错误写法:
word = high << 8 | low;
if (a | b && a & c)
if (a | b < c & d) /* 造成了判断条件出错 */
- 避免使用不易理解的数字,用有意义的标识来替代。涉及物理状态或者含有物理意义的常量,不应直接使用数字,必须用有意义的枚举或宏来代替。
if (Trunk[index].trunk_state == 0) <---- 不规范的写法,应使用有意义的标识
{
Trunk[index].trunk_state = 1; <---- 不规范的写法,应使用有意义的标识
... /* program code */
}
增加一个枚举来对1和0这类赋予意义。
enum trunk_state_e
{
TRUNK_IDLE = 0,
TRUNK_BUSY = 1
};
if (Trunk[index].trunk_state == TRUNK_IDLE)
{
Trunk[index].trunk_state = TRUNK_BUSY;
... /* program code */
}
- 不要使用难懂的技巧性很高的语句,除非很有必要时。
* stat_poi ++ += 1;
* ++ stat_poi += 1;
应改为如下:
*stat_poi += 1;
stat_poi++; /* 此二语句功能相当于“ * stat_poi ++ += 1; ”*/
++ stat_poi;
*stat_poi += 1; /* 此二语句功能相当于“ * ++ stat_poi += 1; ”*/
函数
- 对于函数的命名个人一般是模块名加动词,模块名大写动词首字母大写。
static void KEY_Detect(uint8_t i)
{
}
void KEY_DownFunc(void)
{
}
- 一个函数仅完成一件功能。
- 函数名应准确描述函数的功能。避免使用无意义或含义不清的动词为函数命名。使用动宾词组为执行某操作的函数命名。
- 函数的返回值要清楚、明了。除非必要,最好不要把与函数返回值类型不同的变量,以编译系统默认的转换方式或强制的转换方式作为返回值返回。
- 防止把没有关联的语句放到一个函数中。
说明:防止函数或过程内出现随机内聚。随机内聚是指将没有关联或关联很弱的语句放到同一个函数或过程中。随机内聚给函数或过程的维护、测试及以后的升级等造成了不便,同时也使函数或过程的功能不明确。使用随机内聚函数,常常容易出现在一种应用场合需要改进此函数,而另一种应用场合又不允许这种改进,从而陷入困境。
在编程时,经常遇到在不同函数中使用相同的代码,许多开发人员都愿把这些代码提出来,并构成一个新函数。若这些代码关联较大并且是完成一个功能的,那么这种构造是合理的,否则这种构造将产生随机内聚的函数。
示例:如下函数就是一种随机内聚。
void InitVar( void )
{
Rect.length = 0;
Rect.width = 0; /* 初始化矩形的长与宽 */
Point.x = 10;
Point.y = 10; /* 初始化“点”的坐标 */
}
矩形的长、宽与点的坐标基本没有任何关系,故以上函数是随机内聚。
应如下分为两个函数:
void InitRect( void )
{
Rect.length = 0;
Rect.width = 0; /* 初始化矩形的长与宽 */
}
void InitPoint( void )
{
Point.x = 10;
Point.y = 10; /* 初始化“点”的坐标 */
}
- 减少函数本身或函数间的递归调用。
递归调用特别是函数间的递归调用(如A->B->C->A),影响程序的可理解性;递归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,应减少没必要的递归调用. - 改进模块中函数的结构,降低函数间的耦合度,并提高函数的独立性以及代码可读性、效率和可维护性。优化函数结构时,要遵守以下原则:
(1)能影响模块功能的实现。
(2)仔细考查模块或函数出错处理及模块的性能要求并进行完善。
(3)通过分解或合并函数来改进软件结构。
(4)考查函数的规模,过大的要进行分解。
(5)降低函数间接口的复杂度。
(6)不同层次的函数调用要有较合理的扇入、扇出。
(7)函数功能应可预测。
(8)提高函数内聚。(单一功能的函数内聚最高)
说明:对初步划分后的函数结构应进行改进、优化,使之更为合理。
注:以上大部分摘抄硬汉嵌入式论坛,其中加上了一些个人的习惯,只做学习用途。