版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
版第三版高清PDF总结精品盗料 2原则3:尽可能使用const 5原则5:了解C++默默编写并调用哪些函数 7原则6:若不想使用编译器自动生成的函数,就应该明确拒绝 9原则7:为多态基类声明virtual析构函数 原则8:别让异常逃离析构函数 原则9:绝不在构造和析构过程中调用virtual函数 原则10:令operator=返回一个referenceto*this 原则11:在operator=中处理“自我赋值” 原则12:复制对象时勿忘其每一个成分 原则13:以对象管理资源 原则14:在资源管理类中小心COPYING行为 原则15:在资源管理类中提供对原始资源的访问 原则16:成对使用new和delete时要采用相同形式 29原则17:以独立语句将newed对象置入智能指针 原则18:让接口容易被正确使用,不易被误用 原则19:设计class犹如设计type 原则20:宁以引用传递代替值传递 原则21:必须返回对象时,别妄想返回其引用 原则22:将成员变量声明为private 原则23:宁以非member、非friend替换member函数 原则24:若所有参数皆需要类型转换,请为此采用非member函数 原则25:考虑写出一个不抛出异常的swap函数 42原则26:尽可能延后变量定义式的出现时间 原则27:尽量少做类型转换动作 原则28:避免返回handles指向对象的内部成分 原则29:为“异常安全”而努力是值得的 原则30:透彻了解inline(内联)的里里外外 原则31:将文件间的变异依存关系降至最低 原则32:确定你的public继承塑造出了IS-A关系 条款33:避免屏蔽继承而来的名字 原则34:区分接口继承和实现继承 原则35:考虑virtual函数以外的其他选择 原则36:决不能重新定义继承而来的非virtual函数 原则37:绝不重新定义继承而来的缺省参数值 原则38:通过复合塑造出HAS-A关系或者根据某物实现出来 原则40:明智而审慎地使用多重继承 原则41:了解隐式接口和编译期多态 仅供学习与交流,如有侵权请联系网站删除谢谢2精品盗料原则42:了解typename的双重意义 原则43:学习处理模版化基类内的名称 原则44:将与参数无关的代码抽离templates 原则45:运用成员函数模版接受所有兼容类型 原则46:需要类型转换时请为模版定义非成员函数 原则48:认识template元编程 原则54:不要忽视编译器的警告 原则54:让自己熟悉包括TR1在内的标准程序库 仅供学习与交流,如有侵权请联系网站删除谢谢3本来是写在百度空间的,但是不知道咋回事百度博客中图片看不到了,所以百度博客的不稳定性可见一斑。于是我决定将我的领会和感受写在自己的云盘里面。虽好也弄个目录啥的,最后再整车成PDF格式的。这个标准我就参考我研究生期间论文的格式吧。原则3:尽可能使用const《EffectiveC++》里面第3条原则是尽量使用const。其原因是防止无意中更改而本来不应该更改的变量。本条款也提到const成员函数的重要性,原因之一就是只有const函数才能有的时候会遇到在const函数中更改非const成员变量的情况,这个时候就要用到mutable关键字了。如果一个成员变量被mutable修饰,那么它在const函数中仍然可以被修改,但是前提是该成员变量是非const成员。还有一种情况就是为了防止代码重复,比如两个函数实现了同样的功能只是类型不同而已,这样就会导致两段几乎相同的代码段,这无疑会增加编译时间、维护和代码膨胀等风险。在本原则的有关叙述中,作者采用了强制类型转换来解决之,虽然作者本身在大多数情况下并不提倡做法。为了给用户一个一目了然的接口,一看就知道那些成员函数可以操纵const对象而哪些不能,作者建议在类中明确将那些不改变对象的成员函数声明为const函数,虽然const成员函数可以使用非const成员变量,但是遵守这一原则会给客户带来极大的便利。因为const成员函数不更改对象,这就防止了由于误操作而带来的问题,因为最好用非const成员函数去调用const的实现,说白了就是直接return这个const成员函数,只不过需要对作为这个return的表达式的const成员函数进行一下强制类型转换使其成为非const型的。所以,在这里不得不提一下纯粹的C++的强制类型转换。关键在static_cast<typename>(value)是纯粹的C++强制类型转换的关键词和4}默默编写并调用哪些函数这一篇博客是《EffectiveC++》中第5个条款。但现在感觉我还没太理解它到底说了什么,所以想写写博客,万一写着写着就明白了呢。{上图可见编译器自动生成的析构函数是非virtual的,如果父类中本身存在不过,如果你手动写了它们中的一些,编译器就只会自动生成你没写的。比如你只写了构造函数,那么其他的东西编译器负责给你自动生成。至于说copy构造函数和copy赋值操作符的用法我以前的博文有提到过。而copy构造函数总是层层调用底层的copy构造函数来进行赋值,比如说造函数,实在没办法了,它再自己进行赋值操作。其实本原则着重讨论的是在什么情况下编译器不会自动生成这些东东。对于默认的构造函数而言,当你手动写了一个构造函数的话,编译器就不会再费那个劲了。而对于copy赋值操作符呢也是有自动生成条件的,那就是这个copy赋值操作符确实有存在的意义,并且它能在使用场合能正确工作,否则除非你自己手动写一个,要不然编译器是不会给你生成这些东东的。而在书中作者举了2个例子1个是引用,另一个是const常量,这两者所指的对象都是不能更改的,那你非要给它们赋值,那肯定会导致copy赋值操作符的失败。书中还举个1个例子,一般情况下父类中如果有copy赋值操作符,在子类中编译器是不会再给自动生成copy赋值操作符,直接使用父类的就好了,因为编译器认为子类的copy赋值操作符是要能够处理父类的赋值操作的。所以如果你此时把父类的copy赋值操作符设置为private的,那么你就没有copy赋值操作符可用了,除非你自己在子类中写一个。原则6:若不想使用编译器自动生成的函数,就应该明确拒绝这是《EffectiveC++》中第6个原则,在某些情况下你不想让某些类的对象被拷贝,那么在这种情况下即使你不写copy构造函数和copy赋值操作符编译器也会为你生成,那么你不得不自己写它们俩。旦你写了,编译器就不会自动调用父类的copy构造函数和copy赋值操作符。即便这样本类内部成员函数和友元函数还是可以调用它们,该如何是好?办法就是你只声明这些函数而不去实现,没有实现就自然没有功能了,而既然就像下图类的定义所示的这样:{voidEmptyFunction():46/{运行结果如下所示:):生成成:成功0个,失败1个,最新0个,跳过0个=========出现了错误提示,说copy构造函数无法解析。20//Empty(constEmpty&);/*copy构造函数。*///Empty&operator=(constEmpty&);/*copy赋值操作符。*在运行得如下结果:哈哈而本思想只在阐述如果你不想让编译器为你自动生成函数,你就要自己手仅供学习与交流,如有侵权请联系网站删除谢谢1原则7:为多态基类声明virtual析构函数这是《EffectiveC++》中第7条原则,其内容是在具有多态用途的父类中应该使用virtual析构函数首先要知道啥是多态。我就好说直白的,显得没有深度的东西。多态的一种体现就是通过父类的指针指向不同的子类来实现不同的功能,从而达到接口好了,现在铺垫完毕了,来说正题,为啥要有一个virtual析构函数呢?那用,那么对象的子类部分不会被析构,那么就会造成资源的泄露。现在来看下面的例子:J仅供学习与交流,如有侵权请联系网站删除谢谢12{9)public:virtualbase(){cout<<仅供学习与交流,如有侵权请联系网站删除谢谢13把其析构函数设为virtual的。为啥呢?这与C++中virtual本身的实现机制有关,因为这样的类的对象必须要携带一个表,这个表叫vtbl,所以本来没必要多带这么个表,但是你非要多出一个来占个空间,这就是占个茅坑不拉屎的表时候,应该吧析构函数设为纯virtual函数,并且给与空的实现,如下图所示:{5为啥要这样做呢?因为析构函数调用顺序是从没有子类的子类那里的析构这个原则简而言之就是,只有作为多态用途的父类才有必要使用virtual析原则8:别让异常逃离析构函数析构函数这里漏掉,也就是说析构函数应该承担起拦截异常的责任才行。如果异常越过了析构函数这一关,流窜到其他地方去,那么就会造成程序提早结束对付这种情况通常有两种简单粗暴的手段:1、在析构函数内发现异常,立刻捕捉到并且结束整个程序;2、在析构函数中发现异常,立刻捕捉到并将其扼其中第一种手段比第二种手段要好,这是为啥呢?因为方法1直接结束程序,其结果是可预料的,不会造成太大破坏。而方法2你这个异常是终止了,但是程序中其他部分与这个功能相关的势必会造成影响,也许还会因此带来其不过以上这两种方法都没能去正面处理出现的异常,所以这两种方法都不书中给出的解决方案是,再创建一个类用来处理异常,在这个类中有一个成员函数专门用来处理原来的类中的异常。而这个成员函数是调用原类中的异常处理来完成的,这实际上就是变相的让原类自己处理异常,这是第一道关卡。然后异常处理类的析构函数中也有一份处理异常的代码,这部分是异常处理类自己的,这是第二道关卡。这个就是双保险,如果说在第二道关卡仍然不仅供学习与交流,如有侵权请联系网站删除谢谢17原则9:绝不在构造和析构过程中调用virtual函数这是《EffectiveC++》中的第9条原则。简单的来说,如果你在父类的构造函数中调用了虚拟函数,那么子类的成员就会始终处于未初始化的状态,这样对象的子类成分就会出现不可预知的行那么这是为什么呢?在继承体系当中,你声明了一个子类对象,那么这个子类对象其实是很复杂的,它包含了子类所有父类的成分。而这些成分是要一层一层的进行初始化的,其顺序是按照类的继承层次从上而下进行的,即从最远的那个父类到最近的那个父类,然后是本类的初始化。而C++的机制又是只有在父类成分初始化完毕以后才去处理子类成分的初始化工作,换句话说,如果父类的成分没有初始化完毕,它压根就不会去管子类的初始化工作。因为你在父类的构造函数中调用了虚拟函数,而这个虚拟函数一般在父类中是不进行能实现的,要不你调用它干吗?我想C++的构造函数也是这么想的。但是,你C++就认为这个对象的父类成分还没有初始化完毕,现在不能去初始化子类成分。所以,即使你现在声明了一个子类对象,子类中的成分还是没有得到初始C++对未定位的成员变量是采取无视的态度的,因为还没轮到你,你给我一边凉快去。在构造函数期间无视,在析构函数期间也是无视。而析构的顺序又是先子类再父类,因为对象的子类成分被无视,只有父类成分被析构,所以那些未定义的成员变量自始至终都是未定义的,你也不知道它们最终会怎样。为了证实这一点请看下面的例子:{运行结果是这样的: 仅供学习与交流,如有侵权请联系网站删除谢谢办法。那就是在子类的构造函数的初始化成员列表中调用父类的构造函数去初初始化,这样写比较方便也比较可读。而且,这个辅助原则10:令operator=返回一个原则11:在operator=中处理“自我赋值”在这篇博文里面我打算写两个《EffectiveC++》中的原现在介绍第一个原则:条款10,此条款旨在说明在你自己编写的赋值操作下面来对条款11做一些介绍。条款11的内容很简单,就是一定要妥善处理赋值操作符=的自我赋值问题,就是自己给自己赋值的情况。那么这又是为什么呢?因为在某些时候你要编写用来管理资源的类,那你知道一个资源不用了就要释放掉,以便留给下一个需要该资源的对象。不过,这是很合理的。但是,假设当前占用该资源的对恰好是用来赋值的右值,也就是它俩其实是一个东东。如果还是按照上面处理指向了NULL,那就会发生错误。那么怎样处理这种情况呢?第一种方法很简单,那就是在赋值操作符的实现中最先判断一下赋值的对象和被赋值的对象是不是一个,即if(this==&obj)。不过这种方法不好,因为这所以实际上采用的办法就是所谓的COPYandSWAP方法。什么意思呢?那么就可以完美解决自我赋值的问题。因为既然是COPY那么原来那个右值就还有一种方法就是直接利用赋值操作符重载函数的传参机制是传值这一特性,直接传进来一个COPY。该方法可行,但是作者不提倡,它说这样做的话清晰性变差了,我的这个清晰性大概就是可读性吧。不过,我倒觉得没啥。他仅供学习与交流,如有侵权请联系网站删除谢谢原则12:复制对象时勿忘其每一个成分这个原则是《EffectiveC++》中第12个原则,这原则主要涉及到两个方面:1、自定义的COPY构造函数和赋值操员,包括新增加的成员和父类的所有成员都要复制过来。2、不要尝试COPY第1点没啥好讨论的,现在来着重讨论第2点。咱们只讨论一种情况,就是copy赋值操作符去掉用copy构造函数的情况。因为copy构造函数本身就已经把复制了一个副本,换句话说它自身已经生成了一个崭新的对象。而copy赋值操作符是把传进来的对象赋给这个崭新的对象,那不就是试图在构造一个已经存在的对象了吗,这很荒谬。同理copy构造函数去掉用copy赋值操作符同样荒谬。所以作者建议方法是把它们共同的代码放到第3个函数中,通常这个函数被命名为init。原则13:以对象管理资源这是《EffectiveC++》中提到的第13个原则。资源的管理这一主题从宏观上来讲就是你申请了资源,就一定要释放。你不释放会造成内存泄露,资源泄露,你释放多了有可能导致程序行为异常。所以简而言之就是你申请了多少个资源就释放多少个资源就行了。可是你在编程的过程难免会忘掉或者没有处理好资源释放的过程,那么本原则就是告诉你如何去控制资源的释放的。作者的经验之谈就是以对象作为资源的载体进行传递以代替用单个语句去实现资源的管理过程。因为在这个过程中,程序流说不定就可能被return拐走,被作为异常抛出,被continue或者break跳过,当然还有那几乎被摒弃的goto语句等等,而没有到达delete。因为对象本身有构造函数和析构函数,而且它会在对象的生存期末尾自动调用析构函数来释放资源,从而不会造成资源泄露的情况。其实,我感觉此条款着重强调的是释放资源的重要性,但是它也强调在取得资源的时候马上就进行初始化。而对于操纵对象作者又极力推荐了两种智能指针:au两个指针都会在资源使用结束后自动销毁它们,而不用你管。仅供学习与交流,如有侵权请联系网站删除谢谢24它有个特性,那就是一旦用这种指针指向某资源,那就不能有多个auto_ptr再去指向它了。如果已经有一个指针指向了某资源,你再用新指针指向这个指针的话,就指针就被自动设为NULL。底有多少个对象指向某资源,但是它无法解决环状引用,就是两个没用的指针互指。不过,一般情况下,智能指针里面装的都是一个函数,这个函数返回一个对象的引用,并完成该对象的初始化工作。trl:shared_ptr<Base>ptb(其中factory()函数负责初始化并返回管理资源的对象的引用。仅供学习与交流,如有侵权请联系网站删除谢谢原则14:在资源管理类中小心这是《EffectiveC++》中第14个原则。对这种对像进行复制要怎样处理。因为这种管理资源的对象在复制的过程中很题,当然我的理解可能不对。所以可能出现COPY过来的资源不能及时释放作者给出的4个解决上述问题的办法:1、压根就不复制资源管理对象,这少对象。这往往要用到shared_ptr;3、COPY要拷贝的全面,即在该类的所有继承体系中的类的成分都COPY过来;4、保持资源的独一性,即它不会有多原则15:在资源管理类中提供对原始资源的访问这是《EffectiveC++》中第15条原则,我感觉非常抽象,理解起来很费首先你要明白啥叫原始资源,其实确切的概念我也说不准,但是你可以简管理资源要使用资源管理类,通常这个类被称为RAⅡ类,在理想的情况下,你总是试图使用RAⅡ类来进行资源管理,但是世事无常,总有一些API (ApplicationProgrammingInterface)会直接调用原始资源,它们会绕过RAⅡ类,而这不符合你的原则,而你又不得不去用。那么本原则会叫你处理这种情况的一些方法。作者举了两个例子:1、通过传递资源管理类的对象的某些方法间接传递一份原始资源的COPY,这样真正的原始资源不会得到改变。那就需要RAⅡ类提供一个接口使对象能够暴露出其内所含的原始资源。而这通常是通过显式转换和隐式转换来实现的。书中仍然是以智能指针auto_ptr和shared_ptr为例加以说明,它们提供2、作者又举了一个字体调用的例子。字体本身是一种原始资源,我们创建了一个类用来管理字体。因为这种原始资源比较特殊应用场合也很多,所以存在让资源管理类提供一个向外界开放的接口的必要性,外界通过调用这个接口从而使用字体这种原始资源。又因为最终是要使用这种原始资源的,所以必然而隐式转换可以自动转换为原始资源类型,但是这存在一个问题。那便是如果用户现在就是想使用一个资源管理类RAⅡ的对象,那没办法他现在必须转换为原始资源类型才能使用。而这隐含的凶兆就是如果你不经意间删除了RAⅡ对象,那也就意外地删除了原始资源的对象,那么你转换过来的也就没总结一下,作者强调无论是显示还是隐式转换都是要视情况而定的,没有完全的绝对。另外,RAⅡ是的职责是资源管理重在资源释放,虽然访问原始资源突破了类的封装特性,但是这不是RAⅡ的首要存在意义。原则16:成对使用new和delete时要采用相同形式候,你要使用delete释放。如果搭配错了,后果都是未定义的。这其中的原理,只要你懂得new和delete仅供学习与交流,如有侵权请联系网站删除谢谢29原则17:以独立语句将newed对象置入智能指针这是《EffectiveC++》中第17个原则,作者以一个示例形象地说明了这一有一个资源处理函数A,这个函数中接收两个参数,它们分别是shared_ptr类型的指针和一个整形参数。但是,因为用对象来管理资源的原则,所以在这里首先有了一个资源管理类的对象,并且想把它作为A的第一个参数传进去,而A的第二个参数用一个能返回整形参数的成员函数B作为实参传进来。程序员为了图省事,他直接在A的第一个参数的位置上new了一个对象C,这个对象当然就是资源管理类的类型了,但是A接受的是智能指针类型,所以他还在这里还要介绍一个机制,那就是编译器在产生函数调用码之前,首先要对实参进行核算。那么在核算期间,上述内容就可以分成3步:1、new对象C;2、调用B;3、强制把C转换成shared_ptr类型。在上述三个步骤中,1和3的顺序是确定的,那就是1在先3在后。但是2却不一定了,这是根据语言和编译器的不同而异的,所以它们的顺序可能是213、123、132。但是在123的情况下,如果调用B的过程发生了异常,导致程所以作者在此原则中想着重强调的是,你最好不要在调用函数的过程中直察觉,所以你最好把这些都放在函数调用之前的单独语句里面。原则18:让接口容易被正确使用,不易被误用这是《EffectiveC++》中的第18条原则。1、作者在本原则中举了一个函数接口的例子,在一般的情况下,用户可能错误地输入了参数,而导致程序运行不正确。针对这种情况作者推荐采用导入新类型来解决此问题。而这些类型,你可以使用结构体、枚举类型和带有特定返回值的成员函数等来实现。而带有特定返回值的成员函数一般来讲是以函数2、再有就是,作为接口设计者,你要限制用户能做什么不能做什么。比如3、让你接口提供的行为与内置类型的一般性行为一致。因为用户总喜欢对他们熟悉的东西反复用,并且喜欢套用在新的东西上。所以这样做不仅可以让你的接口更快被用户接受,还不容易犯错。在这里作者举了泛型算法的例子。要求一多,用户就容易头晕,这样使用接口就更容易出错。所以一定要让接口被傻瓜式地使用。在这里作者又举了智能指针的例子。原则19:设计英文是:Treatclassdesignastypedesign。这是则。对类的设计归根结底可以归结为类型的设计,因为类也是一种类型的存在。该原则是若干个原则的集合,而这些原则是设计一个类需要遵循的。这些原则具体如下:1、类对象的创建和销毁如何进行;2、类对象赋值和初始化的区别是什么;3、类对象在何时进行值传递;4、类对象所能接受的值,不能接受哪些值,如何让用户容易使用,不容易犯错;5、类对象需要考虑的继承体系;6、类对象的类型转换该如何实现。比如资源处理类的对象;7、对类对象来说那些操作符是必要的;8、什么样的编译器自动生成的或者已经存在的成员函数你应该自己重写并取而代之;9、该类中哪些成员能提供给外部;11、你是否要定义很多类型?如果是,你还不去写个泛型类。否则,你只写一个type就好了;12、你真的有必要定义一个新类型吗?原则20:宁以引用传递代替值传递这是EffectiveC++中第20个原则。对于类对象而言,采用值传递是非常不明智的,因为它会涉及到COP造函数和析构函数的调用,如果你COPY的那个对象还包含了其他类的对象,那就会涉及到更多的函数调用,而且这是呈指数级增长的。而采用const&不仅极大地提高了效率而且还没有任何构造函数和析构函数的调用。而之所以采用const则是由于先前的原则3.另外采用const&可以避免对象切割问题,虽然这个问题我还没认识到那么深刻。当一个子类对象采用值传递方式被调用,但是被调用时的类型要求是父类类型时,那么这时父类的COPY构造函数会被调用,不要以为这种情况不能发生,记住这正是多态的体现,而父类类型不过是个接口。这样做的话,该对引用的底层是用指针来实现的,所以你会发现引用和指针的行为有些相仿。所以对于内置类型,如果你以值传递的话效率会高些,比如说VS中指针占4个字节,而一个char占1个字节。另外对于STL迭代器和函数,都被设计为采用以值传递,这是合理的,为什么呢,答案在原则1。但是不能因为对象小就采用值传递,这是因为一方面对象虽小但牵连广泛,它所涉及的COPY构造函数和析构函数也不可小觑。另一方面,小对象也原则21:必须返回对象时,別妄想返回其引用其中有一个有理数相乘的成员函数,该成员函数返回该有理数类的对象。在此例中该对象是一个本地对象,什么叫本地对象呢?就是一个普通的,局部的对数的返回值赋给某一变量,然后该函数使命完成被自动销毁,那么它所返回的对象也就被自动销毁了,那么被赋值的变量的行为然后作者又举了动态分配对象的例子。因为是new一个对象出来,所以它肯定是要调用构造函数进行初始化工作的,但是你往往找不到一个合理的时机因为上述两个例子都是因为为本地对象调用构造函数而导致的,那么如果答是no。因为static对象是静态分配,它在内存中的位置是固定的,这样多个对象最后的内容是最后修改的那个内容,基于此种性质。如果你的业务逻辑是多个对象之间才存在的,那么这样做的后果最后作者给出了自己的建议——你既然要返回一个对象,那就直接返回一原则22:将成员变量声明为private这是EffectiveC++中第22个原则,原话是Declaredatamembersprivate,即把数据成员称名为私有的访问权限。为什么要这样做?先说public,public是提供用户的接口,它根本不具备封装特性。从实际来看,被声明为public的都是成员函数,用户通过操作成员函数来操作类内的成员,而至于说这个接口的内部细节对于用户来说是不可见的,其中一个典型的再有就是通过把public成员函数作为接口提供给用户,那么这个接口的实现会有若干种可能以应对不同的需求。还是因为它是接口,是给用户使用的,所以它不能变来变去的,否则你叫用户怎么用。所以一旦某个成员函数被声明为public的,那么一般情况下它就不能再有任何变化了,当然这个变化是从用户的角度来看。从而推出越是广泛使用的类,public成员声明就越不能改变。在这里有个普遍的准则,那就是封装的越好,改变成员时所破坏的代码量就越少。protected并不比public封装性更好,因为你改变public只是改变用户代码,而你改变protected则可能导致所有子类的代码的破坏,它俩的代码破坏量都不可估量,后者更甚。所以成员一旦声明为protected和public那就意味着它们应该是永远不变原则23:宁以非这是EffectiveC++中第23条原则的内容,我感到很奇怪,成员函数调用本类的成员怎么了?难道这还不够封装吗?看下面的解释吧。类中可能存在一系列函数用来处理一系列操作,而这一系列函数可以放在类中某一成员函数中一起执行,但是也可以放在一个非成员函数中一起执行。那么从封装性上来讲哪一个好呢?答案是非成员函数,这是因为成员函数还可以访问类中的私有成员,但是非成员函数连私有成员都访问不了,所以非成员要知道越多东西被封装,就会有更大的余地在不为人知的情况下更改被封装的数据,在这里友元函数和成员函数一样,所以本原则推崇的是非友元函数这对于那些纯面向对象语言,像JAVA,而言很不幸,因为它们没有独立的函数存在。所以在这种情况下可以考虑写一个工具类,在此类中添加函数,这样这些函数就不是成员函数了。而C++的做法是把这些独立的函数和被操作的类放在同一命名空间下。本原则这样做的另一个原因是出于可扩展原则。因为完成某一特定功能可能需要一系列的函数,但是你可能需要完成很多功能,而这些功能都属于同一个工程。这个时候你可以为这个工程明明一个名字空间,然后把每个功能写在一个独立的头文件里面,因为它们共同隶属于同一个命名空间,所以在某个头仅供学习与交流,如有侵权请联系网站删除谢谢39原则24:若所有参数皆需要类型转换,请为此采用非这是EffectiveC++中第24个原则,即非成员函数能够完美解决题目中所叙作者以一个有理数类的例子来诠释本原则所述内容。这个例子大致是这样的,这个有理数类有一个带有默认值参数的构造函数,并且也有一个重载的乘法操作符,并且这个重载操作符函数只接受一个有理数类的对象。现在在它参数的位置上放上一个整数,因为构造函数并非显示,所以它允许将这个整数隐但是奇怪的现象来了,因为这个重载操作符是单目操作符,这时出现了一result=oneHalf*2;result=oneHalf*2;以被隐式转换成有理数类型。但是下面这个表达式从逻辑上将实现的功能是一样的,但是它确报错,这是为啥呢?那是因为这个*的函数原型如下所示:constRationaloperator*(constRational&rhs)const;constRationaloperator*(constRational&rhs)const;因为2并不在参数列表中,根本不存在类型转换,而又因为它要求的是两个有理数类型参与运算,因此2压根不符合类型要求,所以会报错。而这就是所谓的只有被列于参数列表内,这个参数才是隐式转换的合格参与者。那这里你一定会冒出一个疑问,既然参数列表里面只有一个参数,那你就改写一下这个重载操作符的函数呗,这样两个参数不就都能进行隐式转换了吗?嗯,这个想法是好的,不过类中的*重载操作符不支持两个参数的形式,请看下面的图示:但是非成员函数的*重载操作符函数却能支持两个参数的形式,这样两个参数都能进行隐式转换了。那么为什么不把非成员函数设成有原函数呢?那是因为有原函数的存在极大地破坏了封装特性,不符合面向对象的思想,乱用的话会带来很大麻烦,所原则25:考虑写出一个不抛出异常的swap函数话说在STL有这么一个泛型算法名叫SWAP,其功能如其名,就是用来交换,它能交换两个类型的对象,那更不用说内置类型,因为内置类型也可以说是某种类型的对象吧,只要被交换类型支持复制COPY操作就行。但是这个泛型算法的思路还是让一个临时变量tmp先接收一个对象a,然后把另一个对象b作者举了一个常见的场景,那就是用指针指向一个对象的时候(pimpl,pointertoimplementaion)这种做法具体就是一个类A用来操作指针,这个指针指向类B,另一个类B中的内容才是实质的内容。指针很小,但是对象很庞占用的临时空间也非常大。如果用泛型算法SWAP来交换两个A对象的话,因为A对象中有指针已经指向了B的对象,再加上SWAP中间的临时变量tmp,那么SWAP不仅仅赋值了3个A对象,而且还复制了3个B对象,而后者并不是我们想要的,其效率之低下由此可见一斑。而我们想要的只是进行到A的指那这个时候该怎么办呢?那我们就私人定制一个A类专属的SWAP好了,这就是所谓的SWAP针对A类的特化。具体的做法就是弄一个全特化的SWAP版本,用它来交换给定对象中所包含的指针,然而这一版本并不能成功因为这个指针是私有的。解决这个问题的办法你可以考虑使用友元函数,但是基于友元函数对于封装性的破坏,我们能不用就不用。所以最好的做法就是声明一个A类的公有成员函数SWAP,然后在这个公有成员的SWAP函数里面调用泛型算法只用来交换指针,这样它就能访问A的私有成员指针了。同时,在同一命名空间下再声明一个非成员函数的SWAP,它的参数被特化为A类型,这样它直接调用实参对象,也就是A类型对象的公有接口SWAP就可以完成交换了。而这种做法也恰恰符合了STL的风格。不过,上面这种做法并不适合泛型函数级别上进行偏特化,因为C++只允许对泛型类级别上进行。那一般而言应对这种情况的做法就是写一个重载函数,这个重载函数只有参数是特化的。不过,不要往std里面添加重载的泛型有的时候纯粹是为了方便,你希望你的特化SWAP被最大化使用,你还是应该在你自定义的命名空间内std的特化版本SWAP和本类的专版,这样编译常安全性的保障。不过这一点不适用于非成员函数的SWAP,因为它是基于拷是你的SWAP效率越高,它的异常安全性越好,一个特例就是当SWAP操作内置类型时SWAP根本不可能抛出异常。什么是全特化?就是凡事涉及到泛型类型的地方都用特定类型代替之。什么是偏特化?就是非全特化。通过上述内容作者想表达3个观点:1、当那个泛型SWAP效率实在低下时,你自己写一个,但是要确保不抛出异常;2、如果你的SWAP是成员函数,那你一定要用一个非成员函数来调用这个成员SWAP,并且特化一个std:SWAP;原则26:尽可能延后变量定义式的出现时间一眼看到这个变量在哪用了;如果你定义早了,这个变量你可能压根没用到过,那你就是白白浪费了空间。如果你定义的变量是个对象,那你还要花费构造函数和析构函数的代价。如果在你定义变量之后程序由于异常而中断了,这再有就是在你定义对象时最好直接给它赋值,因为如果你不这样做,它是首先调用默认构造函数,然后再调用拷贝赋值函数,但是如果你直接拷贝那就不会调用默认构造函数了,所以这在效率上是一个提升。本原则还讲到一个用于循环的变量是定义在循环内还是外?如果在内,那如果在外,你只调用一次构造和析构函数,循环多少次就调用多少次拷贝赋值作者说这是有条件的,这取决于拷贝构造函数的代价,如果一次拷贝的代价小于一次构造函数+析构函数的代价,那么在外比较好,尤其是在循环次数比较多的情况下,反之在内比较好。不过,从代码的可读性和可维护性方面来原则27:尽量少做类型转换动作这是EffectiveC++中第27个原则,作者花了很长的篇幅来介绍这一原则。总而言之一句话,因为类型转换会导致破坏类型系统,从而带来明显的和C++提供了四种新型的类型转换,就是下面这四种:constconst_cast<T>(expression)const_cast是把const类型转换成非const类型;dynamic_cast是类体系中进行转换;是一个很强大的类型转换,最常用的是指针转换为指针,而且是两个毫不相关的指针之间的转换,它传递的是比特位。换句话说这些比特须知,在C++中,类型转换往往能够令编译器编译出运行时代码,可想而知,这个代码并不是你写的。在这里作者举了一个多态的例子,当你用父类的指针指向子类的对象时,父类的指针指向的地址和子类对象所在的地址并不是作者又举了一个例子。现有一父类和其子类,两类有两个同名的虚函数,现要求子类虚函数中首先调用父类虚函数,这是子类虚函数的代码是这样子classclassWindowfvirtualvoidonResize(){…}virtual.//这里进行SpecialWindow专属行为。}通过子类的this指针强制类型转换成父类类型,然后调用同名函数。但是次转换的过程并不是你所想象的那样简单,这个强制类型转换会创造出一个被转型对象的副本,它是在这个副本身上执行父类的同名函数,而在该对象身上执行本类专属的同名函数,两者本应作用于同一个对象而实际上却没有。而解决这个问题的办法就是不用强制类型转换,你该用父类的成员函数就用就行而dynamic_cast这个转换你最好不要用,因为它是动态转换,它会在整个类层次上进行寻找,而且每转换一次就寻找一次,并且它是按照类名进行字符通常你使用dynamic_cast进行强制类型转换的情景是用一个父类的指针指向子类对象,这种情况你应该使用的是子类类型的只能指针,作者使用的是一可以在父类中提供一个同名空虚函数,并在各个子类中去实现它,然后你再用在这里一定要记住不要在程序中多次使用dynamic_cast做没有必要的类型转换,因为这样做不仅代码多,而且运行慢,因为dynamic_cast需要查不要让用户接触到;推荐使用C++强制类型转换,它不仅容易分辨,而且各司原则28:避免返回handles指向对象的内部成分在这里首先要明确啥是handles,这里所说的handles就是通常所说的引用、指针或者迭代器等具有指代性质的标签。那么题目所说的意思就是你返回的那个handles不要涉及对象内部的东西。在叙述此原则的过程中作者举了一个矩形Rectangular的例子,说举行的四个顶点存储在一个结构体中,Rectangular提供了两个公有接口upperleft和lowerright它们返回的是该矩形左上角和右下角的顶点的坐标结构体,并且是以引用的形式返回的。因为用户只需知道这些顶点的坐标而无需对这些顶点进行操作,因此这俩接口都是常函数。但是矛盾出现了,常函数的作用是不允许用户去修改,但是这两个常函数却返回了Rectangular的私有成员。另外常函数只是说在常函数体内不进行更改数据成员的操作,它返回的东西并不一定是常量。既然如此用户就很有可能通过这俩接口去改变Rectangular类原本私有成员,这是极其不符合该程序的初衷和封装性原则的。很有道理的。作者还说非公有的成员函数也是内部数据,这不是废话嘛,我早就知道啊。作者想表达是啥呢,就是从访问级别上来讲,不要让那些访问级别高的成员函数返回指向访问级别低的成员函数的handles,不过在我看来,具体来讲就是不要让公有接口返回任何非公有的成员的handles。那么这原则中所提及的问题是怎么解决的呢?正如我所说的,常函数只是在函数体内不能对数据成员更改,但是它返回的东西并不一定不能被更改,所以呢,那么就把返回值也设成const就OK了。改,但是作为右值的语句结束以后,它的生命期就结束了,那右值的东西被析作者最后总结道:能不用handles只想对象内部就不用,这样可以增加封装原则29:为“异常安全”而努力是值得的我怎么发现最近记录的这几条原则的叙述内容都很冗长无法一眼两眼就能看懂。作者首先从一个类似于从MFC取材的例子,就是更换背景色的一个功能,旧背景,记录图像更改次数,生成最新的背景图片,去掉互斥锁。然而,这种因为作者说这种实现不符合异常安全性,而所谓的异常安全性,具体来讲包括2个方面,它们是:1、不泄露任何资源:上例很难做到,因为咋生成新的背景图片这一步,如果出现异常,程序就不会继续往下执行,那么互斥锁就永远不会解怎样做到不泄露呢?条款14说过就是用资源管理类。这一点有点区别,在不使用资源管理类时使用互斥锁下图这样的:{{而使用资源管理类机制之后是如下的情形:LockLockml(&mutex);Ml是Lock的一个对象,这样它有自己的构造函数和析构函数,这就避免了资源泄露,而且这不仅使程序较短而且还免去了写unlock,降低了程序的复2、不允许数据被损坏:这个词可以理解为数据的实际行为不符合数据预期的行为,而用于表征数据行为的值又没有如实地反映数据的行为。在本例中就是实际new的图像并未成功,但是记录更改次数的变量记录那么如何处理数据败坏呢?需要做到3点,1、基本承诺;2、强烈保障;2、强烈保证:如果程序抛出异常,程序原正常状态不改变。函数成功就完3、不抛掷异常保障:程序本身绝对不会抛出异常。这种程序主要是针对内关于第3点,作者举了一个空白异常明细的函数,如下图所示:intdoSomething()throw();intdoSomething()throw();所谓异常明细直到现在我所学的C++知识而言,我还不清楚它就是是指异常安全码?这是个啥东东?它代表以上三中保障之一,你必须为你所写用智能指针,而为了解决基本保障的问题作者决定调整语句次序。具体在本例就是因为计数器是用来记录背景图像真的被变更了才加1的,所以它必须放在不过仅仅这样做也会产生一个问题,那就是在new的过程中构造函数仍然可能抛出异常,而这种异常仍然可能改变程序。在这里作者有提供了一种手改变,如果改变没问题就是用那个副本,否则由于原来的对象还在那你还可以使用原来的对象。而又由于交换对象本身内容费时又费力,所以在这里最好采用交换两对象的指针,也就是以前提过的pimpI原则。很有特点的是,在智能不过这还不足以提供强烈异常安全性保障。作者就此举了一个例子,那就是在一个函数中又调用了另外两个函数的例子。不仅仅是在这两个函数不提供异常安全性保障的情况下,就算是这俩函数都提供了异常安全性保障,并且是强烈的异常安全性保障的情况下,仍然无法确保主调函数也是强烈异常安全的。因为这俩函数如果发生了异常,它改动了非局部性的数据,比如说数据库,即便这两函数被恢复了回来,但是外部的数据还是被改变了,那么其他的用户就极有可能使用因发生异常而被改动的那些不应该被改动的数据。还有一个问题就是swapandcopy策略的效率问题。因为这个策略是需要一个元对象的副本的,制作这个副本怎么着的也需要一定时间吧,如果原对象很大那你这个副本的制作时间花销也小不了啊。因此从这一点来讲任何时候都提供强烈的异常安全性保障是不切实际的,这时你只能提供基本保障了,但是这不是你不提供异常安全性保障的理由。总结一下作者的观点就是一下3点:1、就是那三种异常安全性保障了;2、强烈保障能通过swapandcopy策略来实现,但是并不是所有场合都适3、函数的异常安全性等于函数内部函数异常安全性保障中的最弱者。原则30:透彻了解(内联)的里里外外我觉得在这里应该首先介绍一下到底啥叫内联?所谓内联就是在编译期在函数调用点上将函数本体复制过来并在函数调用点上展开的机制。在这里作者很明显的不是想讨论内联的好处有多好而是讨论它的坏处。因为函数调用点是非常小的,所以你在编写程序的时候你的源码量可能较小,但是在编译的时候函数会在函数调用点上展开,当然也有在运行时刻展开的,不过那是少数,那么这样的话它可能使产生的目标码比你想象的大。如果被内联的函数很大的话,那么产生的目标就超大。由此而带来的影响还有会导致额外的换页行为,低效和指令高速缓存装置的击中率(虽然我还不懂这到底是为啥,不过姑且记一下吧)。但是如果内联函数体积很小,那么极有可能函数编译完成时刻产生的目标码比函数调用时候的码要小。内联函数存在意义在于针对那些频繁调用的小函数使用内联的话可以节省栈的存储空间,因为内联内联可以显示指定也可以隐式使用。类体内的成员函数都是内联函数,如果在类中存在友元函数,那这个友元函数也是内联的。而显式指定不是在函数文件中的,而泛型也是出现在头文件中的,这就让不少程序员意外泛型一定是仅供学习与交流,如有侵权请联系网站删除谢谢55为什么这俩东西一般出现在头文件中?那是因为编译器迟早是要将它们具体化,而在具体化之前必须要知道它们长得啥样。再有就是泛型具体化的时如果你写的泛型有必要成为内联的那你就应该将它声明为内联,否则你还是不要那样做,这是为啥呢?因为内联是需要成本的,其中之一就是在调用点编译器是不会对virtual函数进行内联的,因为virtual函数是个虚表,只有在运行期才能确定要指向哪个函数,而内联通常是发生在编译期,所以编译器根本就找不到virtual函数的本体,更别提内联了。所以内联能不能起作用不是看你有没有加inline进行修饰,而是取决于编译器能不能实现内联。如果编译器要内联某个函数,那么编译器可能为它生成一个函数本体,以便取其地址。但是,编译器无法给一个并不存在的函数提供指针,因此通常编译器不会给通过指针而进行调用的行为进行内联,因为编译器也不能确定这个指针的指向是否确有其物。所以内联函数有可能被内联调用也可能不被内联调用。再有就是类的构造函数和析构函数,有的时候类会自动生成构造函数和析构函数的本体,因为这样的话,构造函数和析构函数就可以通过函数指针调用它们的非内联本体了。而将构造函数和析构函数设成内联其实并不明智,因为即使是空的,编译器也会自动添加大量的代码到里面去,而这些在编译、连接、执行时所花费的代价是相当大的。另外内联函数还无法随着程序的升级而升级。这就是它的一个性质,至于为什么我还不清楚。如果程序有所改动,那么所有使用到内联函数的地方都需要重新编译。而如果使用非内联函数的程序在该非内联函数被改动之后直接连接就好了。另外,大部分调试器对内联函数没招,因为在调用点上没有函数体。因此现在在大部分DEBUG程序中都禁止inline。在软件开发中有一个80-20原则,即,一个程序把80%的执行时间花费在20%的代码上面,所以你应该合理的运用inline为你的代码瘦身。1、inline只适合那些小的,频繁调用的程序上面。这使程序调试和二进制升级更容易,也可以最小化代码膨胀,最大化程序执行速度,但不可避免代码膨胀。2、泛型虽然也出现在头文件中,但不一定就是inline的。原则31:将文件间的变异依存关系降至最低这个原则着重讲述的是编译的依存关系,篇幅很长,理解起来也比较费这个原则是针对什么问题而提出来的呢?有的时候你在一个大工程里,你就修改了某个类中的一小部分实现,甚至就是一条语句,结果整个工程各种莫作者说这是由于没有把接口从实现中分离造成的,那这又是什么意思呢?从作者所举的例子Person类来看,它的私有成员里面有很多其他类的对象,而这些对象又是Person类中某些函数的参数,如下图所示:std::stringtheName;//实现细目DatetheBirthDate;//实现细目AddresstheAddress;//实现细目从这图可以看出作者所说的实现也就是私有成员中所列的这些东西,而它们又是其他类的对象。那既然用到了其他类那必须要引用其他类的头文件啊,就是使用#include命令。这样的话就形成了一定的编译依赖关系,这名词还是很有学术气息的。那形成编译依赖关系又能怎么样?那可以用一句话来概括—作者接着引用了一种惯常的思维,既然你说不要把实现和接口掺和在一让后把它们一块放在命名空间里面。这种类的声明叫做前置声明。这样做的好处就是实现不会动,会动的只能是接口,那不过上面这种办法纯属扯淡!因为编译器必须知道编译期间某个对象的大一个声明,编译器哪知道那个对象到底需要多大地方?!在这一点上C++和Smalltalk,Java还是有区别的,因为后两者只提供指向类的指针,而指针大小于是乎得出了一种常用的设计方式,那就是接口和实现分离,再具体点就是一个类提供接口,另一个类提供实现。而接口类和实现类中连接的纽带就是它体现的思想是使用生命的依存性去替换实现的依存性,这是编译依存性很奇怪,你仅仅是声明一个类,你就能用这个类定义一个形参并且放在函数形参列表中。其实还是那种情况,你只需要在用到定义的时候才真正去暴露类的定义,函数也是同理,所以你可以看到某个头文件中有很多声明式包括函把实现放到CPP中去。这样做的目的是降低与实现文件之间的编译依存关系。作者还提到有些泛型类也是采用实现和声明分离的设计方式的,不过要使用关键字export,可是这一关键字已经很少出现在当代编译器当中了,所以我从下面这个代码段可以看出在构造函数的形参列表中类的对象是可以new77元主相同的成英函数,77元主相同的成英函数,Person::Person(conststd::string&name,constDatconstAddress&addr):pImpl(newPersonImpl(name,birthday另一种pimpl实现手段是写一个C++的interface,里面是virtual函数和purevirtual虚拟函数,当然interface里面可以含有成员变量,但是人们通常不类,在这个interface中有那么一个函数,它的作用是返回一个已经实例化的对原则32:确定你的public继承塑造出了IS-A关系在这里不得不阐述一下类的公有继承的逻辑关系。子类继承父类,子类是父类的一个特化,父类是子类的抽象。任何子类对象都是(IS-A)一个父类对象,反之不然。作者其实是想告诉我们在公有继承中父类所具有的一切行为在子类中也必须具有,惟其如此才能把子类对象称作是一个父类对象。如果你的设计不具有上述目的那你还是不要采用公有继承的好。为了阐述这一观点,作者举了两个例子,它们分别是企鹅和正方在企鹅的例子中,父类是鸟,子类是企鹅,鸟都会飞,因为企鹅也是鸟所以企鹅也会飞,这显然不合常理,所以这种情况下子类对象就不是一个父类对象,那么表示这种继承关系就不宜采用public继承。而在正方和矩形的例子中,矩形是父类,正方形是子类,因为正方形的宽和高永远相等,但是在矩形中长是长高是高分得很清楚,你要增加长你就不会顾及到高,这一点正方形做不到,因此你不能说正方形是一个长方形,所以表仅供学习与交流,如有侵权请联系网站删除谢谢61条款33:避免屏蔽继承而来的名字这个说的道理比较浅显易懂,那就是内部作用域的相同名称的什么什么会覆盖掉外部作用域的什么什么。这个什么什么可能是變量也可能是函数,如果是在C++以外的面向对象编程语言而言那就可能叫做方法。作者首先是一个简说一个函数,函数内有一个變量,函数外也有一个同名變量,结果在函数内部使用同名變量的时候,函数内部變量被调用而外部變量却被忽略,即使是不同类型的。在类中也是一个道理,在类的继承体系中,子类的同名函数会覆盖掉父类中的同名函数,不管这些函数是不是同种类型的,也不管是不是但是,当你使用public继承体系时,那就意味着你打算继承父类中所有的非private的东西。但是因为子类中存在与父类同名函数,那么这就没有达到你我门想要的是什么呢?既能继承父类中的同名函数,有能保有自己的同名函数。方法就是你可以使用using指令和转交函数,而所谓转交函数就是写一个函数这个函数调用你想要的父类的函数。OK,本原则内容就这么点。1、在public继承下,不要出现子类同名覆盖父类同名的现象。2、你可以使用using和转交函数来解决此事。原则34:区分接口继承和实现继承这个来源于需求,有的时候你只希望继承接口,有的时候你希望既继承接口又继承实现,并且还希望覆盖父类中已有的方法。这个标题就来源于这个需在这里作者举了一个shape类的例子。这个shape类中有个纯虚函数,这使shape类成为一个抽象类,从而它不能拥有实例,而它的实现只能依靠它的子类纯虚函数的性质有二,1、它必须在子类中重新声明一下,2、通常来讲它在抽象类中没有实现。那么这两者结合在一块说明了什么呢?它说明这个函数另外,纯虚函数通常是没有定义的,这说明什么呢?这说明在抽象类中纯虚函数可以有自己的实现,只不过一般人家不写,因为反正这是给别人去继承那么虚函数存在的意义是什么呢?它通常是有一份实现的,并且这份实现则,就调用那个默认的实现。那如果有的时候子类并未明确提出它想要使用这个默认的实现,同时它有没有自己去实现,这时候该咋办呢?现总结一下问题吧,问题是你现在肯定是要继承接口了,但是它那个默认的是实现你还不一定要。那就只能在父类中使用纯虚拟函数了,然后在纯虚拟函数中去掉用另外一个正常的成员函数,这个正常的成员函数是其默认实现。但要记住本原则的目标是要实现接口和实现的分离,通俗来讲就是你要继承接口的时候你就只继承接口,你要使用默认的实现的时候就能使用默认的实现,你要是不想使用默认的实现你就自己写一个实现出来用。那么如何达到这个目的?其实很简单,具体做法就是让父类中的纯虚函数拥有自己的一份实现代码。因为纯虚函数肯定要被继承,但是它不強求你去掉用它的默认代码,这就说明当你在子类中写了一个函数声明但不给出实现的时候,它不会去自动调用纯虚函数的实现。而如果说你要使用纯虚函数的实现,那你可以使用类名进行限定,这样就能调用纯虚函数的实现代码了。那么这样的话,你只需要在子类的函数实现中通过类名指定父类中的纯虚函数的实现即可。下面只剩下父类中的那个普通成员函数没有讨论到了,就是那个objectID。那么这个普通的成员函数存在的意义是什么呢?其实就是无论你子类怎么變,我这个行为就是不變的,你子类直接用就可以了,也就是说一般来讲它是不變的。这里不得不再谈谈80-20法则。这个法则是什么意思呢?就是说程序执行时间的80%要花在只占工程总量20%的代码身上,而这个代码表示的是逻辑的实现算法的设计上面,而由语言机制带来的效率问题只占小部分,所以一般情1、对接口继承和实现继承要区别对待,在public继承之下,父类接口总是原则35:考虑函数以外的其他选择本原则所叙述的内容比较复杂涉及到设计模式相关的内容。它主要讲的就作者举了一个例子,说有这么一个游戏的血量计算函数,但是你知道啊,这个游戏里面有很多不同的怪物,像什么石头人、萨特一家、人马等,它们计算血量的方式是不同的。那就涉及到一个多态继承的问题了,就是父类中有一承父类的默认实现了,毕竟virtual函数存在的意义就在于此啊。作者在这里没有提及为啥要寻找一个方案去替代virtual函数,而是直接说要去寻找一个替代方案,这不免有些让人不解。很显然作者的态度就是不解释。不过经过阅读我大概了解到作者就是想另辟蹊径,就是想脱离OOP的老套路,我想这是不是有点哗众取宠呢,不过他的方案的确有可取之处。在这里作者推荐了第一种解决方案——用NVI(Non-VirtualInterface)去实现TemplateMethod模式。在这个过程中,作者讲了一个他的前人的方法,他这种前提,他的前人们决定使用一个非virtual公有成员函数接口,然后让这个公有接口去调用它内部的私有virtual函数。那么这种通过non-virtual函数接口去掉用privatevirtual函数的手法就是所谓的NVI了,而且这个NVI还属于什么wrapper(外覆器)。仅供学习与交流,如有侵权请联系网站删除谢谢那么在主调函数中就可以做一些事前工作和事后工作了,这就是采用NVI方法在这个原则当中作者是在类内部直接去实现成员函数的,这样一来这个成员函数就是内联函数了,这涉及到了原则30的一些内容。在类体外定义的函数virtual函数。还是先前那个例子,但是换了一个角度。反正都是计算怪物的血量嘛,那么我们何不抽取其共性做成一个函数,然后弄一个接口去接受指向这首先一个怪物肯定属于一个类,这个怪物有一个血量的属性,而这个血量的属性又属于另一个类。那么就可以把这个血量类的对象设成是这个怪物类的就用默认的。再用这个指针去给这个血量对象进行初始化,最后还是提供若干那这种方式的好处是什么呢?那就是血量的计算方式和具体的对象之间并无绑定关系,使用起来相对灵活自由。另外,如果这个怪物类提供了一个设置计算血量的setXXX函数,还可以实现在运行期改变血量计算从封装性的角度讲,这个独立的血量计算函数与怪物类是相对独立的,那么这个函数就不会去访问怪物类的非public成分。但是有时你为了让血量计算第三种方式是用trl:function来完成Strat马玩意以我现在的见识来看还真不知道,不过从作者的描述来看,这个东西是一个容器,它可以装载函数、函数对象、成员函数指针等,这样一来它包容的参数,并且也能像参数中那个函数一样提供一个返回值,但是它的兼容性更的理解就好。这个trl::bind就是能将某个参数与接受这个参数的函数绑定,并仅供学习与交流,如有侵权请联系网站删除谢谢68说了这么多那到底为什么要去实现Strategy模式呢?因为在Strategy模式中血量计算的部分与游戏人物的类是相对独立的,这样你怎么改动血量计算的方式游戏人物类面对的也只是一个单一的接口。这样血量计算类就可以担负起解仅供学习与交流,如有侵权请联系网站删除谢谢69原则36:决不能重新定义继承而来的非成员函数属于动态绑定。静态绑定决定于指向对象的指针,而动态绑定决定于对象本身。的非virtual成员函数。这是静态绑定,另外,对引用来说也是一样的,毕竟引用的底层也是通过指针来实现的嘛。而对于动态绑定来说,无论你是用父类指针还是子类指针,只要那个对象存在你要调用的virtual函数,那就会调用那个virtual函数。那么坐着为什么要强调题目所述的内容呢?要被继承的,而不是被改写的。从反面来讲。如果子类重新定义父类的非virtual接口和实现,当你使用子类对象的时候,子类对象就不再是一个父类对象了,因为这个非virtual的实现被你改写了。那解决这个问题的方法就是子类不以public继承父类。你不以public方式继承,那你只能以protected方式继承,这样的话你就不存在重写的问题了,你只要在子类的成员函数中去掉用父类的成员函数就行了。Private继承就更没必要提了。所以,现在只能在public继承体系下讨论了。为了不违反非需要被继承的,而不是被改写的原则,父类中的非virtual成员函数就必须被改有这些函数是可供你去修改的,那父类的非virtual函数就没有存在的必要了。可现在的实际是父类中确实存在非virtual函数,你能怎么办?你只能照单全收休想染指。virtual的,子类会去重新定义吗?仅供学习与交流,如有侵权请联系网站删除谢谢71原则37:绝不重新定义继承而来的缺省参数值在这里,你也一定要明白什么是静态绑定什么是动态绑定。静态绑定就是声明绑定,即决定于赋值符号左边那个类型;动态绑定是对象绑定,即赋值符那这又会怎么样呢?当你用下面这样的式子Virtual成员函数本身属于动态绑定,它是Rectangle版本的那么C++为啥要采取这种机制呢?很简单,这是因为你如果在运行期决定缺省实参属于哪个类,必然会在运行期采取某种方法解决此问题,这比在编译仅供学习与交流,如有侵权请联系网站删除谢谢72如果你因为使用virtual成员函数而遭遇苦恼,那就参照原则35去寻找一些仅供学习与交流,如有侵权请联系网站删除谢谢73原则38:通过复合塑造出HAS-A关系或者根据某物实现出来本原则讨论的不是语法和结构上的问题,而是设计方面的内容。首先你要了解复合的概念。所谓复合就是某种类型的对象中包含了其他类型的对象,这是一种集合与子集的关系。复合在软件工程领域包含了两层意义,一种是HAS-A的关系,就是包含关系,另一种是根据某物实现出什么。后一种我感觉不是那么地直观。那本原则就围绕这俩原则展开讨论。作者说HAS-A关系表现在应用域上,不过在我看来其实这是对业务逻辑进行抽象得出的模型之间的关系。作者所说的实现域,据我的理解就是根据应用域中的关系,用具体的手段来进行实现的过程,即根据业务逻辑实现出具体的作者接下来举了一个SET和LIST的关系,他实际上是想说IS-A和IS-EMPLEMENTED-IN-TERMS-OF是不一样的,一定要加以区分,为什么不一样可以参考原则30。1、复合关系和PUBLIC继承关系不同。2、复合在应用域是HAS-A,在实现域是IS-EMPLEMENTED-IN-TERMS-仅供学习与交流,如有侵权请联系网站删除谢谢74Private继承和public继承是软件生产中两个不同层面上的东西。Public侧3345689cin.get()}}仅供学习与交流,如有侵权请联系网站删除谢谢75private方式继承的。这说明通过private方式继承的子类对象并不是一个父类对在面向对象程序设计的过程通常是模块化编程,每个模块是按照功能进行它们通常是public成员函数。而private继承体现了模块内部纯粹的实现细节和代码重用,因此private继承主要用在应用已经存在的具体实现实现出新的实那这里private和复合都是is-implemented-in-terms-of关系,用哪个好呢?在这里作者极力推荐使用复合,不到万不得已不使用private继承,这倒不是说private继承不好,只是想表达private继承没啥必要,但是为啥没必要作者并未赘述这个例子是啥了,我只说说原理就OK了。因为当父类中含有virtual函数的时候,由于virtual函数的特性使之可在子类中被重新定义,因此不适宜使用它的一个替代实现方案是最靠近的是一个功能类A,它需要用到类B的实现,而这个类B又具备类C的所有特性。那现在的设计就是类A中包含了private的类B,并且类B以public方式继承了类C。然后在类A中以private方中到B中进行处理和A一点关系都没有。这也是实现数据封装的一种方式,以后即便类A拥有自己的子类D,D也不会涉及到处理virtual函数的事。这样做也可以降低编译相依性,因为如果A直接继承C势必会导致加载C所在头文件,而如果按照上述方案A只需要B的一个声明。作者提到的这个方案还是个为什么说private继承适用于那些父类中有protected和v呢?那是因为在private继承下继承的protected成员到子类中就是子类的private成员,父类的private成员还是父类的private成员跟子类一点关系都没有,而父类的public成员是面向用户的,它不需要子类去继承。所以你可以看出但凡是需要private继承的父类中最多只含有protected和private成员。而父类中如果现,它不是public接口,还是实现的层面,而子类实现出来的东西就不是父类那么对空间限制非常严格非常小的场合又是什么呢?这涉及到空类的继承与复合的区别。现在假设类A是空类,里面啥都没有,类B是正常类,那么有B复合A所占用的空间比B继承A所占用的空间大。具体来讲B中的各成员的存储的时候要求对齐存储,这一点我在《C与指针》有关结构体各成员在内存中的存储情况有过记录,类中各成员的存储情况也是类似的。而且一般来讲一个空类的对象所占的空间也不是0,一般情况下是1byte,因为C++规定一个独立的对象所占用的空间必须非0。这就决定你在采用复合时那个复合类对象所占用的空间中空类成员占用不只1byte。而你如果继承空类,那么子类对象中空优化),不过它一般用于单继承。它的作用在一般情况下是体现不出来的,因为一般的存typedefinthello仅供学习与交流,如有侵权请联系网站删除谢谢780{0return0其实,只要类中不含有非static成员变量,它就被认为是空的,比如说我再往上述例子中添加一个非static成员变量再看。typedefinthello;仅供学习与交流,如有侵权请联系网站删除谢谢79最后作者总结道:原则40:明智而审慎地使用多重继承众所周知C++是支持多重继承的,而JAVA是只支持单继承的。多重继承的好处就是你可以从多个父类中继承多重特性,而坏处就是如果各父类中有相同的名称就非常容易造成歧义。另外,在继承体系的路径上会出现交叉的情况,即像下面这种情况。假设IOFile中有一个成员继承自InputFile和OutputFile,但是InputFile和OutputFile中的这个成员又继承自File,那么IOFile对象的成分中就会包含至少2份相同的父类的成分,而我们只需要一分即可,毫无疑问这会增加对象的体积,并且还会造成时间和空间的浪费。为此C++提出了virtual继承的概念,如下图所示:仅供学习与交流,如有侵权请联系网站删除谢谢81刀过间接寻址来完成的。另外,最底层子类对象在初始化时还要负责对继承层次A是一个接口interface,它等着类B
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026年税务师高频考点模拟试题(含答案解析)
- 2026年全国计算机一级MS Office考试试题及详细答案解析
- 建筑材料合作合同协议
- 人力资源管理概论第九章跨国公司人力资源管理课件
- 总结计划书(6篇)
- 2026年师生气象灾害预警与应对课件
- 2026中国物流信息平台商业模式创新与盈利路径报告
- 2026创新药研发投入产出比与投资回报研究
- 2026中国再生资源行业绿色金融产品创新与风险控制研究报告
- 2026中国土地储备债券发行风险与偿债保障机制
- 2026年广州市中考英语试题(含答案)
- 2026 年师德师风教育:高校辅导员岗位师德素养培育专题课件
- (正式版)DB11∕T 500-2024 《城市道路城市家具设置与管理规范》
- 树木修剪施工方案
- 2026年秋季五年级数学上册教学计划(人教版)
- 具身智能机器人概论 课件全套 第1-7章-具身智能概述 - 第7章-未来发展
- 2026夏季四川成都濛江投资集团有限公司招聘20人笔试参考试题及答案详解
- 2026年工程监理职业技能竞赛
- 中核集团测评题库2026年
- 门诊输液室医生工作制度
- 《妇产科分娩护理规范实践指南(2025版)》
评论
0/150
提交评论