C++编程规范

esioncao 贡献于2013-03-12

作者 陈建  创建于2001-10-22 07:03:00   修改者hxs  修改于2002-06-26 08:56:00字数29561

文档摘要:本文的目的是表述项目 C++ 编程的规范、指南和提示。规范包括了以下 C++ 开发的方面: 如何组织项目代码; 编程风格(如何编写实际的源代码); 如何记录源代码; 代码内名称和源文件所使用的命名约定; 何时使用某些语言结构以及何时应避免某些语言结构; 相关模板与名词表
关键词:

 卷号 卷内编号 密级 分类:COD 使用者:设计员,实施员 ©托普集团,2001 C++ 编码规范 Version 1.0 作者:陈建 目录 1. 简介 1 1.1 基本原则 1 1.2 适用范围 1 1.3 使用常识 1 1.4 参考资料 1 2. 代码组织 2 2.1 项目组织风格 2 2.1.1 项目取名与最终的可执行文件名一致 2 2.1.2 项目目录设置 2 2.2 集成环境内的项目目录设置 3 2.3 文件声明 3 2.3.1 类声明与实现的文件放置方法 3 2.3.2 每个类声明避免定义多个类 4 2.3.3 使用 #include 来访问类的声明 4 2.4 头文件 4 2.4.1 头文件内容基本结构: 4 2.4.2 文件注释的内容 5 2.4.3 版权声明 5 2.5 代码文件 5 2.5.1 代码文件内容结构: 5 2.5.2 功能模块内容结构: 5 3. 代码布局风格 6 3.1 缩进对齐 6 3.1.1 圆括号 6 3.1.2 缩进 6 3.1.3 空格 6 3.1.4 变量声明应对齐。 6 3.2 语句的布局 6 3.2.1 单语句 6 3.2.2 复合语句 7 3.3 换行 7 3.3.1 每行代码限定为80个字符宽。 7 3.3.2 在分号处折行 8 3.3.3 在操作符前折行 8 3.3.4 有多个可以选择的地方时,选择层次较高的进行折行 8 3.3.5 将新行与上一行的同一级的表达式的开始处对齐 8 3.3.6 方法定义的折行 8 3.3.7 参数折行 8 3.4 类声明的布局 9 3.4.1 类的内容布局 9 3.4.2 变量排列布局 9 3.4.3 方法排列布局 9 4. 注释 10 4.1 注释类型 10 4.1.1 业务逻辑注释 10 4.1.2 外部注释 10 4.1.3 变量注释 10 4.2 注释和代码的比例不少于1 :3。 10 4.3 3种注释的风格 10 4.4 注释与源代码位置:位于代码上方 11 4.5 禁止行末注释 11 4.6 空注释行 11 4.6.1 应加注释行的地方 12 4.7 文件注释 12 4.8 类的注释 12 4.9 函数的注释 13 4.10 修改注释 13 4.11 其它应加注释的地方 14 5. 命名 15 5.1 通用规则 15 5.1.1 基于 Windows 的项目使用 Microsoft* “匈牙利”命名法 15 5.1.2 不要声明以一个或多个下划线 ('_') 开头的名称 15 5.1.3 不要使用两个相连的下划线 15 5.1.4 避免使用只靠字母大小写才能区分的名称 15 5.1.5 避免使用缩写 15 5.1.6 选择清晰的、易辨认的、有意义的名称 15 5.1.7 使用名称的正确拼写 16 5.1.8 布尔值使用正值谓词从句 16 5.2 名字空间 16 5.3 类与结构 16 5.3.1 类与结构的名称使用名词或名词短语 16 5.3.2 类名 16 5.3.3 结构名 16 5.3.4 不同子系统中命名 16 5.3.5 前后缀 16 5.3.6 集合 17 5.4 变量 17 5.4.1 变量名使用混合大小写,并以小写字母开头。 17 5.4.2 声明常量,宏和枚举常量时应全部使用大写字母。 17 5.4.3 变量名是用多个单词表示时 17 5.4.4 变量名命名应有意义 18 5.4.5 变量名大小应至少2~3个以上的字母,避免单字母变量。 18 5.4.6 基本类型变量名中必须有其类型的修饰前缀。 18 5.4.7 子系统变量名前应加子系统小写名加“_”。 18 5.4.8 系统全局变量名前应加前缀“g_”。 18 5.4.9 避免使用auto,register修饰词。 18 5.4.10 变量如果是指针变量,变量应加上字母p 18 5.5 类与结构变量命名 18 5.5.1 变量如果是成员变量时,变量名前加m_。 18 5.5.2 标准类/结构变量名要加上它的标准前缀或后缀; 18 5.5.3 应该用一致的前(后)缀来命名。 18 5.6 函数 18 5.6.1 过程类型的函数名称 18 5.6.2 当需要使用同一通用含义时,使用函数的重载 19 5.7 函数参数 19 5.7.1 实参名使用语法元素强调其含义 19 5.8 异常 19 5.8.1 异常名选用否定的含义 19 5.8.2 异常名使用项目定义过的形容词 20 5.9 其他 20 5.9.1 浮点指数和十六进制数使用大写字母。 20 6. 声明 20 6.1 名字空间 20 6.1.1 将全局声明限定在名字空间中 20 6.1.2 使用名字空间划分非类功能 20 6.1.3 尽量不使用全局和名字空间范围的数据。 20 6.2 类 20 6.2.1 使用 class 而不是 struct 来实现抽象数据类型 20 6.2.2 属性,方法的排列顺序。 20 6.2.3 使用友元保留封装性 20 6.2.4 避免在类声明中定义函数 20 6.2.5 明确声明构造函数的类使用默认构造函数 20 6.2.6 带有指针类型数据成员的类要声明其复制构造函数和赋值操作符 21 6.2.7 不要重复声明构造函数参数有默认值 21 6.2.8 将析构函数总是声明为 virtual 类型 21 6.2.9 初始化列表表中的成员列出的顺序和它们在类声明中的顺序相同。 21 6.2.10 不要重定义非虚函数 21 6.2.11 谨慎使用非虚函数 21 6.2.12 使用初始化构造函数而不是使用构造函数中的赋值语句 21 6.2.13 初始化构造函数避免调用成员函数 21 6.2.14 注意构造函数和析构函数调用时的情况 21 6.2.15 整形类常量使用static const或enum。 21 6.3 函数 22 6.3.1 一定要明确声明函数的返回值类型 22 6.3.2 函数声明中要提供正规参数名称 22 6.3.3 函数要尽量只有一个返回点 22 6.3.4 避免创建对全局有副作用的函数 22 6.3.5 以重要性和活跃性递减的顺序声明函数的参数 22 6.3.6 避免声明带有不定参数个数的函数 23 6.3.7 避免重复声明带有默认参数的函数 23 6.3.8 函数声明中尽可能使用 const 23 6.3.9 避免利用值传递对象 23 6.3.10 不能返回引用到局部对象 23 6.3.11 不可返回由 new 初始化,之后又已解除引用的指针。 24 6.3.12 不要返回非常量引用或指针到成员数据上 24 6.3.13 使用内嵌定义函数,避免使用宏扩展#define 24 6.3.14 使用默认参数而不使用函数重载 24 6.3.15 使用函数重载表达常用语义 25 6.3.16 避免重载以指针和整形数为实参的函数 25 6.3.17 令操作符 operator= 返回对 *this 的引用。 25 6.3.18 使 operator= 检查自赋值 25 6.3.19 尽可能减少复杂性与函数的规模。 25 6.4 类型 25 6.4.1 定义项目范围的全局系统类型 25 6.4.2 使用基本类型 25 6.4.3 使用 typedef 创建同义词来加强局部含义 25 6.5 常量与对象 26 6.5.1 定义常量时避免使用 #define 预处理指示符 26 6.5.2 永远不要忘记常量对象的“不变性”。 26 6.5.3 定义时初始化对象 26 7. 表达式和语句 26 7.1 表达式 26 7.1.1 使用冗余的圆括号使复合表达式含义更加清晰 26 7.1.2 避免表达式的过深嵌套,嵌套深度<5 26 7.1.3 数组,链表等集合类的数据索引以0开始(ZERO_BASE)。 26 7.1.4 空指针使用NULL而不使用 0 26 7.1.5 使用新类型转换符而避免使用强制类型转换。 26 7.1.6 指针不要与不在同一数组中的对象比较 27 7.1.7 已删除的对象指针或句柄要赋予空指针值NULL 27 7.2 语句 27 7.2.1 当从布尔表达式分支时使用 if 语句;当从离散值分支时使用 switch 语句 27 7.2.2 一定为 switch 语句提供一个 default 分支以记录错误 27 7.2.3 不要比较浮点数的相等 27 7.2.4 当循环需要迭代前测试时使用 for 语句或 while 语句 27 7.2.5 当循环需要迭代后测试时使用 do while 语句 27 7.2.6 无限循环使用while(TRUE)。 27 7.2.7 循环中避免使用 jump语句(如: break、return 或 goto) 27 7.2.8 不要使用 goto 语句 27 7.2.9 避免嵌套作用域内隐藏标识符 27 7.2.10 深层代码嵌套中,不要超过10层嵌套,每一层的结束处应加注释说明。 28 8. 最佳实践 28 8.1 内存管理 28 8.1.1 C 和 C++ 内存操作要避免混合使用 28 8.1.2 删除由 new 创建的数组对象时总使用 delete[] 28 8.1.3 析构函数里对指针成员调用delete 28 8.1.4 避免重载opeator new 和operator delete 。 28 8.2 错误处理和异常 28 8.2.1 开发过程中多使用声明语句以发现错误 28 8.2.2 只在真实的异常情况时使用异常 28 8.2.3 从标准异常中派生项目异常 29 8.2.4 将所有异常声明为已引发 29 8.2.5 在异常首次出现时就报告它 29 8.2.6 按照从派生结构最底端到最顶端的顺序定义异常处理 29 8.2.7 避免使用捕获所有异常的处理过程 29 8.2.8 确保函数状态代码有合适的值 29 8.3 数据表示 29 8.3.1 不要假设类型的表示 29 8.3.2 尽可能使用“可伸缩”常量。 29 8.4 类型转换 30 8.4.1 不要从一个“短”类型转换成一个“长类型” 30 8.5 复用 30 8.5.1 尽可能使用标准库构件,标准库函数 30 8.5.2 使用模板复用独立于数据的行为 30 8.5.3 使用公共继承复用类接口(子类型化) 30 8.5.4 使用包容而不是私有继承复用类的实现 30 8.5.5 避免使用多继承,建议只对自建的接口类可使用多继承,MFC类不能使用多继承。 30 8.6 编译问题 30 8.7 尽量减少对编译的依赖 30 9. 附录 31 9.1 Visual C++常用基本类型前缀列表 31 9.2 Visual C++常用宏定义命名列表 31 9.3 Windows对象名称缩写: 33 9.4 MFC类前后缀 33 中央研究院C++ 编码规范 中央研究院C++编码规范 C++编码规范 1. 简介 本文的目的是表述项目 C++ 编程的规范、指南和提示。规范包括了以下 C++ 开发的方面: · 如何组织项目代码; · 编程风格(如何编写实际的源代码); · 如何记录源代码; · 代码内名称和源文件所使用的命名约定; · 何时使用某些语言结构以及何时应避免某些语言结构; · 相关模板与名词表 1.1 基本原则 清晰、可理解的 C++ 源代码是规则和指南的主要目标:清晰、可理解的源代码是软件可靠性和可维护性的主要作用因素。 最小混淆 - 源代码易读、易懂,整个项目中统一样式。 维护的唯一点 - 只要可能,设计决策就应在源中只表述一点,它的多数后果应程序化的派生于此点。 最小干扰 -避免将源代码与可视干扰(如内容较少或对理解软件目的不起作用的信息)相混合: 1.2 适用范围 本文档的规范适用于项目开发中C++编码要求,适用于VC,BC,和其它开发工具编写的C++代码。 1.3 使用常识 本文不对规则的原理进行说明与阐述,如果你想了解这些原理,可以查阅《RUP C++编程指南》对应内容的基本原理。 1.4 参考资料 《RUP C++编程指南》 《Effection C++》 《JAVA编码规范 2001》 2. 代码组织 2.1 项目组织风格 · 项目风格指的是针对整个项目,包括项目目录设置、相关库文件设置、集成环境设置等等的习惯约定。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 2.1.1 项目取名与最终的可执行文件名一致 · 在C++之中,项目名为最后可执行文件名,所以项目名最好以最终的可执行文件名一致。 2.1.2 项目目录设置 · 为保证C++项目的备份方便、快捷,可将所有该项目有关的文件全部放到统一的目录之下,为每个项目在该目录之下建立一个目录,项目之间的公共部分建立在public目录之下,项目所需要的基础库根据所需要的基础库数目和子系统分别建立不同的目录,项目相关的测试程序都统一放在TEST目录之下。 示例:PSS项目目录设置 PSS系统有两个模块PSS_mod1和PSS_mod2,两个模块有一部分共用代码,在工程开发过程之中编写了三个测试程序PSS_TEST1、PSS_TEST2、PSS_TEST3,PSS系统开发过程之中用到了第三方公用模块Third_Mod,则该系统的目录设置如下: 一级目录 二级目录 三级目录 备注 Project PSS_mod1 Debug C++集成环境生成的调试版本目录 Release C++集成环境生成的发行版本目录 Res C++集成环境生成的资源文件目录 Include 所属mod1的头文件 Source 所属mod1的头文件 PSS_mod2 Debug C++集成环境生成的调试版本目录 Release C++集成环境生成的发行版本目录 Res C++集成环境生成的资源文件目录 Include 所属mod1的头文件 Source 所属mod1的头文件 PUBLIC Include Mod1和mod2公用的头文件 Source Mod1和mod2公用的源代码文件 Lib Mod1和mod2公用的库文件 TEST PSS_Test1 测试项目一 PSS_Test2 测试项目二 Third_Mod   第三方公用模块 2.2   集成环境内的项目目录设置 项目设置(Project Setting)中加入该头文件,库文件的所在目录 每个项目在C++编成编辑环境的设置都采用相对路径的设置,不可采用绝对路径。在保证其备份到光盘设备后,恢复到硬盘后,不需要再过多的设置就可直接编译。 示例: 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 #include “../../../somehead.h” (避免绝对路径) #include “somehead.h” (推荐) 2.3 源文件 2.3.1 类声明与实现的文件放置方法 2.3.1.1 文件扩展名 类的声明应放置在与类实现文件不同的文件中,此文件指头文件(扩展名“*.H”)。类的实施文件可能放置于一个或多个实施文件中(.CPP文件); C++文件扩展名是:对声明文件,为.H;对实施文件,为.CPP; C代码的文件文件扩展名是:对声明文件,为.H;对实施文件,为.C; 2.3.1.2 每个源码文件大小最好不超过600行 每个类实现文件的大小不超过600行代码为好,不常用到的函数应当考虑置于它们各自的文件中。 2.3.1.3 将代码按内容分类保存到不同的文件中 如果类实现包括普通私有实现声明、测试代码或具体平台的代码,那就把这些部分分开存文件中,若放入到文件,则文件以那部分内容来命名; 2.3.1.4 文件的命名方式 文件的命名方式。选择一种类划分和命名的机制,使用它要前后一致。 参照以下方式构造文件名: · 以类的主要抽象名作为文件名。 · 为类名附加一部分类型名。选择暗示它们类型的部分类型名。 · 类名和部分名由分隔符 “_”(下划线)分隔; · 为了更好的预测,对文件名使用相同的大小写,这与代码内名称的使用相同。 示例: File_Name::= [ ] '.' 2.3.1.5 类划分与命名策略: · 一般将类实施的内容放到与声明同名的.cpp文件中。 · class_inlines.cpp - 如果一个类`有多个潜在的内嵌函数,就将函数的定义置于一个单独的内嵌文件中(请参见“将类内嵌函数定义置于单独文件中”)。 · class_function_name. cpp - 如果可执行的大小是个要考虑的问题,应当分离出许多程序不需要的特殊成员函数,组成各自的实施文件(请参见“如果程序大小需要考虑,将大型类分成多个变换单元”)。 · class_nested_class. cpp - 类嵌套类的成员函数,置于其本身文件中。 · class_test.[h\cpp] - 如果一个类需要扩展测试代码,则测试代码必须在友测试类中予以声明。友测试类应称为Class.Test。将测试代码声明为友类有助于类及其测试代码的独立开发;这还允许测试代码从最终类对象代码中删除而源并不改变。 · class_platform_name cpp c - 分离出任意类的平台依赖性并以平台名称命名部分名称。 示例 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 SymaNetwork.h //包括类 // “SymaNetwork”的声明。 SymaNetwork_Inlines.cpp //内嵌定义子单元 SymaNetwork.cpp //类的主要实施单元 SymaNetwork_Private.cpp //私有实施子单元 SymaNetwork_Test.cpp //测试代码子单元 2.3.2 每个类声明避免定义多个类 · 只有在极少数情况下多个类才能置于同一个.H或.CPP文件中;此时这些类必须是紧密关联的(如容器与它的迭代程序)。如果所有类都需要对客户类是可见的,可以把类的主要类及其支持类置于同一个头文件中。 2.4 头文件 2.4.1 头文件内容基本结构: 文件注释 编译标识 #ifndef #include 头文件声明 #define macro 宏定义 declare class or struct 类与结构声明 declare typedef 别名声明 declare variable 变量声明 declare function 函数声明 2.4.2 文件注释的内容 每个C++源文件必须以一个C语言风格的注释开始,该注释包括文件名,版本信息,日期,模块功能描述,变更记录及版权声明。布局风格见“文件注释”一节 2.4.3 版权声明 版权声明是必须的,其内容必须包括: 公司 年份 可用如下格式: Copyright 2000-2001 Topgroup. All Rights Reserved. 2.4.4 使用 #include 来访问类的声明 · 类要使用另一个类,则必须使用预处理 #include 指令来访问提供者类的声明。 相应的,类不能再次声明提供者类声明的任何一部分。 (1)当包含文件时,只有对“标准”头文件使用 #include
