《C++编码规范》读书摘要

  一,命名原则
1.1
关于类型名
类型名中每个英文单词的首字母大写,最后以_T结尾。
1.2
关于变量和函数名
首字母小写,以后每个单词的首字母大写。
1.3
关于全大写的函数名
有一类函数,调用普通函数,只是多了对于错误返回的一般化处理。这类函数可以用普通
函数的全大写名字。
1.4
关于宏,变量和模板名
全部用大写,多个单词用下划线分隔。
1.5
关于指针标识符名,建议以p开头或Ptr结尾
1.6
关于变量名前缀
i_
类内数据成员 instance scope
c_
类内静态数据成员 class scope
g_
全局变量 global scope
f_
文件变量 file scope
1.7
关于全局命名空间级标识符的前缀,最好加一个公共前缀以区分。
1.8
减少全局命名空间级标识符,可统一放到一个类中。
1.9
命名时避免使用国际组织占用的格式
1.10
名字清楚简单
1.11
尽量用可发音名字
1.12
尽量用英文命名
1.13
尽量选择通用词汇
1.14
避免用缩写
1.15
避免用会引起误解的词汇
1.16
减少名字中的冗余信息
1.17
建议起名尽量通俗
1.18
名字最好尽可能精确表达其内容
1.19
避免名字中出现混淆的数字或字母
1.20
命名类和成员使得object.method()有意义
1.21
类和对象名应是名词
1.22
实现行为的类成员函数名应是动词
1.23
类的存取和查询成员函数名应是名词或形容词
1.24
变量名是名词
1.25
布尔型的名字要直观
1.26
关于函数的左值参数用lhs,右值参数用rhs
1.27
避免局部名和外层的名字冲突
1.28
a, an, any区分重名参数
1.29
模板类型名应有意义 template < class TYPE, int SIZE >
二,类型的使用
2.1
避免隐式声明类型:函数返回值和变量类型隐式为int
2.2
慎用无符号类型
2.3
少用浮点类型除非必须
2.4
typedef简化程序中的复杂语法,常用于函数指针的定义。
2.5
少用union
2.6
慎用位操作
2.7
enum取代一组常量
2.8
使用内置bool类型
2.9
尽量用引用取代指针,以NULL是否有效为判断。
在下述情况下引用优于指针:

