C/C++编程规范

C/C++编程规范

 

本规范制定的目的是通过详阐述如何进行C编码来减少团队开发中给项目管理带来的复杂性,增强代码的一致性,以利于项目成员间和后期维护中的交流。

保持统一编程风格,意味着可以轻松根据“模式匹配”规则推断各种符号的含义。创建通用的、必要的习惯用语和模式可以使代码更加容易理解,同时我们遵循一致性原则,尽量不创建独特的编程风格。

本规范的使用者对C应非常熟悉。

 

一、头文件

通常,每一个.c文件(C的源文件)都有一个对应的.h文件(头文件),也有一些例外,如单元测试代码和只包含main()的.c文件。

正确使用头文件可增强代码在可读性、文件大小和性能。

下面的规则将引导你规避使用头文件时的各种麻烦。

⒈ #define保护

⑴ 为保证唯一性,头文件的命名应基于其所在项目源代码树的全路径

命名格式为:

<PROJECT>_<PATH>_<FILE>_H_ 

例如,项目foo中的头文件foo\src\bar\baz.h

⑵ 所有头文件都应该使用#ifndef防止头文件被多重包含(multiple inclusion)。

例如,

#ifndef FOO_BAR_BAZ_H_ 

#define FOO_BAR_BAZ_H_ 

...

#endif // FOO_BAR_BAZ_H_

避免多重包含是学习编程时最基本的要求

⒉ 包含文件的名称及次序

⑴ 包含文件的排列次序

C系统库

其他库的.h

项目内的.h。

将包含次序标准化可增强程序的可读性、有效减少隐藏依赖避免隐藏依赖(hidden dependencies,隐藏依赖主要是指包含的文件编译)。

⑵ 项目内头文件应按照项目源代码目录树结构排列,并且避免使用Windows的文件路径.(当前目录)和..(父目录)

例如,google-awesome-project\src\base\logging.h

 #include "base\logging.h"

 dir\foo.c的主要作用是执行或测试dir2/foo2.h的功能。

对应的.c.h文件,通常位于相同目录下。

⒊ 函数参数顺序(Function Parameter Ordering)

定义函数时,参数顺序为:输入参数在前,输出参数在后。

C函数参数分为输入参数和输出参数两种,有时输入参数也会输出(注:值被修改时)。

这一点并不是必须遵循的规则,输入/输出两用参数(通常是结构体变量)混在其中,会使得规则难以遵循。


二、作用域

作用域的使用,除了考虑名称污染、可读性之外,主要是为降低耦合度,提高编译、执行效率。

⒈ 局部变量(Local Variables)

⑴ 将函数变量尽可能置于最小作用域内,在声明变量时将其初始化

我们提倡在尽可能小的作用域中声明变量,离第一次使用越近越好。这使得代码易于阅读,易于定位变量的声明位置、变量类型和初始值。

C可正确执行for (int i = 0; i < 10; ++i)(i的作用域仅限for循环),因此其他for循环中可重用i。if和while等语句中可同样使用。

⑵ 使用初始化代替声明+赋值的方式。

int i;

i = f();   // ——初始化和声明分离

int i = g();  // ——初始化时声明

⒉ 全局变量(Global Variables)

⑴ 尽量不用全局函数和全局变量,考虑作用域的限制,尽量单独形成编译单元;

⑵ 永远不要使用函数返回值初始化全局发量。


三、C特性

⒈编写短小函数(Write Short Functions)

长函数有时是恰当的,因此对于函数长度并没有严格限制。更倾向于选择短小、凝练的函数,函数体尽量短小、紧凑,功能单一。

如果函数超过40行,可以考虑在不影响程序结构的情冴下将其分割一下。 即使一个长函数现在工作的非常好,一旦有人对其修改,有可能出现新的问题,甚至导致难以发现的bugs。使函数尽量短小、简单,便于他人阅诺和修改代码。

在处理代码时,你可能会发现复杂的长函数,不要害怕修改现有代码:如果证实这些代码使用、调试困难,或者你需要使用其中的一小块,考虑将其分割为更加短小、易于管理的若干函数。

