C

总体原则

  1. 清晰可读。
  2. 简洁为美。
  3. 合适的风格。

文件

1、头文件中适合放置接口的声明,不适合放置实现

头文件是模块(Module)或单元(Unit)的对外接口。头文件中应放置对外部的声明,如对外提供的函数声明、宏定义、类型定义等。

  • 内部使用的函数(相当于类的私有方法)声明不应放在头文件中。
  • 内部使用的宏、枚举、结构定义不应放入头文件中。
  • 变量定义不应放在头文件中,应放在.c 文件中。

变量的声明尽量不要放在头文件中,亦即尽量不要使用全局变量作为接口。变量是模块或单元的内部实现细节,不应通过在头文件中声明的方式直接暴露给外部,应通过函数接口的方式进行对外暴露。即使必须使用全局变量,也只应当在.c 中定义全局变量,在.h 中仅声明变量为全局的。

2、头文件应当职责单一,切忌依赖复杂,禁止头文件循环依赖

头文件应向稳定的方向包含,头文件应当自包含。

每一个 .c 文件应有一个同名 .h 文件,用于声明需要对外公开的接口,.c/.h 文件禁止包含用不到的头文件。

3、命名

头文件不要使用非习惯用法的扩展名,如 .inc*

命名:文件名统一使用小写

4、所有源文件在文件开头添加注释

/**
* @brief		简述
* @author	作者
* @date		2021-11-11
* @history	
*/

5、除内联函数外,所有函数不得在头文件实例化

7、头文件应使用 ifndef 进行保护

一般以单下划线 "" 和双下划线 ”__” 开头的标识符为 ANSIC 等使用,在有些静态检查工具中,若全局可见的标识符以 "" 开头会给出告警

<sample.h>
#ifnef	SAMPLE_H_
#define SAMPLE_H_
  
  
#endif

8、接口

只能通过包含头文件的方式使用其他 .c 提供的接口,禁止在.c 中通过 extern 的方式使用外部函数接口、变量。

若 a.c 使用了 b.c 定义的 foo() 函数,则应当在 b.h 中声明 extern int foo(int input);并在 a.c 中通过#include <b.h>来使用 foo。禁止通过在 a.c 中直接写 extern int foo(int input); 来使用 foo,后面这种写法容易在 foo 改变时可能导致声明和定义不一致。

如果一个模块包含多个子模块,则建议每一个子模块提供一个对外的 .h,文件名为子模块名。

一个模块通常包含多个 .c 文件,建议放在同一个目录下,目录名即为模块名。为方便外部使用者,建议每一个模块提供一个 .h,文件名为目录名。

9、禁止在 extern “C” 中包含头文件

在 extern “C” 中包含头文件,会导致 extern “C” 嵌套,Visual Studio 对 extern “C” 嵌套层次有限制,嵌套层次太多会编译错误。

在 extern “C” 中包含头文件,可能会导致被包含头文件的原有意图遭到破坏。

12、统一包含头文件排列方式

函数

逻辑层 + 物理层

  • 一个函数仅完成一个功能。
  • 重复代码提炼成函数,函数避免过长。
  • 函数嵌套避免过深,不超过四层。
  • 设计高扇入、合理扇出(小于 7)。扇入即有多少上级调用,扇出级直接调用其他函数。

1. 注释

/**
*	@brief		
* @parameter1
* @parameter2
* @return	
*/

2. 命名

小驼峰 (myName)

动词 + 名词 (getLength,setParam),或,名词 + 动词 (uartInit,dataRst)

3. 作用域

  • 在源文件范围内声明定义的所有函数,除非外部可见即只在当前文件内部使用的函数:声明为 static
  • 需要外部调用的函数,使用接口函数 + 函数指针
#ifdef _DEBUG
#define STATIC static
#else
#define STATIC
#endif

4. 共享变量

可重入函数——可能被多个任务并发调用的函数,在多任务操作系统中,函数有可重入性是多个任务可以共用此函数的必要条件,共享变量指 global\static。应避免使用共享变量;若需要,应使用互斥手段(关中断、信号量)对其加以保护。

函数应避免使用全局、静态局部变量和 IO 操作,不可避免时集中使用。

int g_exam;
unsigned int example( int para )
{
    unsigned int temp;
    [申请信号量操作] // 若申请不到“信号量”,说明另外的进程正处于
    g_exam = para; //给g_exam赋值并计算其平方过程中(即正在使用此
    temp = square_exam( ); // 信号),本进程必须等待其释放信号后,才可继
    [释放信号量操作] // 续执行。其它线程必须等待本线程释放信号量后
    // 才能再使用本信号。
    return temp;
}

5. Const

不变参数使用 const

int strncmp(const char *s1, const char *s2, register size_t n)
{
    register unsigned char u1, u2;
    while (n-- > 0)
    {
        u1 = (unsigned char) *s1++;
        u2 = (unsigned char) *s2++;
        if (u1 != u2)
        {
            return u1 - u2;
        }
        if (u1 == '\0')
        {
            return 0;
        }
    }
    return 0;
}

