【001】视C++为一个语言联邦
C++体系包括C、Object-Oriented(面向对象编程)、Template(模板)、STL(标准模板库)
【002】尽量使用Enum、const、inline代替#define
- a.#define:在预编译阶段采用直接替代方式,不方便调试
- b.const:修饰变量、函数、指针、引用、对象
- c.inline:对于短小且调用频繁的函数可以声明为内联函数,提高效率
【003】切莫使用未初始化的变量或者对象
- a.为内置类型进行手动初始化,C++不保证初始化他们
- b.构造函数最好(或者一定)使用初始化列表进行初始化,而不是赋值进行初始化(提高效率)
- c.成员变量初始化顺序为声明顺序,与初始化列表顺序无关
【005】了解C++默默产生哪些函数
- a.当用户未声明构造函数、赋值、复制构造函数,在需要的情况下(也就是说并不一定会生成,只是在需要建立类对象)
- b.编译器会为类生成默认的构造函数、复制函数、赋值函数
- c.一旦用户声明上述函数,则编译器不再自动生成。
- d.编译器自动生成的赋值、复制构造函数属于浅复制,当存在指针、引用成员变量时,会导致严重问题。
【006】若不需要编译器自动生成上述函数,应明确拒绝
- a.方式-:用户自己声明赋值、复制构造函数,且权限设为private
- b.方式二:定义Class UnCopy{
- private:
- UnCopy operator=(UnCopy &rhs);
- }
【007】为多态基类定义Virtual析构函数
- a.当采用多态形式 Base *pBase = new Derived();
- b.若基类析构函数为virtual: delete pBase;//会调用子类的析构函数,不会产生内存泄漏
- 若基类析构函数为non-virtual:delete pBase;//不会调用子类的析构函数,只能删除子类中基类成员部分,会产生内存泄漏
【008】别让异常逃离析构函数
- a. try...catch捕获
【009】不要在构造函数中调用Virtual函数,会导致不可预期的错误
- a.在子类构造函数未完成之前,其类型是不完整类型,因此在构造函数中调用virtual函数只会调用基类中virtual函数
【010】令operator=返回引用,链式编程
【011】在operator=中处理“自我复制”
- class ClassName{
- public:
- //构造函数
- .....
- private:
- char* str;
- }
- 方式一:ClassName& operator=(ClassName& const T){
- if(*this==T){
- return *this;
- }else{
- delete str;
- str = T.str;
- return *this;
- //复制操作
- }
- }
- 方式二:方式一:ClassName& operator=(ClassName& const T){
- //保存旧值
- char *oldStr = T.str;
- delete str;
- str = oldStr;
- return *this;
- }
【012】复制对象时,切莫忘记复制基类成员变量(复制函数中调用基类的复制函数)
【013】以对象管理资源(在对象析构函数中释放资源),避免资源泄漏
- 例如:智能指针
- auto_ptr<ClassName> pInt(new ClassName());
- auto_ptr:底层实现调用delete ptr,不能应用于数组,不能复制
- shared_ptr:底层实现引用计数,支持复制,资源可以共享
【014】在资源管理类中小心copying行为
- 如上auto_ptr、shared_ptr行为
- 浅复制和深复制的区别,内部含有指针时,只是简单复制指针,造成两个对象耦合,
- 内部指针指向同一个对象,当一个对象消亡,调用析构函数释放内部指针指向的对象时,
- 另一个对象的内部指针成为野指针,造成不可预期的错误
【015】在资源管理类中提供对原始资源的访问方式
- 即是在资源管理类中提供接口,返回内部原始资源的指针或者引用
【016】成对使用new和delete。
- 注意delete和delete[]区别
【017】以独立语句将new资源指针放入资源管理类(智能指针),防止产生异常
- auto_ptr<ClassName> pInt(new ClassName());//有问题,当new ClassName()产生异常时,传入参数是null指针
- ClassName *pClassName = new ClassName();
- auto_ptr<ClassName> pInt(pClassName);//合理做法
【018】让接口能够被正确使用,不能被误用。
- 做好异常值判断,即用户传入任何值都能获得应得的行为
【019】设计Class犹如设计Type
- 考虑到各种异常输入
【020】用pass-by-reference取代pass-by-value
- 对于一个类对象:传值意味着一次复制构造+一次析构函数的时间成本
- 对于内置类型pass-by-value更合理
【021】必须返回对象时,别妄想返回reference
- 局部对象在函数返回时消亡,返回引用则是无效引用
- ClassName& function()
- {
- ClassName tt;
- //do something;
- return tt;
- }//错误
【022】将成员变量设为private,提供统一接口,增强封装性
- 定义setter和getter函数
- 越少的“人”看到内部成员,则改动时影响越小
【023】宁以非成员、非友元函数取代成员函数
【024】若所有参数皆需类型转换,则采用非成员函数
- 解决classValue*2和2*classValue问题
【025】考虑写出一个不抛出异常的Swap函数
【026】尽可能延后对象定义的时间(使用时再定义)
【027】尽量少做转型操作
- const_cast:去除变量const属性
- static_cast:隐式转型
- dynamic_cast:向下转型
- reinterpret_cast:任意转型,破坏性最大
【028】避免返回Handle指向对象内部成分
【029】为异常安全而努力是值得的
【030】透彻了解inline里里外外
- 将大多数inline限制在小型被频繁调用的函数身上,这可使日后的调试更容易,也可使代码膨胀问题最小化,使程序的速度提升机会更大化
- 不要只因为function template出现在头文件中,就将他们声明为inline
【031】将文件间的依存关系降到最低
【032】确定你的public继承塑造出is-a关系
【033】避免遮掩继承而来的名称
- derived class内的名称会遮掩base class内的名称,在public继承下从来没有人希望如此
- 为了让被遮掩的名称重见天日,可用using声明式或转交函数
【034】区分接口继承和实现继承
- 声明一个纯虚函数目的是让子类只继承函数接口
- 声明非纯虚函数目的是让子类继承该函数的接口和缺省实现
【035】考虑virtual函数以外的其他选择
【036】绝不重新定义继承而来的non-virtual函数
【037】绝不重新定义继承而来的缺省参数值
【038】通过组合构造出has-a关系
【039】明智而审慎使用private继承
【040】明智而审慎的使用多重继承
- 多重继承带来对象布局不同,比单一继承复杂,他可能导致新的歧义性,以及对virtual继承的需要
- virtual继承会增加大小、速度、初始化复杂度等等成本,如果virtual base class不带任何数据将是最具实用价值的情况
【041】了解隐式接口和编译器多态
- classes和templates都支持接口和多态
- class而言接口是显式的,以函数签名为中心,多态则是通过virtual函数发生于运行期
- template参数而言,接口是隐式的,奠基于有效表达式,多态则是通过template具现化和函数重载解析发生于编译器
【042】了解typename的双重意义
- template<class T>和template<typename T>意义相同
- 请使用关键字typename表示嵌套丛书类型名称;但不得在base classlist内 以它作为base class修饰符