现在的位置: 首页 > 综合 > 正文

C++编码规范与指导

2013年05月30日 ⁄ 综合 ⁄ 共 7696字 ⁄ 字号 评论关闭
 

C++编码规范与指导

版本:1.27

作者:白杨

http://baiy.cn

推荐浏览设置:

  • 屏幕分辨率:≥ 1024x768

  • 字体:中(Ctrl+鼠标滚轮设置)

  • 最大化本窗口

 

文档控制

版本号 修改时间 修改内容 修改人 审稿人
1.0 2004-07-22
  • 创建
白杨 田振军
1.1 2004-08-05
  • 根据审稿意见修改
白杨 田振军、马浩军、叶晓峰
1.2 2004-08-09 白杨 田振军、马浩军、叶晓峰
1.3 2004-08-10
  • 重写目录;一些小改动
白杨  
1.4 2004-08-10 白杨 广大CSDN上的网友,鸣谢 :-)
1.5 2004-08-28
  • 根据网友审稿意见修改
  • 函数中增加“让相同的代码只出现一次”的条目
  • 函数头中增加复杂性描述
白杨  
1.6 2004-11-22
  • 修正了一些笔误
白杨  
1.7 2005-03-30 白杨  
1.8 2005-05-04
  • 小改动,修正了一些笔误;在类命名规范中增加了界面类的概念
白杨  
1.9 2005-05-11
  • 再次调整类命名规范,引入界面、类型和类的概念
白杨  
1.10 2005-06-06 白杨  
1.11 2005-06-07 白杨  
1.12 2005-06-28
  • 修正一些笔误
白杨  
1.13 2005-08-20
  • 小幅修正和调整
白杨  
1.14 2005-11-08
  • 对全文再次进行修正及调整
白杨  
1.15 2005-11-14 白杨  
1.16 2005-11-16 白杨  
1.17 2005-11-21 白杨  
1.18 2005-12-02
  • 文件头中增加多线程和异常时安全性描述
白杨  
1.19 2005-12-25 白杨 CCF上的网友,特别感谢smartsl
1.20 2006-04-03
  • 根据网友意见修正一些笔误
白杨  
1.21 2007-02-26 白杨  
1.22 2007-07-18 白杨  
1.23 2007-11-22 白杨  
1.24 2008-01-17
  • 措辞和表达上的细微调整
白杨 CCF 上的 Jiang Haibin
1.25 2008-03-12
  • 根据审稿人建议修正一些错别字,感谢 Haibin 兄 :)
白杨  
1.26 2008-06-26 白杨  
1.27 2008-07-27
  • 修正一些错别字
白杨  
         

 


目录

附件

 

返回目录


版权声明

本文档版权归作者所有。您可以以任意形式免费使用本文档的任意部分,并且无需通知作者。作者对使用本文档所造成的任何直接或者间接的损失不负任何责任。

 

返回目录


概述

对于任何工程项目来说,统一的施工标准都是保证工程质量的重要因素。堪称当今人类最抽象、最复杂的工程——软件工程,自然更加不能例外。

高品质、易维护的软件开发离不开清晰严格的编码规范。本文档详细描述C++软件开发过程中的编码规范。本规范也适用于所有在文档中出现的源码。

除了“语法高亮”部分,本文档中的编码规范都以:

规则(或建议) 解释

的格式给出,其中强制性规则使用黑色,建议性规则使用灰色

 

 

返回目录


针对 C 程序员的快速回顾

