随想录(写给自己的C++编程规范)

随想录(写给自己的C++编程规范)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

对于我这样一个C语言的程序员来说,编写C++的机会其实不太多。但是我还是比较喜欢写C++语言,原因主要有几个方面:(1)自己学C++语言的时间比较长了,也比较了解,如果从大一的时候算起,现在也有小十年了;(2)windows下面的开发工具确实很好用,比如调试器调用、汇编代码查看也方便,学起来其实没有什么障碍;(3)基于C++语言的开源项目还是比较多的,比如说的eMule、webkit、notepad++、libsvm,掌握好C++语言对自己的帮助还是挺大的。

和很多朋友一样,我自己的C++学习也是非常曲折的,这其中主要有下面几个阶段。刚开始,在大一刚刚入学的时候,我们集中学习了C++的基本语法,其实我也不清楚为什么要在大学设置C++这门这么难的语言,其实对于很多工科的同学来说,C语言已经是绰绰有余了,况且能把C语言学好本身就已经很不错了。后面,随着C++的了解,我开始用MFC编写一些小程序,什么串口工具、聊天工具、FTP下载工具、图像处理工具等等,这个时候看得比较多的就是侯捷的《深入浅出MFC》。当然后来随着MFC使用的频繁,对C++语言的了解也更加深入,这个时候更多地关注C++语言的实现细节,什么《Effective C++》、《C++ Template》、《Inside the C++ object》、《Effective STL》,此时恨不得不自己会的所有技巧都用上,充分发挥C++的特性。等到工作之后,由于工作的关系更加偏重于实时操作系统,自己对编程语言有了新的认识,不再盲目追求语言的特性,而是更注重系统的稳定、项目的开发进度和语言本身的简洁和高效。无疑,在这些要求下面,C语言就是最佳的选择,因为你可以清楚地了解每一行语句后面CPU都帮我们做了些什么。我看过的很多操作系统代码,比如ucos、rt-thread、linux、vxworks都是用C语言编写的,很简洁也很高效。

网上有位知名的朋友叫云风,早期在他写的书《我的编程感悟》当中也是对C++大加褒奖,可是在后来的工程实践中反而感觉到C语言才是最好用的语言。相信很多的朋友都有类似的经验。当然,不可否认C++还是存在很大的市场,但是它的应用范围和过去相比,确实是大大缩小了。一方面,精通C++的人实在是太少了,用好C++的人更是凤毛麟角;另外一方面,C++的学习代价、应用代价实在是很高,你没有办法要求项目组里边的每个人都有很高的C++应用水平,这是没有办法做到的,就算做到了,代价是十分昂贵的。所以,为了消除彼此成员之间的差别,很多时候C++的编程规范就变成了C语言的编程规范,这可以从google C++ style guide可以看出来。

所以,今天这里写这篇文章,主要是就C++写一些自己的总结,谈谈自己的看法,欢迎各位提出宝贵意见。

(1) 总则

a)尽量选择所有编译器都支持的C++标准

b)必须了解类的内存分布结构

c)必须了解编译器对C++的隐形操作

d)指针是所有错误的来源,尽量用引用代替

e)用const对入参、出参和类函数进行限制

f)多用namespace限定类的作用范围

g)少用C++的高级特性

h)时刻在需要处理C文件的时候添加extern “C”

i)string类是万恶之源,尽量少用

(2)头文件

a)定义头文件的时候首先添加编译宏

b)添加最少的头文件依赖

c)头文件名争取和类名一致

d)头文件中只包括类型定义、宏定义和函数声明

e)类的头文件和实现文件一一对应

(3)命名

a)类的命名按照首字母大写的格式进行,比如Parent

b)宏按照全大写的格式进行,比如MAX

c)函数按照小写进行,中间用连词号连接,比如get_max_number

d)变量按照一个单词进行,比如number,index等等

e)变量、函数多用static限定范围,类多用namespace限定范围

(4)函数

a)杜绝可变参的函数,不利于编译器检查

b)少用全局函数

c)函数入参多用引用类型

d)返回值多用引用类型,但是临时变量不能用引用

