0x04 C代码规范
命名
- 只要提到代码规范,就不得不说的一个问题。
- 在一些小的演示程序中,也许费尽心思去构思一个 命名 是一件十分傻的行为,但是只要程序上升到你需要严正设计,思考,复查的层次,你就需要好好考虑 命名 这个问题。
函数命名:
C语言中,我们可以让下划线或者词汇帮助我们表达函数功能:
- 前缀:
set
可以表示设置一个参数为某值get
可以表示获取某一个参数的值is
可以表示询问是否是这种情况
后缀:
max/min
可以表示某种操作的最大(小)次数cnt
可以表示当前的操作次数key
某种关键值size_t get_counts();
size_t retry_max();
int is_empty();
- 前缀:
- 需要注意的只是,不要让命名过于赘述其义,只简单保留动作以及目的即可,详细功能可以通过文档来进行进一步的解释。
结构体命名:
由于结构体的 标签,不会污染命名,即标签不在命名搜索范围之内,所以可以放心使用:
有人习惯使用
typedef
, 而有人喜欢使用struct tag obj
,后者比较多,但是前者也不失为一种好方法,仁者见仁智者见智。/*方法1*/
struct inetaddr_4{
int port;
char * name;
};
struct inetaddr_4 *addr_info;
/*方法2*/
typedef struct _addr{
int port;
char * name;
}inetaddr_4;
inetaddr_4 *addr_info_2;
两者同处一个文件内亦不会发生编译错误。
变量命名
- 所有字符都使用小写
- 含义多的可以用
_
进行辅助 - 以
=
为标准进行对齐 - 类型, 变量名左对齐。
等号左右两端,最少有一个空格。
int main(void)
{
int counts = 0;
inetaddr_4 *addr = NULL;
return 0;
}
为了防止指针声明定义时候出错,将
*
紧贴着变量名总不会出错。inetaddr_4 *addr, object, *addr_2;
其中
addr
和addr_2
是指针,而object
则是一个栈上的完整对象,并不是指针。全局变量能少用就少用,必须要用的情况下,可以考虑添加前缀
g_
int g_counts;
#define
命名- 所有字符都是用大写,并用
_
进行分割 如果多于一个语句,使用
do{...}while(0)
进行包裹,防止;
错误。#define SWAP(x, y) \
do{ \
x = x + y; \
y = x - y; \
x = x - y; \
}while(0)
当然这个交换宏实际上有一点缺陷,在大后方会提出。此处是代码规范,就不重复强调。
- 所有字符都是用大写,并用
enum
命名- 所有字符都是用大写,并用
_
进行分割 - 与
define
相比,enum
适用于同一类型的常量声明,而不是单一独立的常量。往往出现都是成组。
- 所有字符都是用大写,并用
格式化代码
花括号
{}
- 混合使用符合节俭思想,但会稍微有一点结构紊乱。
- 单一使用能更好让代码结构清晰。
- 所谓混合,单一指的是是否一直使用
{}
进行代码包裹。 - 有人认为 当单一语句的时候不必要添加
{}
,有的人则习惯添加 当作用域超过一个屏幕的时候,可以适当的使用注释来指明
{}
作用域while(1){
if(tmp == NULL){
break;
}
else if(fanny == 1){
... 大概超过了一个屏幕的代码
} /*else if fanny*/
}/*end while*/
如果是代码量少的情况下,但嵌套比较多,也可以使用这个方式进行注释。
括号
()
有人建议除了函数调用以外,在条件语句等类似情况下使用
()
要在关键字后空一格,再接上()
语句,对于这一点,我个人习惯是不空格,但总有这种说法。if (space == NULL) {
/**TODO**/
}
while(1){
/**我习惯于如此写**/
}
strcpy(str1, str2); /**第一种写法是为了和函数调用写法进行区分**/
return 0;
switch
- 一定要放一个
default
在最后,即使它永远不会用到。 每个
case
如果需要使用新变量,可以用{}
包裹起来,并在里面完成所有操作。switch(...)
{
case 1:
/**TODO**/
break;
case 2:
{
int new_vari;
/**创建新变量则用 {} 包裹起来**/
}
break;
default:
call_error();
}
- 一定要放一个
goto
- 虽然许多人,许多书都提醒不再使用
goto
关键字,而是使用setjmp
和longjmp
来取代它,但是这还是那句话,仁者见仁智者见智,如果goto
能够让代码清晰,那何乐而不为呢,这个观点也是最近才体会到的(并非我一己之言)。 - 具体使用可以查询官方文档。
- 虽然许多人,许多书都提醒不再使用
语句
- 应该让完整的语句在每一行中,只出现一次。
- 对于变量声明定义亦是如此
- 原因是这样能让文档更有针对性
头文件保护
- 对于头文件而言,在一个程序中有可能被多次包含(
#include
),如果缺少头文件保护,则会发生编译错误 不要将
_
作为宏的开头或者结尾。#ifndef VECTOR_H_INCLUDE
#define VECTOR_H_INCLUDE
/**TODO**/
#endif
- 对于头文件而言,在一个程序中有可能被多次包含(
宏
- C语言的宏有诸多弊端,所以尽量使用
inline
函数来代替宏。在大后方会有解释 - 但是,请不要因此抛弃了宏,比如在
C11
中有一个新兴的宏。
- C语言的宏有诸多弊端,所以尽量使用
变量
- 第一时刻初始化所有所声明的变量,因为这么做总没有坏处,而且能减少出错的可能。
函数
- 函数应该尽可能的短小,一个ANSI屏幕的为最佳。
如果某个循环带着空语句,使用
{}
进行挂载,以免出现意外。while(*is_end++ != '\0')
{
;
}
虽然是空的循环体,但是写出来以免造成误循环。
尽量不要让函数返回值直接作为条件语句的判断,这样会极大降低可读性
if(is_eof(file) == 0)
好过
if(!is_eof(file))
不要为了方便或者一点点的所谓速度提升(也许根本没有),而放弃可读性,使用嵌入式的赋值语句
int add = 10;
int num = 11;
int thr = 20;
add = add + thr;
num = add + 20;
不要写成
num = (add = add + thr) + 20;
浮点数
- 万万记住不要再使用浮点数比较彼此是否相等或不等。
- 如果把浮点数用在离散性的数据上,比如循环计数器,那就等死吧。
其他
- 使用
#if
而不是#ifdef
可以使用
define()
来代替#ifdef
的功能#if !define(USERS_DEFINE)
#define USERS_DEFINE ...
#endif
对于某些大段需要消除的代码,我们不能使用注释
/**/
,因为注释不能内嵌着注释(//
除外),我们可以使用黑魔法:#if NOT_DECLARATION
/**想要注释的代码**/
#endif
不要使用纯数字
- 意味着,不在使用毫无标记的数字,因为可能你过了几个月再看源代码的时候,你根本不知道这个数字代表着什么
- 而应该使用
#define
给它一个名字,来说明这个数字的意义。