本节旨在较高层面上快速回顾 C 与 C++ 的主要区别。专门针对 C 思想根深蒂固的老咖和经常需要在 C / C++ 项目间频繁切换的 coder。C 与 C++ 的主要区别包括:

  • 空参函数声明的默认参数类型为 void 而不是 int [编译时]。
  • 强类型检查和专门的类型转换操作 [编译时,除 dynamic_cast 操作]。
  • 名空间:用于归类接口和模块以及防止重名。名空间有自动向父级查询匹配和凯氏匹配。名空间的一个副作用是名称粉碎,可以用 extern "C" 声明解决 [编译时]。
  • :类将一个 C 结构体和与之相关的函数打包在一起,并且提供了编译时的访问控制检查,为了拟真内置类型,还提供了操作符重载 [编译时]。
  • 类层次结构:类可以通过相互间的继承和派生形成层次结构,派生类继承了基类的数据结构和方法 [编译时]。
  • 模板:本质上是类型参数化。在编译时生成 C++ 代码的过程叫做模板实例化,默认的实例化点在当前编译单元第一次使用该模板时,也可以通过显式实例化来增加编译速度。通过部分或完全的专门化可以为指定类型提供优化算法 [编译时]。
  • 抽象类和虚方法:可以通过基类指针 或引用访问的动态重载技术 [运行时]。
  • 多重继承和虚基类:一个类可以有多个父亲,为了避免层次结构中出现重复的基类,C++ 提供了虚基类 [运行时]。
  • 运行时类型信息(RTTI):允许程序员在类层次结构中漫游;完成动态转换(向上、向下和交叉强制);获取指定类型的 typeid 信息,用户可以以此为基础实现反射、高级调试等各类功能。
  • 异常处理:本质上是一种安全的 longjmp 机制,在退栈期间能够完成必要的对象析构动作。主要用于在发生错误时跳转到相应的错误处理分支。

 

返回目录


语法高亮与字体

字体

文字是信息的载体;文字使我们能够把个人的经验和思想长久的保存下来;文字使我们得以站在前人的肩膀上向前发展;文字的诞生标志着人类文明的开始……

