More effective C++阅读整理笔记(条款一、二、三)

本文介绍了C++中的指针和引用的区别,强调了引用的不变性和指针的可变性,并给出了何时选择使用引用的指导原则。此外,详细阐述了C++四种类型的转型操作符——`static_cast`、`const_cast`、`dynamic_cast`和`reinterpret_cast`的用途和注意事项。最后,警告了以多态方式处理数组可能导致的问题,特别是当基类指针操作派生类对象数组时可能引发的错误。
摘要由CSDN通过智能技术生成

条款一:仔细区别指针和引用

        指针和引用看起来很不一样,但他们似乎做相同的事情,不论指针还是引用都使得可以间接参考其他对象,那么,怎么样选择呢?

首先认识到第一点,引用必须总代表某个对象,但是当它不代表(指向)任何对象时,就应该使用指针。

	//========================================
	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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值