指针与引用的区别
任何情况下都不能使用指向空值的引用。一个引用必须总是指向某些对象。不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在
使用引用之前不需要测试它的合法性。
指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。
尽量使用C++风格的类型转换
const_cast<>用于转换掉对象的const属性。
dynamic_cast 把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,而且你能知道转换是否成功。失败的转换将返回空指针(当对
指针进行类型转换时)或者抛出异常(当对引用进行类型转换时)。
不要对数组使用多态
class BST { public: BST() :iData(1) {} BST(int d) :iData(d) {} int iData; }; class BalancedBST :public BST { public: BalancedBST() :BST(1), dData(0.1) { } BalancedBST(int i, double d) :BST(i), dData(d) {} double dData; }; void printBSTArray(ostream& s, const BST array[], int num) { for (int i=0;i<num;++i) { s << array[i].iData<<" "; //编译器认为元素间隔为sizeof(BST) } } int main() { BalancedBST bst[10]; printBSTArray(cout, bst, 10);//错误 system("pause"); return 0; }
避免无用的缺省构造函数
谨慎定义类型转换函数
使用explict关键字避免单参数构造函数的隐式类型转换问题
让编译器进行隐式类型转换所造成的弊端要大于它所带来的好处,所以除非你确实需要,不要定义类型转换函数。
自增自减操作符前缀与后缀形式的区别
UPInt& operator++() //前缀++ { *this += 1; return *this; } const UPInt operator++(int) //后缀++,参数只是用来区分前缀与后缀函数调用 { UPInt oldValue = *this; ++(*this); return oldValue; }
后缀++函数内部需要建立一个临时对象作为返回值,因此后缀++函数效率比前缀++低。
不要重载“&&”,“||”, 或“,”
与 C 一样,C++使用布尔表达式短路求值法(short-circuit evaluation)。这表示一旦确定了布尔表达式的真假值,即使还有部分表达式没有被测试,布尔表达式也停止运算。
如果重载&&,||函数时,其变成函数调用,函数调用时需要计算所有参数,没有采用短路计算法,而且C++语言规范没有定义函数参数的计算顺序。
理解各种不同含义的new和delete
new操作符完成的功能:1.分配内存;2.调用构造函数初始化内存中的对象
delete操作符完成的功能:1.调用析构函数析构对象;2.释放内存
使用析构函数防止资源泄漏
资源应该被封装在一个对象里,遵循这个规则,你通常就能避免在存在异常环境里发生资源泄漏。
在构造函数中防止资源泄漏
BookEntry* pb =0
pb= new BookEntry("Addison-Wesley Publishing Company", "One Jacob Way, Reading, MA 01867");
如果构造函数抛出异常,new操作没有成功完成,则程序不会给指针pb赋值,pb将是一个空值。
C++拒绝为没有完成构造操作的对象调用析构函数是有一些原因的,而不是故意为你制造困难。
因为当对象在构造中抛出异常后 C++不负责清除对象,所以你必须重新设计你的构造函数以让它们自己清除。经常用的方法是捕获所有的异常,然后执行一些清除代码,最后再重新抛出异常让它继续转递。
使用智能指针代替raw指针成员变量,就可以防止构造函数在发生异常时发生资源泄漏,你也不用手工在析构函数中释放资源,并且你还能象以前使用非
const 指针一样使用 const 指针,给其赋值。
禁止异常信息exceptions传递到析构函数外
禁止异常传递到析构函数外有两个原因:能够在异常传递的堆栈展开过程中,防止terminate被调用;能够帮助确保析构函数总能完成我们希望它做的所有事情。
理解“抛出一个异常”与“传递一个参数”或“调用一个虚函数”间的差异
调用函数时,程序的控制权最终还会返回到函数的调用处,但是当你抛出一个异常时,控制权永远不会回到抛出异常的地方。
无论通过传值捕获异常还是通过引用捕获,都将进行拷贝操作,因为原对象离开生存空间后其析构函数会被调用。抛出异常运行速度比参数传递慢。
当异常对象被拷贝时,拷贝操作是由对象的拷贝构造函数完成的。该拷贝构造函数是对象的静态类型(static type)所对应类的拷贝构造函数,而不是对象的动态类型(dynamic type)对应类的拷贝构造函数。
通过引用reference捕获异常
避免通过指针传递异常
通过值捕获异常,抛出异常时将对异常对象拷贝两次,而且会产生slicing problem,即派生类的异常对象被作为基类异常对象捕获时,它的派生类行为被切掉了。
通过引用捕获异常,抛出异常时异常对象只被拷贝一次,通过引用捕获异常(catch by reference),你就能避开上述所有问题,不会为是否删除异常对象而烦恼;能够避开 slicing 异常对象;能够捕获标准异常类型;减少异常对象需要被拷贝的数目。
牢记80-20准则
考虑使用lazy evaluation(懒惰计算法)
摊还期望的计算
核心就是over-eager evaluation(过度热情计算法),在要求你做某些事情以前就完成它们。
例如需要经常调用一个集合类的max(),min(),avg()时,在类内部随时跟踪目前集合的最大值最小值平均值,这样在外部调用max(),min(),avg()时就可以迅速的获取结果。(以空间换取时间)
采用 over-eager 最简单的方法就是 caching(缓存)那些已经被计算出来而以后还有可能需要的值。例如你编写了一个程序,用来提供有关雇员的信息,这些信息中的经常被需要的部分是雇员的办公隔间号码。而假设雇员信息存储在数据库里,但是对于大多数应用程序来说,雇员隔间号都是不相关的,所以数据库不对查抄它们进行优化。为了避免你的程序给数据库造成沉重的负担,可以编写一个函数 findCubicleNumber,用来缓存查找到的数据。以后需要已经被获取的隔间号时,可以在 cache 里找到,而不用向数据库查询。
理解临时对象的来源
为使函数成功调用而建立临时对象,当传递给函数的对象类型和参数类型不匹配时会产生这种情况。仅当通过传值方式和常量引用方式传递参数时,才会发生这些类型转换。C++禁止为非常量引用产生临时对象。
建立临时对象的第二种环境是函数返回对象时。
协助完成返回值优化
// the most efficient way to write a function returning an object inline const Rational operator*(const Rational& lhs,const Rational& rhs) { return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); }
编译器进行返回值优化
通过重载避免隐式类型转换
在 C++中有一条规则是每一个重载的 operator 必须带有一个用户定义类型(user-defined type)的参数。
理解虚拟函数、多继承、虚基类和 RTTI 所需的代价
大多数编译器使用virtual table(vtbl)和virtual table pointer(vptr)实现虚函数调用,一个vtbl通常是一个函数指针数组,数组的元素是指向类中虚函数实现的指针。每个类只有一个vtbl,其大小与类中声明的虚函数的数量成正比(包括继承而来的虚函数)。
每个声明了虚函数的对象都有一个看不见的数据成员vptr,指向对应类的vtbl
虚函数所需的代价与内联函数有关。实际上虚函数不能是内联的。
限制某个类所能产生的对象数量
控制只能建立一个对象:
class Printer { public: void reset() { cout << "reset... "; } void selfTest() { cout << "self test... "; } friend Printer& thePrinter(); private: Printer() { cout << "default constructor... "; } Printer(const Printer& p) { cout << "copy constructor... "; } }; Printer& thePrinter() { //唯一的Printer对象是函数的静态成员,第一次调用该函数时才会建立该对象 static Printer p; return p; } int main() { thePrinter().reset(); thePrinter().selfTest(); system("pause"); return 0; }