语法; (2)对其余的使用 #include "header" 语法; (3)类声明文件只包含绝对需要的文件; (4)使用#include 指令也适用于类自身的实施文件:类实施文件必须包括它本身的声明;通过在每个头文件中使用以下结构可以防止重复包含与编译文件 ,使用VC开发时,预处理符由VC工具自动生成。 (4) 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 #include 规则的一个例外是当类只通过引用(使用指针或引用类型声明)使用或包含提供者类的类型(类);这种情况下引用或包含通过使用预先声明来声明,而不使用 #include 指令。 2.5 代码文件 2.5.1 代码文件内容结构: 注释头 编译标识 #ifndef #include 头文件声明 #define macro 宏定义 全局模块区 功能模块1 功能模块2 ……… 2.5.2 功能模块内容结构: 模块说明 module description 定义功能模块宏 define macro 声明功能模块的类与结构 declare class and struct 声明功能模块的别名 declare typedef 声明功能模块变量 declare variable 声明功能模块使用的函数 declare function 定义功能模块的函数,类 define function and class 3. 代码布局风格 3.1 缩进对齐 一种提高代码可读性的方法是给代码分段,换句话说,就是在代码块内让代码缩进。所有在括号{和}之内的代码,构成一个块。基本思想是,块内的代码都应统一地缩进去一个单位。 3.1.1 圆括号 在表达式、方法调用及方法声明中圆括号“(”后及“)”前不应该有空格。 3.1.2 缩进 · 同级之间在同一个缩进位置。 · 下一级与上一级之间需要缩进。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 3.1.3 空格 · 关键字与其后紧挨的括号之间应有一个空格进行分隔。 · 参数列表的逗号之后应有一个空格进行分隔。 · 指针符号“*”与类型间1个空格进行分隔 示例: void *    pOutputBuf; CRect *    pPath; · 大括号开始之前应有一个空格进行分隔。 · 所有的二元操作符的前后均应有一个空格,二元操作符的例子有加号,除号,等于符号,赋值符号等。 · for的每一个语句之间应有空格,即for (expr1; expr2; expr3) · 强制类型转换的括号之后应有一个空格。如 · MyMethod((byte) aNum, (Object) x); · MyMethod((int) (cp + 5), ((int) (i + 3)) + 1); 3.1.4 变量声明应对齐。 变量定义最好通过添加空格形成对齐。如下例所示: int      Value; int      Result; int      Length; DWORD    Size; DWORD    BufSize; char *    pBuf; 3.2 语句的布局 3.2.1 单语句 每行最多只允许一条单语句。 3.2.2 复合语句 复合语句是指在两个大括号之内的语句列表。复合语句遵守下列格式要求: 复合语句内部的语句必须比复合语句自身所在的位置上缩进一级。 类别 缩进风格 If if (true) { statements; } else { statements; } While与do while while(true) { statements; 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 } do{ statements; }while(true) Switch switch (i) { case 1: statements; break; case 2: //... default: //... } For for(int i=0; i void advance(InputIterator& i, Distance n); 3.4 类声明的布局 3.4.1 类的内容布局 类声明的内容按照以下的顺序布局: 变量声明 方法声明 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 3.4.2 变量排列布局 静态变量在最前,按可见性从高到低排列。若有静态块,则静态块在其后,其后是非静态的类变量,按可见性从高到低排列,即按公共、保护、私有的顺序进行排列,不同可见性的类变量组之间应用一个空行进行分隔。 3.4.3 方法排列布局 · 内部按照 友元,public ,protected ,private排列方法 · 方法顺序为: 构造方法 初始化方法 静态方法 自定义方法 重载方法 消息处理方法 · 按照功能聚合方法。将完成某一功能或功能相近的一组方法放在一起 4. 注释 注释应当作为源代码的补充,而不是直译源代码。注释中应避免重复程序标识符,避免复制别处有的信息(此时可使用一个指向信息的指针)。否则程序中的任何一处改动都可能需要多处进行相应的变动。如果其他地方没有进行所需的注释改动,将会导致误注释:这种结果比根本没有注释还要糟糕。 · 它们应当解释不能直接从源代码看出东西;它们不应复制语言的语法或语义。 · 它们应当帮助读者掌握背景中的概念、依赖性、特别是复杂的数据代码和算法。 · 它们应当突出:与代码或设计标准的不同点、受限特性的使用、以及特殊的“技巧”。 4.1 注释类型 注释可按其目的分为外部注释、业务逻辑注释及其它注释。业务逻辑注释是指在类的方法中用于解释业务逻辑的注释,外部注释是指不在类的方法中,是用于对外解释类或类的接口的注释。变量注释是指不用于外部注释中,对局部变量或私有类变量等变量的注释。 4.1.1 业务逻辑注释 在代码块(如类的方法,静态方法等)中用于解释业务逻辑的注释称为业务逻辑注释,除特殊情况外,业务逻辑注释一般采用单行注释的方式。 代码数,代码数即语句数,不包括变量定义,与代码行数不同。代码数可用自动化工具获得。 注释数为针对于语句的注释数。注释数与注释的行数不同,在两条语句之间的注释,不管行数多少,都算作一个注释。 注释率=注释数/代码数*100%。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 4.1.2 外部注释 不在代码块中,用于对外解释类或类的接口的注释称为外部注释。外部注释包括类的内容注释、公共方法及变量注释、保护方法及变量注释、包方法及变量注释。除非特殊情况,外部注释最好采用内容注释的方式。 外部注释的注释率=内容注释数/定义数*100%。 其中定义数是指公用的类或类的接口的数量。 4.1.3 变量注释 不用于外部注释中,对局部变量或私有类变量等变量的注释称为变量注释。 变量注释的注释率=注释数/变量数*100%。 4.2 注释和代码的比例不少于1 :3。 · 能直接看出程序加构成和功能的代码行可不要注释,比较复杂的算法在函数前面部分或文件头部描述 4.3 3种注释的风格 · 单行注释应使用 C++ 风格注释分界符"//", · 多行(3行以上)注释用C 风格的"/* … */"。 · 文档注释用Java风格 “/** … */” 注释语句类型 用法 示例 文档注释 在接口、类、方法和字段声明之前紧靠它们的位置用文档注释进行说明。 /** Customer(顾客)顾客是指作为我们的服务及产品的销售对象的任何个人或组织。 @author S.W. Ambler */ C 语言风格注释 多行注释。 /* 这部分代码已被它前面的代码替代,所以于 1999 年6 月 4 日被 B. Gustafsson 注释掉。如果两年之后仍未用这些代码,将其删除。 . . . (源代码) */ 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 单行注释 在方法内部采用单行注释语句对业务逻辑、代码片段和临时变量声明进行说明。 // 因为让利活动 // 从 1995 年 2 月开始, // 所以给所有超过 $1000 的 // 发货单 5% 的折扣。 4.4 注释与源代码位置:位于代码上方 · 注释应紧贴它们所要注释的代码,它们使用相同的缩进 4.5 禁止行末注释 · 注释应避免与源结构处于同一行:否则会使注释与其所注释的源代码不对齐。但在描述长声明中的元素(如 enum 声明中的枚举操作符)时,也能容忍这种不好的注释方式。 4.6 空注释行 在代码中使用空白。在代码中加入几个空行,也叫空白,将代码分为一些小的、容易理解的部分,可以使它更加可读。建议采用一个空行来分隔代码的逻辑组,例如控制结构,采用两个空行来分隔方法定义。没有空白的代码很难读,很难理解。 4.6.1 应加注释行的地方 · 头文件和代码文件内容区域间隔3行 · 版本变更记录间隔1行 · 变量之间,函数声明之间不间隔; · 超过1行的宏定义,与下一个宏间隔1行 · 类与结构的声明之间间隔3行 · 类中的friend,public ,protected,private之间间隔2行 · 超过1行的声明,与下一个声明间隔1行 · 代码功能模块内的函数实现之间间隔2行 · 代码文件主要功能模块实现之间间隔3行以上 4.7 文件注释 每个C++源文件必须以一个C语言风格的注释开始,该注释包括文件名,版本信息,日期,模块功能描述,变更记录及版权声明。参考格式如下: /** * File Name : 文件名 * Version : 版本号 * Project Information : 项目名称 与 项目编号 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 * Copyright : 版权声明 * Brief Description : 模块简单描述 *====================================================== * Revision History :版本变更记录 * “Revision”+RevisionNumber Date&Time Editor Description * * “Revision”+RevisionNumber Date&Time Editor Description */ 4.8 类的注释 · 在类的定义或声明处应注释说明定义或声明该类的目的。 · 出现异常的条件; · 对类型和对象来说,语言无法表达的任何不变量或其他限制。 · 其他数据访问,特别是当数据被修改时:对有副作用的函数特别重要; · 公共的类变量和方法必须有注释。 · 合理使用类所需的限制或其他信息; · 每个C++类必须以一个C语言风格的注释开始,该注释包括类名,功能描述,变更记录。参考格式如下: /* *类名:ClassName *说明:Description of the class。 *变更记录:Revision Control 参照变更修改注释 */ 示例: /* *类名:class TimageManipulation *说明:用于图象处理,实现图象亮度、对比度、反白、色彩平衡等处理 *版本: *Revision   1.0    2001/05/09     陈建 完成基本的图象处理功能设计 *Revisiotn    1.1     2001/05/25    姜磊   修改完成一个小Bug. */ 4.9 函数的注释 · 返回值的含义;如不可预测函数布尔返回值的含义:如,ture 值是否表示函数执行成功; · 出现异常的条件; · 其他数据访问,特别是当数据被修改时:对有副作用的函数特别重要; 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 · 合理使用函数所需的限制或其他信息; · 在每一函数定义代码段前,应该有一段注释,用于说明该函数的函数名,函数功能描述,输入参数描述,输出变量说明,算法描述。 函数注释的参考风格: /* *-------------------------------------------------------------------------------- * Member Name : 成员函数名 * Function Description : 功能描述 * Parameter Specification : 输入参数描述 * Return Specification : 返回变量说明 * Algorithm : 算法描述 * Addtion : 附加说明,如调用说明,前置条件,后置条件。 *-------------------------------------------------------------------------------- */ 4.10 修改注释 · 关键修改的地方应加注释说明。 参考风格如下: /* * Revision : 修改版本 * Revision Date and Time : 修改日期与时间 * Mender : 修改人 * Revision Description : 修改内容简要描述 */ 示例: //Revision 1.2 2001-5-15 17:00 陈建 修改特技处理函数FadeOut() 4.11 其它应加注释的地方 · switch语句的入口(case),应加注释说明。 · 深层代码嵌套中,每一层的结束处应加注释说明。 · 对于易产生误解的代码行,应单独加以注释。 · 对关键性代码行,必须单独注释详细说明。 · 引入外部变量声明处,应加注释说明。 · 在代码不明晰或不可移植处必须有一定的说明 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 5. 命名 5.1 通用规则 5.1.1 基于 Windows 的项目使用 Microsoft* “匈牙利”命名法 5.1.2 不要声明以一个或多个下划线 ('_') 开头的名称 开头带有一个下划线(“_”)的名称通常由库函数(“_main”和“_exit”)使用。开头带有两个下划线(“__”)或一个下划线后接一个大写字母的名称保留给编译器内部使用。 名称还要避免下划线相邻,否则很难识别下划线的确切个数。 5.1.3 不要使用两个相连的下划线 5.1.4 避免使用只靠字母大小写才能区分的名称 只通过大小写才能区分的类型名称,它们间的差异是很难记忆的,也就很容易造成混淆。 5.1.5 避免使用缩写 应用领域中常用的缩写(如 FFT 指快速傅立叶变换),或在项目缩写清单中有定义的缩写,才能使用相应的缩写。否则,很有可能相似但不相同的缩写到处出现,之后就引进了混淆和错误(如将 trackIdentification 缩写成 trid、trck_id、tr_iden、tid、tr_ident等)。 5.1.6 选择清晰的、易辨认的、有意义的名称 从应用的角度选择名称,名词使用形容词修饰来提高局部(具体上下文)的含义。确保名称与其类型保持一致。 选择合适的名称,使以下结构: objectName.FunctionName(...); objectName->FunctionName(...); 易于阅读并有实际含义。 不要使用短名称或缩写名称,也不要太长,以免编译截断名称。使用人们熟知的 E 作为自然对数的底数或 Pi 作为圆周率是例外。 示例: 原代码: void SetForegroundColor(CColor fg) 读者直觉上会认为 fg 意指 foreground(前景);但是,任何一个好的编程风格都不应留给读者作直觉推导的余地。 void SetForegroundColor(CColor foreground) { theForegroundColor = foreground; } 当使用参数 foreground(远离其声明)时,读者会认为 foreground 实际上就是指 foreground 的颜色。可以想象,它能代表任何一种可暗中转化为color的类型。 推荐风格: void SetColor(CColor newColor) { ... theColor = newColor; ... } 对 newColor 进行了限定使它与其类型一致;因此增强了函数的语义。 5.1.7 使用名称的正确拼写 英语字符名称部分应正确的拼写,遵守项目要求的形式,如使用一致的英国英语或美国英语,但不能同时使用。这对注释也同样适用。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 5.1.8 布尔值使用正值谓词从句 对布尔值类型的对象、函数及函数实参使用正值形式的判定句式,如FoundIt、IsAvailable,但不使用IsNotAvailable。 5.2 名字空间 单词全部大写。 5.3 类与结构 5.3.1 名称使用名词或名词短语 使用简单形式的常用名词或名词短语,为类取名时要表达出它的抽象含义。基类使用更通用的名称,而对派生类使用更专用的名称。 typedef ... Reference; //出自标准库 typedef ... Pointer; //出自标准库 typedef ... Iterator; //出自标准库 struct tagPoint{....}; class CBankAccount {...}; class CSavingsAccount : public CBankAccount {...}; class CCheckingAccount : public CBankAccount {...}; 5.3.2 类名 · VC开发的类以大写字母“C”开头,其后每一个单词都以大写字母开头 如:CGlobalLocation,CFont,CDialog · BC开发的类以大写字母“T”开头 ,其后每一个单词都以大写字母开头 如:TGlobalLocation,TFont,TDialog 5.3.3 结构名 结构名加小写前缀"tag",结构“}”之后其后每一个单词都以大写字母开头 例: typedef struct tagPoint { int nx; int ny; } Point; 5.3.4 联合体名 联合名前加小写字母“uni”,在“}”之后每个单词以大写字母开头。 例: typedef union uniName{ char chVal; int nVal; long lVal; 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 float ftVal; ... } Name; 5.3.5 枚举名 枚举名前加小写字母“enu”,在“}”之后全部大写,单词之间以下划线“_”相连。 全部大写 例: typedef enum enuKFILE_OPEN_MODE { OPEN_READONLY = 0, OPEN_READWRITE = 1, CREATE_ALWAY = 3 } KFILE_OPEN_MODE; 5.3.6 不同子系统中命名 不同子系统中命名冲突的类应在类与结构名前附加域名大写加“_”,其后每一个单词都以大写字母开头 如:URL_CGlobalLocation,DOM_CglobalLocation 5.3.7 前后缀 · 如果是派生类应在其类名中使用后缀来包含其基类信息,并且取名应能较准确描述该类的含义,否则不加后缀。 如:CColorDlg,CEditView,COleServerDoc等。 · 当对象和类型的名称冲突或缺少合适的名称时,对象使用简单名称,类型名称添加后缀 mode、kind、code 等。 · 非强制的一些命名规则 类型 规则 例 抽象类 以Abstract开始 CAbstractDjinn, CAbstractCat, CAbstractClass, CAbstractPlayer Factory类 以Factory结束 CDjinnFactory, CCatFactory, CClassFactory, CPlayerFactor 实现类 以Impl结束 CDjinnImpl, CCatImpl, CClassImpl, CPlayerImpl Exception 以Exception结束 CDjinnException, CCatException, CClassException, CPlayerException · 5.3.8 集合 当需要对象集合外的其他语义时,使用以下标准库中的数据结构作为行为模式和名称后缀: Vector(向量) - 一种可随机访问的顺序容器; List(表) - 一种有序的顺序容器; Queue(队列) - 一种先入先出的顺序容器; Deque(双端队列) - 一种两个端口都可进行操作的队列; 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 Stack(堆栈) - 一种后入先出的顺序容器; Set(集合) - 一种关键词访问(关联)容器; Map(图) - 一种关键词访问(关联)容器; 5.4 变量 5.4.1 基本格式 变量名的命名遵从匈牙利记法。即:前缀 + 类型 + 变量名 [m_|g_] type [class name|struct name] variable name 5.4.2 变量名使用混合大小写,并以小写字母开头。 如:long nTemp; //normal long NTemp; //abnormal LPCTSTR lpszMenuName; 5.4.3 声明常量,宏,枚举常量 声明常量,宏,枚举常量时应全部使用大写字母,单词之间以单下划线相连。 如:const double PAI=3.1415926; //noraml const double Pai=3.1415926; //abnoraml #define PAI 3.1415926 //normal #define Pai 3.1415926 //abnormal enum Color{RED,BLUE,GREEN}; //normal enum Color{red,blue,green}; //abnormal 5.4.4 变量名是用多个单词表示时 · 当使用混合大小写时,将修饰词放在前面,并且尽量拼写完整增加可读性。如:maxElement,currentFile 等。 · 当全部使用大写时,单词之间应用下划线连起来。如:BUFFER_SIZE,FILE_SIZE 等。 5.4.5 变量名命名应有意义 · 一律使用英文字母进行拼写;不用汉语拼音。 5.4.6 变量名大小在2~20个字母,避免单字母变量。 变量名大小应至少2~3个以上的字母,避免单字母变量。当变量单纯作为整型循环变量时,可以使用单个字母。如:i,j,k等。 例:chrName,hWnd等。 5.4.7 基本类型变量名中必须有其类型的修饰前缀。 5.4.8 子系统变量名前应加子系统小写名加“_”。 如:icu_nNumber,dom_chrSelect. 5.4.9 系统全局变量名前应加前缀“g_”。 如:g_nNumber,g_nSelect. 5.4.10 避免使用auto,register修饰词。 5.4.11 变量如果是指针变量,变量应加上字母p 如:pMemDC,pGUIFactory,g_pCtrl 。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 5.5 类与结构变量命名 对象变量命名除了具备普通变量的规则,还包括: 5.5.1 类成员变量名前加“m_”。 如:m_bConnection,m_chrName 。 5.5.2 类静态成员变量名前加“ms_”。 如:ms_bConnection,ms_chrName 。 5.5.3 标准类/结构变量名要加上它的标准前缀或后缀; 如:dlgFileOpen,strName,mainWnd,memDC 如果类/结构没有缩写命名,不用加类/结构名前后缀。 如 localHttpProxy,domSomeObject,newPlan; 5.5.4 应该用一致的前(后)缀来命名。 应该用一致的前(后)缀来命名同一类对象(或指向对象的指针),使人们容易识别对象(指针)的类型。 5.6 函数 5.6.1 过程类型的函数名称 · 所有函数名以尽量以动词开始,大写第一个字母,其后每个单词以大写开头,尽量采用组合词表示其功能 如: CreateWindow,GetValue,FindNextObject · Window系统的消息处理函数要求用加前缀On,如OnMouseMove() · 对无返回值的函数(函数声明为 void 返回类型)或返回值使用指针或引用类型的函数,使用动词或动词短语。 · 对使用非 void 返回类型返回唯一一个值的函数,使用名词或名词短语。 · 对带有常用操作(行为模式)的类,使用项目选项列表中的操作名。例如:begin, end, insert, erase (标准库中的容器操作). · 返回布尔值(谓词)的函数使用形容词(或过去分词)。谓词经常使用名词前添加前缀 is 或 has 的形式使名称表达一个肯定的声明。当对象、类型名或枚举型常量也使用简单名称时,这也同样非常有用。时态上要保持一致,力求准确。 示例 void Insert(...); void Erase(...); Name FirstName(); bool HasFirstName(); bool IsFound(); bool IsAvailable(); · 不要使用否定意义的名称,因为这会导致双重否定表达式出现(如 !isNotFound);这使得代码更难以理解。有些情况下,通过使用反义词,如“IsInvalid”代替“IsNotValid”,可以使否定谓词变成肯定的而不需改变其语义。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 示例 bool IsNotValid(...); void FindClient(CName withTheName, bool& notFound); Should be re-defined as: bool IsValid(...); void FindClient(CName withTheName, bool& found); 当需要使用同一通用含义时,使用函数的重载 当操作目的相同时,使用重载而不使用同义词;这尽量减少了系统中概念的数量和不同的操作,因此也就降低了整体的复杂性。 当重载操作符时,要确保操作符的语义保留了下来;如果不能保留约定俗成的操作符含义,则为函数选用另一个名称而不使用操作符重载方式。 5.7 函数参数 5.7.1 实参名使用语法元素强调其含义 为表明其唯一性或表明本实体是活动的主要核心,在对象或参数名称前加前缀“the”或“this”。要表明次要、临时、辅助对象,则加前缀“a”,“current”, “tmp”: 示例 void ChangeName( Subscriber& theSubscriber, const Subscriber::name newName) void Update(subscriberList& theList, const subscriber::identification withId, structure& onStructure, const value forValue); void Change( CObject& theObject,const CObject usingObject); 5.8 异常 5.8.1 异常名选用否定的含义 · 只有在处理错误时才必须使用异常,故使用名词或名词短语来清楚的表达一个否定的含义: overFlow, CThresholdExceeded, badInitialValue 5.8.2 异常名使用项目定义过的形容词 使用项目定义列表中以下词的一个bad、incomplete、invalid、wrong、missing 或 illegal 作为名称的一部分,而不要机械的套用 error 或 exception,因为它们并不表达具体的信息。 5.9 其他 5.9.1 浮点指数和十六进制数使用大写字母。 浮点数中的字母“E”和十六进制数从“A”到“F”应当一直保持大写。 6. 声明 6.1 名字空间 C++ 语言中名字空间存在之前,管理名称的作用域只有有限的几种手段;因此,全局名字空间的使用过于泛滥,导致众多的冲突,这使同一程序中难以同时使用一些库。新的名字空间语言特征解决了全局名字空间的干扰问题。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 6.1.1 将全局声明限定在名字空间中 这意味着只有名字空间的名称可以是全局的;所有其他的声明都必须在某个名字空间的作用域内。 6.1.2 使用名字空间划分非类功能 逻辑上划分非类功能类的类别,或作用域远大于类的功能(如库或子系统),使用名字空间逻辑上统一的声明(请参见“使用名字空间由系统或库划分潜在的全局名称”)。 示例 namespace MATH { /* ...*/ }; 6.1.3 尽量不使用全局和名字空间范围的数据。 6.2 类 C++ 中类是基本的设计实施单元。使用它们纪录域与设计的抽象,并作为实施抽象数据类型 (ADT) 的封装机制。 6.2.1 使用 class 而不是 struct 来实现抽象数据类型 · 使用 class(类的关键字)而不是 struct 来实现一个表示抽象数据类型的类。 · 无方法函数的可以用struct来实现。 6.2.2 属性,方法的排列顺序。 · 参见“类声明的布局”一节。 6.2.3 使用友元保留封装性 6.2.4 避免在类声明中定义函数 类的声明应当只包含函数的声明,永远不要进行函数定义(实现)。应在类的实现文件(.cpp)中定义。 6.2.5 明确声明构造函数的类使用默认构造函数 为了在数组中或任何 STL 容器中使用类,类必须提供公共的默认构造函数或允许编译器生成一个构造函数. 当类有非静态引用类型的数据成员时是以上规则的一个例外,这种情况下通常不可能创建一个有意义的默认构造函数.因此,使用对象数据成员的引用就是不可靠的。 6.2.6 带有指针类型数据成员的类要声明其复制构造函数和赋值操作符 如果需要,并且没有明确声明时,编译器会为类暗中生成一个复制构造函数和一个赋值操作符。编译器定义了复制构造函数和赋值操作符,这通常称为“shallow-copy”(浅拷贝):明确的说,即按成员拷贝以及对指针按位拷贝。使用编译器生成的复制构造函数和默认的赋值操作符肯定会造成内存泄漏。 6.2.7 不要重复声明构造函数参数有默认值 6.2.8 将析构函数总是声明为 virtual 类型 除非明确将类设计为不可继承的,否则应将析构函数声明为 virtual 类型。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 6.2.9 初始化列表表中的成员列出的顺序和它们在类声明中的顺序相同。 6.2.10 不要重定义非虚函数 非虚函数实现固定的行为,并且不希望专用于派生类。违反这一原则将导致不可预料的行为:同一对象不同时间可能表现不同的行为。 非虚函数是静态绑定的;以下示例中,对象函数的调用由变量(指向 A 或 B 的指针)的静态类型控制,而不是对象的实际类型。 6.2.11 谨慎使用非虚函数 因为非虚函数通过限制特殊化(即重载,译者注)和多态的使用来限制子类,声明为非虚拟之前,必须注意确保操作对所有子类确实是固定不变的。 6.2.12 使用初始化构造函数而不是使用构造函数中的赋值语句 对象构造时其状态的初始化应由初始化构造函数(一个成员初始化列表)完成,而不是通过构造函数内的赋值操作符完成. 6.2.13 初始化构造函数避免调用成员函数 6.2.14 注意构造函数和析构函数调用时的情况 构造函数中调用成员函数时要格外注意;即使调用的是虚函数时也要注意。执行的将是在类或其基类的构造函数、析构函数中定义的操作。 6.2.15 整形类常量使用static const或enum。 定义整形(整数)类常量时,使用 static const 数据成员,不要使用 #define 或全局常量。如果编译器不支持 static const,则使用enum。 示例 代码如下: class X { static const BUFFERSIZE = 100; char chrBuffer[BUFFERSIZE]; }; 或: class C { enum { BUFFERSIZE = 100 }; char chrBuffer[BUFFERSIZE]; }; 但避免使用: #define BUFFER_SIZE 100 class C { char buffer[BUFFER_SIZE]; }; 6.3 函数 6.3.1 一定要明确声明函数的返回值类型 这可以防止编译器发现函数未声明返回值类型时所造成的模糊不清。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 6.3.2 函数声明中要提供正规参数名称 函数的声明和定义中要使用相同的名称;这可使混淆程度降至最小。提供参数名称有利于提高代码的易注释性和易读性。 函数要尽量只有一个返回点 返回语句在程序体中自由放置与 goto 语句的情况类似,会造成代码难以阅读和维护。 只有在少数几种函数中才能容忍出现多个返回语句,此时可以同时看到所有的返回语句 return 并且代码还有非常规则的结构: type_t Foo() { if (this_condition) return this_value; else return some_other_value; } 函数的返回类型为 void 时无返回语句。 6.3.3 避免创建对全局有副作用的函数 应尽量避免创建对全局有副作用的函数,因为它可能改变并不知情的数据而非它们内部对象的状态,如全局和名字空间数据。但如果这是不可避免的,则应明确记录下所有的副作用以作为函数声明的一部分。 只在所需的对象中传递参数增加了代码的上下文无关性,并提高了它的强壮性和易理解性。 6.3.4 以重要性和活跃性递减的顺序声明函数的参数 从调用者的角度讲,参数声明的顺序是非常重要的: · 首先以重要性递减的顺序定义非默认参数 · 再以被修改可能性递减的顺序定义有默认值的参数 这种顺序使用了参数默认值的优点来减少函数调用中实参的个数。 6.3.5 避免声明带有不定参数个数的函数 · 带有不定参数个数的函数无法对其实参进行类型检查。 6.3.6 避免重复声明带有默认参数的函数 函数的进一步重复声明中应避免添加默认值:远离先前声明,一个函数只应声明一次。否则,如果读者没有意识到此后的声明,这将会造成混淆。 6.3.7 函数声明中尽可能使用 const 检查函数是否有常量行为(返回常数值;接受常量实参;或其操作不带有副作用),如果有则使用限定符 const 表明其行为。 示例 const T f(...); //函数返回一个常量 //对象。 T f(T* const arg); //函数接受一个常量指针 //为其参数。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 //可以改变指针所指的对象, //但不可以改变指针本身。 T f(const T* arg); //函数参数为指针类型 T f(const T& arg); // 函数参数为引用类型 //它们都指向一个常量对象。指针可以 //改变,但所指的对象 //不能改变。 T f(const T* const arg); //函数参数为 //常量指针,它指向常量对象。 //指针和它所指的对象 //都不改变。 T f(...) const; //函数没有副作用: //不改变它对象的状态 //所以可应用于 //常量对象。 6.3.8 避免利用值传递对象 利用值传递和返回对象可能带来构造函数和析构函数的大量开支。可通过引用传递和返回对象,这样可避免构造函数和析构函数的开支。 可用 Const 引用指定通过引用传递的实参是不可改变的。复制构造函数和赋值操作符是典型的示例 示例: C::C(const C& aC); C& C::operator=(const C& aC); 建议开发人员在传递和返回对象时是使用引用类型或指针类型,但应格外当心不要返回引用到局部对象上,当需要对象时,也不要返回引用。返回引用到局部对象会造成灾难性后果,因为函数返回时,所返回的引用绑定到了所销毁的对象上。 对于基本类型的参数,优先使用值调用,其次使用引用调用,最后考虑使用指针。 6.3.9 不能返回引用到局部对象 离开函数作用域时会销毁局部对象;使用销毁了的对象会造成灾难。 6.3.10 不可返回由 new 初始化,之后又已解除引用的指针。 将导致内存泄漏 示例 class C { public: ... friend C& operator+( const C& left,const C& right); }; C& operator+(const C& left, const C& right) { 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 C* new_c = new C(left..., right...); return *new_c; } C a, b, c, d; C sum; sum = a + b + c + d; 因为在计算加和 sum 值时没有存储操作符“+”的中间结果,因此中间对象不能删除,否则会导致内存泄漏。 不要返回非常量引用或指针到成员数据上 违背了这一原则也就违背了数据的封装性,可能导致不好的结果。 使用内嵌定义函数,避免使用宏扩展#define 但要有选择的使用内嵌定义:只对非常小的函数才使用;内嵌定义大型函数可能导致代码膨胀。由于用户代码编译时需要内嵌定义函数的实现,因此内嵌定义函数也增加了模块间编译的依赖。 示例 不要这样做: #define MAX(a, b) ((a) > (b) ?(a) : (b)) 而应这样做: inline int Max(int a, int b) { return a > b ? a : b; } 宏 MAX 有好几个问题:它不是安全的类型;它的行为不是确定的: int a = 1, b = 0; MAX(a++, b); // a 增 1 了两次 MAX(a++, b+10); // a 增 1 了一次 MAX(a, "Hello"); //比较整形和指针 6.3.11 使用默认参数而不使用函数重载 当只使用一个算法并且此算法可由少量几个参数进行参数化时,使用默认参数而不使用函数重载。 使用默认参数有利于减少重载函数的数量、提高可维护性、减少函数调用时所需的实参数量、以及提高代码的可读性。 6.3.12 使用函数重载表达常用语义 当同一语义操作需要多个实现时使用函数重载,但重载使用不同的实参类型。 重载操作符时保留其传统含义。不要忘记定义关系操作符,如操作符 operator== 和 operator!=。 6.3.13 避免重载以指针和整形数为实参的函数 避免使用带单一整形实参的函数来重载带单一指针实参的函数: void f(char* p); void f(int i); 以下调用可能会导致混淆: if(NULL); f(0); 此重载解析为 f(int) 而不是 f(char*)。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 6.3.14 令操作符 operator= 返回对 *this 的引用。 6.3.15 使 operator= 检查自赋值 执行检查有两点重要的理由:首先,派生类对象的赋值涉及到调用每一个基类(在继承层次结构中位于此类的上方)的赋值操作符,跳过这些操作符就可以节省很多运行时间。其次,在复制“rvalue”对象前,赋值涉及到解构“lvalue”对象。在自赋值时,rvalue 对象在赋值前就已销毁了,因此赋值的结果是不确定的。 6.3.16 尽可能减少复杂性与函数的规模。 · 不要书写过长的函数,例如代码应小于 80 行。 · 尽可能减少返回语句的数量,1 是理想值。 · 尽量使循环复杂性降到 10 以下(对单一退出语句函数为判定语句总数加 1)。 · 尽量使扩展循环复杂性降到 15 以下(对单一退出语句函数为判定语句+逻辑操作符+1的和)。 · 尽量减小引用的平均最大作用范围(局部对象声明与对象第一次使用间的行距)。 6.4 类型 6.4.1 定义项目范围的全局系统类型 6.4.2 使用基本类型 · 尽量不使用int,float;而使用long,double。 如:对int(2/4),应定为short(2)或long(4)。 · char类型只在使用字符数据时使用。 6.4.3 使用 typedef 创建同义词来加强局部含义 · 对现有名称使用 typedef 创建同义词,提供更有意义的局部名称并提高易读性(这样做不会增加运行时间)。 typedef 也可用来提供受限名称的速记法。 · 使用 typedef 创建的名称时,同一代码段中不要混合使用原名称和它的同义词。 6.5 常量与对象 6.5.1 定义常量时避免使用 #define 预处理指示符 6.5.1.1 对象声明靠近其第一次使用点声明时总要初始化 const 对象。 · 未声明为 extern 的 const 对象有内部链接,声明时初始化这些常量对象允许在编译时使用初始化函数。 6.5.2 永远不要忘记常量对象的“不变性”。 · 常量对象可能存在于只读存储器中。 6.5.3 定义时初始化对象 · 如果对象不是自初始化的,在对象定义中指定初始值。如果不可能指定一个有意义的初始值,则赋值“NULL”或考虑后面再作定义。 对大型对象,通常不建议先构造对象,此后再使用赋值语句初始化的作法。因为这样做的代价高昂(请参见“使用初始化构造函数而不使用构造函数中的赋值语句”)。 如果构造时不可能合适的初始化对象,则使用传统的“NULL”值进行对象初始化,它意味着“未初始化”。只在初始化过程中声明“不可用但值已知”的对象时才使用 NULL 值。但算法在受控模式下可以拒绝接受:在对象合适的初始化之前使用它时可以指出这是未经初始化的变量错误。 注意有时不可能所有类型都声明为 NULL 值,尤其是在模运算类型中,例如角度。I这种情况下选择最不可能出现的值。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 7. 表达式和语句 7.1 表达式 7.1.1 使用冗余的圆括号使复合表达式含义更加清晰 如:尽管if(nTemp0&&nTemp1||nTemp2&&nTemp3)语法正确,还是建议使用 圆括号写成if( (nTemp0&&nTemp1) || (nTemp2&&nTemp3) )形式,以增加可读性。 7.1.2 避免表达式的过深嵌套,嵌套深度<5 7.1.3 数组,链表等集合类的数据索引以0开始(ZERO_BASE)。 C++的索引,MFC的索引,Windows API索引大多数都是Zero_base。若数据的检索不是从0开始,应当注释说明。 7.1.4 空指针使用NULL而不使用 0 7.1.5 使用新类型转换符而避免使用强制类型转换。 使用转换操作符如下: · dynamic_cast - 类同一层次(子类型)成员间的的转换,使用运行类型信息(对带虚函数的类,运行类型信息是可用的)。这种类间的转换一定是安全的。 · static_cast - 类同一层次成员间的的转换,不使用运行类型信息;所以这可能是不安全的。如果程序员不能保证类型的安全性,则使用 dynamic_cast。 · reinterpret_cast- 不相关指针类型和整形(整数)间的转换;这是不安全的,只应在所提及的类型间使用。 · const_cast- 将指定为 const 型函数实参的“不变性”转换掉。注意:const_cast 不打算将确实定义为常量对象(它可能位于只读存储器中)的“不变性”转换掉。 7.1.6 指针不要与不在同一数组中的对象比较 7.1.7 已删除的对象指针或句柄要赋予空指针值NULL · 设置已删除对象的指针为空指针可避免灾难的发生:重复删除非空指针是有害的,但重复删除空指针是无害的。 即使在函数返回前,删除操作后也总要赋一个空指针,因为以后可能添加新的代码。 例:delete pClassA; pClassA=NULL; 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 7.2 语句 7.2.1 当从布尔表达式分支时使用 if 语句;当从离散值分支时使用 switch 语句 · 当分支条件为离散值时使用 switch 语句而不使用一系列的“else if”。 7.2.2 一定为 switch 语句提供一个 default 分支以记录错误 · switch 语句应总包含一个 default 分支,default 分支应用以捕获错误。 保证了当引入新的 switch 值而处理这一新值的分支被删除时,现有的 default 分支可以捕获到错误。 7.2.3 不要比较浮点数的相等 · 如: 10.0 * 0.1 == 1.0 ,是 不可靠 7.2.4 当循环需要迭代前测试时使用 for 语句或 while 语句 · 当迭代和循环的结束是根据循环计数器的值时使用 for 语句不使用 while 语句。 7.2.5 当循环需要迭代后测试时使用 do while 语句 7.2.6 无限循环使用while(TRUE)。 7.2.7 循环中避免使用 jump语句(如: break、return 或 goto) · 避免使用除循环结束条件以外的循环退出方式(使用 break、return 或 goto),不要使用 continue 不成熟的跳转到下一迭代上。这减少了控制路径流程的数量,使代码更易理解。 7.2.8 不要使用 goto 语句 7.2.9 避免嵌套作用域内隐藏标识符 · 这会使读者模糊不清,维护时造成潜在的危险。 7.2.10 深层代码嵌套中,不要超过10层嵌套,每一层的结束处应加注释说明。 8. 最佳实践 8.1 内存管理 8.1.1 C 和 C++ 内存操作要避免混合使用 · C 库函数 malloc、calloc 和 realloc 不应用于分配对象空间:此时应使用 C++ 操作符 new。 · 只有在内存传递给C 库函数处理时,才使用 C 函数分配内存。 · 不要使用 delete 来释放由 C 函数分配的内存,delete释放由 new 创建的对象。 8.1.2 删除由 new 创建的数组对象时总使用 delete[] · 使用 delete 删除数组对象时如果不使用空方括号(“[]”),则只删除数组的第一个元素,因此导致内存泄漏。 8.1.3 析构函数里对指针成员调用delete 示例: if(m_pObject) { 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 delete m_pObject; m_pObject=NULL; } 8.1.4 避免重载opeator new 和operator delete 。 8.2 错误处理和异常 由于使用 C++ 异常机制的经验不足,此处的指南将来可能会作较大的改变。 C++ 草拟标准定义了两大类错误:逻辑错误和运行错误。逻辑错误是可避免的编程错误。运行错误定义为程序范围外事件导致的错误。 使用异常的通用规则为:正常条件下的系统如果没有重载或硬件错误则不应当产生任何异常。 8.2.1 开发过程中多使用声明语句以发现错误 · 开发过程中使用函数的前置和后置条件声明语句以发现“倒毙”错误。 · 在实现最终错误处理代码前,MFC中使用ASSERT宏简单有用的临时错误监测机制。 8.2.2 只在真实的异常情况时使用异常 · 对经常的、预料中的事件不要使用异常:异常打断了代码的正常控制流程,使它难以理解和维护。 · 预料中的事件应用代码的正常控制流程处理;需要时使用函数返回值或“out”参数状态码。 · 异常也不应用于实现控制结构:这会是另一种形式的“goto”语句。 8.2.3 从标准异常中派生项目异常 · 这保证了所有异常都支持常用操作的最小集合并可由一小组高级处理程序处理。 使用逻辑错误(域错误、非法实参错误、长度错误和越界错误)表示应用程序域错误、函数调用传递了非法实参、对象构造超出了它们允许的大小以及实参值不在允许范围内。 · 使用运行错误(范围错误和溢出错误)表示只有在运行时才能查出的算术和配置错误、数据已遭破坏或资源耗尽错误。 · 给定抽象尽量少使用异常 大型系统中,每一层都不得不处理大量的异常会使代码难以阅读和维护。异常处理可能会严重影响了正常处理。 减少使用异常的方法有: (1)通过使用少量的异常种类在抽象间共享异常。 (2)引发派生于标准异常的特殊异常,处理更通用的异常。 (3)为对象添加“异常”状态,提供明确检查对象合法性的原语。 8.2.4 将所有异常声明为已引发 · 产生异常(而不仅仅是传递异常)的函数应在它们的异常声明中将所有异常声明为已引发:它们不应平静的产生异常而不警告它们的用户。 8.2.5 在异常首次出现时就报告它 · 开发过程中,采用合适的记录机制尽早报告异常,包括在“异常引发点”。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 8.2.6 按照从派生结构最底端到最顶端的顺序定义异常处理 · 定义异常处理要按照从派生结构最底端到最顶端(即从最终子类到最初父类)的顺序,这样可以避免编写无法到达的处理过程;请参见下面的示例how-not-to-it。因为是以声明的顺序匹配处理过程的,所以这也保证了异常由最合适的处理过程捕获。 8.2.7 避免使用捕获所有异常的处理过程 · 避免使用捕获所有异常的处理过程(处理过程声明使用 “确保,除非重引发了异常”。 只有在做本地内务管理时才使用捕获所有异常的处理过程,这时应重引发异常以避免掩盖了本层不能处理此异常的事实。 8.2.8 确保函数状态代码有合适的值 · 当状态代码作为函数参数返回时,要赋值给参数作为程序体中的第一个可执行语句。系统的设置所有状态的默认值为成功或失败。考虑函数所有可能的出口,包括异常处理。 8.3 数据表示 8.3.1 不要假设类型的表示 · 特别的,不可以在 int、长整形或任何其他数字类型中存储指针类型,因为这是高度不可移植的。 8.3.2 尽可能使用“可伸缩”常量。 · 可伸缩常量避免了字长变化的问题。 示例 const int all_ones = ~0; const int last_3_bits = ~0x7; 8.4 类型转换 8.4.1 不要从一个“短”类型转换成一个“长类型” · 机器结构可能指定了某种类型的排列方式。从需要较松散排列的类型转换为需要较严密排列的类型可能会导致程序错误。 8.5 复用 8.5.1 尽可能使用标准库构件,标准库函数 · 使用STL,MFC(或ATL)标准类与模板。 8.5.2 使用模板复用独立于数据的行为 · 当行为不依赖于一个具体的数据类型时,使用模板复用行为。 8.5.3 使用公共继承复用类接口(子类型化) · 使用public继承表达“is a”关系并复用基类接口,还可有选择的复用它们的实现。 8.5.4 使用包容而不是私有继承复用类的实现 · 当复用实现或建模“部分/整体”关系时,避免使用私有继承。复用未重新定义的实现最好由包容而非私有继承来实现。 当需要重新定义基类的操作时使用私有继承。 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 8.5.5 避免使用多继承,建议只对自建的接口类可使用多继承,MFC类不能使用多继承。 多继承应谨慎使用,因为它带来了许多额外的复杂性。 复杂性来自于: 歧义,当多个类使用相同的名称时,任何对名称不加限定的引用天生就是产生歧义 的。可通过使用类名称限定其成员名称来解决歧义的问题。但这也带来了不幸的后果:使多态无效且将虚函数准变成了静态绑定函数。 从同一基类重复继承(派生类通过继承结构的不同路径多次继承一个基类)多组数据成员产生了如下问题:应当使用多组数据成员中的哪一个? 可使用虚继承(对虚基类的继承)防止多继承数据成员。那么为什么不总使用虚继承呢?虚继承有负面影响,会改变基础对象表示并降低访问的效率。 要求所有的继承都是虚拟的,同时也就强加了包罗一切的空间,带来了时间上的低效,实现这样的政策过于独断了。 8.6 编译问题 8.7 尽量减少对编译的依赖 模块声明中不要包含只是此模块实施所需的其他头文件。 避免在只需要指针或引用可见时就为了能看到其他类而在声明中包含头文件;代之以预先声明。 9. 附录 9.1 Visual C++常用基本类型前缀列表 前缀 类型 描述 例子 ch Char 8-bit character chGrade ch TCHAR 16-bit character if _UNICODE is defined chName b BOOL bool Boolean value bEnabled n Int Integer (size dependent on operating system) nLength u UINT Unsigned value (size dependent on operating system) uLength w WORD 16-bit unsigned value wPos l LONG 32-bit signed integer lOffset dw DWORD 32-bit unsigned integer dwRange P * Ambient memory model pointer pdoc lp FAR* Far pointer lpDoc 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 lpsz LPSTR 32-bit pointer to character string lpszName lpsz LPCSTR 32-bit pointer to constant character string lpszName lpsz LPCTSTR 32-bit pointer to constant character string if _UNICODE is defined lpszName h Handle Handle to Windows object hWnd lpfn (*fn)() callbackFar pointer to CALLBACK function lpfnAbort ft Float fValue by BYTE 9.2 Visual C++常用宏定义命名列表 前缀 符号类型 符号例子 范围 IDR_ 标识多个资源共享的类型 IDR_MAINFRAME 1 to 0x6FFF IDD_ 对话框资源(Dialog) IDD_SPELL_CHECK 1 to 0x6FFF HIDD_ 基于对话框的上下文帮助(Context Help) HIDD_SPELL_CHECK 0x20001 to 0x26FF IDB_ 位图资源(Bitmap) IDB_COMPANY_LOGO 1 to 0x6FFF IDC_ 光标资源(Cursor) IDC_PENCIL 1 to 0x6FFF IDI_ 图标资源(Icon) IDI_NOTEPAD 1 to 0x6FFF ID_ IDM_ 工具栏或菜单栏的命令项 ID_TOOLS_SPELLING 0x8000 to 0xDFFF HID_ 命令上下文帮助(Command Help context) HID_TOOLS_SPELLING 0x18000 to 0x1DFFF IDP_ 消息框提示文字资源 IDP_INVALID_PARTNO 8 to 0xDFFF 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 HIDP_ 消息框上下文帮助(Message-box Help context) HIDP_INVALID_PARTNO 0x30008 to 0x3DFFF IDS_ 字符串资源(String) IDS_COPYRIGHT 1 to 0x7FFF IDC_ 对话框内的控制资源(Control) IDC_RECALC 8 to 0xDFFF 9.3 Windows对象名称缩写: Windows 对象 例子变量 Windows 对象 例子变量 HWND hWnd; HPEN hPen; HDLG hDlg; HBRUSH hBrush; HDC hDC; HFONT hFont; HGDIOBJ hGdiObj; HBITMAP hBitmap; HPALETTE hPalette; HMENU hMenu; HRGN hRgn; HWND hCtl; 9.4 MFC类前后缀 类 名 前(后) 缀 例 子 CObject 无 CMyData CWinThread Thread CGdiThread CWinApp App CMyApp CWnd Wnd CMyWnd CFrameWnd Frame CMainFrame CControlBar Bar CToolBar CPropertySheet Sheet CSetupSheet CDocument Doc CDataDoc CDocItem Item CClientItem CDialog Dialog CFileDialog 60f78c9e9173c1175afae16a28a75c33.doc Page 中央研究院C++ 编码规范 中央研究院C++编码规范 CPropertyPage Page CColorPage CException Exception CUserException CFiew View CListView CFile File CMemFile CDC DC CClientDC CButton Button CBitmapButton CAsyncSocket Socket CListenSocket CArray Array CStringArray CString str strName CBitmap Bitmap tempBitMap CRgn Rgn circleRgn CException Exception fileException 一般情况下,除CString对象和数据库访问对象(DAO)采用前缀外, 其它类的对象,就以该类的名子作为后缀。 如:myHttpServer 为CHttpServer的对象等 。 其它的派生类,以什么类作为基类,就以基类的名子作为后缀,如: CMyListCtrl为CListCtrl的派生类, CMyListCtrl为CListCtrl的派生类, CMyHttpServer 为CHttpServer的派生类, 依次类推。 60f78c9e9173c1175afae16a28a75c33.doc Page

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ] 1 人已下载

下载文档