Effective C++读书笔记
《Effective C++》读书笔记
导读 Introduction1. 所谓declaration ,是用来将一个object 、function 、class 或template 的类型告诉编译器,它不带细节信息。
所谓definition ,是用来将细节信息提供给编译器。
对object 而言,其定义式是编译器为它配置内存的地点;对function 或function template 而言,其定义式提供函数本体function body ;对class 或class template 而言,其定义式必须列出该class 或template 的所有members 。
2. 所谓default constructor 是指不需任何参数就可被调用的constructor ,不是没有任何参数,就是每个参数都有默认值。
通常在定义对象数组时,就会需要一个default constructor 。
如果该类没有提供default constructor ,通常的做法是定义一个指针数组,然后利用new 将每个指针一一初始化;在该方法无效的情况下,可以使用placement new 方法。
3. 所谓copy constructor 是以某对象作为另一同种类型对象的初值,或许它最重要的用途就是用来定义何谓以by value 方式传递和返回对象。
事实上,只要编译器决定产生中介的临时性对象,就会需要一些copy constructor 调用动作,重点是:pass-by-value 便意味着调用“copy constructor ”。
4. 初始化initialization 行为发生在对象初次获得一个值的时候。
对于带有constructors 的classes 或structs ,初始化总是经由调用某个constructor 达成。
对象的assignment 动作发生于已初始化的对象被赋予新值的时候。
纯粹从操作观点看,initialization 和assignment 之间的差异在于前者由constructor 执行,后者由operator =执行。
Effective C++(学习笔记)By. SnowVirus
• 条款 :若不想使用编译器自动生成的函数,就该明确拒绝 条款06:若不想使用编译器自动生成的函数,
• Explicity disallow the use of compiler-generated functions you to not want.
– 如果关键字const出现在星号左边,表示被指类型是常量;如果出现 在星号右边,表示指针自身是常量;如果在星号两边,表示被指类 型和指针两者都是常量。 – Const最具有代表性的是函数声明时的应用。Const可以和函数返回 值、各参数、函数自身产生关联。 – Const 成员函数
• 1)它们使class接口比较容易理解。 • 2)它们使“操作const对象”成为可能。
• 吞下因调用析构函数而发生的异常;
– 总结
• 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出 异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。 • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class 应该提供一个普通函数(而非在析构函数中)执行该操作。
• 用法:const_cast<type_id>(expression)
– 该运算符用来修改类型的const或volatile属性。 – 常量指针被转化成非常量指针,并且仍然指向原来的 对象; – 常量引用被转换成非常量引用,并且仍然指向原来的 对象; – 常量对象被转换成非常量对象; – Static_cast • 用法:static_cast<type_id>(expression) – 该运算符把expression转换为type-id类型,但没有 运行时类型检查来保证转换的安全性。 – C++ primer 里说明在进行隐式类型转换都用 » Int I = Static_cast<int>f; // float f 对象必须携带某些信息,主要用来在运行期 决定哪一个virtual函数被调用。这份信息通常由一个所谓vptr (virtual table pointer)指针指出。 – 令class带一个pure virtual(纯虚)析构函数会导致abstract(抽 象)classes ---也就是不能被实体化(instantiated)的class. – 总结
Effective_C++读书笔记版
}
// 成员初始列做法,这才是初始化
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
: theName(name),
theAddress(address),
惭愧。
条款 01:视 C++为一个语言联邦
凭个人体会,不同背景的人会将 C++看作不同的语言,正应了“横看成岭侧成峰,远近高低各不同”。
C++时代,对于 C 程序员最大的幸事和不幸都是基于一点:C++ >= C,我可以抱残守缺,认为 C 就是 C++。
当然,大多数 C 程序员并不会这么做,起码,我不会一直这么做。我首先会积极地用 class 替代 struct,然后试着自定义 constructors
什么 printf 的结果和预期的不一致?另一个问题是 scanf 和 printf 对于复杂的结构不能扩展使用。
至于使用<iostream>的理由,听听 Scott 怎么说吧:“iostream 库的类和函数所提供的类型安全和可扩展性的价值远远超过你当
初的想象,所以不要仅仅因为你用惯了<stdio.h>而舍弃它。毕竟,转换到 iostream 后,你也不会忘掉<stdio.h>。”
// these are now all initializations
thePhones(phones),
numTimesConsulted(0)
// 这个 built-in type 无所谓,不存在效率上的问题
EffectiveC中文版第三版高清PDF总结
Effective C++阅读笔记Effective C++阅读笔记 (1)原则3:尽可能使用const (4)原则5:了解C++默默编写并调用哪些函数 (6)原则6:若不想使用编译器自动生成的函数,就应该明确拒绝 (8)原则7:为多态基类声明virtual析构函数 (11)原则8:别让异常逃离析构函数 (15)原则9:绝不在构造和析构过程中调用virtual函数 (16)原则10:令operator=返回一个reference to *this (19)原则11:在operator=中处理“自我赋值” (19)原则12:复制对象时勿忘其每一个成分 (21)原则13:以对象管理资源 (22)原则14:在资源管理类中小心COPYING行为 (24)原则15:在资源管理类中提供对原始资源的访问 (25)原则16:成对使用new和delete时要采用相同形式 (27)原则17:以独立语句将newed对象置入智能指针 (28)原则18:让接口容易被正确使用,不易被误用 (29)原则19:设计class犹如设计type (30)原则20:宁以引用传递代替值传递 (31)原则21:必须返回对象时,别妄想返回其引用 (32)原则22:将成员变量声明为private (33)原则23:宁以非member、非friend替换member函数 (34)原则24:若所有参数皆需要类型转换,请为此采用非member函数 (35)原则25:考虑写出一个不抛出异常的swap函数 (37)原则26:尽可能延后变量定义式的出现时间 (39)原则27:尽量少做类型转换动作 (40)原则28:避免返回handles指向对象的部成分 (43)原则29:为“异常安全”而努力是值得的 (45)原则30:透彻了解inline(联)的里里外外 (48)原则31:将文件间的变异依存关系降至最低 (50)原则32:确定你的public继承塑造出了IS-A关系 (53)条款33:避免屏蔽继承而来的名字 (54)原则34:区分接口继承和实现继承 (55)原则35:考虑virtual函数以外的其他选择 (57)原则36:决不能重新定义继承而来的非virtual函数 (60)原则37:绝不重新定义继承而来的缺省参数值 (62)原则38:通过复合塑造出HAS-A关系或者根据某物实现出来 (63)原则39:明知而审慎地使用PRIVATE继承 (64)原则40:明智而审慎地使用多重继承 (69)原则41:了解隐式接口和编译期多态 (71)原则42:了解typename的双重意义 (72)原则43:学习处理模版化基类的名称 (74)原则44:将与参数无关的代码抽离templates (76)原则45:运用成员函数模版接受所有兼容类型 (78)原则46:需要类型转换时请为模版定义非成员函数 (80)原则47:请使用traits classes表现类型信息 (82)原则48:认识template元编程 (85)原则49:了解new-handler的行为 (87)原则50:了解new和delete的合理替换时机 (90)条款51:编写new和delete时需固守常规 (92)原则52:写了placement new也要写placement delete (94)原则54:不要忽视编译器的警告 (95)原则54:让自己熟悉包括TR1在的标准程序库 (96)原则55:让自己熟悉Boost (97)本来是写在百度空间的,但是不知道咋回事百度博客中图片看不到了,所以百度博客的不稳定性可见一斑。
Effective C#读书笔记
《Effective C#》读书笔记一、验证二进制兼容性昨天读了《Effective C#》的第一个条款“使用属性代替可访问的数据成员”,讲到要把公有字段修改为私有字段,并用公有属性把它封装起来。
这一点不难理解,不过里面提到了一个关于二进制兼容性的问题,很是有趣,今天来验证一下。
验证思路:创建一个类库内有一个public的类,该类内有一个public的字段。
另外创建一个WinForm程序去读区该字段并显示。
然后修改类库中的字段为属性。
再去运行WinForm,就应该会出错了。
出错具体原因请参看《Effective C#》讲解。
开始吧!创建一个ClassLibrary,叫做TheDLL。
代码如下:public class DataHolder{public String Data = "Hey! Hey!You!You!";}用它生成一个dll。
然后创建一个WindowsFormsApplication,叫做TheForm,上有一个按钮,点击按钮就去读取dll中的数据,把它显示在按钮上。
具体代码如下:public partial class TheForm : Form{public TheForm(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){TheDLL.DataHolder DH = new TheDLL.DataHolder();button1.Text = DH.Data;}}运行,点击按钮,效果如下:然后修改TheDLL的代码为如下:public class DataHolder{private String data = "Hey! Hey!You!You!";public String Data{get { return data; }set { data = value; }}}重新生成dll,把新生成的dll复制到TheForm的debug文件夹下去覆盖原来的dll文件。
More Effective C++读书笔记
Effective C++和More Effective C++More Effective C++读书笔记条款1:指针与引用的区别二者之间的区别是:在任何情况下都不能用指向空值的引用,而指针则可以;指针可以被重新赋值以指向另一个不同的对象,但是引用则总是指向在初始化时被指定的对象,以后不能改变在以下情况下使用指针:一是存在不指向任何对象的可能性;二是需要能够在不同的时刻指向不同的对象在以下情况使用引用:总是指向一个对象且一旦指向一个对象之后就不会改变指向;重载某个操作符时,使用指针会造成语义误解条款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和deletenew操作符完成的功能分两部分:第一部分是分配足够的内存以便容纳所需类型的对象;第二部分是它调用构造函数初始化内存中的对象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+ +规则允许编译器优化不出现的临时对象,所有最佳的办法莫过于:retrunRatinal(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的包容器;算法是对包容器进行处理的函数,并使用选择子来实现Effective C++读书笔记条款1:尽量用const和inline而不用#define1.为方便调试,最好使用常量注意:常量定义一般放在头文件中,可将指针和指针所指的类型都定义成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 deleteoperator 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的成员函数。
高效率(Effective)C++心得
C++ effective心得介绍一些编写C++代码时可以使用的小技巧1.Operator overloading一般的: c& c::operator=(const c&){。
return *this;}; --------格式运算符的重载w = x = y = z = "hello";//可以返回引用,效率高a+b+c+d //必须返回类,而不是返回引用c c::operator=(const c &){ 。
return *this;}; Operators that can be overloaded(可以重载的操作符)+, -, *, /, %, ^, &, +=,-=,*=,/=, (), [], >>, ==,=,->,++,--, ->*, … new, deleteOperators that can not be overloaded()不可以重载的操作符., ::, ?:, .*//Notes for ++, -- operator区别:T& operator ++() //prefix ++aT operator++(int) //postfix a++一般情况下几乎总要遵循operator = 输入和返回的都是类对象的引用的原则.必须返回一个对象时不要试图返回一个引用,当需要在返回引用和返回对象间做决定时,你的职责是选择可以完成正确功能的那个,至于怎么让这个选择所产生的代价尽可能的小,那是编译器的生产商去想的事。
2.const成员函数不被允许修改它所在对象的任何一个数据成员。
mutable在处理“bitwise - constness 限制” 问题时是一个很好的方案,但它被加入到c++标准中的时间不长,所以有的编译器可能还不支持它。
explicit表示必须显示的调用该函数。
3.void including more times#ifndef#define#endif等价于#progma once4 .struct#pragma pack(pop, n)struct Employee{char cName[5];short nAge;float dSalary;bool bMarried;};sizeof(Employee) depends on struct member alignment. Default is 4 bytes alignment. sizeof(Employee) = 162 bytes data wants to start from even address, 4 bytes data wants to start from address that is 4 times, 1 byte data can start from any address我们可以用以下改变默认的对齐方式。
Effective C++
Effective C++一书是一本“专家经验的累积”第一部分让自己习惯C++条款01:视C++为一个语言联邦为了理解C++,你必须认识其主要的次语言,其共有四部分组成:(1)C(2)Object-Oriented C++(3)Template C++ (这是C++的泛型编程--generic programming部分)(4)STL (是个template程序库)!C++高效编程守则视状况而变,取决于你使用C++的哪一部分。
条款02:尽量以const,enum,inline替换#define!对于单纯常量,最好以const对象或enums替换#define。
!对于形似函数的宏(macros),最好改用inline函数替换#define。
条款03:尽可能使用const!将某些东西声明为const可帮助编译器侦测出错误用法。
const可被施加于任何作用域内的对象、函数参数,函数返回类型,成员函数本体。
!编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”!当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
条款04:确定对象被使用前已先初始化!为内置型对象进行手工初始化,因为C++不保证初始化它们。
!构造函数最好使用成员初值列,而不是在构造函数本体内使用赋值操作。
初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
!为免除“跨编译单元之初始化次序”的问题,请以local static对象替换non-local static对象。
第二部分构造/析构/赋值运算条款05:了解C++默默编写并调用哪些函数class Empty{public:Empty() {... }Empty(const Empty &E){...}~Empty(){...}Empty &operator=(const Empty &E){...}};!编译器可以暗自为class创建default构造函数、copy函数、copy assignemnt操作符,以及析构函数。
effectiveC++(Chapter5) 学习笔记
7
© Nokia 2016
<Change information classification in footer>
Item #27 Minimizing casting
Old –style casts: • C style cast: (T)expression • function style cast: T(expression)
// derived onResize impl;
static_cast<Window>(*this).onResize(); // cast *this to Window, then call its onResize;
... // do SpecialWindow-specific stuff
8
© Nokia 2016
<Change information classification in footer>
Item #27 Minimizing casting
class Window { // base class public: virtual void onResize() { ... } // base onResize impl
2 © Nokia 2016 NOKIA Proprietary
Item #26 Postpone variable definition as long as possible
std::string encryptPassword(const std::string& password) { using namespace std; string encrypted;
Effective C++ Learning
Effective C#中文版:改善C#程序的50种方法
为什么程序已经可以正常工作了,我们还要改变它们呢?答案就是我们可以让它们变得更好。
我们常常会改变所使用的工具或者语言,因为新的工具或者语言更富生产力。
如果固守旧有的习惯,我们将得不到期望的结果。
对于C#这种和我们已经熟悉的语言(如C++或Java)有诸多共通之处的新语言,情况更是如此。
人们很容易回到旧的习惯中去。
当然,这些旧的习惯绝大多数都很好,C#语言的设计者们也确实希望我们能够利用这些旧习惯下所获取的知识。
但是,为了让C#和公共语言运行库(Common Language Runtime,CLR)能够更好地集成在一起,从而为面向组件的软件开发提供更好的支持,这些设计者们不可避免地需要添加或者改变某些元素。
本章将讨论那些在C#中应该改变的旧习惯,以及对应的新的推荐做法。
条款1:使用属性代替可访问的数据成员C#将属性从其他语言中的一种特殊约定提升成为一种第一等(first-class)的语言特性。
如果大家还在类型中定义公有的数据成员,或者还在手工添加get和set方法,请赶快停下来。
属性在使我们可以将数据成员暴露为公有接口的同时,还为我们提供了在面向对象环境中所期望的封装。
在C#中,属性(property)是这样一种语言元素:它们在被访问的时候看起来好像是数据成员,但是它们却是用方法实现的。
有时候,一些类型成员最好的表示形式就是数据,例如一个客户的名字、一个点的x/y坐标,或者上一年的收入。
使用属性我们可以创建一种特殊的接口——这种接口在行为上像数据访问,但却仍能获得函数的全部好处。
客户代码[1]对属性的访问就像访问公有变量一样。
但实际的实现采用的却是方法,这些方法内部定义了属性访问器的行为。
.NET框架假定我们会使用属性来表达公有数据成员。
事实上,.NET框架中的数据绑定类只支持属性,而不支持公有数据成员。
这些数据绑定类会将对象的属性关联到用户界面控件(Web控件或者Windows Forms控件)上。
EffectiveC++条款总结
EffectiveC++条款总结⾃⼰在看这本书的时候,回去翻看⽬录的时候,有些规则会被遗忘,因此做个简单的⼩总结供⾃⼰和其他⼈参考,没读过的还是要先去读⼀遍的⼀.让⾃⼰习惯C++1.视C++为⼀个语⾔联邦 C++是⼀种包含许多特性的语⾔,因⽽不要把它视为⼀个单⼀语⾔。
理解C++⾄少需要学习⼀下4个部分: ①C语⾔。
C++仍以C为基础 ②objected-oriented C++。
⾯向对象编程,类、封装、继承、多态 ③template C++。
C++泛型编程、模板元编程的基础 ④STL。
容器、迭代器、算法2.尽量使⽤const等替换#define 这条个⼈觉得没必要,有许多功能除了宏是很难实现的,像许多优秀的开源项⽬,例如tensorflow中,就⼤量使⽤的宏。
3.尽可能使⽤const ①const可以作⽤于:变量、指针、函数参数类型、类中的常函数。
const可以防⽌变量被意外的修改,有助于编译器检测这些意外改变。
②当non-const和const实现相同逻辑时,non-const对象可以调⽤const成员函数,这样可以缩减代码量。
另外注意const对象不能调⽤non-const成员函数,编译报错:discards qualifiers。
4.确定对象使⽤前被初始化 这⾥提到⼀个重要的基本概念: 在构造函数的初始化列表中的才算是初始化,⽽构造函数的内容是在初始化列表之后执⾏的,已经不算是初始化操作。
这⾥就存在效率问题,假设你的类成员是个其他类的对象,⽐如std::string name,你在初始化列表中进⾏初始化,调⽤的是string的拷贝构造函数,⽽在构造函数中进⾏赋值的话,调⽤的是:默认构造函数+赋值函数,调⽤默认构造的原因是,调⽤构造函数之前会先对成员进⾏初始化(这也就是为什么在构造函数中进⾏的操作不能称之为初始化操作),⽽对于⼤多数类,默认构造函数+赋值函数的效率是⼩于只调⽤拷贝构造函数的。
EFFECTIVE C 读书笔记
Effective C++读书笔记标签:读书c++编译器deleteraiireference2010-05-1716:2911020人阅读评论(6)收藏举报分类:C++(5)作者同类文章X版权声明:本文为博主原创文章,未经博主允许不得转载。
记得前段时间又一次拿起《Effective C++》的时候,有种豁然开朗的感觉,所以翻出了我第一遍读时做的笔记。
只做参考以及查阅之用。
如有需要请参阅《Effective C++》书本。
by shenzi/2010.5.17一.让自己习惯C++条款01:视C++为一个语言联邦为了更好的理解C++,我们将C++分解为四个主要次语言:?C。
说到底C++仍是以C为基础。
区块,语句,预处理器,内置数据类型,数组,指针统统来自C。
?Object-Oreinted C++。
这一部分是面向对象设计之古典守则在C++上的最直接实施。
类,封装,继承,多态,virtual函数等等...?Template C++。
这是C++泛型编程部分。
?STL。
STL是个template程序库。
容器(containers),迭代器(iterators),算法(algorithms)以及函数对象(function objects)...请记住:?这四个次语言,当你从某个次语言切换到另一个,导致高效编程守则要求你改变策略。
C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。
条款02:尽量以const,enum,inline替换#define这个条款或许可以改为“宁可以编译器替换预处理器”。
即尽量少用预处理。
编译过程:.c文件--预处理-->.i文件--编译-->.o文件--链接-->bin文件预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。
检查包含预处理指令的语句和宏定义,并对源代码进行相应的转换。
预处理过程还会删除程序中的注释和多余的空白字符。
可见预处理过程先于编译器对源代码进行处理。
Effective_C_
Effective C#中文版:改善C#程序的50种方法《Effective C#》Item 1:用属性来访问类的私有成员在程序中,难免要访问某个对象的私有成员。
那么以前实现这类功能的方法有两种,第一种方法最简单,就是把成员访问符从“private”改为“public”即可;而另一个就是提供公有的成员访问函数来进行访问。
那么现在用C#编写程序,就不再需要采用前面所说的两种方法了,而直接使用属性来完成。
首先来看看三种方法的如何实现以及调用的,这里用一个例子来说明,即访问“EmployeeInfo”类的私有成员strName,具体如下表格所示。
因此可以看出使用属性不但没有破坏类的封装性,没有减弱代码的安全性,而且和第一种方法一样简便,只是在效率方面要略低于第一种方法。
但总体看来,在C#中用属性来访问类的私有成员是不二的选择。
不过对于使用属性,以及如上表格所说的,难免会有人产生如下一些疑问。
疑问一:就是用属性是否能达到成员函数那样的效果,即完成复杂的代码操作。
其实属性的底层实现是借助于成员函数,只不过这部分转换是由系统帮忙做的,所以在编写属性的时候,可以像编写成员函数一样,即在成员函数中所能写的代码片断,完全可以在属性中套用。
下面就贴出属性所转换的微软中间语言(MSIL)代码。
.property instance string Name(){.get instance string NameSpace.EmployeeInfo::get_Name().set instance void NameSpace.EmployeeInfo::set_Name(string)}// end of property EmployeeInfo::Name.method public hidebysig specialname instance string get_Name() cil managed{...}// end of method EmployeeInfo::get_Name.method public hidebysig specialname instance void set_Name(string 'value') cil managed {...}// end of method EmployeeInfo::set_Name如上就是前面EmployeeInfo类的Name属性所转换的中间语言代码(不过省略了函数的具体实现代码,因为这里并不是为了研究中间语言代码,如果需要对这部分有更多地了解,参看中间语言相关书籍)。
effective C++笔记
要不没有参数,要不每个参数都有缺省的值 例子:
class A{ public:
A(); //default 构造函数 }; class B{ public:
explicit B(int x=0, bool b=true); //defualt 构造函数,同时禁止隐式初始化 }; class C{
……………………... const char& operator[](size_t position) const; {
………. return text[positon]; } char& operator[](size_t position); { //让 non-const operator[]调用其 const 兄弟 //明确的指出调用的是 const operator[] //首先将*this 从原始的 TextBlock&转化为 const TextBlock& //从 const operator[]的返回值中移除 const //*this 表示什么???
3) 函数对象(function objects)
“行为像函数”的对象,这样的对象来自于重载 operator()的 classes
4) 命名习惯
例子: Rational a, b; a*b; a) non-member operator*函数的声明: const Rational operator* (const Rational& lhs, const Rational& rhs); note: lhs->left hand side rhs->right hand side b) 成员函数声明:左侧的实参由指针 this 表现出来了 const Rational operator* (const Rational& rhs);
《effective c++》读后感
《Effective C++》读后感记得大一时第一门专业课就是C++,毫无计算机基础的我当时对计算机并不太感兴趣,云里雾里地学了一年C++,之后C++用得也不多,最近读了《Effective C++》,就更加觉得自己对C++的认识非常肤浅。
正如本书的副标题“改善程序与设计的55个具体做法”所说,本书并没有你告诉什么是C++语言,怎样使用C++语言,而是从一个经验丰富的C++大师的角度告诉程序员:怎么样快速编写健壮的,高效的,稳定的,易于移植和易于重用的C++程序。
所以阅读本书需要有一定的C++基础,《C++ Primer》是个不错的入门选择。
不过,纸上得来终觉浅,绝知此事要躬行,想要更好地理解书中内容,还是得多实践才行,程序员终究还是要和代码打交道的。
本书共55个条款,分为9章,从多个角度介绍了C++的使用经验和应遵循的编程原则。
书中语言平实,主要以“问题+解答”的形式来阐述。
个人比较感兴趣的是关于设计的这三章(第4章:设计与声明,第5章:实现,第6章,继承与面向对象设计),由于平时Java用得比较多,这几章的内容与Java比较相近,读起来也更流畅,最主要的还是编程时非常实用。
文中所提到的成员函数、非成员函数、虚函数声明,重写派生类中函数、单一继承、多重继承等等,都与这些内容紧密相关。
在模板与泛型编程方面,C++ templates的最初发展动机很直接:让我们得以建立“类型安全”的容器如vector,list和map。
模板是泛型编程的基础,而泛型编程使写出的代码和其所处理的对象类型相互独立。
面向对象提供了运行期的多态,而模板则提供了编译期的多态。
模板的编译期多态机制使得函数匹配机制相对于非模板的函数匹配发生了一些变化,也影响了继承体系下的一些声明与设计。
当然,印象最深的还是C++的内存管理。
内存管理一直是C++的难题,也是C++最有争议的问题之一,C++高手能够从中获得更好的性能,更大的自由,C++菜鸟收获的则是一遍一遍的代码检查和对C++的痛恨,内存管理在C++中无处不在,内存泄漏就像地雷一样让人防不胜防,几乎在每个C++程序中都会发生。
EffectiveCPP总结
Effective C++ 总结1.类中有指针成员时需要主意的问题如果类中包含一个或多个指针成员,也即是说该类需要动态分配内存,那么就意味着构造函数、析构函数、复制构造函数及赋值操作符都不能使用合成的版本,都必须写自己的版本,具体说来有以下几点:1.在每个构造函数里对指针进行用new开辟内存或初始化。
对于一些构造函数,如果没有内存要分配给指针的话,指针要被初始化为0(即空指针)。
2.在析构函数里用delete释放指针指向的内存。
3.在复制构造函数内,拷贝那些被指针指向的数据,从而使每个对象都有自己的拷贝,而不是拷贝指针本身。
否则,可能会有几个对象的指针成员指向同一个数据内存,那么其中任一个对象析构时,都会释放掉该数据内存。
4.在赋值操作符函数内,首先先用delete释放掉指针当前指向的内存;然后再拷贝那些被指针指向的数据,而不是拷贝指针本身。
否则会造成两个问题,一是内存泄露(因为当指针当前指向的内存没有被释放),二是也会有几个对象的指针指向同一个数据内存的情况。
2.构造函数与初始化列表初始化类中的非静态数据成员有两种方式:一是使用初始化列表对类中的非静态数据成员进行初始化,一是在构造函数内对非静态数据成员赋值。
尽量使用初始化列表而不是在构造函数内对数据成语赋值。
1.使用初始化列表的必要性和优越性:对于const和引用类型的数据成员,必须使用初始化列表进行初始化;对于其他类型的数据成员,尽量使用初始化列表,因为在构造函数内赋值要经过两个步骤,即先初始化,再赋值,效率低下。
2.初始化方式:如果没有写上初始化列表,则C++会自动合成一个,其初始化方式是采用默认的初始化方式,对于内置类型的数据成员,如果在全局域内,被初始化为0,否则无固定初值,对于非内置类型的数据成员,如果有默认构造函数,则使用之,否则无初始化;如果写上初始化列表,则相当于覆盖了合成的版本,其初始化方式按照所写的方式进行。
3.初始化顺序:初始化顺序与类中声明数据成员的顺序一致,而与在初始化列表中所写的初始化式完全无关;如果有基类,则先初始化基类中的数据成员;析构时的顺序与初始化的顺序恰好相反。
Effective_C++学习笔记
• STL。STL 是个template 程序库,但它是非常特殊的一个。它对容器(containers)、 迭代器(iterators)、算法(algorithms)以及函数对象(function objects)的规约有极 佳的紧密配合与协调,然而templates 及程序库也可以其他想法建置出来。
2.6 条款10:令operator=返回一个reference to *this . . . . . . . . . . . . . . . . 7
2.7 条款11:在operator=中处理“自我赋值” . . . . . . . . . . . . . . . . . . . . 8
2.8 条款12:复制对象时勿忘其每一个成分 . . . . . . . . . . . . . . . . . . . . . 10
6.2 条款36:绝不重新定义继承而来的non-virtual 函数 . . . . . . . . . . . . . . 21
6.3 条款37:绝不重新定义继承而来的缺省参数值 . . . . . . . . . . . . . . . . . 22
6.4 条款38:通过复合塑模出has-a 或“根据某物实现出” . . . . . . . . . . . . . 23
2
目录
第一章 让自己习惯C++
1
1.1 条款01:视C++为一个语言联邦 . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 条款02:尽量以const,enum,inline替换#define . . . . . . . . . . . . . . . . . 1
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Effective C++读书笔记标签:读书c++编译器deleteraiireference2010-05-17 16:29 11020人阅读评论(6) 收藏举报分类:C++(5)作者同类文章X版权声明:本文为博主原创文章,未经博主允许不得转载。
记得前段时间又一次拿起《Effective C++》的时候,有种豁然开朗的感觉,所以翻出了我第一遍读时做的笔记。
只做参考以及查阅之用。
如有需要请参阅《Effective C++》书本。
by shenzi/2010.5.17一.让自己习惯C++条款01:视C++为一个语言联邦为了更好的理解C++,我们将C++分解为四个主要次语言:?C。
说到底C++仍是以C为基础。
区块,语句,预处理器,内置数据类型,数组,指针统统来自C。
?Object-Oreinted C++。
这一部分是面向对象设计之古典守则在C++上的最直接实施。
类,封装,继承,多态,virtual函数等等...?Template C++。
这是C++泛型编程部分。
?STL。
STL是个template程序库。
容器(containers),迭代器(iterators),算法(algorithms)以及函数对象(function objects)...请记住:?这四个次语言,当你从某个次语言切换到另一个,导致高效编程守则要求你改变策略。
C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。
条款02:尽量以const,enum,inline替换#define这个条款或许可以改为“宁可以编译器替换预处理器”。
即尽量少用预处理。
编译过程:.c文件--预处理-->.i文件--编译-->.o文件--链接-->bin文件预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。
检查包含预处理指令的语句和宏定义,并对源代码进行相应的转换。
预处理过程还会删除程序中的注释和多余的空白字符。
可见预处理过程先于编译器对源代码进行处理。
预处理指令是以#号开头的代码行。
例:#define ASPECT_RATIO 1.653记号名称ASPECT_RATIO也许从未被编译器看见,也许在编译器开始处理源代码之前它就被预处理器移走了。
即编译源代码时ASPECT_RATIO已被1.653取代。
ASPECT_RATIO可能并未进入记号表(symbol table)。
替换:const double AspectRatio = 1.653;好处应该有:多了类型检查,因为#define 只是单纯的替换,而这种替换在目标码中可能出现多份1.653;改用常量绝不会出现相同情况。
常量替换#define两点注意:?定义常量指针:const char *authorName = “Shenzi”;cosnt std::string authorName("Shenzi");?类专属常量:static const int NumTurns = 5;//static 静态常量所有的对象只有一份拷贝。
万一你编译器不允许“static整数型class常量”完成“in calss初值设定”(即在类的声明中设定静态整形的初值),我们可以通过枚举类型予以补偿:enum { NumTurns = 5 };*取一个const的地址是合法的,但取一个enum的地址就不合法,而取一个#define的地址通常也不合法。
如果你不想让别人获取一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束。
例:#define CALL_WITH_MAX(a,b) f((a) > (b)) ? (a) : (b))宏看起来像函数,但不会招致函数调用带来的额外开销,而是一种简单的替换。
替换:template<typename T>inline void callWithMax(cosnt T &a, cosnt T &b){f(a > b ? a : b);}callWithMax是个真正的函数,它遵循作用于和访问规则。
请记住:?对于单纯常量,最好以const对象或enums替换#defines;?对于形似函数的宏,最好改用inline函数替换#defines。
条款03:尽可能使用constconst允许你告诉编译器和其他程序员某值应保持不变,只要“某值”确实是不该被改变的,那就该确实说出来。
关键字const多才多艺:例:char greeting[] = "Hello";char *p = greeting; //指针p及所指的字符串都可改变;const char *p = greeting; //指针p本身可以改变,如p = &Anyother;p所指的字符串不可改变;char * cosnt p = greeting; //指针p不可改变,所指对象可改变;const char * const p = greeting; //指针p及所致对象都不可改变;说明:?如果关键字const出现在星号左边,表示被指物事常量。
const char *p和char const *p 两种写法意义一样,都说明所致对象为常量;?如果关键字const出现在星号右边,表示指针自身是常量。
STL例子:const std::vector<int>::interator iter = vec.begin();//作用像T *const, ++iter 错误:iter是conststd::vector<int>::const_iterator cIter = vec.begin();//作用像const T*,*cIter = 10 错误:*cIter是const以下几点注意:?令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而不至于放弃安全性和高效性。
例:const Rational operator* (const Rational &lhs, cosnt Rational &rhs); ?const成员函数使class接口比较容易被理解,它们使“操作const对象”称为可能;说明:声明为const的成员函数,不可改变non-static成员变量,在成员变量声明之前添加mutable可让其在const成员函数中可被改变。
const_cast<char &>(static_cast<const TextBlock &>(*this))[position];//static_cast 将TextBlock &转为const TextBlock &;//const_cast将返回值去掉const约束;请记住:?将某些东西声明为const可帮助编译器侦测出错误用法。
const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体;(conceptual ?编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的车辆”constness);?当cosnt和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
条款04:确定对象被使用前已先被初始化永远在使用对象之前先将它初始化。
对于无任何成员的内置类型,你必须手工完成此事。
至于内置类型以外的任何其它东西,初始化责任落在构造函数身上,确保每一个构造函数都将对象的每一个成员初始化。
赋值和初始化:C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。
所以应将成员变量的初始化置于构造函数的初始化列表中。
ABEntry::ABEntry(const std::string& name, const std::string& address,const std::list<PhoneNumber>& phones){theName = name; //这些都是赋值,而非初始化theAddress = address; //这些成员变量在进入函数体之前已调用默认构造函数,接着又调用赋值函数,thePhones = phones; //即要经过两次的函数调用。
numTimesConsulted = 0;}ABEntry::ABEntry(const std::string& name, const std::string& address,const std::list<PhoneNumber>& phones): theName(name), //这些才是初始化theAddress(address), //这些成员变量只用相应的值进行拷贝构造函数,所以通常效率更高。
thePhones(phones),numTimesConsulted(0){ }所以,对于非内置类型变量的初始化应在初始化列表中完成,以提高效率。
而对于内置类型对象,如numTimesConsulted(int),其初始化和赋值的成本相同,但为了一致性最好也通过成员初始化表来初始化。
如果成员变量时const或reference,它们就一定需要初值,不能被赋值。
C++有着十分固定的“成员初始化次序”。
基类总是在派生类之前被初始化,而类的成员变量总是以其说明次序被初始化。
所以:当在成员初始化列表中列各成员时,最好总是以其声明次序为次序。
请记住:?为内置对象进行手工初始化,因为C++不保证初始化它们;?构造函数最好使用成员初始化列表,而不要在构造函数本体内使用赋值操作。
初始化列表列出的成员变量,其排列次序应该和它们在类中的声明次序相同;?为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static 对象。
二.构造/析构/赋值运算几乎你写的每个类都会有一或多个构造函数、一个析构函数、一个拷贝赋值操作符。
如果这些函数犯错,会导致深远且令人不愉快的后果,遍及整个类。
所以确保它们行为正确时生死攸关的大事。
条款05:了解C++默默编写并调用哪些函数如果你自己美声明,编译器就会为类声明(编译器版本的)一个拷贝构造函数,一个拷贝赋值操作符和一个析构函数。
此外如果你没有声明任何构造函数,编译器也会成为你声明一个默认构造函数。
所有这些函数都是public且inline。
惟有当这些函数被需要(被调用),它们才会被编译器创建出来。
即有需求,编译器才会创建它们。
默认构造函数和析构函数主要是给编译器一个地方用来放置“藏身幕后”的代码,像是调用基类和非静态成员变量的构造函数和析构函数(要不然它们该在哪里被调用呢??)。