effective C++是一本很好的书籍,但由于翻译的影响,有时候读起来不是那么美好,所以在这整理了一份在实际开发过程中遇到的,个人认为重点的部分,希望各位同学和前辈进行指正,有问题可以提出,之后慢慢修改,C++基础知识不再过多解释,后续会补充C++11新特性的一些知识,希望大家共同进步
1、使用const、enum来替换#define
优点:
(1)const和enum在编译期间会有类型检查,define只有字符替换,并且在预处理期间替换完成,define没有类型检查
(2)const会被调试器识别,define不会
(3)const有作用域的限制,define没有
(4)enum会为每一个值分配一个类型,这样把enum赋值给非enum对象的时候,编译器会报错
(5)enum也会被调试器识别,define不会
(6)enum有作用域的限制,define没有
PS:使用inline替换define
(1)在制作宏函数的时候,inline函数可以对参数进行类型检查,define宏函数则没有类型检查
(2)inline可以被调试器识别,宏函数不会
(3)inline有作用域,宏函数没有
const、enum、inline可以调高代码的健壮性和可读性,但是define不能,无论何时,代码中出现比较多的define宏定义时,代码的可读性大大的被降低,阅读者会感到非常头疼
2、确定对象在使用前被初始化完成
(1)代码调试过程中,声明一个结构体,结构体中成员变量类型包括int、double等类型,在某些平台上,程序运行到这里时,可以看到声明完成后,对象内部的变量是一堆随机数,这在之后的使用过程中,会出现不可控的情况,极有可能导致程序崩溃,
(2)除了内置类型之外,剩余初始化的责任落在了构造函数的身上,需要确保构造函数对每一个成员变量进行了初始化
(3)构造函数对成员变量进行初始化是,应使用初始化列表进行初始化, 如果在构造函数内部进行初始化,本质上是一次赋值的过程,通常浪费成本
3、创建一个类时,如果是空类,系统会默认编写什么函数
构造函数、拷贝构造函数、析构函数、重载=运算符函数
4、如果不想使用这些默认编写的函数,应该明确的进行拒绝
将内置函数(拷贝构造函数等)声明为private,并且不去实现它(空实现),这样做的时候,可以将函数的参数中只写类型,不写参数,本来就没有意义,写不写都一样
class student
{
private:
student(cosnt student & ){}
}
5、为多态基类声明virtual析构函数
用于多态情况,当父类的指针指向子类的对象时,进行析构时,父类的析构函数不是virtual声明的,那么只会释放父类的空间,不会释放子类的空间,造成空间浪费,如果父类的析构函数声明为virtual时,调用父类的析构时,会先调用子类的析构函数,确保空间释放完成,在释放父类的空间,这样不会造成资源浪费
6、如果一个类不准备作为基类使用是,内部不要使用virtual进行声明管理
类内部存在virtual关键字时,就会创建一份虚表,虚表会占用一部分内存空间,使得本来的类空间增加,造成资源浪费
7、不要在构造或者析构函数中调用virtual函数
class Transaction
{
public:
Transaction();
virtual void logTransaction() const = 0
}
Transaction:: Transaction()
{
....
logTransaction();
}
class BuyTransaction: public Transaction
{
public:
virtual void logTransaction() const;
}
class sellTransaction: public Transaction
{
public:
virtual void logTransaction() const;
}
执行
BuyTransaction b
会调用BuyTransaction的构造函数,但是会先调用Transaction的构造函数,基类的构造函数会在派生类构造之前调用,Transaction构造函数会调用logTransaction,这时候调用的是基类Transaction中的版本,在构造期间,virtual不会下降到派生类,并且在构造期间,virtual函数还不是virtual函数
8、重载=号返回一个对象 *this
9、重载=号中的自我赋值
在函数最前面添加一个证同测试,达到自我赋值的检验目的
if(this == &other) return *this
含义是,如果传入的就是对象本身,就不做任何事情
10、赋值对象时,不要忘记每一个成员
当一个对象中已经写好拷贝构造函数和一个重载=号函数时,函数运行不会再有问题,但是出现各位同学自己制作的拷贝构造函数时,编译器会生气!!,不会对拷贝构造函数做出任何的警告,为什么这么说,想想看,当你再向这个对象中添加一个成员变量,而又忘记在拷贝构造函数中对其进行赋值,或者在重载函数中漏掉新增成员变量,此时,编译器不会出现任何报错,也不会有任何提醒,相信经验丰富的同学都遇到过此种情况,执行拷贝之后,数据跑偏到外星球了,或者直接崩溃掉,如果此时再发生继承呢,后果不堪设想
11 资源管理,以对象管理资源
以往C风格的代码通常对申请空间的变量进行手动释放,比如new delete,这是一种好的习惯,但是代码量越来越大的时候,不同的人去维护,可能会出现代码中间添加return,当然这是一种非常不好的编码习惯,非常不好!!!,会导致return在delete之前,这样资源一直被占用,这也是为什么代码跑起来,越来越卡,需要重启才能继续使用的一方面原因,同时,delete也有可能会出现多次调用的情况,如果同学们遇到过,一定体验过这种非凡的威力。。。。。(别问为什么我知道,这些死机问题,都体验过o(╥﹏╥)o)
把对象放进对象内,依赖析构函数自动调用的方式,确保资源被释放,这种方式叫RAII机制(resource acquisition is initialization)
在大型程序模型中一定会涉及到多线程操作,优秀的资源分配机制都是通过RAII锁机制来管理,非常方便,重点是安全、可靠
智能指针就是基于这种思想的指针(C++11新特性),注意auto_ptr这种不要使用了
12 new 和delete需要成对并且要采取相同形式
这个比较简单,不多废话了
13、引用传递代替值传递
函数参数都是以实际参数的副本为初值,调用端获得的是函数返回值的一个副本,出现副本一定会出现拷贝构造函数的调用
class Person
{
public:
Person();
virtual ~Person();
private:
string name;
string address;
}
class student : public Person
{
public:
student();
virtual ~student();
private:
string name1;
string address1;
}
bool func(student& s);
student ss;
bool b = func(ss)
这里我们来看一下一共进行了多少看不见的操作,ss对象内有两个string对象,每一次构造都会调用两次string类型的构造,ss继承Person对象,所以又增加连词string类型的构造,通过传值的方式,一个student会调用一次拷贝构造,一次Person的拷贝构造,四次string的拷贝构造,当函数的副本被销毁时,每一个调用构造的动作所对应的析构函数都会被调用,因此一共是六次构造和六次析构
这只是简单类型,如果是我们自己定义的复杂类型呢
所以使用引用来代替值传递,这样不会导致任何的构造函数或者析构函数被调用,没有任何的新对象被创建,如果func不会对引用进行操作,只进行读取,name建议加上const修饰
还有一种情况,避免切割的问题出现
但是遇到内置类型的时候需要特殊考虑,一般内置类型的传值效率比传递引用效率更高,这个同样适用于STL的迭代器和函数对象,原则上他们都是值传递效率更高,所以内置类型、STL的迭代器和函数对象请使用值传递,其他的情况使用引用代替值传递。
内置类型:int double float long等