⒉ 声明次序(Declaration Order)

⑴ typedefs和enums

⑵ 常量(defines)

⑶ 函数

⑷ 变量

⒊ 引用参数

如果函数需要修改变量的值,形参(parameter)必须为指针。

⒋ 前置自增和自减(Preincrement and Predecrement) 

⑴ 能用前置自增/减不用后置自增/减

⑵ 对于迭代器和其他模板对象使用前缀形式(++i)的自增、自减运算符

⑶ 定义

对于变量在自增(++i或i++)或自减(--i或i--)后表达式的值又没有没用到的情冴下,需要确定到底是使用前置还是后置的自增自减。 

⑷ 优点

不考虑返回值的话,前置自增(++i)通常要比后置自增(i++)效率更高,因为后置的自增自减需要对表达式的值i进行一次拷贝,如果i是迭代器或其他非数值类型,拷贝的代价是比较大的。既然两种自增方式动作一样(注,不考虑表达式的值),为什么不直接使用前置自增呢? 

⑸ 缺点

C语言中,当表达式的值没有使用时,传统的做法是使用后置自增,特别是在for循环中,有些人觉得后置自增更加易懂,因为这很像自然语言,主语(i)在谓语动词(++)前。 

⑹ 结论

对简单数值(非对象)来说,两种都无所谓,对迭代器和模板类型来说,要使用前置自增(自减)

⒌ 无符号整型(Unsigned Integer Types)

一些教科书作者,推荐使用无符号类型表示非负数,类型表明了数值取值形式。但是,在C语言中,这一优点被由其导致的bugs所淹没。如:

for (unsigned int i = foo.Length()-1; i >= 0; --i) ... 

上述代码永远不会终止!有时C会发现该bug并报警,但通常不会。

类似的bug还会出现在比较有符号变量和无符号变量时,主要是C的类型提升机制(type-promotion scheme,C语言中各种内建类型之间的提升转换关系)会致使无符号类型的行为出乎你的意料。

因此,使用确定大小的整型,除位组(bit pattern)外不要使用无符号型。

⒍ 预处理宏(Preprocessor Macros)

使用宏时要谨慎,尽量以枚举和常量代替之。

宏意味着你和编译器看到的代码是不同的,因此可能导致异常行为,尤其是当宏存在于全局作用域中时。

使用宏进行条件编译,最好不要返么做,会令测试更加痛苦(#define防止头文件重包含当然是个例外)。 

宏可以做一些其他技术无法实现的事情,在一些代码库(尤其是底层库中)可以看到宏的某些特性(如字符串化(stringifying,使用#)、连接(concatenation,使用##)等等)。但在使用前,仔细考虑一下能不能不使用宏实现同样效果。 

关于宏的高级应用,可以参考《C语言宏的高级应用》。

因此,除字符串化、连接外尽量避免使用宏

下面给出的用法模式可以避免一些使用宏的问题,供使用宏时参考:

⑴ 不要在.h文件中定义宏;

⑵ 使用前正确#define,使用后正确#undef;

⑶ 不要只是对已经存在的宏使用#undef,选择一个不会冲突的名称。

⒎ 0和NULL(0 and NULL)

整数用0,实数用0.0,指针用NULL,字符(串)用'\0'。

整数用0,实数用0.0,这一点是毫无争议的。 

对于指针(地址值),到底是用0还是NULL,Bjarne Stroustrup建议使用最原始的0,我们建议使用看上去像是指针的NULL,事实上一些C编译器专门提供了NULL的定义,可以给出有用的警告,尤其是sizeof(NULL)和sizeof(0)不相等的情况。

字符(串)用'\0',不仅类型正确而且可读性好。

⒏ sizeof(sizeof)

尽可能用sizeof(varname)代替sizeof(type)。

使用sizeof(varname)是因为当变量类型改变时代码自动同步,有些情况下sizeof(type)或许有意义,还是要尽量避免,如果变量类型改变的话不能同步。

Struct data;

<
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值