条款一:仔细区别指针和引用
指针和引用看起来很不一样,但他们似乎做相同的事情,不论指针还是引用都使得可以间接参考其他对象,那么,怎么样选择呢?
首先认识到第一点,引用必须总代表某个对象,但是当它不代表(指向)任何对象时,就应该使用指针。
//========================================
char* pc = 0; //将指针设定为nullptr
char& rc = *pc; //让引用代表nullptr的解引用,这是有害的,结果不可预料
//========================================
string& rs; //错误!引用必须被初始化
string s("xyzxyz");
string& rs = s; //正确
string *ps; //未初始化的指针,有效,但是风险高
*ps = rs; //错误!使用了未初始化的局部变量ps
ps = &rs; //正确
其次,因为引用总代表某个对象,所以在使用时无需测试其有效性。
void printDouble(const double& rd) {
cout << rd << endl; //不需要测试rd,一定代表某个double
}
void printDouble(const double* pd) {
if (pd)
cout << *pd << endl; //检查指针是否为空指针
}
引用和指针的一个重要差异在于,指针可以被重新赋值,指向另一个对象;但是引用却总是代表它最初获得的哪个对象。
//引用指向最初获得的那个对象,指针可以重新被赋值,指向另一个对象
string s1("Nancy");
string s2("Ciancy");
string& rs = s1; //rs代表s1
string* ps = &s1; //ps指向s1
rs = s2; //rs仍然代表s1,只不过值变成了"Ciancy"
ps = &s2; //ps指向s2, s1没有变化
当你实现某些操作符的时候,也需要使用引用。
vector<int> v1(10);
v1[5] = 10;
vector<int*> v2(10);
*v2[5] = 5; //返回的是指针,[]表示返回某种能够当作assignment赋值对象的东西
总结:当知道指向某个东西并且绝不会改变指向其他东西的时候,或者实现一个操作符而无法由指针达成的时候,选择引用;其他时候请采用指针。
条款二:最好使用c++转型操作符
转型是必要的!旧时的C转型方式几乎允许把任何类型转换为任何其他类型,是十分拙劣的,并且其语法结构是由一对小括号加上一个标识符组成,这是难以辨识的。所以,C++导入4个新的转型操作符。
(type) expression; //旧时c风格
static_cast<type> expression; //现在c++风格
static_cast基本拥有和c旧时转型相同的威力和意义,以及相同的限制,但是不能改变表达式的常量性。
int firstNumber = 1, secondNumber = 1;
double result1 = ((double)firstNumber) / secondNumber; //c风格旧式转型
double result2 = static_cast<double>(firstNumber) / secondNumber; //c++风格转型
const_cast专门用来改变表达式的常量性或者变易性。
class Widget {
public:
virtual void func() {}
};
class SpecialWidget : public Widget {
public:
virtual void func() {}
};
void update(SpecialWidget* psw){}
void updateViaRef(SpecialWidget& rsw){}
int main()
{
//const_cast 常量性和变易性的改变
SpecialWidget sw; //sw是一个非const对象
const SpecialWidget& csw = sw; //csw是sw的一个引用,它是一个const对象
update(&csw); //错误!不能将const SpecialWidget* 传给一个需要SpecialWidget* 的函数
update(const_cast<SpecialWidget*>(&csw)); //正确, &csw的常量性被去除了,因此csw(sw)在该函数中可被更改
update((SpecialWidget*)(&csw)); //正确,使用的是c风格,较难辨识
Widget* pw = new SpecialWidget;
update(pw); //错误!pw类型是Widget*,但是update需要SpecialWidget*
update(const_cast<SpecialWidget*>(pw)); //错误!const_cast只能更改常量性,无法进行继承的向下转型(cast down)动作
}
dynamic_cast继承体系中安全的向下转型和跨系转型动作
//dynamic_cast 继承体系中安全的向下转型和跨系转型动作
Widget* pw2 = nullptr;
update(dynamic_cast<SpecialWidget*>(pw2)); //正确,传递给update函数一个指针是指向变量类型为SpecialWidget的pw2的指针, 如果pw2确实指向一个对象,否则传递过去的将是空指针
Widget* pw3 = new SpecialWidget;
updateViaRef(dynamic_cast<SpecialWidget&>(*pw3)); //正确,传递给updateViaRef函数的是pw3所指向的SpecialWidget
//dynamic_cast 只能用于继承体系,无法改变常量性和应用于缺乏虚函数的类型身上
double result3 = dynamic_cast<double>(firstNumber) / secondNumber; //错误!
const SpecialWidget sw4;
update(dynamic_cast<SpecialWidget*>(&sw4)); //错误!
reinterpret_cast用于转换函数指针,当函数返回类型不同时使用,FuncPtr的类型是void(*)()。
typedef void (*FuncPtr)(); //FuncPtr是一个指向函数的指针
int doSomething() { return 1; }
int main()
{
//reinterpret_cast用于转换函数指针
FuncPtr funcPtrArr[10]; //funcPtrArr是一个能容纳10个FuncPtr指针的数组
//funcPtrArr[0] = &doSomething; //错误,类型不匹配
funcPtrArr[0] = reinterpret_cast<FuncPtr>(&doSomething); //转换函数指针的代码是不可移植的(C++不保证所有的函数指针都被用一样的方法表示),在一些情况下这样的转换会产生不正确的结果,所以应该避免转换函数指针类型
}
条款三:绝对不要以多态的方式处理数组
C++允许通过base class的pointers和references来操作"derived class objects所形成的数组",但他几乎绝不会如你预期般运作。
主要原因在于array[i]实际上是一个"指针算数表达式"的简写,代表的其实是*(array+i),那么array和array所指内存两者相距多远呢?答案是i*sizeof(数组中的对象)。
class BST {
public:
virtual ~BST() { fprintf(stdout, "BST::~BST\n"); }
private:
int score;
};
class BalanceBST : public BST {
public:
virtual ~BalanceBST() { fprintf(stdout, "BalanceBST::~BalanceBST\n"); }
private:
int length;
int size;
};
//有个函数打印BST数组的每一个BST内容, 假设BST对象有一个operator<<可用
void printBSTArray(ostream& s, const BST array[], int numElements) {
for (int i = 0; i < numElements; i++) {
s << array[i];
}
}
int main3()
{
fprintf(stdout, "BST size: %d\n", sizeof(BST)); //8
fprintf(stdout, "BalanceBST size: %d\n", sizeof(BalanceBST)); //16
//========================================
//将由BST对象组成的数组传给以上函数没问题
BST BSTArray[10];
printBSTArray(cout, BSTArray, 10); //运行良好
BalanceBST bBSTArray[10]; //传入BalanceBST对象组成的数组
printBSTArray(cout, bBSTArray, 10); //运行结果不可预测
//原因是array[i]代表*(array+i),array是指向数组起始处的指针,array所指内存与array+i所指内存相距array+i*sizeof(数组中对象)
//传入bBSTarray,但是函数接受的仍以为每一元素为BST的大小,并且继承类对象通常比基类大。
BST* p = new BalanceBST[10];
delete[]p; //如果sizeof(BST)!=sizeof(BalancedBST), 则会segmentation fault
//多态和指针不能混用
return 0;
}