e)全局函数少用重载,最好不用

f)最好不用使用模板函数,即使使用也只限定于自己使用

(5)类

a)少用继承,多用组合

b)对于构造函数,多用explicit,防止编译器强行转换

c)对于指针成员变量,务必注意拷贝构造函数和赋值函数的编写

d)对于类指针,为了防止内存泄漏或者资源重复释放,最好自己管理,不要相信智能指针

e)继承类中的析构函数要定义为virtual类型

f)构造函数要简单,复杂的资源分配请在类成员函数init中定义

g)对于全局类变量,相互定义之间不要存在依赖

h)严格区分类指针的释放,注意delete和delete[]的区别

i)对于类中锁的问题,可以利用类的构造、析构、引用特性解决

j)多用class,少用struct

(6)类的高级特性

a)不要使用算术符重载

b)不要用模板,除非自己用,自己用也要少用

c)不用使用C++中的typeid属性

d)不要用异常

e)不要用virtual继承

f)不要用多类继承

g)不用要stl,当类中存在指针变量的时候,stl很多时候是搞不定的,而vector、list、find、sort你是搞得定的

 

第二篇:C C++语言编程规范

C、C++语言编程规范

一.目的

良好的编程风格是提高程序可靠性非常重要的手段,也是大型项目多人合作开发的技术基础。为了提高C/C++源程序的质量和可维护性,通过本规范定义来避免不好的编程风格,增强程序的易读性,便于自己及他人阅读。本规范的内容包括:排版、注释、标识符命名、可读性、变量、结构、函数、过程、可测性、质量保证等。

二.排版

1、相对独立的程序块之间要加空行分隔,在每个类声明之后、每个函数定义结束之后都要加空行,变量声明与执行代码之间加空行分隔(C++代码中变量声明与使用合在一起的可不加空行)。

2、函数或过程的开始、类或结构的定义、枚举的定义及循环、判断等语句中以及折行的代码都要采用缩进风格。每次缩进一个制表符宽度,或者缩进2个或4个空格宽度,代码中应统一使用制表符或空格来进行缩进,不可混用,否则在使用不同的源代码阅读工具时制表符将因为用户设置的不同而扩展为不同的宽度,造成显示混乱。制表符具有占用字节少、易定位、不容易错位(使用空格缩进容易出现多一个或少一个空格的现象,看上去不明显,但却是错位的)、扩展宽度可设置的优点,推荐使用。

3、较长的语句(>80字符,或以编辑屏幕可见范围为准)要分成多行书写(折行),长表达式要在低优先级操作符处划分新行,操作符放在新行之首。

4、循环、判断等语句中若有较长的表达式或语句,则要进行适应的划分,长表达式要在低优先级操作符处划分新行,操作符放在新行之首。

5、若函数或过程中的参数列表较长,则要进行适当的划分。

6、特殊情况下,长代码的折行可采用灵活的方式,宗旨是使代码易读。

7、划分出的新行要进行适当的缩进,以便识别。将一行代码划分为多行时,划分出的新行的缩进量要一致。

8、一行代码只做一件事情,例如只定义一个变量,或只写一条语句,这样的代码容易阅读,并且方便于写注释。不允许把多个短语句写在一行中,特殊代码(例如宏)除外。

9、if、for、do、while、case、switch、default、continue、goto、extern、return、typedef等语句自占一行,且if、for、do、while等语句的执行语句部分无论多少都要用大括号'{'和'}'括起来。

10、C/C++语言是用大括号'{'和'}'界定一段程序块的,编写程序块时'{'和'}'应各独占一行并且位于同一列,同时与引用它们的语句左对齐。空函数或简单的内联函数除外。在函数体的开始、类的定义、结构的定义、枚举的定义以及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式。

11、标识符和操作符之间加适当的空格,使代码错落有致,容易阅读。一元运算符(++、--、求址符&、求值符*、求非值符!、求反值符~、表示正负的+、-等)紧贴操作数,不加空格;二元运算符(算术运算符+、-、*、/、%、位运算符&、|、^、移位符<<、>>、赋值符+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=、比较符&&、||等)与前后的操作数之间各保留一个空格;逗号、分号紧跟前面的标识符,后面保留一个空格;类、结构成员访问符.、->等前后不加空格;声明

