《Effictive C++》读书笔记(一)

一.让自己习惯C++


1.      视C++为一个语言集合:

C语言基础,面向对象部分,Template,STL。C++感觉好博大精深,现在貌似还是第二个阶段,想起刚学C++那会儿,一直用着C++的语法,写着面向过程的代码,实在是囧啊…


2.      尽量以const ,enum,inline替换#define:

a)        当我们定义一个常量时,最好使用const常量,而非#define,因为define会无脑的替换,这个常量可以是专属于某处的,而#define所定义的是全局的。

b)        使用enum类似于#define,但是效果更好,而且enum也可以作为int类型的变量使用。

c)        有时候我们会用#define来定义一个函数,这样做很方便而且没有调用函数的开销,但是这样的函数很容易出问题。而我们使用inline函数,加上模板可以更好地的实现这种效果。

d)        虽然#define可以被这么多种东西替代,但是#define还是必不可少的。#ifndef/#ifdef更是对编译流控制的好方法。


3.      尽可能地使用const:

a)        防止意外修改不该被修改的内容,使之尽早在编译时暴露出来。Const可以修饰任何对象,函数参数,函数返回类型,成员函数本体

b)        对于成员函数,如果不希望其改变对象,可以声明为const的。

c)        const版本和non-const版本的成员函数实现等价时,可以令non-const版本的调用const版本的,使用const_cast去除const即可。


4.      保证对象在使用前被初始化:

a)        在使用对象或者变量前,保证其被初始化,防止其为随机值。

b)        对于类的构造函数,最好使用成员初值列表(member initialization list)来初始化,初值列的成员变量排列次序与声明次序相同(这个不是强制,但是最好如是执行,比如我们申请一个数组,数组大小需要先有初值才行)。而不要用构造函数内进行赋值操作(assignment),这样会先默认初始化再赋值,相当于两次操作。

c)        跨编译单元的初始化顺序,以local static对象替换non-local-static对象。说实话这条没看懂...不过我的简单理解就是类似单例模式,我们在使用对象时,有时候不确定初始化的时机,那么我们不如在使用前,将要用的东东一起初始化了。这样就可以控制顺序了。


二.构造,析构,赋值运算


5.了解C++默默编写并调用了哪些函数

a)      如果我们什么都不写的话,编译器也会为我们生成默认构造函数,析构函数,拷贝构造函数,以及一个赋值运算符。

b)      如果我们自己声明了这些函数,那么C++会使用我们自己编写的这些函数,不会再使用那些默认的。

c)      编译器为我们生成的默认构造函数是没有参数的,如果我们声明了其他的有参数的构造函数,那么这个默认构造函数也会消失,我们如果仍想要无参数的构造函数,那么就要自己再声明一个。

d)      编译器为我们生成的析构函数,不会是virtual的,除非这个类的基类有virtual析构函数。


6.若明确不想使用编辑器自动生成的函数,就应该明确拒绝

a)有时候我们不容许拷贝或者赋值一个对象时,即使我们不写拷贝或者赋值函数,编译器也会为我们生成一个,肿么办呢?答案是自己写一个private的拷贝构造函数和赋值函数,这样从类外就不能调用了。但是从类内还是可以调用的,这时候,我们可以只声明,而不定义,这样,即使有人不小心使用了,在编译链接的时候,也会报错。把错误提前暴露,才容易发现。

b)如果我们怕忘记,或者为了方便,也可以定义一个基类uncopyable,让我们的类继承这个类即可。


7.为多态基类声明虚析构函数:

a)      如果我们不这样做,当使用基类指针控制子类对象时,当析构时会发生只析构基类部分,而子类部分不被释放的尴尬情况。导致内存泄露。

b)      但是这也不代表着为了保险起见,我们全都要用virtual析构函数,因为虚函数的开销是比较大的,所以一般判定需要使用虚析构函数的条件为:只要这个类中有一个其他的虚函数,那么就使用虚析构函数。

c)      既然要使用多态的特性,那么如果基类不是虚析构函数,那我们就不要去继承这个类,不然会引出一大串麻烦。比如string,标准stl等都是非虚析构函数的!(前提是使用多态特性)

d)      析构函数调用的顺序:基类的析构函数先调用,然后依次向下调用子类的析构函数。


8.别让异常逃离析构函数:

a)      我的理解感觉就是,能不抛出异常的,或者感觉会抛出异常的功能,不要放在析构函数中,额外设置一个函数。比如.close()关闭函数。但是,在析构函数中仍然要做一下关闭的操作,防止客户忘记调用close发生异常。但是,在析构函数中的异常,不要抛出,可能会导致程序提前结束。应该在析构函数中就catch并处理。

b)      被析构函数调用的函数如果抛出异常,析构函数也应该catch掉,保证不要再抛出。

c)      如果客户需要对某个操作函数运行期间的异常做出反应,那么最好提供一个普通函数,而不要把这个东东放在析构函数中。


9.绝不在构造和析构函数中调用virtual函数:

a)      我们知道,如果有继承,构造函数会先调用父类构造函数,而如果构造函数中有虚函数,此时子类还没有构造,所以此时的对象还是父类的,不会触发多态。更容易记的是基类构造期间,virtual函数不是virtual函数。

b)      析构函数也是一样,子类先进行析构,这时,如果有virtual函数的话,子类的内容已经被析构了,C++会视其父类,执行父类的virtual函数。

c)      总之,在构造和析构函数中,不要用虚函数。如果必须用,那么分离出一个Init函数和一个close函数,实现相关功能即可。


10.令operator=返回一个referenceto *this:

我们经常会为我们的类写一个operator=的函数,为了保证连锁赋值,比如x= y=z的这种情况能正常执行,最好是返回一个*this的引用。


11.在operator=中处理“自我赋值”:

a)      自我赋值看起来不是那么容易发生,但是程序复杂了什么情况都有,所以为了我们程序的健壮性,还是要处理一下的。

b)      自我赋值会导致程序出错的情况在于,如果字段中有指针,先删除指针原来指向的对象,指向=右边的对象。而自我赋值时会导致原来的对象被删除,而=右边的对象也被删除,因而会出错。

c)解决的方法其实最简单的就是先判定是否相等,如果和自己是同一个对象,直接返回*this,结束。


12.复制一个对象时勿忘其每一个成分:

a)      正常情况下,如果我们不写拷贝构造函数,编译器会为我们生成一个默认的拷贝构造函数,但是这个函数只能实现浅拷贝,如果要实现深拷贝就必须要自己写一个拷贝构造函数。

b)      但是如果我们自己写了拷贝构造函数,就一定要拷贝每一个成分,如果忘记了某个成分,编译器是不会提醒的。尤其在发生继承的时候,更加需要注意拷贝基类成员部分。通过调用基类函数来进行拷贝。

c)      关于initialization和assignment:前者由构造函数执行,后者由operator =执行。两者对应不同的函数动作。两者可能有差不多的内容,但是不要使用一个调用另一个函数,如果实在想要抽象,不如提取一个Init函数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值