条款1:指针与引用的区别
两者之间的区别是:在任何状况下都不能用指向空值的引用,而指针则能够;指针能够被从新赋值以指向另外一个不一样的对象,可是引用则老是指向在初始化时被指定的对象,之后不能改变
在如下状况下使用指针:一是存在不指向任何对象的可能性;二是须要可以在不一样的时刻指向不一样的对象
在如下状况使用引用:老是指向一个对象且一旦指向一个对象以后就不会改变指向;重载某个操做符时,使用指针会形成语义误解 ios
条款2:尽可能使用C++风格的类型转换
static_cast:功能上基本上与C风格的类型转换同样强大,含义也同样可是不能把struct转换成int类型或者把double类型转换成指针类型另外,它不能从表达式中去除const属性
const_cast:用于类型转换掉表达式的const或volatileness属性可是不能用它来完成修改这两个属性以外的事情
dynamic_cast:用于安全地沿着类的继承关系向下类型转换失败的转换将返回空指针或者抛出异常
reinterpret_cast:这个操做符被用于的类型转换的转换结果时实现时定义所以,使用它的代码很难移植最普通的用途就是在函数指针之间进行转换 程序员
条款3:不要使用多态性数组
多态和指针算法不能混合在一块儿使用,因此数组和多态也不能用在一块儿
数组中各元素的内存地址是数组的起始地址加上以前各个元素的大小获得的,若是各元素大小不一,那么编译器将不能正确地定位元素,从而产生错误 算法
条款4:避免无用的缺省构造函数
没有缺省构造函数形成的问题:一般不可能创建对象数组,对于使用非堆数组,能够在定义时提供必要的参数另外一种方法是使用指针数组,可是必须删除数组里的每一个指针指向的对象,并且还增长了内存分配量
提供无心义的缺省构造函数会影响类的工做效率,成员函数必须测试全部的部分是否都被正确的初始化 数据库
条款5:谨慎定义类型转换函数
缺省的隐式转换将带来出乎意料的结果,所以应该尽可能消除,使用显式转换函数经过不声明运算符的方法,能够克服隐式类型转换运算符的缺点,经过使用explicit关键字和代理类的方法能够消除单参数构造函数形成的隐式转换 编程
条款6:自增和自减操做符前缀形式与后缀形式的区别
后缀式有一个int类型参数,当函数被调用时,编译器传递一个0做为int参数的值传递给该函数能够在定义时省略掉不想使用的参数名称,以免警告信息
后缀式返回const对象,缘由是 :使该类的行为和int一致,而int不容许连续两次自增后缀运算;连续两次运算实际只增一次,和直觉不符
前缀比后缀效率更高,由于后缀要返回对象,而前缀只返回引用另外,能够用前缀来实现后缀,以方便维护 数组
条款7:不要重载&&,||,或者,
对 于以上操做符来讲,计算的顺序是从左到右,返回最右边表达式的值若是重载的话,不能保证其计算顺序和基本类型想同操做符重载的目的是使程序更容易阅 读,书写和理解,而不是来迷惑其余人若是没有一个好理由重载操做符,就不要重载而对于&&,||和,,很难找到一个好理由 缓存
条款8:理解各类不一样含义的new和delete
new操做符完成的功能分两部分:第一部分是分配足够的内存以便容纳所需类型的对象;第二部分是它调用构造函数初始化内存中的对象new操做符老是作这两件事,咱们不能以任何方式改变它的行为
咱们能改变的是如何为对象分配内存new操做符经过调用operator new来完成必需的内存分配,能够重写或重载这个函数来改变它的行为能够显式调用operator来分配原始内存
若是已经分配了内存,须要以此内存来构造对象,可使用placement new,其调用形式为new(void* buffer)class(int size)
对于delete来讲,应该和new保持一致,怎样分配内存,就应该采用相应的办法释放内存
operator new[]与operator delete[]和new与delete相相似 安全
条款9:使用析构函数防止资源泄漏
使用指针时,若是在delete指针以前产生异常,将会致使不能删除指针,从而产生资源泄漏
解决办法:使用对象封装资源,如使用auto_ptr,使得资源可以自动被释放 数据结构
条款10:在构造函数中防止资源泄漏
类中存在指针时,在构造函数中须要考虑出现异常的状况:异常将致使之前初始化的其它指针成员不能删除,从而产生资源泄漏解决办法是在构造函数中考虑异常处理,产生异常时释放已分配的资源最好的方法是使用对象封装资源 函数
条款11:禁止异常信息传递到析构函数外
禁止异常传递到析构函数外的两个缘由:第一可以在异常传递的堆栈展转开解的过程当中,防止terminate被调用;第二它能帮助确保析构函数总能完成咱们但愿它作的全部事情
解决方法是在析构函数中使用try-catch块屏蔽全部异常
条款12:理解抛出一个异常与传递一个参数或调用一个虚函数间的差别
有 三个主要区别:第一,异常对象在传递时总被进行拷贝当经过传值方式捕获时,异常对象被拷贝了两次对象做为参数传递给函数时不须要被拷贝;第二,对象做 为异常被抛出与做为参数传递给函数相比,前者类型转换比后者少(前者只有两种转换形式:继承类与基类的转换,类型化指针到无类型指针的转换);最后一点, catch子句进行异常类型匹配的顺序是它们在源代码中出现的顺序,第一个类型匹配成功的擦他处将被用来执行当一个对象调用一个虚函数时,被选择的函数 位于与对象类型匹配最佳的类里,急事该类不是在源代码的最前头
条款13:经过引用捕获异常
有三个选择能够捕获异常:第一指 针,创建在堆中的对象必需删除,而对于不是创建在堆中的对象,删除它会形成不可预测的后果,所以将面临一个难题:对象创建在堆中仍是不在堆中;第二传 值,异常对象被抛出时系统将对异常对象拷贝两次,并且它会产生对象切割,即派生类的异常对象被做为基类异常对象捕获时,它的派生类行为就被切割调了 这样产生的对象其实是基类对象;第三引用,完美解决以上问题
条款14:审慎使用异常规格
避免调用unexpected函数 的办法:第一避免在带有类型参数的模板内使用异常规格由于咱们没有办法知道某种模板类型参数抛出什么样的异常,因此不可能为一个模板提供一个有意义的 异常规格;第二若是在一个函数内调用其它没有异常规格的函数时应该去除这个函数的异常规格;第三处理系统自己抛出的异常能够将全部的 unexpected异常都被替换为自定义的异常对象,或者替换unexpected函数,使其从新抛出当前异常,这样异常将被替换为 bad_exception,从而代替原来的异常继续传递
很容易写出违反异常规格的代码,因此应该审慎使用异常规格
条款15:了解异常处理的系统开销
三 个方面:第一须要空间创建数据结构来跟踪对象是否被彻底构造,还须要系统时间保持这些数据结构不断更新;第二try块不管什么时候使用它,都得为此付出 代价编译器为异常规格生成的代码与它们为try块生成的代码同样多,因此一个异常规格通常花掉与try块同样多的系统开销第三抛出异常的开销由于 异常不多见,因此这样的事件不会对整个程序的性能形成太大的影响
条款16:牢记8020准则
8020准则说的是大约20%的代码使用了80%的程序资源,即软件总体的性能取决于代码组成中的一小部分使用profiler来肯定程序中的那20%,关注那些局部效率可以被极大提升的地方
条款17:考虑使用懒惰计算法
懒惰计算法的含义是拖延计算的时间,等到须要时才进行计算其做用为:能避免不须要的对象拷贝,经过使用operator[]区分出读写操做,避免不须要的数据库读取操做,避免不须要的数字操做可是,若是计算都是重要的,懒惰计算法可能会减慢速度并增长内存的使用
条款18:分期摊还指望的计算
核心是使用过分热情算法,有两种方法:缓存那些已经被计算出来而之后还有可能须要的值;预提取,作比当前须要作的更多事情
当必须支持某些操做而不总须要其结果时,可使用懒惰计算法提升程序运行效率;当必须支持某些操做而其结果几乎老是被须要或不止一次地须要时,可使用过分热情算法提升程序运行效率
条款19:理解临时对象的来源
临时对象产生的两种条件:为了是函数成功调用而进行隐式类型转换和函数返回对象时
临时对象是有开销的,所以要尽量去消除它们,然而更重要的是训练本身寻找可能创建临时对象的地方在任什么时候候只要见到常量引用参数,就存在创建临时对象而绑定在参数上的可能性在任什么时候候只要见到函数返回对象,就会有一个临时对象被创建(之后被释放)
条款20:协助完成返回值优化
应当返回一个对象时不要试图返回一个指针或引用
C+ +规则容许编译器优化不出现的临时对象,全部最佳的办法莫过于:retrun Ratinal(lhs.numerator()*rhs.numerator(), lhs.denominator()*rhs.denominator())这种优化是经过使用函数的retuan location(或者用在一个函数调用位置的对象来替代),来消除局部临时对象,这种优化还有一个名字:返回值优化
条款21:经过重载避免隐式类型转换
隐式类型转换将产生临时对象,从而带来额外的系统开销
解决办法是使用重载,以免隐式类型转换要注意的一点是在C++中有一条规则是每个重载的operator必须带有一个用户定义类型的参数(这条规定是有道理的,若是没有的话,程序员将能改变预约义的操做,这样作确定吧程序引入混乱的境地)
另外,牢记8020规则,没有必要实现大量的重载函数,除非有理由确信程序使用重载函数后总体效率会有显著提升
条款22:考虑用运算符的赋值形式取代其单独形式
运算符的赋值形式不须要产生临时对象,所以应该尽可能使用对运算符的单独形式的最佳实现方法是return Rational(lhs) += rhs;这种方法将返回值优化和运算符的赋值形式结合起来,即高效,又方便
条款23:考虑变动程序库
程序库必须在效率和功能等各个方面有各自的权衡,所以在具体实现时应该考虑利用程序库的优势例如程序存在I/O瓶颈,就能够考虑用stdio替代iostream
条款24:理解虚拟函数多继承虚基类和RTTI所需的代价
虚函数所需的代价:必须为每一个包含虚函数的类的virtual table留出空间;每一个包含虚函数的类的对象里,必须为额外的指针付出代价;实际上放弃了使用内联函数
多继承时,在单个对象里有多个vptr(一个基类对应一个)它和虚基类同样,会增长对象体积的大小
RTTI能让咱们在运行时找到对象和类的有关信息,因此确定有某个地方存储了这些信息,让咱们查询这些信息被存储在类型为type_info的对象里,能够经过typeid操做符访问到一个类的typeid对象一般,RTTI被设计为在类的vbtl上实现
条款25:将构造函数和非成员函数虚拟化
构 造函数的虚拟化看似无心义,可是在实际当中有必定的用处例如,在类中构建一个虚拟函数,其功能仅仅是实现构造函数,就能够对外界提供一组派生类的公共构 造接口虚拟拷贝构造函数也是能够实现的,可是要利用到最近才被采纳的较宽松的虚拟函数返回值类型规则被派生类重定义的虚拟函数不用必须与基类的虚拟函 数具备同样的返回类型
具备虚拟行为的非成员函数很简单首先编写一个虚拟函数完成工做,而后再写衣一个非虚拟函数,它什么也不作只是调用这个函数,可使用内联来避免函数调用的开销
条款26:限制某个类所能产生的对象数量
只 有一个对象:使用单一模式,将类的构造函数声明为private,再声明一个静态函数,该函数中有一个类的静态对象不将该静态对象放在类中缘由是放在函 数中时,执行函数时才创建对象,而且对象初始化时间肯定的,即第一次执行该函数时另外,该函数不能声明为内联,若是内联可能形成程序的静态对象拷贝超过 一个
限制对象个数:创建一个基类,构造函数中计数加一,若超过最大值则抛出异常;析构函数中计数减一
编程点滴:
将模板类的定义和实现放在一个文件中,不然将形成引用未定义错误(血的教训);
静态数据成员须要先声明再初始化;
用常量值做初始化的有序类型的const静态数据成员是一个常量表达式(能够做为数组定义的维数);
构造函数中抛出异常,将致使静态数组成员从新初始化
条款27:要求或禁止在堆中产生对象
在堆中的对象不必定是用new分配的对象,例如成员对象,虽然不是用new分配的可是仍然在堆中
要 求在堆中创建对象能够将析构函数声明未private,再创建一个虚拟析构函数进行对象析构此时若是创建非堆对象将致使析构函数不能经过编译固然也可 以将构造函数声明为private,可是这样将致使必须声明n个构造函数(缺省,拷贝等等)为了解决继承问题,能够将其声明为protected,解决 包容问题则只能将其声明为指针
没有办法不能判断一个对象是否在堆中,可是能够判断一个对象是否能够安全用delete删除,只需在operator new中将其指针加入一个列表,而后根据此列表进行判断
把一个指针dynamic_cast成void*类型(或const void*或volatile void*等),生成的指针将指向原指针指向对象内存的开始处可是dynamic_cast只能用于指向至少具备一个虚拟函数的对象的指针上
禁止创建堆对象能够简单的将operator new声明为private,可是仍然不能判断其是否在堆中
条款28:灵巧(smart)指针
灵巧指针的用处是能够对操做进行封装,同一用户接口
灵巧指针从模板生成,由于要与内建指针相似,必须是强类型的;模板参数肯定指向对象的类型
灵巧指针的拷贝和赋值,采起的方案是当auto_ptr被拷贝和赋值时,对象全部权随之被传递此时,经过传值方式传递灵巧指针对象将致使不肯定的后果,应该使用引用
记住当返回类型是基类而返回对象实际上派生类对象时,不能传递对象,应该传递引用或指针,不然将产生对象切割
测试灵巧指针是否为NULL有两种方案:一种是使用类型转换,将其转换为void*,可是这样将致使类型不安全,由于不一样类型的灵巧指针之间将可以互相比较;另外一种是重载operator!,这种方案只能使用!ptr这种方式检测
最好不要提供转换到内建指针的隐式类型转换操做符,直接提供内建指针将破坏灵巧指针的灵巧特性
灵巧指针的继承类到基类的类型转换的一个最佳解决方案是使用模板成员函数,这将使得内建指针全部能够转换的类型也能够在灵巧指针中进行转换可是对于间接继承的状况,必须用dynamic_cast指定其要转换的类型是直接基类仍是间接基类
为了实现const灵巧指针,能够新建一个类,该类从非const灵巧指针继承这样的化,const灵巧指针能作的,非const灵巧指针也能作,从而与标准形式相同
条款29:引用计数
使用引用计数后,对象本身拥有本身,当没有人再使用它时,它本身自动销毁本身所以,引用计数是个简单的垃圾回收体系
在基类中调用delete this将致使派生类的对象被销毁
写时拷贝:与其它对象共享一个值直到写操做时才拥有本身的拷贝它是Lazy原则的特例
精彩的类层次结构:
RCObject类提供计数操做;StringValue包含指向数据的指针并继承RCObject的计数操做;RCPtr是一个灵巧指针,封装了本属于String的一些计数操做
条款30:代理类
能够用两个类来实现二维数组:Array1D是一个一维数组,而Array2D则是一个Array1D的一维数组Array1D的实例扮演的是一个在概念上不存在的一维数组,它是一个代理类
代 理类最神奇的功能是区分经过operator[]进行的是读操做仍是写操做,它的思想是对于operator[]操做,返回的不是真正的对象,而是一个 proxy类,这个代理类记录了对象的信息,将它做为赋值操做的目标时,proxy类扮演的是左值,用其它方式使用它,proxy类扮演的是右值用赋值 操做符来实现左值操做,用隐式类型转换来实现右值操做
用proxy类区分operator[]做左值仍是右值的局限性:要实现proxy类和原类型的无缝替代,必须申明原类型的一整套操做符;另外,使用proxy类还有隐式类型转换的全部缺点
编程点滴:不能将临时对象绑定为非const的引用的行参
条款31:让函数根据一个以上的对象来决定怎么虚拟
有 三种方式:用虚函数加RTTI,在派生类的重载虚函数中使用if-else对传进的不一样类型参数执行不一样的操做,这样作几乎放弃了封装,每增长一个新的类 型时,必须更新每个基于RTTI的if-else链以处理这个新的类型,所以程序本质上是没有可维护性的;只使用虚函数,经过几回单独的虚函数调用,第 一次决定第一个对象的动态类型,第二次决定第二个对象动态类型,如此这般然而,这种方法的缺陷仍然是:每一个类必须知道它的全部同胞类,增长新类时,全部 代码必须更新;模拟虚函数表,在类外创建一张模拟虚函数表,该表是类型和函数指针的映射,加入新类型是不须改动其它类代码,只需在类外增长一个处理函数即 可
条款32:在将来时态开发程序
将来时态的考虑只是简单地增长了一些额外约束:
提供完备的类,即便某些部分如今尚未被使用
将接口设计得便于常见操做并防止常见错误使得类容易正确使用而不易用错
若是没有限制不能通用化代码,那么通用化它
条款33:将非尾端类设计为抽象类
若是有一个实体类公有继承自另外一个实体类,应该将两个类的继承层次改成三个类的继承层次,经过创造一个新的抽象类并将其它两个实体类都从它继承所以,设计类层次的通常规则是:非尾端类应该是抽象类在处理外来的类库,可能不得不违反这个规则
编程点滴:抽象类的派生类不能是抽象类;实现纯虚函数通常不常见,但对纯虚析构函数,它必须实现
条款34:如何在同一程序中混合使用C++和C
混合编程的指导原则:
确保C++和C编译器产生兼容的obj文件
将在两种语言下都使用的函数申明为extern C
只要可能,用C++写main()
总用delete释放new分配的内存;总用free释放malloc分配的内存
将在两种语言间传递的东西限制在用C编译的数据结构的范围内;这些结构的C++版本能够包含非虚成员函数
条款35:让本身习惯使用标准C++语言
STL 基于三个基本概念:包容器(container)选择子(iterator)和算法(algorithms)包容器是被包容的对象的封装;选择子是类 指针的对象,让你能如同使用指针操做内建类型的数组同样操做STL的包容器;算法是对包容器进行处理的函数,并使用选择子来实现
条款1:尽可能用const和inline而不用#define
1.为方便调试,最好使用常量
注意:常量定义通常放在头文件中,可将指针和指针所指的类型都定义成const,如const char * const authorName = Scott Meyers;
类中常量一般定义为静态成员, 并且须要先声明后定义能够在声明时或定义时赋值,也可以使用借用enum的方法如enum{Num = 5};
2.#define语句形成的问题
如#define max(a, b) ((a) > (b) ? (a) : (b))
在下面状况下:
Int a= 5, b = 0;
max(++ a, b);
max(++ a, b + 10);
max内部发生些什么取决于它比较的是什么值解决方法是使用inline函数,可使用template来产生一个函数集
条款2:尽可能用而不用
用>> 和<<使得编译器本身能够根据不一样的变量类型选择操做符的不一样形式,而采起的语法形式相同
条款3:尽可能用new和delete而不用malloc和free
使用malloc和free的时候不会本身调用构造函数和析构函数,所以若是对象本身分配了内存的话,那么这些内存会所有丢失另外,将new和malloc混用会致使不可预测的后果
条款4:尽可能使用C++风格的注释
C++的注释能够在注释里还有注释,因此注释掉一个代码块不用删除这段代码的注释C则不行
条款5:对应的new和delete要采用相同的形式
调 用new时用了[],调用delete时也要用 []若是调用new时没有用[],那调用delete时也不要用[]对于typedef来讲,用new建立了一个typedef定义的类型的对象后, delete时必须根据typedef定义的类型来删除所以,为了不混乱,最好杜绝数组类型用typedef
条款6:析构函数里对指针成员调用delete
删除空指针是安全的,所以在析构函数里能够简单的delete类的指针成员,而不用担忧他们是否被new过
条款7:预先准备好内存不足的状况
1.用try-cache来捕获抛出的异常
2. 当内存分配请求不能知足时,调用预先指定的一个出错处理函数这个方法基于一个常规,即当operator new不能知足请求时,会在抛出异常以前调用客户指定的一个出错处理函数通常称之为new-handler函数还能够建立一个混合风格的基类这种基 类容许子类继承它某一特定的功能(即函数)
条款8:写operator new和operator delete时要遵循常规
内存分配程序支持new-handler函数并正确地处理了零内存请求,而且内存释放程序处理了空指针此外还必需要有正确的返回值
条款9:避免隐藏标准形式的new
在 类里定义了一个称为operator new的函数后,会不经意地阻止了对标准new的访问(到底如何隐藏的???)一个办法是在类里写一个支持标准new调用方式的operator new,它和标准new作一样的事,这能够用一个高效的内联函数来封装实现另外一种方法是为每个增长到operator new的参数提供缺省值
条款10:若是写了operator new就要同时写operator delete
operator new和operator delete须要同时工做,若是写了operator new,就必定要写operator delete对于为大量的小对象分配内存的状况,能够考虑使用内存池,以牺牲灵活性来换取高效率
条款11:为须要动态分配内存的类声明一个拷贝构造函数和一个赋值操做符
若是没有自定已拷贝构造函数和赋值操做符,C++会生成并调用缺省的拷贝构造函数和赋值操做符,它们对对象里的指针进行逐位拷贝,这会致使内存泄漏和指针重复删除所以,只要类里有指针时,就要写本身版本的拷贝构造函数和赋值运算符函数
条款12:尽可能使用初始化而不要在构造函数里赋值
尽可能使用成员初始化列表,一方面对于成员来讲只需承担一次拷贝构造函数的代价,而非构造函数里赋值时的一次(缺省)构造函数和一次赋值函数的代价;另外一方面const和引用成员只能被初始化而不能被赋值
条款13:初始化列表中的成员列出的顺序和它们在类中声明的顺序相同
类的成员是按照它们在类里被声明的顺序进行初始化的,和它们在成员初始化列表中列出的顺序没有关系
条款14:肯定基类有虚析构函数
经过基类的指针去删除派生类的对象,而基类有没有虚析构函数时,结果将是不可肯定的所以必须将基类的析构函数声明为virtual可是,无端的声明虚析构函数和永远不去声明同样是错误的,声明虚函数将影响效率
条款15:让operator=返回*this的引用
当 定义本身的赋值运算符时,必须返回赋值运算符左边参数的引用,*this若是不这样作,就会致使不能连续赋值,或致使调用时的隐式类型转换不能进行(隐 式类型转换时要用到临时对象,而临时对象是const的),或两种状况同时发生对于没有声明相应参数为const的函数来讲,传递一个const对象是 非法的
条款16:在operator=中对全部数据成员赋值
当类里增长新的数据成员时,要记住更新赋值运算符函数对基类的私有成员赋值时,能够显示调用基类的operator=函数派生类的拷贝构造函数中必须调用基类的拷贝构造函数而不是缺省构造函数,不然基类的数据成员将不能初始化
条款17:在operator=中检查给本身赋值的状况
显 示的本身给本身赋值不常见,可是程序中可能存在隐式的自我赋值:一个对象的两个不一样名字(引用)互相赋值首先,若是检查到本身给本身赋值就当即返回,可 以节省大量的工做;其次,一个赋值运算符必须首先释放掉一个对象的资源,而后根据新值分配新的资源,在本身给本身的状况下,释放旧的资源将是灾难性的
条款18:争取使类的接口完整而且最小
必要的函数是拷贝构造函数,赋值运算符函数,而后在此基础上选择必要的方便的函数功能进行添加
条款19:分清成员函数,非成员函数和友元函数
虚函数必须是成员函数若是f必须是虚函数,就让它称为类c的成员函数
ioerator>>和operator<<决不能是成员函数若是f是operator>>或operator<<,让f称为非成员函数若是f还须要 访问c的非公有成员,让f称为c的友元
其它状况下都声明为成员函数若是以上状况都不是,让f称为c的成员函数
Result = onehalf * 2;能经过编译的缘由:调用重载*操做符的成员函数,对参数2进行隐式类型转换
Result = 2 * onehalf;不能经过编译的缘由:不能对成员函数所在对象(即成员函数中this指针指向的对象)进行转换
条款20:避免public接口出现数据成员
访问一致性,public接口里都是函数
精确的访问控制,能够精确设置数据成员的读写权限
功能分离,能够用一段计算来取代一个数据成员举例:计算汽车行驶的平均速度
条款21:尽可能使用const
若是const出如今*号左边,指针指向的数据为常量;若是const出如今*号右边,则指针自己为常量;若是const在两边都出现,两者都是常量
将operator的返回结果声明为const,以防止对返回结果赋值,这样不合常规
c+ +中的const:成员函数不修改对象中的任何数据成员时,即不修改对象中的任何一个比特时,这个成员函数才是const的形成的问题是能够修改指针指 向的值,并且不能修改对象中的一些必要修改的值解决方案是将必要修改的成员运用mutable关键字另外一种方法是使用const_cast初始化一个 局部变量指针,使之指向this所指的同一个对象来间接实现还有一种有用又安全的方法:在知道参数不会在函数内部被修改的状况下,将一个const对象 传递到一个取非const参数的函数中
条款22:尽可能用传引用而不用传值
传值将致使昂贵的对象开销,而传引用则很是高效
传引用避免了切割问题,即当一个派生类的对象做为基类对象被传递是,派生类的对象的做为派生类所具备的全部行为特性会被切割掉,从而变成了一个简单的基类对象
条款23:必须返回一个对象时不要试图返回一个引用缩写
典型状况:操做符重载
常见的错误:
返回引用,返回的是局部对象的引用
堆中构造,使用new分配内存,可是无人负责delete的调用,从而形成内存泄漏
返回静态对象,致使调用同一函数比较时老是相等
正确的方法是直接在堆栈中建立对象并返回
条款24:在函数重载和设定参数缺省值间慎重选择
若是能够选择一个合适的缺省参数,不然就使用函数重载
有一些状况必须使用重载:函数的结果取决于传入参数的个数;须要完成一项特殊的任务
条款25:避免对指针和数字类型重载
对于f(0):0表明int仍是null编译器认为是int,这和人们的想象不同解决办法是使用成员模板,构造一个能够产生null指针对象的类最重要的是,只要有可能,就要避免对一个数字和一个指针类型重载
条款26:小心潜在二义性
情形1:能够经过构造函数和转换运算符产生另外一个类的对象,这时编译器将拒绝对其中的一种方法进行选择
情形2:f(int);f(char);对于f(double)时产生二义
情形3:多继承时,两个基类有同名的成员此时必须指定基类方可调用,而不考虑访问控制权限和返回值
条款27:若是不想使用隐式生成的函数就要显式地禁止它
方法是声明该函数,并使之为private显式地声明一个成员函数,就防止了编译器去自动生成它的版本;使函数为private,就防止了别人去调用它为了防止成员函数和友元函数的调用,只声明而不定义这个函数
条款28:划分全局名字空间
使用名字空间,以防止不一样库的名字冲突对于不支持名字空间的编译器,可使用struct来模拟名字空间,可是此时运算符只能经过函数调用来使用
条款29:避免返回内部数据的句柄
对于const成员函数来讲,返回句柄可能会破坏数据抽象若是返回的不是指向const数据的句柄,数据可能被修改对非const成员函数来讲,返回句柄会带来麻烦,特别是涉及到临时对象时句柄就象指针同样,能够是悬浮的
条款30:避免这样的成员函数:其返回值是指向成员的非const指针或引用,但成员的访问级比这个函数要低
若是得到了私有或保护成员(包括成员函数)的地址(指针或引用),那么就能够象对待公有成员同样进行访问若是不得不返回其引用或指针,能够经过返回指向const对象的指针或引用来达到一箭双鵰的效果
条款31:千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针的引用
如 果返回局部对象的引用,那个局部对象其实已经在函数调用者使用它以前就被销毁了而返回废弃指针的问题是必需要有人负责调用delete,并且对于 product=one*two*three*four;的状况,将产生内存泄漏所以,写一个返回废弃指针的函数无异于坐等内存泄漏的来临
条款32:尽量地推迟变量的定义
不 仅要强变量的定义推迟到必须使用它的时候,还要尽可能推迟到能够为它提供一个初始化参数位置这样作,不只能够避免对没必要要的对象进行构造和析构,还能够避 免无心义的对缺省构造函数的调用并且,在对变量初始化的场合下,变量自己的用途不言自明,在这里定义变量有益于代表变量的含义
条款33:明智使用内联
内联函数的本质是将每一个函数调用以它的代码体来替换
大多数编译器拒绝复杂的内联函数(例如,包含循环和递归的函数);还有,即便是最简单的虚函数调用,编译器的内联处理程序对它也心有余而力不足
若编译器不进行内联,则将内联函数看成通常的外联函数来处理这称为被外联的内联
找出重要的函数,将它内联同时要注意代码膨胀带来的问题,并监视编译器的警告信息,看看是否有内联函数没有被编译器内联
条款34:将文件间的编译依赖性降至最低
若是可使用对象的引用和指针,就要避免使用对象自己定义某个类型的引用和指针只会涉及到这个类型的声明,定义此类型的对象则须要类型定义的参与
尽量使用类的声明,而不使用类的定义由于在声明一个函数时,若是用到某个类,是绝对不须要这个类的定义的,即便函数是经过传值来传递和返回这个类
不要在头文件中再包含其它头文件,除非缺乏了它们就不能编译相反,要一个一个地声明所须要的类,让使用这个头文件的用户本身去包含其它的头文件
最后一点,句柄类和协议类都不大会使用类联函数使用任何内联函数时都要访问实现细节,而设计句柄类和协议类的初衷正是为了不这种状况
条款35:使公有继承体现是一个的含义
若是类D从类B公有继承时,类型D的每个对象也是类型B的一个对象,但反之不成立任何可使用类型B的对象的地方,类型D的对象也可使用
特别注意通常理解中的是一个,好比企鹅是鸟,并不严密若是涉及到飞这个动做,两者之间不适合使用公有继承
条款36:区分接口继承课实现继承
定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口也能够为纯虚函数提供一种缺省实现
声明简单虚函数的目的在于,使派生类继承函数的接口和缺省实现
声明非虚函数的目的在于,使派生类继承函数的接口和强制性实现
条款37:毫不要从新定义继承而来的非虚函数
若是从新定义继承而来的非虚函数,将致使对象对函数的调用结果由指向其的指针决定,而不是由对象自己的类型来决定另外,也是类的设计产生矛盾,由于公有继承的含义是是一个,改变继承而来的方法显然是不合理的
条款38:毫不要从新定义继承而来的缺省参数值
虚函数动态绑定,而缺省参数是静态绑定所以从新定义继承而来的缺省参数值可能形成调用的是定义在派生类,但使用了基类中缺省参数值的虚函数
条款39:避免向下转换继承层次
采用向下转换时,将不利于对代码进行维护,能够采用虚函数的方法来解决
不得不进行向下转换时,采用安全的向下转换:dynamic_cast运算符dynamic_cast运算符先尝试转换,若转换成功就返回新类型的合法指针,若失败则返回空指针
条款40:经过分层来体现有一个或用来实现
公有继承的含义是是一个对应地,分层的含义是有一个或用来实现例如,要实现set类,由于list中能够包含重复元素,所以set不是一个listset能够用list来实现,即在set中包含一个list
条款41:区分继承和模板
当对象的类型不影响类中函数的行为时,就要使用模板来生成这样一组类
当对象的类型影响类中函数的行为时,就要使用继承来获得这样一组类
条款42:明智地使用私有继承
关于私有继承的两个规则:和公有继承相反,若是两个类之间的继承关系为私有,编译器通常 不会将派生类对象转换为基类对象;从私有基类继承而来的成员都称为了派生类的私有成员,即便它们在基类中是保护或公有成员
私有继承意味这用来实现,可是应尽量使用分层,必须时才使用私有继承
条款43:明智地使用多继承
多 继承后果:二义性,若是一个派生类从多个基类继承了一个成员名,全部对这个名字的访问都是二义的,你必须明确说出你所指的是哪一个成员这可能致使虚函数的 失效,而且不能对多继承而来的几个相同名称虚函数同时进行重定义钻石型继承,此时向虚基类传递构造函数参数时要在继承结构中最底层派生类的成员初始化列 表中指定同时还要仔细想一想虚函数的优先度
然而在适当时候仍是可使用多继承,例如将接口的公有继承和实现的私有继承结合起来的状况
以增长中间类的代价来消除多继承有时侯是值得的通常应该避免使用多继承以减小继承结构的复杂性
条款44:说你想说的,理解你所说的
理解不一样的面向对象构件在C++中的含义:
· 共同的基类意味着共同的特性
· 公有继承意味着 是一个
· 私有继承意味着 用来实现
· 分层意味着 有一个 或 用来实现
下面的对应关系只适用于公有继承的状况:
· 纯虚函数意味着仅仅继承函数的接口
· 简单虚函数意味着继承函数的接口加上一个缺省实现
· 非虚函数意味着继承函数的接口加上一个强制实现
条款45:弄清C++在幕后为你所写所调用的函数
如 果没有声明下列函数,编译器会声明它本身的版本:一个拷贝构造函数,一个赋值运算符,一个析构函数,一对取址运算符和一个缺省构造函数对于拷贝构造函数 和赋值运算符,官方的规则是:缺省拷贝构造函数(赋值运算符)对类的非静态数据成员进行以成员为单位的逐一拷贝构造(赋值)
特别要注意因为编译器自动生成的函数形成的编译错误
条款46:宁肯编译和连接是出错,也不要运行时出错
一般,对设计作一点小小的改动,就能够在编译期间消除可能产生的运行时错误这经常涉及到在程序中增长新的数据类型例如对于须要类型检查的Month,能够将其设为一个Month类:构造函数私有,产生对象使用静态成员函数,每一个Month对象为const
条款47:确保非局部静态对象在使用前被初始化
若是在某个被编译单元中,一个对象的初始化要依赖于另外一个被编译单元中的另外一个对象的值,而且这第二个对象自己也须要初始化,就有可能形成混乱
虽 然关于 非局部 静态对象何时被初始化,C++几乎没有作过说明;但对于函数中的静态对象(即,局部 静态对象)何时被初始化,C++却明确指出:它们在函数调用过程当中初次碰到对象的定义时被初始化若是不对非局部静态对象直接访问,而用返回局部静态 对象引用的函数调用来代替,就能保证从函数获得的引用指向的是被初始化了的对象
条款48:重视编译器警告
重视编译器产生的每一条警告信息在忽略一个警告以前,必定要准确理解它想告诉你的含义
条款49:熟悉标准库
对 于C++头文件,最大的挑战是把字符串头文件理清楚:是旧的C头文件,对应的是基于char*的字符串处理函数; 是包装了std的C++头文件,对应的是新的string类(看下文);是对应于旧C头文件 的std版本若是能掌握这些,其他的也就容易了
库中的一切都是模板
条款50:提升对C++的认识 C++的设计目标:和C兼容,效率,和传统开发工具及环境的兼容性,解决真实问题的可应用性 参考C++标准,理解C++的设计过程