函数和单个变量时,类型修饰符&和*靠近类型名,与函数名或变量名之间留一个空格;在一条语句中声明多个变量时,类型修饰符&和*靠近变量名,避免阅读代码时产生误解。

12、修改代码时排版风格应与原代码风格保持一致,或者彻底修改整份代码的风格(代码基线后不提倡这样大改)。

三.注释

1、源程序有效注释量必须在20%以上。注释应尽量采用C++的注释风格,即使用// 注释。

2、在代码的逻辑含义的层次上进行注释。注释总是加在程序的需要一个概括性说明或不易理解或易理解错的地方。注释语言应该简练、易懂而又含义准确,避免二义性;所采用的语种首选是中文,如有输入困难、编译环境限制或特殊需求也可采用英文。

3、注释应与其描述的代码相近,对代码的注释应放在其上方或右方(针对单条语句的注释)相邻位置,不可放在下方,避免在一行代码或表达式中间使用注释,如放于上方则需与其上面的代码用空行隔开(较紧凑的代码除外)。

4、正确命名变量、结构、函数、过程以及合理地组织代码地结构,使代码成为自注释的,可减少不必要的注释。

5、数据结构声明(包括数组、结构、类、枚举等),如果其命名不是充分自注释的,必须加以注释。对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每个域的注释放在此域的右方。

6、在常量名字(或有宏机制的语言中的宏)声明后应对该名字作适当注释,注释说明的要点是:被保存值的含义(必须);合法取值的范围(可选)。

7、函数注释包括:函数功能描述(必须,除非函数非常简单明了),输入、输出等,复杂的函数需要加上变量用途说明。

8、说明文件(.h、.inc、.def、.cfg等)头部应进行注释,注释内容包括:作者名称、创建时间、模块用途等,复杂的算法需要加上流程说明,与其他文件有密切依赖关系的要进行说明。

9、程序中注释包括:修改时间和作者、方便理解的注释等。

10、当if、for等语句后的代码块比较长,特别是有多重嵌套时,应当在一些段落结束处的"}"符号后加注释,简单标明其对应起始位置,例如// end of if (...) 、// end of while (...),以便阅读。

11、边写代码边注释。代码比较复杂时,先写注释标识出程序的处理过程,然后再对每一个逻辑处理过程进行语句书写。修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。

四.标识符命名

1、标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。命名中使用特殊约定或缩写时,要有注释说明。

2、命名规范必须与所使用的系统风格保持一致,UNIX系统下采用全小写加下划线的形式,Windows系统下采用大小写混排的形式。自己特有的命名风格,要自始至终保持一致,不可来回变化,且首先要符合项目组或产品组的命名规则。

3、对于变量命名,尽量使用完整的单词而不是缩写;禁止取单个字符(如i、j、

k...),但i、j、k作局部循环变量、p作为指针、x、y作为坐标变量以及在数学公式中合理使用单字符变量是允许的,建议除了要有具体含义外,还能表明其变量类型、数据类型、有效范围等(采用匈牙利命名法或简化的匈牙利命名法)。静态变量要加前缀s_(表示static),全局变量要加前缀g_(表示global),类的数据成员要加前缀m_(表示member),常量要加前缀c_(表示const)或使用全大写加下划线的形式。

4、应尽量使用const定义常量而不使用宏定义常量。

5、程序中不要使用仅靠大小写区分的相似的标识符;也不要使用标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但容易使人误解。

6、复用一个变量时不要出于不同的目的,因为这样容易把读代码的人搞糊涂。

7、变量的名字应当使用“名词”或者“形容词+名词”。全局函数的名字应当使用“动词”、“动词+名词”或者“动词+副词(动词短语)”的形式。类的成员函数(如果可以的话)应当只使用“动词”,被省略掉的名词就是类本身。

