C 编程规范.docx
- 文档编号:28558374
- 上传时间:2023-07-19
- 格式:DOCX
- 页数:24
- 大小:32.08KB
C 编程规范.docx
《C 编程规范.docx》由会员分享,可在线阅读,更多相关《C 编程规范.docx(24页珍藏版)》请在冰豆网上搜索。
C编程规范
C++编程规范
1简介
为规范项目中以C++为基础语言的代码风格,提到代码的健壮性、可维护性,提高开发效率,特制定本规范。
本规范具有法律效力,除特别说明,或者项目组得到职权部门书面核准,本规范必须执行。
1.1目的
颁布本规范的目的是:
1.增加代码的健壮性、可读性、易维护性;减少有经验和无经验开发人员编程所需的脑力工作;
2.在项目范围内统一代码风格;
3.通过人为以及自动的方式对最终软件应用质量标准;
4.使新的开发人员快速适应项目环境;
5.支持项目资源的复用:
允许开发人员从一个项目区域(或子项目团队)移动到另一个,而不需要重新适应新的子项目团队的氛围。
1.2适用范围
本规范适用于公司所有以C++语言为基础的平台下开发的项目。
1.3概述
本规范包括内容:
6.如何组织项目代码;
7.编程风格(如何编写实际的源代码);
8.如何记录源代码;
9.代码内名称和源文件所使用的命名约定;
10.何时使用某些语言结构以及何时应避免某些语言结构。
2基本原则
11.清晰、可理解的源代码是影响软件可靠性和可维护性的主要因素。
清晰、可理解的代码可以表示为以下三个简单的基础原理:
⏹最小混淆:
软件的生存期中,源代码的读远比写多,规范、标准更是这样。
理想情况下,源代码读起来应该象英语一样描述了所要做的事,这同时还带来了它执行的好处。
程序本质上是为人编写,而不是为计算机编写的。
阅读代码是一个复杂的脑力过程,它可由统一标准来简化,在本文中还指最小混淆原则。
整个项目中统一样式是软件开发团队在编程标准上达成一致的主要原因,它不应视为一种惩罚或对创造性和生产力的阻碍。
⏹维护的唯一点:
只要可能,设计决策就应在源中只表述一点,它的多数后果应程序化的派生于此点。
不遵守这一原则严重损害了可维护性、可靠性和可理解性。
⏹最小干扰:
避免将源代码与可视干扰(如内容较少或对理解软件目的不起作用的信息)相混合:
12.所表达的精神不过于苛刻;而对正确安全的使用语言特性提供指导。
优秀软件的关键在于:
⏹了解每一个特性以及它的限制和潜在的危险;
⏹确切了解此特性可安全的使用于哪一个环境中;
⏹做出使用高度可视特性的决定;
⏹在合适的地方小心适度的使用特性。
3文件结构
每个C++/C程序通常分为两个文件。
一个文件用于保存程序的声明(declaration),称为头文件。
另一个文件用于保存程序的实现(implementation),称为定义(definition)文件。
C++/C程序的头文件以“.h”为后缀,C程序的定义文件以“.c”为后缀,C++程序的定义文件通常以“.cpp”为后缀(也有一些系统以“.cc”或“.cxx”为后缀)。
3.1版权和版本的声明
版权和版本的声明位于头文件和定义文件的开头,主要内容有:
13.版权信息。
14.文件名称,标识符,摘要。
15.当前版本号,作者/修改者,完成日期。
16.版本历史信息。
/**
*Copyright(c)2004,光庭导航数据(武汉)有限公司
*Allrightsreserved.
*
*文件名称:
*摘要:
简要描述本文件的内容
*
*当前版本:
*作者:
输入作者(或修改者)名字
*完成日期:
2004年×月×日
*
*取代版本:
*原作者:
输入原作者(或修改者)名字
*完成日期:
2004年月日
**/
【说明】
关于类的版权和版本申明要保持C++工程和RoseUML模型的统一,鉴于在RoseUML模型中编写这些声明比较麻烦导致工作量增加,所以可以在VC中使用“VC助手”工具帮助快速编写该类的版权和版本申明,在VC中编写好申明后要将该C++工程反转到RoseUML模型中,以保持C++工程和RoseUML模型的统一。
使用VC助手的方法:
17.点击助手工具栏的Options按钮
18.点击Completion页面的Edit按钮
19.找到
/**:
/************************************************************************/
/*?
*/
/************************************************************************/
修改为:
/**:
/**
*Copyright(c)2004,光庭导航数据(武汉)有限公司
*Allrightsreserved.
*?
*文件名称:
*摘要:
简要描述本文件的内容
*
*当前版本:
*作者:
输入作者(或修改者)名字
*完成日期:
2004年月日
*
*取代版本:
*原作者:
输入原作者(或修改者)名字
*完成日期:
2004年月日
**/
使用方法:
在VC中输入"/**"等待出现提示,然后回车即出现类注释。
⏹【提示3-1-1】通过上述方法可以在“VC助手”中编辑各种模板以提高编写代码的效率。
3.2头文件的结构
头文件由三部分内容组成:
20.头文件开头处的版权和版本声明。
21.预处理块。
22.函数和类结构声明等。
⏹【规则3-2-1】为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块。
⏹【规则3-2-2】用#include<>格式来引用标准库的头文件(编译器将从标准库目录开始搜索)。
⏹【规则3-2-3】用#include“”格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)。
⏹【规则3-2-4】头文件中只存放“声明”而不存放“定义”
在C++语法中,类的成员函数可以在声明的同时被定义,并且自动成为内联函数。
这虽然会带来书写上的方便,但却造成了风格不一致,弊大于利。
建议将成员函数的定义与声明分开,不论该函数体有多么小。
即使是缺省的构造函数和析构函数也不允许在头文件中定义。
⏹【规则3-2-5】不提倡使用全局变量,尽量不要在头文件中出现象externintvalue这类声明。
如果要保留全局变量,那么全局变量要保存在一个类中。
3.3定义文件的结构
定义文件有三部分内容:
23.定义文件开头处的版权和版本声明。
24.对一些头文件的引用。
25.程序的实现体(包括数据和代码)
3.4头文件的作用
26.通过头文件来调用库功能。
在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。
用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。
编译器会从库中提取相应的代码。
27.头文件能加强类型安全检查。
如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。
3.5目录结构
如果一个软件的头文件数目比较多(如超过十个),通常应将头文件和定义文件分别保存于不同的目录,以便于维护。
例如可将头文件保存于include目录,将定义文件保存于source目录(可以是多级目录)。
如果某些头文件是私有的,它不会被用户的程序直接引用,则没有必要公开其“声明”。
为了加强信息隐藏,这些私有的头文件可以和定义文件存放于同一个目录。
4程序的版式
版式虽然不会影响程序的功能,但会影响可读性。
程序的版式追求清晰、美观,是程序风格的重要构成因素。
4.1空行
空行起着分隔程序段落的作用。
空行得体(不过多也不过少)将使程序的布局更加清晰。
空行不会浪费内存。
。
⏹【规则4-1-1】在每个类声明之后、每个函数定义结束之后都要加一行空行。
⏹【规则4-1-2】在一个函数体内,逻揖上密切相关的语句之间不能加空行,而在逻辑上有区别的段落之间必须加空行。
例如:
⏹、“->”这类操作符前后不加空格。
4.2对齐
⏹【规则4-4-1】程序的分界符‘}’应独占一行并且与引用它们的语句左对齐。
‘{’可以另起一行,于‘}’左对齐,也可以放在引用它们的语句后面。
⏹【规则4-4-2】{}之内的代码块在引用语句的右边间隔一个“Tab”处左对齐。
4.3长行拆分
⏹【规则4-5-1】代码行最大长度不能超过80个字符。
⏹【规则4-5-2】长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。
拆分出的新行要进行适当的缩进,使排版整齐,语句可读。
4.4修饰符的位置
⏹【规则4-6-1】修饰符靠近数据类型和变量名。
例如:
⏹int*x;
从左至右
!
~++--(类型)sizeof
+-*&
从右至左
*/%
从左至右
+-
从左至右
<<>>
从左至右
<<=>>=
从左至右
==!
=
从左至右
&
从左至右
^
从左至右
|
从左至右
&&
从左至右
||
从右至左
?
:
从右至左
=+=-=*=/=%=&=^=
|=<<=>>=
从左至右
表6-1运算符的优先级与结合律
⏹【规则6-1-1】如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级。
由于将表6-1熟记是比较困难的,为了防止产生歧义并提高可读性,应当用括号确定表达式的操作顺序。
4.5复合表达式
如a=b=c=0这样的表达式称为复合表达式。
允许复合表达式存在的理由是:
(1)书写简洁;
(2)可以提高编译效率。
但要防止滥用复合表达式。
同时建议尽量避免使用复合表达式。
⏹【规则6-2-1】不要编写太复杂的复合表达式。
⏹【规则6-2-2】不要有多用途的复合表达式。
⏹【规则6-2-3】不要把程序中的复合表达式与“真正的数学表达式”混淆。
4.5.1if语句
if语句是C++/C语言中最简单、最常用的语句,然而很多程序员用隐含错误的方式写if语句。
本节以“与零值比较”为例,展开讨论。
4.5.2布尔变量与零值比较
【规则6-3-1】不可将布尔变量直接与TRUE、FALSE或者1、0进行比较,而是直接判断该值。
4.5.3整型变量与零值比较
【规则6-3-2】应当将整型变量用“==”或“!
=”直接与0比较。
不可模仿布尔变量的风格而写。
4.5.4浮点变量与零值比较
【规则6-3-3】不可将浮点变量用“==”或“!
=”与任何数字比较。
千万要留意,无论是float还是double类型的变量,都有精度限制。
所以一定要避免将浮点变量用“==”或“!
=”与数字比较,应该设法转化成“>=”或“<=”形式。
4.5.5指针变量与零值比较
【规则6-3-4】应当将指针变量用“==”或“!
=”与NULL比较。
4.5.6循环语句的效率
C++/C循环语句中,for语句使用频率最高,while语句其次,do语句很少用。
本节重点论述循环体的效率。
提高循环体效率的基本办法是降低循环体的复杂性。
⏹【建议6-4-1】在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数。
⏹【建议6-4-2】如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。
示例6-4(c)的程序比示例6-4(d)多执行了N-1次逻辑判断。
并且由于前者老要进行逻辑判断,打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。
如果N非常大,最好采用示例6-4(d)的写法,可以提高效率。
如果N非常小,两者效率差别并不明显,采用示例6-4(c)的写法比较好,因为程序更加简洁。
for(i=0;i { if(condition) DoSomething(); else DoOtherthing(); } if(condition) { for(i=0;i DoSomething(); } else { for(i=0;i DoOtherthing(); } 表6-4(c)效率低但程序简洁 表6-4(d)效率高但程序不简洁 4.6for语句的循环控制变量 ⏹【规则6-5-1】不可在for循环体内修改循环变量,防止for循环失去控制。 ⏹【建议6-5-1】建议for语句的循环控制变量的取值采用“半开半闭区间”写法。 如: x值属于半开半闭区间“0= 4.7switch语句 switch是多分支选择语句,而if语句只有两个分支可供选择。 虽然可以用嵌套的if语句来实现多分支选择,但那样的程序冗长难读。 这是switch语句存在的理由。 switch语句的基本格式是: switch(variable) { casevalue1: break; casevalue2: break; … default: break; } ⏹【规则6-6-1】每个case语句的结尾必须加break,以免导致多个分支重叠。 也不允许设计多个分支重叠。 ⏹【规则6-6-2】switch最后必须有default分支。 即使程序真的不需要default处理,也应该保留语句default: break;这样做并非多此一举,而是为了防止别人误以为你忘了default处理。 4.8goto语句 【建议6-7-1】避免使用goto语句。 5常量 常量是一种标识符,它的值在运行期间恒定不变。 C语言用#define来定义常量(称为宏常量)。 C++语言除了#define外还可以用const来定义常量(称为const常量)。 5.1为什么需要常量 如果不使用常量,直接在程序中填写数字或字符串,将会有什么麻烦? 28.程序的可读性(可理解性)变差。 程序员自己会忘记那些数字或字符串是什么意思,用户则更加不知它们从何处来、表示什么。 29.在程序的很多地方输入同样的数字或字符串,难保不发生书写错误。 30.如果要修改数字或字符串,则会在很多地方改动,既麻烦又容易出错。 【规则7-1-1】尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串。 例如PI等等 5.2const与#define的比较 C++语言可以用const来定义常量,也可以用#define来定义常量。 但是前者比后者有很多的优点: 31.const常量有数据类型,而宏常量没有数据类型。 编译器可以对前者进行类型安全检查。 而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。 32.有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。 33.但#define来定义常量也有其优点就是不用占用内存空间。 ⏹【建议7-2-1】在对内存无过多要求的C++程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。 而在对内存有很大限制的嵌入式开发环境中则需要使用宏常量。 ⏹【规则7-3-1】需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。 为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。 ⏹【规则7-3-2】如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。 5.3类中的常量 有时我们希望某些常量只在类中有效。 由于#define定义的宏常量是全局的,不能达到目。 怎样才能建立在整个类中都恒定的常量呢? 应该用类中的枚举常量来实现。 枚举常量不会占用对象的存储空间,它们在编译时被全部求值。 枚举常量的缺点是: 它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如PI=)。 6函数设计 函数是C++/C程序的基本功能单元,其重要性不言而喻。 函数设计的细微缺点很容易导致该函数被错用,所以光使函数的功能正确是不够的。 函数接口的两个要素是参数和返回值。 C语言中,函数的参数和返回值的传递方式有两种: 值传递(passbyvalue)和指针传递(passbypointer)。 C++语言中多了引用传递(passbyreference)。 由于引用传递的性质象指针传递,而使用方式却象值传递,大家常常迷惑不解,容易引起混乱。 6.1参数的规则 ⏹【规则8-1-1】参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。 ⏹【规则8-1-2】参数命名要恰当,顺序要合理。 例如,编写字符串拷贝函数StringCopy,它有两个参数。 如果把参数名字起为str1和str2。 voidStringCopy(char*str1,char*str2); 那么我们很难搞清楚究竟是把str1拷贝到str2中,还是刚好倒过来。 可以把参数名字起得更有意义,如叫strSource和strDestination。 这样从名字上就可以看出应该把strSource拷贝到strDestination。 ⏹【规则8-1-3】如果参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改。 ⏹【规则8-1-4】如果输入参数以值传递的方式传递对象,则宜改用“const&”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率,对于普通int型等等的变量则不需要采用“const&”方式。 ⏹【建议8-1-1】避免函数有太多的参数,参数个数尽量控制在5个以内。 如果参数太多,在使用时容易将参数类型或顺序搞错。 ⏹【建议8-1-2】尽量不要使用类型和数目不确定的参数。 6.2返回值的规则 ⏹【规则8-2-1】不要省略返回值的类型。 C语言中,凡不加类型说明的函数,一律自动按整型处理。 这样做不会有什么好处,却容易被误解为void类型。 C++语言有很严格的类型安全检查,不允许上述情况发生。 由于C++程序可以调用C函数,为了避免混乱,规定任何C++/C函数都必须有类型。 如果函数没有返回值,那么应声明为void类型。 ⏹【规则8-2-2】函数名字与返回值类型在语义上不可冲突。 ⏹【规则8-2-3】不要将正常值和错误标志混在一起返回。 正常值用输出参数获得,而错误标志用return语句返回。 ⏹【建议8-2-1】如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。 但不是所有场合都适合用“引用传递”,而有些场合只能用“值传递”而不能用“引用传递”,否则会出错。 6.3函数内部实现的规则 不同功能的函数其内部实现各不相同,看起来似乎无法就“内部实现”达成一致的观点。 但根据经验,我们可以在函数体的“入口处”和“出口处”从严把关,从而提高函数的质量。 ⏹【规则8-3-1】在函数体的“入口处”,对参数的有效性进行检查。 很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言”(assert)来防止此类错误。 ⏹【规则8-3-2】在函数体的“出口处”,对return语句的正确性和效率进行检查。 如果函数有返回值,那么函数的“出口处”是return语句。 我们不要轻视return语句。 如果return语句写得不好,函数要么出错,要么效率低下。 注意事项如下: 34.return语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。 例如 35.要搞清楚返回的究竟是“值”、“指针”还是“引用”。 36.如果函数返回值是一个对象,要考虑return语句的效率。 例如 returnString(s1+s2); 这是临时对象的语法,表示“创建一个临时对象并返回它”。 不要以为它与“先创建一个局部对象temp并返回它的结果”是等价的,如 Stringtemp(s1+s2); returntemp; 实质不然,上述代码将发生三件事。 首先,temp对象被创建,同时完成初始化;然后拷贝构造函数把temp拷贝到保存返回值的外部存储单元中;最后,temp在函数结束时被销毁(调用析构函数)。 然而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了效率。 类似地,我们不要将 returnint(x+y); 37.,因为‘.’在类中对任何成员都有意义,已经成为标准用法。 38.不能重载目前C++运算符集合中没有的符号,如#,@,$等。 原因有两点,一是难以理解,二是难以确定优先级。 39.对已经存在的运算符进行重载时,不能改变优先级规则,否则将引起混乱。 6.4函数内联 6.4.1可以用内联取代宏代码 C++语言支持函数内联,其目的是为了提高函数的执行效率(速度)。 在C程序中,可以用宏代码提高执行效率。 宏代码本身不是函数,但使用起来象函数。 预处理器用复制宏代码的方式代替函数调用,省去了参数压栈、生成汇编语言的CALL调用、返回参数、执行return等过程,从而提高了速度。 使用宏代码最大的缺点是容易出错。 让我们看看C++的“函数内联”是如何工作的。 对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。 如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。 在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样)。 如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。 这个过程与预处理有显着的不同,因为预处理器不能进行类型安全检查,或者进行自动类型转换。 假如内联函数是成员函数,对象的地址(this)会被放在合适的地方,这也是预处理器办不到的。 C++语言的函数内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作类的数据成员。 “断言assert”恐怕是个例外。 assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。 为了不在程序的Debug版本和Release版本引起差别,assert不应该产生任何副作用。 如果assert是函数,由于函数调用会引起内存、代码的变动,那么将导致Debug版本与Release版本存在差异。 所以assert不是函数,而是宏。 ⏹【建议10-5-1】基本上宏代码函数都可以用内联函数替代 如果编程人员仍然习惯用写宏代码函数,那么就应该遵守以下规则: ⏹【规则10-5-2】用宏定义表达式时,要使用完备的括号。 ⏹【规则10-5-3】将宏所定义的多条表达式放在大括号中。 ⏹【规则10-5-4】使用宏时,不允许参数发生变化。 6.4.2内联函数的编程风格 关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用。 inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。 一般地,用户可以阅读函数的声明,但是看不到函数的定义。 尽管在大多数教科书中内联函数的声明、定义体前面都加了inline关键字,但我认为inline不应该出现在函数的声明中。 这个细节虽然不会影响函数的功能,但是
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 编程规范 编程 规范