Item 1 Distinguish between pointers and references.仔细区别指针和引用。
首先:没有null references。一个reference必须总代表某个对象。因此在用引用作为函数参数时,在函数内部可以不用对参数进行有效性测试。
如果有个变量,其目的是用来指向另一个对象,也有可能不指向另一个对象,请使用pointers。
其次:pointers可以被重新赋值,指向另一个对象,reference却总是指向它最初获得的那个对象。
Item 2 Prefer C++ style casts.最好使用C++转型操作符。
首先,C++转型能够精确指明意图。其次,旧式的转型难以辨识。
在林锐《高质量程序设计指南C/C++语言》中:类型转换的本质是创建新的目标对象,并以源对象的值来初始化,所以源对象没有丝毫改变。
static_cast<type>(expression):相当于C风格的强制类型转换。在多重继承下,会正确调整指针的值。
const_cast<type>(expression):用于去除一个对象的const/volatile属性。
reinterpret_cast<type>(expression):可以把一个整数转成地址,或者任何两种类型的指针之间转换,与编译平台息息相关,不具有可移植性。
dynamic_cast<type>(expression):用来执行继承体系中“安全的向下转型或跨系转型动作”。
Item 3 Never treat arrays polymorphically.绝对不要以多态方式处理数组。
见如下代码:
#include <iostream>
using namespace std;
class BST
{
public:
BST(int e=100):element(e) {}
virtual ~BST() {}
friend ostream& operator<<(ostream& os,const BST& bst)
{
os<<bst.element<<" ";
return os;
}
protected:
int element;
};
class BalanceBST:public BST
{
public:
BalanceBST(int e=100,int de=10):BST(e),derivedElement(de) {}
friend ostream& operator<<(ostream& os,const BalanceBST& bbst)
{
os<<bbst.element<<" "<<bbst.derivedElement<<" ";
return os;
}
private:
int derivedElement;
};
void printArray(ostream& s,const BST array[],int numElements)
{
for(int i=0;i<numElements;i++)
s<<array[i];
}
int _tmain(int argc, _TCHAR* argv[])
{
BalanceBST bBSTArray[10];
printArray(cout,bBSTArray,10);
return 0;
}
以上代码的输出是让人莫名其妙的,主要原因在于:array[i]其实是一个“指针算术表达式”,代表的其实是*(array+i),array指向数组的首地址,array+i与array的距离是i*sizeof(数组中的对象),编译器看到参数是BST类型的数组,就会按如下计算,bBSTArray+i*sizeof(BST),被误导了。因此输出是莫名其妙的。
当释放资源的时候也会出这样的情况:
void deleteArray(BST array[])
{
delete[] array;
}
定义了如上的删除数组的函数,你可能会想这样使用:
BalanceBST *balTreeArray=new BalanceBST[50];
deleteArray(balTreeArray);
出现了通过一个基类指针来删除派生类数组的情况,而这样的情况的结果,在C++规范中并没有定义。顾可能导致错误的行为。
Item 4 Avoid gratuitous default contructors.非必要不提供默认构造函数。
如果没有构造函数可能会在三种情况下产生问题:
(1)产生数组。
如果一个类如下定义:
class EquipmentPiece
{
public:
EquipmentPiece(int i):id(i) {}
private:
int id;
};
则在产生数组的时候,就没有任何一种办法为数组的构造函数指定自变量。
另外记录一个有意思的事情:
如果要用指针数组可以这样解决
typedef EquipmentPiece* REP;
然后在使用的地方:
REP* pieces=new REP[10];
for(int i=0;i<10;i++)
pieces[i]=new EquipmentPiece(i);
使用完之后,要记得释放:
for(int i=0;i<10;i++)
delete pieces[i];
delete[] pieces;
如果忘记,则会出现内存泄露的问题。
另外如上的使用方式,还存在的另外一个缺点是需要的内存总量比较大,需要额外的内存来放置指针,一个解决办法是使用placement new,用法如下:
void *rawMemory=operator new[] (10*sizeof(EquipmentPiece));
EquipmentPiece *bestPieces=static_cast<EquipmentPiece*>(rawMemory);
for(int i=0;i<10;i++)
new (&bestPieces[i])EquipmentPiece(i);
for(int i=9;i>=0;i--)
bestPieces[i].~EquipmentPiece();
operator delete[](rawMemory);
(2)不适用于许多模板容器类。对于那些模板比如vector,list等,被实例化的“目标类型”必须有一个默认构造函数。
添加无意义的构造函数也会影响效率,带来的额外负担就是成员函数必须测试字段是否真的被初始化了,初始化是否有效,这样就必须为测试行为付出时间代价,并未测试代码付出空间代价。所以一个类的构造函数要确保对象的所有数据成员都被正确的初始化,如果无法挺高这样的保证,就避免提供默认构造函数。