8、用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。常用反义词组有: add/delete, add/remove, begin/end, create/destroy, cut/paste, first/last, get/put, get/set, increase/decrease, increment/decrement, insert/delete, insert/remove, lock/unlock, min/max, old/new, open/close, previous/next, save/load, send/receive, set/unset, show/hide, source/destination, source/target, start/finish, start/stop, up/down。

9、在Windows系统下编程时,类名、结构名用首字母大写的单词组合而成,结构名也可以用全大写单词加下划线分隔组成。类名以大写字母C为前缀,结构名以Stru为后缀,以区分函数名;全大写单词加下划线的结构名要以_STRU为后缀(或使用其他能体现结构含义的前/后缀),以区分全大写单词加下划线组成的常量名;函数名用首字母大写的单词组合而成。

五.可读性

1、避免使用不易理解的数字,用有意义的宏或常量标识符来替代。涉及物理状态或者含有物理意义的常量必须用有意义的枚举或宏来代替。被多处使用的数字应替换为宏以便修改。

2、注意运算符的优先级,表达式比较复杂时用括号明确表达式的操作顺序,避免使用默认优先级让阅读者容易产生误解。

3、程序中关系较为紧密的代码应尽可能相邻,结构成员赋值代码不应被其它代码隔开。不要使用难懂的技巧性很高的语句,除非很有必要。

六.变量、结构

1、去掉没有必要的公共变量以降低模块间的耦合度。定义公共变量时,应对其含义、作用、取值范围进行注释说明,若有必要还应说明与其它变量的关系。明确公共变量与操作此变量的函数或过程的关系,如:被哪个函数访问、修改等。对公共变量赋值应避免不合理的值或数组下标越界。避免局部变量与公共变量同名。

2、严禁使用未经初始化的变量作为右值,为避免不同的编译器在变量初始化上的差异,对变量,尤其是指针,在使用前将其初始化,尽可能在定义变量的同时初始化该变量(就近原则)。

3、使用可移植的数据类型,尽量不要使用与具体硬件或软件环境关系密切的变量。

4、结构的功能要单一,是针对一种事务的抽象。

说明:设计结构时应力争使结构代表一种现实事务的抽象,而不是同时代表多种。结构中的各元素应代表同一事务的不同侧面,而不应把描述没有关系或关系很弱的不同事务的元素放到同一结构中。如果两个结构间关系比较复杂、密切,应将它们合并为一个结构。

5、结构中元素的个数应适中。若结构中元素个数过多可考虑依据某种原则把元素组成不同的子结构,以减少原结构中元素的个数。

6、仔细设计结构中元素的布局与排列顺序,使结构容易理解、节省占用空间(针对位域),并减少引起误用现象。考虑存取效率,结构元素尽量位于字节对齐的位置上,例如32位机上,如果结构成员在4字节倍数的位置上,CPU一个指令就可以存取,否则要耗费更多的CPU指令。

7、结构的设计要尽量考虑向前兼容和以后的版本升级,并为某些未来可能的应用保留余地(如预留一些空间等)。

8、对自定义数据类型进行恰当命名,使它成为自描述性的,以提高代码可读性。注意其命名方式在同一产品中的统一。

9、当声明用于分布式环境或不同CPU间通信环境的数据结构时,必须考虑机器的字节顺序、使用的位域及字节对齐等问题。

10、定义结构体成员变量应尽量使用基本的数据类型,如果使用结构体、类或指针为成员变量,应定义执行初始化的构造函数、拷贝构造函数和重载赋值操作符。声明不能自行初始化的结构或类的变量时,应及时初始化该变量。

11、定义以行为为主的类时,将public函数、方法声明写在前面,将private数据声明写在后面;定义以数据为主的结构体时,将数据声明写在前面,将辅助函数(执行初始化的构造函数、拷贝构造函数、赋值函数等)写在后面。

12、在类和结构声明中,复用public、protected、private关键字,将函数声明和成员变量声明分开。

七.函数、过程

1、对所调用函数的错误返回码要仔细、全面地处理。

2、编写可重入函数时,应注意局部变量的使用(例如编写C/C++语言的可重入函数时,应使用auto即缺省态局部变量或寄存器变量)。