扯的太离谱了?好吧,至少你应该承认:

  • 没有文字就不可能出现计算机(先不管他是哪国字
  • 没有文字大家就不可能(也没必要)学会如何写程序
  • 在过去、现在和可见的将来,使用文字符号都是编写计算机软件的主要方式方法

既然文字如此重要,它的长相自然会受到广泛的关注。如今这个连MM都可以“千面”的年头,字体的种类当然是数不胜数。

然而,前辈先贤们曾经用篆体教导偶们:。想让大家读到缩进、对齐正确一致,而且不出现乱码的源文件,我们就要使用相互兼容的字体。

字体规范如下:

使用等宽字体 由于非等宽字体在对齐等方面问题多多,任何情况下,源码都必须使用等宽字体编辑和显示。

 

每个制表符(TAB)的宽度为4个半角字符 不一致的缩进宽度会导致行与行之间的参差不齐,进而严重影响代码的可读性。

 

优先使用Fixedsys 在Windows平台中,应该优先使用字体:Fixedsys,这也是操作系统UI(所有的菜单、按钮、标题栏、对话框等等)默认使用的字体。该字体的好处很多:

  • 兼容性好:所有Windows平台都支持该字体
     
  • 显示清晰:该字体为点阵字体,相对于矢量字体来说在显示器中呈现的影像更为清晰。矢量字体虽然可以自由缩放,但这个功能对于纯文本格式的程序源码来说没有任何实际作用。

    而且当显示字号较小(12pt以下)时,矢量字体还有一些明显的缺陷:
     

    • 文字的边缘会有严重的凹凸感。
    • 一些笔画的比例也会失调。
    • 开启了柔化字体边缘后,还会使文字显得模糊不清。

    说句题外话,这也是Gnome和KDE等其它GUI环境不如Windows的一个重要方面。
     

  • 支持多语言:Fixedsys是UNICODE字体,支持世界上几乎所有的文字符号。这对编写中文注释是很方便的。

 

语法高亮

几乎所有的现代源码编辑器均不同在程度上支持语法高亮显示的功能。缤纷的色彩不但可以吸引MM们的目光,还可以在很大程度上帮助我们阅读那些奥涩如咒语般的源代码。

统一的语法高亮规则不仅能让我们望色生意,还可以帮助我们阅读没有编码规范,或者规范执行很烂的源码。

所有在文档中出现的代码段均必须严格符合下表定义的语法高亮规范。在编辑源码时,应该根据编辑器支持的自定义选项最大限度地满足下表定义的高亮规范。

类型 颜色 举例
注释 R0;G128;B0(深绿) // 注释例子
关键字 R0;G0;B255(蓝) typedef, int, dynamic_cast class ...
类、结构、联合、枚举等其它自定义类型 R0;G0;B255(蓝) class CMyClass, enum ERRTYPE, typedef int CODE ...
名空间 R0;G0;B255(蓝) namespace BaiY
数字 R255;G0;B0(红) 012 119u 0xff ...
字符、字符串 R0;G128;B128(深蓝绿) "string", 'c ...
宏定义、枚举值 R255;G128;B0(橙黄) #define UNICODE, enum { RED, GREEN, BLUE };
操作符 R136;G0;B0(棕色) < > , = + - * / ; { } ( ) [ ] ...
方法/函数 R136;G0;B0(棕色) MyFunc()
变量 R128;G128;B128(中灰色) int nMyVar;
背景 R255;G255;B255(白色)  
其它 R0;G0;B0(黑色) other things(通常是一个错误)

 

返回目录


文件结构

文件头注释

所有C++的源文件均必须包含一个规范的文件头,文件头包含了该文件的名称、功能概述、作者、版权和版本历史信息等内容。标准文件头的格式为:

 

/*! @file
********************************************************************************
<PRE>
模块名       : <文件所属的模块名称>
文件名       : <文件名>
相关文件     : <与此文件相关的其它文件>
文件实现功能 : <描述该文件实现的主要功能>
作者         : <作者部门和姓名>
版本         : <当前版本号>
--------------------------------------------------------------------------------
多线程安全性 : <
是/否>[,说明]
异常时安全性 : <
是/否>[,说明]
--------------------------------------------------------------------------------
备注         : <其它说明>
--------------------------------------------------------------------------------
修改记录 :
日 期        版本     修改人              修改内容
YYYY/MM/DD   X.Y      <作者或修改者名>    <
修改内容>
</PRE>
*******************************************************************************/

 

如果该文件有其它需要说明的地方,还可以专门为此扩展一节 ,节与节之间用长度为80的“=”带分割:

/*! @file
********************************************************************************
<PRE>
模块名       : <文件所属的模块名称>
文件名       : <文件名>
相关文件     : <与此文件相关的其它文件>
文件实现功能 : <描述该文件实现的主要功能>
作者         : <作者部门和姓名>
版本         : <当前版本号>
--------------------------------------------------------------------------------
多线程安全性 : <
是/否>[,说明]
异常时安全性 : <
是/否>[,说明]
--------------------------------------------------------------------------------
备注         : <其它说明>
--------------------------------------------------------------------------------
修改记录 :
日 期        版本     修改人              修改内容
YYYY/MM/DD   X.Y      <作者或修改者名>    <
修改内容>
</PRE>
********************************************************************************

* 项目1
  - 项目1.1
  - 项目1.2

================================================================================
* 项目2
  - 项目2.1
  -
项目2.2
....

*******************************************************************************/

每行注释的长度都不应该超过80个半角字符。还要注意缩进和对齐,以利阅读。

注意:将多线程和异常时安全性描述放在文件头,而不是类或者函数注释中,是为了体现以下设计思想:同一个模块中的界面,其各方面的操作方式和使用风格应该尽量保持一致。

关于文件头的完整例子,请参见:文件头例子

关于文件头的模板,请参见:文件头注释模板

 

头文件

头文件通常由以下几部分组成:

 

文件头注释 每个头文件,无论是内部的还是外部的,都应该由一个规范的文件头注释作为开始。

 

预处理块 为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块。

 

函数和类/结构的声明等 声明模块的接口

 

需要包含的内联函数定义文件(如果有的话) 如果类中的内联函数较多,或者一个头文件中包含多个类的定义(不推荐),可以将所有内联函数定义放入一个单独的内联函数定义文件中,并在类声明之后用“#include”指令把它包含进来。

头文件的编码规则:

引用文件的格式 用 #include <filename.h> 格式来引用标准库和系统库的头文件(编译器将从标准库目录开始搜索)。

用 #include "filename.h" 格式来引用当前工程中的头文件(编译器将从该文件所在目录开始搜索)。

 

分割多组接口(如果有的话) 如果在一个头件中定义了多个类或者多组接口(不推荐),为了便于浏览,应该在每个类/每组接口间使用分割带把它们相互分开。

关于头文件的完整例子,请参见:头文件例子

 

内联函数定义文件

如上所述,在内联函数较多的情况下,为了避免头文件过长、版面混乱,可以将所有的内联函数定义移到一个单独的文件中去,然后再用#include指令将它包含到类声明的后面。这样的文件称为一个内联函数定义文件。

按照惯例,应该将这个文件命名为“filename.inl”,其中“filename”与相应的头文件和实现文件相同。

内联函数定义文件由以下几部分组成:

文件头注释 每内联函数定义文件都应该由一个规范的文件头注释作为开始
内联函数定义 内联函数的实现体

内联函数定义文件的编码规则:

分割多组接口(如果有的话) 如果在一个内联函数定义文件中定义了多个类或者多组接口的内联函数(不推荐),必须在每个类/每组接口间使用分割带把它们相互分开。

 

文件组成中为什么没有预处理块? 与头文件不同,内联函数定义文件通常不需要定义预处理块,这是因为它通常被包含在与其相应的头文件预处理块内。

关于内联函数定义文件的完整例子,请参见:内联函数定义文件例子

 

实现文件

实现文件包含所有数据和代码的实现体。实现文件的格式为:

 

文件头注释 每个实现文件都应该由一个规范的文件头注释作为开始

 

对配套头文件的引用 引用声明了此文件实现的类、函数及数据的头文件

 

对一些仅用于实现的头文件的引用(如果有的话) 将仅与实现相关的接口包含在实现文件里(而不是头文件中)是一个非常好的编程习惯。这样可以有效地屏蔽不应该暴露的实现细节,将实现改变对其它模块的影响降低到最少 。

 

程序的实现体 数据和函数的定义

实现文件的编码规则:

分割每个部分 在本地(静态)定义和外部定义间,以及不同接口或不同类的实现之间,应使用分割带相互分开。

关于实现文件的完整例子,请参见:实现文件例子

 

文件的组织结构

由于项目性质、规模上存在着差异,不同项目间的文件组织形式差别很大。但文件、目录组织的基本原则应当是一致的:使外部接口与内部实现尽量分离;尽可能清晰地表达软件的层次结构……

为此提供两组典型项目的文件组织结构范例作为参考:

功能模块/库的文件组织形式

显而易见,编写功能模块和库的主要目的是为其它模块提供一套完成特定功能的API,这类项目的文件组织结构通常如下图所示:

其中:

contrib 当前项目所依赖的所有第三方软件,可以按类别分设子目录。
doc 项目文档
include 声明外部接口的所有头文件和内联定义文件。
lib 编译好的二进制库文件,可以按编译器、平台分设子目录。
makefile 用于编译项目的makefile文件和project文件等。可以按编译器、平台分设子目录。
src 所有实现文件和声明内部接口的头文件、内联定义文件。可按功能划分;支持编译器、平台等类别分设子目录。
test 存放测试用代码的目录。

应用程序的文件组织形式

与功能模块不同,应用程序是一个交付给最终用于使用的、可以独立运行并提供完整功能的软件产品,它通常不提供编程接口,应用程序的典型文件组织形式如下图所示:

contrib 当前项目所依赖的所有第三方软件,可以按类别分设子目录。
doc 项目文档
makefile 用于编译项目的makefile文件和project文件等。可以按编译器、平台分设子目录。
setup 安装程序,以及制作安装程序所需要的项目文件和角本。
src 所有源文件。可按功能划分;支持编译器、平台等类别分设子目录。
test 存放测试用代码的目录。

 

返回目录


命名规则

如果想要有效的管理一个稍微复杂一点的体系,针对其中事物的一套统一、带层次结构、清晰明了的命名准则就是必不可少而且非常好用的工具。

活跃在生物学、化学、军队、监狱、黑社会、恐怖组织等各个领域内的大量有识先辈们都曾经无数次地以实际行动证明了以上公理的正确性。除了上帝(设它可以改变世间万物的秩序)以外,相信没人有实力对它不屑一顾

在软件开发这一高度抽象而且十分复杂的活动中,命名规则的重要性更显得尤为突出。一套定义良好并且完整的、在整个项目中统一使用的命名规范将大大提升源代码的可读性和软件的可维护性。

在引入细节之前,先说明一下命名规范的整体原则:

同一性 在编写一个子模块或派生类的时候,要遵循其基类或整体模块的命名风格,保持命

抱歉!评论已关闭.