6. 参数

  • 参数不超过 5 个,要检查函数的非参数输入的有效性,如数据文件、公用变量。
  • 除打印类函数外,不使用可变长参函数。
  • 对函数的错误返回码要全面处理。
  • 参数的合法性检查,应由调用者负责。

变量

1. 命名

类型规范示例
结构体类型定义大驼峰,最后加 _ttypedef struct{
}MotorData_t;
结构体变量大驼峰MotorData_t YawMotor
一般指针全小写,下划线分割present_value
指针变量以 p_ 开头int* p_value
全局变量以 g_ 开头int g_judge
宏定义,常量全部大写RX_BUFFER_MAX
枚举类型全部大写,最后加 _ENUMtypedef enum{
}MOTOR_MODEL_ENUM

2. 使用

  • 变量名称需有意义,能表明含义,使用英文。
  • 同一模块,使用结构体封装管理。
  • 特定模块的变量,封装在函数内部,定义为函数内部变量 (static)
  • 文件内部变量均为 static
  • 必需时才声明为全局变量,用 “g_” 标注;静态变量增加 “s_”
  • 减少没必要的数据类型转换和强制转换。

3. 功能

  • 一个变量只有一个功能,不能用多种用途。
  • 不用或少用全局变量,static 见上。若需要,构造仅有一个模块或函数可以修改、创建,而其余模块或函数只能访问的全局变量,降低全局变量耦合度。
  • 避免局部变量和全局变量同名。
  • 使用面向接口思想,通过 API 访问数据,如果本模块的数据需要对外部开放,应提供接口函数,同时主义全局数据的访问互斥。

4. 宏、常量

  • 宏定义,使用完备的括号。宏知识简单的替换。
  • 多条表达式放在大括号里。
  • 使用宏时不允许参数发生变化。
  • 尽可能使用函数替换宏。
  • 常量建议使用 const 代替宏,尽量使用编译器而不用预处理。
const char * const authorName = "Scott Meyers";
const double ASPECT_RATIO = 1.653;

表达式

  • 表达式的值在标准所允许的任何运算次序下都应该是相同的
  • 函数调用不要作为另一个函数的参数使用,否则对于代码的调试、阅读都不利
  • 赋值语句不要写在 if 等语句中,或者作为函数的参数使用。因为 if 语句中会根据条件依次判断。可能导致后续语句不再运行。
  • 用括号表明顺序,避免过度依赖默认优先级
  • 赋值操作符不能使用在产生布尔值的表达式上

注释与排版

1、优秀的代码可以自我解释,不通过注释即可轻易读懂,注释的内容要清楚、明了,含义准确,防止注释二义性

2、在代码的功能、意图层次上进行注释,即注释解释 代码难以直接表达的意图,而不是重复描述代码。

3、函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等。

4、全局变量要有较详细的注释,包括对其功能、取值范围以及存取时注意事项等的说明。

5、注释应放在其代码上方相邻位置或右方,不可放在下面,如放于上方则需与其上面的代码用空行隔开,且与下方代码缩进相同。

6、对于 switch 语句下的 case 语句,如果因为特殊情况需要处理完一个 case 后进入下一个 case 处理,必须在该 case 语句处理完、下一个 case 语句前加上明确的注释。

7、程序块采用缩进风格编写,每级缩进为4 个空格,相对独立的程序块之间、变量说明之后必须加空行,避免在一行代码或表达式的中间插入注释。

9、多个短语句(包括赋值语句)不允许写在同一行内,即一行只写一条语句

10、if、for、do、while、case、switch、default 等语句独占一行。执行语句必须用缩进风格写,属于 if、for、do、while、case、switch、default 等下一个缩进级别;

11、在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如-> >),后不应加空格

采用这种松散方式编写代码的目的是使代码更加清晰。

在已经非常清晰的语句中没有必要再留空格,如括号内侧 (即左括号后面和右括号前面) 不需要加空格,多重括号间不必加空格,因为在 C 语言中括号已经是最清晰的标志了。在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连续留两个以上空格。

12、源程序中关系较为紧密的代码应尽可能相邻

1、逗号、分号只在后面加空格。
int a, b, c;
 
2、比较操作符, 赋值操作符"=""+=",算术操作符"+""%",逻辑操作符"&&""&",位域操作符"<<""^"等双目操作符的前后加空格。 
if (current_time >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b ^ 2;
 
3"!""~""++""--""&"(地址操作符)等单目操作符前后不加空格。
*p = 'a'; // 内容操作"*"与内容之间
flag = !is_empty; // 非操作"!"与内容之间
p = &mem; // 地址操作"&" 与内容之间
i++; 
 
4"->""."前后不加空格。
p->id = pid; // "->"指针前后不加空格
 
5ifforwhile、switch等与后面的括号间应加空格,使if等关键字更为突出、明显。
if (a >= b && c > d)
 
7、注释符(包括/**///)与注释内容之间要用一个空格进行分隔
 

Google C++