说明:编写C/C++语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。

3、编写可重入函数时,若使用全局变量,则应通过设置互斥量、关中断、信号量(即P、V操作)等手段对其加以保护。

4、在同一项目组应明确规定对接口函数参数的合法性检查应由函数的调用者负责还是由接口函数本身负责,缺省是由函数调用者负责。

说明:对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。

5、函数的规模尽量限制在200行以内,一个函数仅完成一件功能,为简单功能编写函数,不要设计多用途面面俱到的函数。

6、函数的功能应该是可以预测的,也就是只要输入的数据相同就应产生同样的输出。

7、如果多段代码重复做同一件事情,应考虑函数重构。

8、设计高扇入、合理扇出(小于7)的函数,改进模块中函数的结构,降低函数间的耦合度,并提高函数的独立性以及代码可读性、效率和可维护性。

9、在多任务操作系统的环境下编程,要注意函数可重入性的考虑。

10、为了防止头文件被重复包含,应当用#include guards(#ifndef xx_h / #define xx_h / #endif)结构产生预处理块。#include应一律出现在文件开头, 如果是头文件则放在#include guards之后。尽量避免头文件对头文件的依赖,如果某个头文件中需要用到某个类的引用或指针,那么只需要一个forward declaration(例如class CMyClass;)就足够了。源程序应该首先#include对应的头文件 确保每个头文件都可以单独被include,而不需要额外的头文件依赖。

11、使用宏定义表达式时,要使用完备的括号。将宏定义的多条表达式放在大括号中,避免if、for等语句书写不规范造成代码漏执行。使用宏时,避免执行使参数值发生变化的操作,例如增1操作。

12、在主要的函数和过程的入口、出口及大的分支记录最低级别(DEBUGGING)的调试日志;在模块间互发消息时,发送方和接受方都要记录信息级别(INFORMATION)的日志;在调用系统函数发生错误处记录错误级别(ERROR)的日志;在与程序性能关系密切的函数调用处记录性能日志;进行多线程编程时在日志中记录线程ID以便定位错误。

八.可测性

1、在同一项目组或产品组内,调测打印出的信息串的格式要有统一的形式。信息串中至少要有所在模块名(或源文件名)及行号。

2、使用断言来发现软件问题,提高代码可测性,用断言确认函数的参数的有效性。

九.质量保证

1、代码质量保证优先原则:

1)正确性,指程序要实现设计要求的功能。

2)稳定性、安全性,指程序稳定、可靠、安全。

3)可测试性,指程序要具有良好的可测试性。

4)规范/可读性,指程序书写风格、命名规则等要符合规范。

5)全局效率,指软件系统的整体效率。

6)局部效率,指某个模块/子模块/函数的本身效率。

7)个人表达方式/个人方便性,指个人编程习惯。

2、防止引用已经释放的内存空间。

3、在每个要用到内存申请的模块文件头部定义#define new DEBUG_NEW宏以跟踪内存的申请和释放。过程/函数中分配的内存,在过程/函数退出之前要释放;过程/函数中申请的(为打开文件而使用的)文件句柄,在过程/函数退出之前要关闭。特殊需要保留的资源,应注释说明该资源将在什么时候释放。用free或delete释放了内存之后,如果指针生命周期未结束,应立即将指针设置为NULL,防止产生“野指针”。

4、系统运行之初,要初始化有关变量及运行环境,防止未经初始化的变量被引

用;要对加载到系统中的数据进行一致性检查。

5、使用断言(assert)来检测是否有未满足的前提条件和编程错误。

6、防止内存操作越界。

7、仅仅在没有适当或是有效的方法告知调用者错误情况时使用异常,如果可能的话,当函数抛出异常时,它必须没有任何效果,程序状态应该恢复到函数调用之前,仿佛没有执行函数一样。不要将那些拷贝构造函数会抛出异常的类型作为异常抛出,也不要在析构函数中抛出异常。

8、有可能的话,if语句尽量加上else分支,对没有else分支的语句要小心对待;switch语句必须有default分支。