# 1.1 前言
C 语言学习之路,道阻且跻,为了不让我自己写代码时轻易迷失在指针和函数的海洋以及提高自己编程的质量和效率,我收集了一些资料,整理归纳并稍作修改,整理出这篇编程规范和注释风格,希望自己能好好遵守,以后写出越来越容易理解和阅读的代码。
操作系统:ubuntu22.04
编辑器:Vim 9.0
编译器:gcc10.3.0
。
C 语言标准:c11
。
# 1.2 编码
# 1.2.1 排版
- 缩进为 4 个空格。
- 每行不超过 80 列。
- 文件用
UTF-8(without BOM)
编码。 - 括号采用
K&R
风格。 - 逗号、分号在后面加空格。
- 比较操作符,赋值操作符、算术操作符、逻辑操作符、位域操作符等双目操作符的前后加空格。
- 位运算操作符、自增、自减、取地址等单目操作符前后不加空格。
->
、.
前后不加空格。if
、switch
、else
、for
、do
、while
等关键字后要加一个空格。- 不要在小括号里的表达式两侧加空格。
- 表达式换行要保持换行的一致性,操作符放行末。
# 1.2.2 命名
- 变量:全小写,
_
分隔。如student_score
。 - 文件:全小写,不分隔。如
myccode.h
。 - 宏:全大写,
_
分隔。如API_KEY
。 - goto 标签:和宏一致。如
CLEAN
。 - 只读变量:全大写,
_
分隔。如:const int DAYS_IN_A_DAY = 7
。 - 枚举常数:与常量一致:
ENUM_NAME
。 - 函数:
- 动作类函数使用动宾结构:
add_user()
。 - 判断型函数:
is_working()
。 - 数据型函数:
get_loacal_count()
。
- 动作类函数使用动宾结构:
tyepdef
自定义类型:加_t
后缀。如time_t
,pid_t
等。- 静态变量:加
_
前缀。如_students_num
。 - 全局变量:加
g_
前缀。如g_errno
。 bool
变量:加is_
前缀。如is_check
。- 指针变量:加
ptr
后缀。如student_info_ptr
。 - 较短的单词可通过去掉“元音”形成缩写;较长的单词可取单词的头几个字母形成缩写,避免使人产生误解。如
tmp
、stu_info
。 _
开头的函数或变量用来命名不希望被外部调用的函数(静态函数)。
# 1.2.3 头文件防卫式声明
#ifndef HEADER_FILENAME_
#define HEADER_FILENAME_
#endif // _HEADER_FILENAME
1
2
3
4
2
3
4
# 1.2.4 函数
- 不对外使用的函数用
static
限制。 - 不在函数原型之前使用
extern
关键字。 - 设计函数时,优先使用返回值而不是输出参数。
- 对于模块外部传入的参数,必须进行合法性检查,保护程序免遭非法输入数据的破坏。
- 契约式编程,模块内部函数参数的合法性检查,由调用者负责。
- 函数的指针参数如果不是用于修改所指向的对象就应该声明为指向
const
的指针。 - 一个函数仅完成一个功能。
- 对函数的错误返回码要全面处理。
- 如果不关心返回值,应将函数声明为
void
类型。 - 多个
.c
文件要调用同一个内联函数,那么这个内联函数应该在.h
中定义。 - 在函数原型中包含参数名和它们的数据类型。
- 使用
goto
集中函数的退出路径,好处:- 无条件语句容易理解和跟踪。
- 嵌套程度减小。
- 可以避免由于修改时忘记更新个别的退出点而导致错误。
- 如果一个函数有 3 行以上,就不要把它变成内联函数。
- 如果函数的名字是一个动作或者强制性的命令,那么这个函数应该返回错误代码整数。如果是一个判断,那么函数应该返回一个“成功”布尔值。
- 没有返回值的函数要用
noreturn
,(stdnoreturn.h)修饰。
# 1.2.5 其他
- 使用
C11
标准。 - 使用标准数据类型。(
stdint.h
和stdbool.h
内的类型)。 - 永远不要用
pragma pack
。 - 代码中所有无符号整型数字,均应加
U
后缀。如#define MAX_DEVICES (128U)
。 - 每一个
.c
文件都应该有对应的.h
文件,用于声明需要对外公开的接口。 - 包含多条语句的函数式宏的实现语句必须放在
do-while(0)
中。 - 不要冗余初始化,对于后续有条件赋值的变量,可以在定义时初始化成默认值,针对大数组的冗余清零,更是会影响到性能。
switch
语句要有default
分支,所有的switch
的case
里都要有break
,如果 default 用于不应该抵达,应使用assert(false)
。- 使用
goto
集中所有函数出口。 - 尽量只用数组索引做指针运算,而不是使用指针变量。
- 整型常量应该用枚举来表示,而不是用宏来定义。
- 宏函数并没有类型检查,可以考虑用内联函数代替。
- 头文件中,内联功能启用应用
static inline
来完成。 - 尽量不用
typedef
给结构体、指针重命名。 - 操作符两边的数据类型需要一致,以防止隐式类型转换会发生与程序员想法不一致。如:
uint8_t
和int16_t
类型的>
,<
,=
比较。 - 包含头文件时使用相对路径,不使用绝对路径。
- 结构体内含有多个 bool 变量,请考虑将它们合并为具有1比特成员的位域。
# 1.3 注释
之前本来是采用 Doxygen
风格的,但用了一阵子觉得太复杂了,还是简单点好。
错误的注释不但无益反而有害。注释主要阐述代码做了什么(What),或者如果有必要的话,阐述为什么要这么做(Why),注释并不是用来阐述它究竟是如何实现算法(How)的。
# 1.3.1 条件编译
#ednif
后需要加注释。
#if MY_MACRO_NAME
// ...
#endif // MY_MACRO_NAME
1
2
3
2
3
# 1.3.2 TODO 注释
对那些临时的、短期的解决方案,或已经够好但并不完美的代码使用 TODO
注释。
例如:
// TODO: 问 ChatGPT,然后粘贴到这里。
// FIXME: 完蛋了,我不知道我删了什么,现在程序出问题了。
// XXX: 这里本来有一个绝佳的算法可以优化,但是时间太短,下次再改。
1
2
3
4
5
2
3
4
5
# 1.3.3 文件头注释
/******************************************************************************
* Copyright: (C) 2023 shachi
* All rights reserved.
*
* Filename: temp.c
* Description: This file
*
* Version: 0.1.0
* Author: shachi <shachi1758@outlook.com>
* ChangeLog:
*
* +------------+----------------------------------------------------+--------+
* | Date | Change | Author |
* +------------+----------------------------------------------------+--------+
* | 2023-05-09 | Create file. | shachi |
* +------------+----------------------------------------------------+--------+
*
*****************************************************************************/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1.3.4 函数注释
/**
* Description:
* Input Args:
* Output Args:
* Return Value:
*/
1
2
3
4
5
6
2
3
4
5
6
# 1.3.5 结构体注释
/**
* Brief
*/
1
2
3
2
3
# 1.3.6 变量注释
int32_t i = 0; //< 这个是我。
1