c\c++编程规范
一、文件结构
1.1 版权和版本的声明
版权和版本的声明位于头文件和定义文件的开头(参见示例1-1),主要内容有:
(1)版权信息。
(2)文件名称,标识符,摘要。
(3)当前版本号,作者/修改者,完成日期。
(4)版本历史信息。
/*
* 版权信息
* All rights reserved.
*
* 文件名称:filename.h
* 文件标识:
* 摘 要:简要描述本文件的内容
*
* 当前版本:1.1
* 作 者:输入作者(或修改者)名字
* 完成日期:xxxx年xx月xx日
*
* 取代版本:1.0
* 原作者 :输入原作者(或修改者)名字
* 完成日期:xxxx年xx月xx日
*
*/
1.2 头文件的结构
为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块。
用#include <filename.h> 格式来引用标准库的头文件(编译器将从标准库目录开始搜索);
用#include “filename.h” 格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)。
头文件中只存放“声明”而不存放“定义”
不提倡使用全局变量,尽量不要在头文件中出现象extern int value 这类声明。
#ifndef GRAPHICS_H // 防止graphics.h被重复引用
#define GRAPHICS_H
#include <math.h> // 引用标准库的头文件
…
#include “myheader.h” // 引用非标准库的头文件
…
void Function1(…); // 全局函数声明
…
class Box // 类结构声明
{
…
};
#endif
1.3 定义文件的结构
假设定义文件的名称为graphics.cpp,定义文件的结构参见示例:
#include “graphics.h” // 引用头文件
…
// 全局函数的实现体
void Function1(…)
{
…
}
// 类成员函数的实现体
void Box::Draw(…)
{
…
}
1.4 头文件的作用
(1)通过头文件来调用库功能。
(2)头文件能加强类型安全检查。
二、程序的排版
2.1 空行
在每个类声明之后、每个函数定义结束之后都要加空行。
在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。
2.2 代码行
1.一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。
if、for、while、do等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}。这样可以防止书写失误。
2.尽可能在定义变量的同时初始化该变量(就近原则)。如果变量的引用处和其定义处相隔比较远,变量的初始化很容易被忘记。如果引用了未被初始化的变量,可能会导致程序错误。
2.3 代码行内的空格
1.关键字之后要留空格。象if、for、while等关键字之后应留一个空格再跟左括号‘(’,以突出关键字。
函数名之后不要留空格,紧跟左括号‘(’,以与关键字区别。
2.‘(’向后紧跟,‘)’、‘,’、‘;’向前紧跟,紧跟处不留空格。‘,’之后要留空格,如Function(x, y, z)。如果‘;’不是一行的结束符号,其后要留空格,如for (initialization; condition; update)。
3.赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+=” 、“>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。
4.一元操作符如“!”、“~”、“++”、“--”、“&”(地址运算符)等前后不加空格。象“[]”、“.”、“->”这类操作符前后不加空格。
5.对于表达式比较长的for语句和if语句,为了紧凑起见可以适当地去掉一些空格,如for (i=0; i<10; i++)和if((a<=b) && (c<=d))
void Func1(int x, int y, int z); // 良好的风格
void Func1 (int x,int y,int z); // 不良的风格
if (year >= 2000) // 良好的风格
if(year>=2000) // 不良的风格
if ((a>=b) && (c<=d)) // 良好的风格
if(a>=b&&c<=d) // 不良的风格
for (i=0; i<10; i++) // 良好的风格
for(i=0;i<10;i++) // 不良的风格
for (i = 0; I < 10; i ++) // 过多的空格
x = a < b ? a : b; // 良好的风格
x=a<b?a:b; // 不好的风格
int *x = &y; // 良好的风格
int * x = & y; // 不良的风格
array[5] = 0; // 不要写成 array [ 5 ] = 0;
a.Function(); // 不要写成 a . Function();
b->Function(); // 不要写成 b -> Function();
2.4 对齐
程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。
{ }之内的代码块在‘{’右边数格处左对齐。
2.5 长行拆分
1.代码行最大长度宜控制在70至80个字符以内。
2.长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。
2.6 修饰符的位置
1.应当将修饰符* 和 & 紧靠变量名
例如:
char *name;
int *x, y; // 此处y不会被误解为指针
2.7 注释
1.注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。
2.当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。
2.8 类的版式
三、命名规则
3.1 共性规则
1.命名规则尽量与所采用的操作系统或开发工具的风格保持一致。
2.程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解。
3.尽量避免名字中出现数字编号,如Valu e1 ,Va lu e2等,除非逻辑上的确需要编号。
3.2 简单的Windows应用程序命名规则
1.类名和函数名用大写字母开头的单词组合而成。
例如:
class Node; // 类名
class LeafNode; // 类名
void Draw(void); // 函数名
void SetValue(int value); // 函数名
2.变量和参数用小写字母开头的单词组合而成。
例如:
BOOL flag;
int drawMode;
3.常量全用大写的字母,用下划线分割单词。
例如:
const int MAX = 100;
const int MAX_LENGTH = 100;
4.静态变量加前缀s_(表示static)。
5.如果不得已需要全局变量,则使全局变量加前缀g_(表示global)。
6.类的数据成员加前缀m_(表示member),这样可以避免数据成员与成员函数的参数同名。
四、表达式和基本语句
1.不可将布尔变量直接与TRUE、FALSE或者1、0进行比较。
假设布尔变量名字为flag,它与零值比较的标准if语句如下:
if (flag) // 表示flag为真
if (!flag) // 表示flag为假
其它的用法都属于不良风格,例如:
if (flag == TRUE)
if (flag == 1 )
if (flag == FALSE)
if (flag == 0)
2.整型变量与零值比较应当将整型变量用“==”或“!=”直接与0比较。
假设整型变量的名字为value,它与零值比较的标准if语句如下:
if (value == 0)
if (value != 0)
不可模仿布尔变量的风格而写成
if (value) // 会让人误解value是布尔变量
if (!value)
3.浮点变量与零值比较。不可将浮点变量用“==”或“!=”与任何数字比较。千万要留意,无论是float还是double类型的变量,都有精度限制。 所以一定要避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。
假设浮点变量的名字为x,应当将
if (x == 0.0) // 隐含错误的比较
转化为
#define EPSINON 0.00001
if ((x>=-EPSINON) && (x<=EPSINON))
其中EPSINON是允许的误差(即精度)。
5.循环语句的效率
在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数。
五、函数设计
5.1 参数的规则
1.参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。如果函数没有参数,则用void填充。
例如:
void SetValue(int width, int height); // 良好的风格
void SetValue(int, int); // 不良的风格
float GetValue(void); // 良好的风格
float GetValue(); // 不良的风格
2.参数命名要恰当,顺序要合理。
例如:
void StringCopy(char *str1, char *str2); // 不良的风格
void StringCopy(char *strSource,char *strDestination);// 良好的风格
3.如果参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改。
例如:
void StringCopy(char *strDestination,const char *strSource);
5.2 返回值的规则1.不要省略返回值的类型。
2.C语言中,凡不加类型说明的函数,一律自动按整型处理。这样做不会有什么好处,却容易被误解为void类型。C++语言有很严格的类型安全检查,不允许上述情况发生。由于C++程序可以调用C函数,为了避免混乱,规定任何C++/ C函数都必须有类型。如果函数没有返回值,那么应声明为void类型。
不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用return语句返回。
3.有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。例如字符串拷贝函数strcpy的原型:
char *strcpy(char *strDest,const char *strSrc);
strcpy函数将strSrc拷贝至输出参数strDest中,同时函数的返回值又是strDest。这样做并非多此一举,可以获得如下灵活性:
char str[20];
int length = strlen( strcpy(str, “Hello World”) );
5.3 函数内部实现的规则
1.在函数体的“入口处”,对参数的有效性进行检查。很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言”(assert)来防止此类错误。
2.在函数体的“出口处”,对return语句的正确性和效率进行检查。
3.函数的功能要单一,不要设计多用途的函数。
4.函数体的规模要小,尽量控制在50行代码之内。
5.4使用断言
1.使用断言捕捉不应该发生的非法情况。
2.在函数的入口处,使用断言检查参数的有效性(合法性)。
六、内存管理
6.1内存分配方式
1.内存分配方式有三种:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活。
2.用malloc或new申请内存之后,应该立即检查指针值是否为NULL。
防止使用指针值为NULL的内存。
3.用free或delete释放了内存之后,立即将指针设置为NULL,防止产
生“野指针”。
6.2有了malloc/free为什么还要new/delete ?
malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
类Obj的函数Initialize模拟了构造函数的功能,函数Destroy模拟了析构函数的功能。函数UseMallocFree中,由于malloc/free不能执行构造函数与析构函数,必须调用成员函数Initialize和Destroy来完成初始化与清除工作。函数UseNewDelete则简单得多。所以我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。