被引用的对象永远不可能是空
引用某一个对象后决不会再去引用其他对象
( 某些 )operator 函数的返回值,如 operator[],operator+=
函数的参数传递
但在下述情况下指针更好:
NULL 是合法的参数值
其他情况见原则 3.6
原因:
引用更安全,因为它一定不为空,且一定不会再指向其他目标
不需要检查非法值(如 NULL )情况。
使用更简洁(“ . ”比“ -> ”好用)。
不需要解除引用:指针所指的内存释放后,一般让它指向 NULL
三,函数
3.1
函数一定要做到先声明后使用
3.2
函数原型声明放在一个头文件中
3.3
函数无参数一定要使用 void
3.4
对于内置类型参数应传值,除非要修改参数内容
内置类型指 int,char 等(相对于自定义的 class struct unit
原因:
传值既安全又简单。
内置类型拷贝的代价与传指针或引用相同,因为(绝大多数)内置类型所占内存小于等于指针或引用所占内存。
3.5
对于非内置类型参数应传递引用或指针
如要防止参数被修改,可用 const 修饰。
引用 / 指针的选择请参看原则 2.9
原因:
不用拷贝:非内置类型的尺寸一般都大于引用和指针类型,尤其还要考虑非内置类型可能包含隐式的数据成员,比如虚函数指针等,所以拷贝的代价高于传递引用和指针。
不用构造和析构:如果拷贝对象,还要在传入时调用(拷贝)构造函数,函数退出时还要析构。
有利于非内置类型的扩充:对于小对象虽然传值代价也不大,但将来的修改 / 扩充可能使这一优势丧失,到时再漫山遍野地将函数接口从传值改成传引用 / 指针就太费劲了。
有利于函数支持派生类:若将派生类对象传给以基类为参数的函数(传值方式),就会导致派生类对象被“切割”成基类对象,这样在函数内部实际上用的是一个基类对象的拷贝,而不是最初传入的派生类对象。这多半不是你的本意。
这种错误非常难常查,因为它是编译器(隐式 / 自动)做的;编译器也不会报警,因为这是合法的。如果传入的是引用或指针,则不会有对象“切割”现象。函数内部可以将传入的对象视作基类对象使用(因为任何派生类对象都可以作为基类对象)。如果传入的对象有虚函数,则恰当的实现版本还会被正确调用,而不局限于传入的参数类型(基类)。
3.6
关于何时用指针传递参数,如果函数内部要将参数以指针形式传给其他函数的,或者
参数是被 new 出来,要在函数内释放的,用指针。
3.7
避免使用参数不确定的函数
3.8
如果不得不使用,用提供的方法, va_start, va_list, va_arg, _va_end
3.9
避免函数的参数过多,一般最多为 5
3.10
尽量保持函数只有唯一出口
3.11
显式定义返回类型
3.12
void ,任何情况下都要有返回值
3.13
如果函数返回状态,尝试用 enum 作为返回类型
3.14
返回指针类型的函数应该用 NULL 表示出错。
3.15
函数尽量返回引用
3.16
如果必须返回值,不要强行返回引用,防止返回局部变量的引用
不要返回局部变量的引用,因为当函数返回后它就不存在了。
最好不要返回 new 出来的对象的引用,因为: 1 new 出来的对象总要被删掉,而函数的返回值是匿名的(临时的),要保证的是 new 出来的内存,当不再使用时要回收。通常这是一件困难的事情,有时甚至根本做不到。 2 、当 new 出来的对象被删掉后,那个引用已经是无效的了,此时只能靠操作者记住这件事,这样非常容易导致错误。
3.17
当函数返回引用或指针,用文字描述其有效期。
3.18
禁止成员函数返回成员的引用或指针:破坏了封装
3.19
重复使用的代码用函数替代
3.20
关于虚友元函数, c++ 不支持,间接实现如下 :
class Base { public: virtual void print(ostream& out); }
class Dervie : public Base { public: virtual print(ostream& out); }
ostream& operator<< (ostream& out, Base& anObject) { anObject.print(out); }
原因:
若在每个类中都声明 operator<<() 是友元并实现之,有些罗嗦。
友元破坏了类的封装性,现在这种方法不需要声明任何友元了。
编程者可以把基类的 print() 声明成纯虚 (pure virtual) 函数,以强迫派生类实现 print() ,而友元方式无此类控制方法。就是说,如果用友元方式,使用者不能随便使用 “cout<<anObject” ,因为你不知道这个对象的类型有没有定义并实现了相应的友元函数 operator<<()( 没人强迫它这么做 ) ;与此相反,在“虚友元”方式下,任何派生类(不论是现有的,还是将来可能要加的)使用者都可以随时使用“ cout<<anObject ”,所有机制都已建好,唯一需要新加派生类做的是输出自己特殊的成员,而这项工作也有编译器强迫编程者实现,不会漏掉。
3.21
关于虚构造函数, c++ 不支持,间接实现如下 :
class Base { public: virtual Base& clone(void); }
提供虚工厂函数。
四,类的设计和声明
4.1
类应是描述一组对象的集合
4.2
类成员应是私有的 private
4.3
保持对象状态信息的持续有效:确保对象的描述状态信息的成员变量始终有效
4.4
提高类内的聚合度:单一职责原则
4.5
降低类间的耦合度
4.6
努力使类的接口小而完备
4.7
保持类的不同接口在实现上的一致性
4.8
保持不同类的接口在实现上的一致性
4.9
避免为每个类成员提供访问函数
4.10
不要在类定义时提供成员函数体
4.11
函数声明是定义参数的缺省值
4.12
恰当选择成员函数,全局函数,友元函数
1
虚函数必为成员函数,因为非成员函数不能为虚
2 Operator>> operator<< 必为非成员函数,因为其第一个参数是 cin cout 等,而不是你自己的类。若需访问类成员,它们还必须是友元函数。
3 、若函数第一个参数(包括隐含参数,比如 this )需要做隐式类型转换,则其必不能是成员函数,因为编译器不会对 this 做类型转换。若需访问类成员,它们还必须是友元函数。
4 、对操作符函数的选择请参见原则 10.3
5 、其他情况首选成员函数,因为它最符合面向对象规则。
4.13
防范,杜绝潜在的二义性
class Type_T{public:void f(char c);void f(int i);}; Type_T t; t.f(0);//
二义性
4.14
显式禁止编译器自动生成不需要的函数,通过声明为 private ,不给出定义。
编译器可自动 / 隐式生成缺省构造函数、拷贝构造函数、赋值函数等。如果不需要,一定显式禁止,特别不能认为“现在不会用到,以后用到再说”。
4.15
保证,对象对每个错误都有应对措施
4.16
用嵌套类减少全局命名空间类的数量
五,继承
5.1
公共继承表示派生类是基类
5.2
关于 ... 实现
包含意味着有一个或由 ... 实现,私有继承表示由 ... 实现(和包含类似,完全不同于“公共继承”)
区别: 包含的两个类不能互相替换,而公共继承的派生类可以替换基类。
编译器不会将私有继承的派生类转换为基类。私有继承可以访问基类的 protected 成员,
包含不行。
私有继承可以重写基类的虚函数,包含不行。尽量使用包含。
5.3
关于继承和模板,当类型差异不影响类的行为时用模板,否则用继承
5.4
关于继承接口和继承实现
纯虚函数 : 只继承接口,强制派生类实现接口(注意:纯虚函数也能有函数体,提供缺省实现。如果派生类要使用缺省实现,必须在自己的函数体中显式调用)
一般虚函数 : 继承接口提供缺省实现,可修改
非虚函数 : 继承接口和实现
私有继承:只继承实现
5.5
限制继承的层数,一般最大为 5
5.6
继承树上非叶子节点的类应是虚基类
如果有两个非抽象类 C1 C2 ,且希望 C2 派生于 C1 ,则应该增加一个抽象基类 A ,且将 C1 C2 都从 A 派生出来。
原因:
1 、基类 A 的存在明确了 C1 C2 的公共部分和差异,即 C2 到底想从 C2 继承什么、改写什么。
2 、今后的修改很明确:对 A 的修改会影响 C1 C2 两个类。而对 C1 C2 各自的修改不会影响对方。
3 、因为 C++ 的类封装了具体实现,而只把接口露在外面,所以 C1 的接口应该是 C2 最想要继承的。换句话说,只要 C2 继承了 C1 的接口, C2 的对象就可以被看作 C1 的对象而参与任何 C1 对象可参与的活动。由此可见,接口本身(而不是具体实现)是参与活动的充分必要条件,这就是为什么 A 通常是只定义接口的完全的抽象基类(类似于 JAVA 中的 Interface )。
4 、用完全抽象基类的另一个原因:继承存在一个很大的缺陷,就是打破封装,即把基类的实现细节暴露给派生类。导致的结果是对基类的修改将无法保证不会波及派生类,即基类和派生类是紧耦合关系。
5 、用完全抽象基类可以防止多重继承带来的各种问题(参见原则 5.14 )。
5.7
显式提供继承和访问修饰, public, protected, private
缺省继承是私有继承。
类的缺省访问权是私有。
结构 (struct) 和联合 (union) 的缺省访问权是公共 (public)
5.8
显式指出继承的虚函数
派生类从基类继承而来的虚函数自动是虚函数,当需要提供新的实现时,为了清晰起见,还是把关键字 virtual 加上为妙。
5.9
基类析构函数首选虚函数
基类—派生类最常见的用法 是:基类提供接口,派生类提供实现。这样,使用者就不用关心到底是哪个派生类提供实现,这就是所谓的多态。使用时用基类指针或引用指向 / 引用派生类的对象,而所有的操作是在这些基类指针 / 引用上进行的。若基类析构不是虚函数,这种用法就有问题:基类指针或引用只能调用基类的析构函数,从而使得派生类对象析构不成功(不完全)。
5.10
绝不要重新定义非虚函数,考虑用虚函数
class Bast_T{public:void someMethod(void);};
class Derived_T:public Base_T{public: void someMethod(void);};
Derived_T derivedObj;
Base_T* pBase=&derivedObj;
Derived_T* pDerived=&derivedObj;
pBase->someMethod();// 结果不令人满意:实际上在调 Base_T::someMethod()
pDerived->someMethod();// 结果不令人满意:实际上在调 Derived_T::someMethod()
5.11
绝不要重新定义缺省参数值
因为缺省参数值是静态绑定的,而调用哪个函数是动态绑定的。这种基类和派生类共同作用的产物太令人费解了。
5.12
不要将基类强制转换成派生类
C++ 提供了更好的解决方案:虚函数
更安全:不会出现(强制)转换错的情况
效率更高:用函数指针实现,从而免去了(每次的)条件判断。
适用性更强:真正的动态绑定,而强制类型转换本身是静态绑定(编译时决定)
5.13
关于 c++ 中分支用法选择
若分支依赖于对象的值,用 if else/switch, 若依赖于对象的类型,用虚函数。
5.14
慎用多重继承,防止菱形继承
5.15
所有多重继承的基类析构函数都应是虚函数
六,内存分配和释放
6.1 new,delete 取代 malloc,calloc,realloc free
6.2 new,delete
new[], delete[] 要成对使用
调用 new[n] 所包含的动作:
1 、从系统堆中申请可容纳 n 个对象外加一个整型的一块内存。
2 、将 n 记录在额外的那个整型内存中(其位置有的在开头,有的实现时放在末尾)。
3 、调用 n 次构造函数初始化这块内存中的 n 个连续对象。
调用 delete[] 所包含的动作:
1 、从 new[] 记录 n 的地方将 n 值找出。
2 、调用 n 次析构函数析构这块内存中的 n 个连续对象。
3 、将这一块整块内存(包括记录 n 的整型)归还系统堆。
6.3
确保所有 new 出来的东西适时被 delete
广义上的内存泄漏,是指没有及时释放内存。而不单单指没有释放内存。
6.4
谁申请谁释放
引入了 auto_ptr ,自动指针的概念。
6.5
当对象消亡时确保指针成员指向的系统堆内存全部被释放
6.6
自定义类的 new/delete 操作符一定要符合原操作符的行为规范
6.7
自定义类的 new 操作符一定要自定义类的 delete 操作符
6.8
当所指的内存被释放后,指针应有一个合理的值,比如 NULL
6.9
记住给字符串结束符申请空间, '/0'
七,初始化和清除
7.1
声明后就初始化强于使用前才初始化,因为前者只调用一次构造函数,后者还调用赋
值函数
Sing_T systemName(“Kitchen sink”);
7.2
初始化要彻底:所有成员变量都应有初始值
7.3
确保每一个构造函数都实现完全的初始化:通过显式调用 init() 函数来初始化不安全。
7.4
尽量使用初始化列表,在初始化列表上的成员,只调用一次构造,而在内部的多调用
一个赋值函数。 常量、引用等的初始化只能用初始化列表。
Square_T::Square_T(int aHeight,int aWidth,Color_T aColor):
           i_height(aHeight),i_width(aWidth),i_color(aColor){//___}
7.5 初始化列表要按成员声明顺序初始化 , 因为初始化顺序是按照定义时的顺序来进行的。
7.6
构造函数没结束,对象就没有构造出来。
构造函数结束以前,不能做:
1 、不能调用析构函数:不但编程者不能,编译器也不能:比如隐式调用的构造函数未结束(被打断),编译器永远不会隐式调用析构函数,因为编译器认为该对象没有构造出来。
2 、构造函数全程不能使用 this 成员,因为 this 所指的对象还不完整。
3 、不要在构造函数中调用虚函数成员。因为基类构造构造函数所用的 this 参数,其类型均为派生类(而非该基类),所以(基类)构造函数若调用虚函数,执行的函数体可能是派生类的成员函数,但此时派生类自己还未开始构造。这一点一定要切记!
7.7
不要用构造函数初始化静态成员,因为静态成员属于类,而不属于对象。
7.8
拷贝构造函数和赋值函数尽量用常量参数
7.9
让赋值函数返回当前对象的引用, return *this
7.10
在赋值函数中防范自己赋值自己
MyClass_T& MyClass_T::operator=(const MyClass_T& rhs)
{if(this!=rhs){//…} return *this;}
7.11
拷贝和赋值要确保彻底
7.12
关于构造函数,析构函数,赋值函数,相等或不等函数的格式
ClassA::ClassA(const ClassA&);
~ClassA(void);
ClassA& operator=(const ClassA& rhs);
bool operator==(const ClassA& lhs, const ClassA& rhs);
bool operator!=(const ClassA& lhs, const ClassA& rhs);

7.13
为大多数类提供缺省和拷贝构造函数,析构函数,赋值函数,相等函数
析构函数经常是虚函数,但自动生成的不能保证这一点。如果父类析构是虚函数,则自动生成的析构函数也是虚函数,否则是普通函数。
7.14
只有在有意义时,才提供缺省构造函数

7.15
包含资源管理的类应自定义拷贝构造函数,赋值函数和析构函数
除了系统堆内存以外,文件、管道、连接等都是资源。
7.16
拷贝构造函数,赋值函数和析构函数要么全自定义,要么全自动生成

7.17
类应有自己合理的拷贝原则:或浅拷贝或深拷贝

7.18
若编译时会完全初始化,不要给出数组的尺寸。
int array[4] = { 1, 2, 3, 4}; 改用 int array[] = { 1, 2, 3, 4};
当要用到数组大小时,用 sizeof(array)/sizeof(array[0]) 。因为这两个大多数情况下编译器会当成常量,所以一点也不影响效率。
7.19
将循环索引的初值定在循环点附近

7.20
确保全局变量在使用前被初始化
具体方法:
1 、假设全局变量 g 的类型是 G g 的声明在头文件 g.h 中,则在 g.h 中定义一个新的类 GI ,它包含一个静态整型计数器 i_count 、一个构造函数和一个析构函数。
2 、文件 g.cpp g.h 对应的实现文件,类 GI 的成员 i_count 在此定义(值为 0 );类 GI 的构造函数在 i_count 0 时初始化 g ,否则只将 i_count 1 GI 的析构函数将 i_count 1 ,若为 0 ,则调用 g 的清除代码(如果有的话)。
3 、在 g.h 中定义一个 GI 的静态变量 gi ,并将其放在变量 g 的声明之后。
// 文件 iostream.h
Class istream_withassign{//…};               // 全局变量 cin 的类定义(相当于类 G
Extern istream_withassign cin;                // 全局变量 cin 的声明(相当于 g
Class IostreamInit_T                             // 相当于类 GI 的定义
{public:IostreamInit_T(void);~IostreamInit_T(void);
Private:static unsigned int i_count;};
Static IostreamInit_T iostreamInit;           // 相当于 gi
// 文件 iostream.cpp
istream_withassign cin;                         // 全局变量 cin 的定义
unsigned int IostreamInit_T::i_count=0;   // 类静态成员的定义
IostreamInit_T:: IostreamInit_T(void) // 构造函数
{if(i_count++==0){// 初始化 cin 的代码 }}
IostreamInit_T::~ IostreamInit_T(void)    // 析构函数
{if(--i_count==0){// 清除 cin 的代码 }}
八,常量

8.1
关于常量修饰符的含义
const char *p;
表示指向常量的指针
char * const p
表示指向 char 的常量指针
const char* const p
;表示指向常量的常量指针

8.2
在设计函数原型时,对那些不可能被修改的参数用 const 修饰

8.3
类成员可以转换成常量形式暴露出来

8.4
关于常量成员函数,声明应该以 const 结尾。
常量成员函数和同名同参数(显式)的非常量成员函数可重载,这是因为它们的第一个 (this) 参数类型不同。
这一点常被人忽视,但很有用:如在多线程中,成员函数要上锁。但常量对象是不能上锁的,因为锁本身也是成员,上锁是修改锁成员的动作,在常量对象中是不能做的。其实你只要重装一个常量成员函数,不需上锁,直接读取成员做相应的操作即可。因为在这个新提供的成员函数中, this 指向常量对象,不用担心读取时有人修改,上锁也就不必要了。
class MyClass_T{
int func(int aPara) const;
// 相当于 int func(MyClass_T const*this, int aPara);
int func(int aPara);
// 相当于 int func(MyClass_T *this, int aPara);
//….
};
8.5
不要让常量成员函数修改程序的状态

8.6
不要将常量强制转换成非常量

8.7
任何变量和成员函数,首选用 const 修饰

九,重载
c++ 的重载功能使得同名函数可以有多种实现方法。

9.1
仔细区分带缺省值参数的函数和重载函数
void report(char const* pMessage,bool doLog=false,bool doPrint=false);
如用重载,需用三个函数
9.2
确保重载函数的所有版本有共同的目的和相似的行为
9.3
避免重载在指针和整型类型上,以防止二义性
void foo(int someInt);void foo(char* charPtr)
foo(0);// 试图调用 foo(int)
foo(NULL);// 试图调用 foo(char*)
9.4
尽量避免重载在模板类型上
template<class TYPE_T>
void foo(int para);void foo(TYPE_T para);// TYPE_T 实例化为整型怎么办
十,操作符
可自定义的操作符: operator new operator delete operator new[] operator
delete[]
+ - * / % += -= *= /= %=
& | ^ ~ &= |= ^= >> << >>= <<= ! = < > != == <= >= && || ++ -- -> ->*
, () []
不可自定义的操作符: . .* :: ?: new delete sizeof typeid
static_cast dynamic_cast const_cast reinterpret_cast

10.1
遵守操作符原本的含义,不要创新

10.2
确保自定义操作符能和其他操作符混合使用

10.3
区分作为成员函数和作为友元的操作符
+= = 等需要左值,应是成员函数。而 == + 不需要左值,应是友元
操作符作为成员函数可以确保左值一定是该类的对象,而作为友元,可以使第一个操作
数为任何类型。

10.4
关于前后缀操作符
定义时的分别 : 前缀 ClassA& operator++(void); 后缀 ClassA const
operator++(int);
后操作符应返回常量,防止类似 i++++ 的用法。可以 ++++i

10.5
确保相关的一组操作符行为统一

10.6
决不要自定义 operator&&(), operator||() operator,()

十一,类型转换

11.1
尽量避免强制类型转换

11.2
如果不得不作类型转换,尽量用显式方式

11.3
使用新型的类型转换并确保选择正确
const_cast
:将常量或非常量转换成非常量或常量
dynamic_cast
:将继承类转换成派生类或相反,运行时确定转换是否成功
static _cast
:类似于 c 的强制类型转换,但不能同 const_cast 一样
reinterpret_cast:
可以转换成任意类型

11.4
用虚函数方式取代 dynamic_cast
1
、虚函数不需要在编码时确定对象的真实类型,而 dynamic_cast 必须告知要转成的类型,类型当时还要抛出一个异常,所以还需提供失败检测机制。
2 、虚函数的执行效率远高于 dynamic_cast
3 、当增加或删队一个派生类时, dynamic_cast 还得增减相应的代码。参见原则 5.13
4 、提供完善的 dynamic_cast 失败检测机制非常难,因为运行时各种情况的组合情况非常多。
11.5
自定义类最好提供显式而不是隐式转换函数
知识点:对象转换成其它类型的方式
class String_T
{private:     string name;
public:   String_T(string aname):name(aname)     {}
      operator int(void);
};
String_T::operator int(void)
      {          return atoi(name.data());     }
int main(void)
{    String_T i_string(string("1000"));
      cout<<i_string;                  // 隐式调用 operator int ,如果没有这个函数,会报错
      return EXIT_SUCCESS;
}
11.6
使用关键字 explicit 防止单参数构造函数的类型转换功能
知识点:其它类型转换为对象类型的方式
class MyClass_T
{public:MyClass_T(int);   // int 隐式转换为 MyClass_T 类型
Explict MyClass_T(char);// 禁止将 char 隐式转换为 MyClass_T 类型
};
Void foo(MyClass_T anObj);
foo(5);// 成功      foo(‘a’);// 失败
11.7
限制隐式类型转换的类型数

11.8
避免多个函数提供相同的类型转换
// 转换操作符函数:将类型 From_T 隐式转换成类型 To_T
Form_T::operator To_T(void);
// 转换构造函数:将类型 From_T 隐式转换成类型 To_T
To_T::To_T(const From_T& from);
Void ambiguous(To_T to);
 
From_T from;
ambiguous(from);         // 二义性:哪个隐式类型转换应该被调用
十二,友元

12.1
少用友元,除非,有些操作符函数,协作类之间的内部通信,比如容器和迭代器

12.2
减少拥有友元特权的个数

十三,模板

13.1
使用模板如果有限制条件一定要在注释和文档中描述清楚

13.2
模板类型应传引用 / 指针而不是值

13.3
注意模板编译的特殊性

13.4
嵌套 template >> 中间要加空格以区别于 operator >>
vector< < set > >

十四,表达式和控制流程

14.1
让表达式直观

14.2
避免在表达式中用赋值语句

14.3
不能将枚举类型进行运算后再赋给枚举变量

14.4
避免对浮点类型作等于或不等于判断

14.5
尝试用范围比较代替精确比较
for (i = 0; i != 10; ++i);
for (i = 0; i<10; ++i);

14.6
范围用包含下限不包含上限方式表示

14.7
关于 goto
不能用 goto 跳出 / 跳入循环体,不能用 goto 跳出 / 跳入程序块

14.8
在循环过程中不要修改循环计数器

十五,宏

15.1
彻底用常量代替类似的宏

15.2
代码中的数值应由一个有意义的标识符代替

15.3
若宏值多于一项,一定要使用括号

15.4
不要用分号结束宏定义

15.5
彻底用 inline 函数替代类似的宏函数

15.6
不好被替代的宏函数,当不想指明参数类型时

15.7
函数宏的每个参数都要括起来

15.8
不带参数的宏函数也要定义成函数形式

15.9
{ } 将函数宏的函数体括起来

15.10
彻底用 typedef 代替宏定义新类型

15.11
不要在公共头文件中定义宏,除非用于头文件的多次引用而定义的宏

15.12
不要用宏改写语言

十六,异常处理

16.1
确保代码在异常出现时能正确处理

16.2
正确注释代码的异常处理能力

16.3
减少不必要的异常处理

16.4
不要利用异常处理机制处理其他功能

16.5
注意模板类型可能会破坏异常处理的一些约定

16.6
确保异常发生后资源还能被回收

16.7
特别当心析构时发生异常
c++
规定,异常处理期间,如果再发生异常,程序就终止,所以如果因为异常而调用到
了析构,那么再发生异常,程序一定会终止。

16.8
抛出的异常最好是一个对象

16.9
捕捉异常时,绝不要先基类后派生类

16.10
捕捉异常时,用引用

十七,代码格式

17.1
水平缩近每次用两个空格

17.2
不要在引用操作符前后加空格

17.3
不要在单目操作符和其操作对象间加空格

17.4
不要在 :: 前后加空格

17.5
, ; 之后加空格

17.6
在关键字和其后的 ( 之间加一个空格

17.7
文件中的主要部分用空行分开

17.8
函数间要用空行分开

17.9
组局部变量声明和代码之间用空行分开

17.10
用空行将代码按逻辑片断划分

17.11
可以考虑将 if 块和 else/else if 块用空行分开

17.12
函数返回语句要和其他语句用空行分开

17.13
每一行不超过 78 个字符

17.14
当一条语句超过 78 个字符时,按逻辑划分成不同行

17.15
花括号 { } 要单独占一行

17.16
花括号中没有或只有一条语句时也不省略花括号

17.17
不要在一行中放多于一条语句

17.18
语句 switch 中的每一个 case 各占一行

17.19
语句 switch 中的 case 按字母顺序排列

17.20
为所有 switch 语句提供 default 分支

17.21
若某个 case 不需要 break 一定要加注释声明

17.22
变量定义应集中放置,各占一行,并按字母顺序排列

17.23
定义指针和引用时 * & 紧跟类型

17.24
按编译器解析顺序放置变量声明的修饰符
const char* pName;
最好写成 char const* pName

17.25
关于函数声明和定义的格式,声明原则上放在一行。定义可以按以下顺序放在多行
模板描述, 修饰符 inline 和返回值类型, 函数名及其参数,函数体

17.26
函数名和左括号间不空格

17.27
声明函数时,给出参数的名字,除非没有用处

17.28
关于类内不同级别的元素排列顺序
类的公共部分 (public) 被禁止使用的函数部分, 注释横线,类的保护部分,类的私
有成员函数部分,类的私有成员,最后是类的友元声明

17.29
关于类成员函数的排列顺序
先是构造函数和析构函数,然后是操作符函数和其他函数,按功能归类。

17.30
类成员变量按字母顺序排列

17.31
关于静态成员的访问,用操作符 ::, 避免用 . ->

17.32
关于字符常量,用 'a' 的形式,避免使用 0x64

17.33
用带颜色的编辑器

十八,注释

18.1
用英语写全部的注释

18.2
确保注释完善你的代码,而不是重复你的代码

18.3
注释用词要精确,不能有二义性

18.4
注释中的术语要通用

18.5
注释要简单,清楚,切中要害

18.6
注释不能超出被注释代码所包含的内容

18.7
注释中避免引用容易变化的信息

18.8
确保所有注释及时更新

18.9
注释不具备约束使用者行为的能力

18.10
注释不要嵌套

18.11
不要用 /* */ 注释掉大块代码,应该用 #if 0

18.12
区分段落注释和单行注释

18.13
行末注释尽量对齐

18.14
单独的注释行和被注释语句缩近相同的空格

18.15
减少不必要的单独占一行的注释

18.16
对每个 #else #endif 给出行末注释

18.17
对每个引用的头文件给出行末注释

18.18
对每个空循环体给出确认性注释

18.19
关于函数注释,使用方法的注释,实现的注释

18.20
关于注释频率 大约 5 行一句注释

十九,文件和目录

19.1
使用统一而且通用的文件名后缀
.hpp     C++ 的头文件
_I.hpp   C++ inline 函数实现(头)文件。
.cpp      C++ 的源文件
.h         C 的头文件
.c         C 的源文件
19.2
关于文件名的选择,清晰

19.3
关于文件 / 目录名的字符集选择,尽量用 [A-Za-z0-9._-]

19.4
关于每个类的文件组成,类接口,实现, inline 函数

19.5
关于模板类的文件安排,尽量放在头文件中实现。

19.6
保持文件前言的简洁性

19.7
关于文件的段落安排,前言,防止重复引用设置, #include 部分, #define 部分,常
量声明,类型声明和定义,全局变量声明,文件级变量,文件级函数,函数实现,结束注


19.8
关于目录组织,按逻辑功能划分,公共头文件目录

二十,头文件

20.1
头文件多次引用的防范,头文件头加上
#ifndef MY_CLASS_HPP
#define MY_CLASS_HPP
// 真正的头文件内容
#endif 标记

20.2
确保公共头文件的自足性 , 包含自己所需的所有头文件

20.3
只引用需要的头文件

20.4 #include
引用时 " " < > 的用法
< >
只用在引用系统头文件,其他用 ""

20.5
引用头文件的顺序,系统头文件,自定义头文件

20.6
引用时不要用绝对路径

20.7
将函数库房在一个单独的目录下引用

20.8
不要在头文件中定义常量 / 变量

20.9
任何声明若被多个源文件引用则应在一个头文件中

20.10
在源文件中不要用关键字 extern extern 是声明,应放在头文件中

二十一,条件编译

21.1
最小化条件编译的使用范围

21.2
若使用 #if #ifdef 不要遗漏 #else

21.3
编译条件的含义要具体

21.4
对复杂的编译条件用括号使其清晰

21.5
条件编译和普通条件语句不要混合使用

21.6
若只测试某符号是否存在,不要给该符号赋值

二十二,编译

22.1
关注编译时的警告错误

22.2
把问题尽量暴露在编译时而不是运行时

22.3
减少文件的依赖程度

22.4
减少编译时间

22.5
透彻研究编译器

二十三,兼容性

23.1
遵守 ANSI C ISO C++ 国际标准

23.2
将不符合国际标准的代码与其他代码分开

23.3
不要假设字符类型是否是有符号类型

23.4
运算时显式转换有符号和无符号类型

23.5
注意双字节字符的兼容性

23.6
恰当使用位操作符

23.7
注意位域变量的兼容性问题

23.8
不要强制引用 / 指针指向尺寸不同的目标

23.9
确保类型转换不会丢失信息

23.10
不要假设类型的存储尺寸

23.11
如果一定要规定类型的存储尺寸,自行定义一套类型

23.12
不要假设对象的存储结构

23.13
注意运算溢出问题

23.14
不要假设表达式的运算顺序

23.15
不要假设函数参数的计算顺序

23.16
不要假设不同源文件中静态或全局变量的初始化顺序

23.17
不要依赖编译器基于实现,未明确或未定义的功能

23.18
注意数据文件的兼容性

23.19
注意引用公共库的兼容性

23.20
一定不要重新实现标准库函数

23.21
将所有 #include 的文件名视为大小写敏感

23.22
代码中用到的路径只用 / 而不要用 /

23.23
确保 main 函数总有返回一个整型

23.24
不要依赖 pragmas

二十四,性能

24.1
使用性能追踪分析工具

24.2
不要用移位代替乘除运算:编译会优化

24.3
如无必要,不要用非 int 的整型类型

24.4
不要使用关键字 register :编译会优化

24.5
避免在循环体内部定义对象

24.6
减少代价很高的对象拷贝

24.7
减少临时对象

24.8
注意大尺寸对象数组

24.9
返回对象的优化: return Complex_T(lhs.real+rhs.real,lhs.imag+rhs.imag);
而不要 Complex_T tmp;
tmp.real= lhs.real+rhs.real;tmp.imag= lhs.imag+rhs.imag;return tmp;

24.10
前缀 ++ -- 的效率更高

24.11
恰当使用递归

24.12
恰当使用 inline 函数

24.13
虚函数和虚继承效率会有一点损失
1
、增大对象的尺寸:虚函数和虚继承会隐式增加对象的大小,因为每个对象对每个虚函数要增加一个虚函数指针。
2 、增加程序尺寸、降低运行速度:每类虚函数都需要一张单独的虚函数表,表中每项都是一个函数指针,指向该虚函数在基类或派生类中的一个不同实现的函数入口。这样具体对象才能通过它保存的虚函数指针(实际上是虚函数表的一个索引值),找到虚函数表的某一项,然后根据该项中的指针跳到相应的函数入口执行。
3 、虚函数在绝大多数情况下不能是 inline 方式。
4 、对象不能跨进程共享
包含虚函数或虚继承的对象不能在进程之间共享,因为它们都隐式需要指针,而指针实际上是一个进程的内部地址,这个值在另一个进程空间中毫无意义。其实,所有指针和引用都不能在进程间共享,而不单是虚函数和虚继承,但虚函数和虚继承的指针是隐式加入的,容易被忽略。
24.14
如果合理,使用编译器生成的函数:编译器生成的函数会最原始,最快。

24.15
如果合理,构造直传类,既可以以字节 copy 的对象为直传类,有自定义 copy 函数的
为非直传类

24.16
关于缓存 类成员

24.17
关于标准库的性能

24.18
关于偷懒 : 延迟写等

24.19
关于勤快:预读等

24.20 80-20
原则
80-20 原则具体到性能方面是指:要用 20% 的小代价获得 80% 的大性能提高——所谓事半功倍。而对剩余 20% 的性能提高余地,不值得用 80% 的代价去换取,否则就是事倍功半。
二十五,其他

25.1
避免产生全局数据对象

25.2
确保任何定义只发生一次

25.3
指针操作中用 NULL 代替 0

25.4
不要把 NULL 用在指针以外的地方

25.5
不要弄巧成拙,不要使用花哨的方法。

25.6
将不要使用的代码删除

25.7
运行时不要改变进程的环境变量

25.8
该用 volatile 时一定要用
volatile
的含义是:其所修饰的变量可能会发生改变,而这种改变是编译器觉察不到的
。编译器就不会优化掉这个变量。

25.9
不要使用鲜为人知的替换符号

25.10
关于代码审查规范
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值