【more effective c++读书笔记】【第2章】操作符(1)

条款5:对定制的“类型转换函数”保持警觉

1、两种函数允许编译器执行隐式转换:单自变量constructor 和隐式类型转换操作符。单自变量constructor是指能够以单一自变量成功调用的constructor,这样的constructor可能声明拥有单一参数,也可能拥有多个参数,并且除了第一参数之外都有默认值。隐式类型转换操作符,是一个拥有奇怪名称的member function:关键词operator之后加上一个类型名称。不能为此函数指定返回值类型,因为其返回值类型基本上已经表现于函数名称上了。

2、最好不要提供任何类型转换函数,因为在你从未打算也未预期的情况下,此类函数可能会被调用,而其结果可能是不正确、不直观的程序行为,很难调试。下面用例子来说明:

一、隐式类型转换操作符例子:

#include<iostream>
using namespace std;
//参见http://blog.csdn.net/ruan875417/article/details/47260827条款24的例子
class Rational{
public:
	Rational(int n = 0, int d = 1) :numerator(n), denominator(d){}//可以把int转换为Rational
	int getNumerator() const{ return numerator; }
	int getDenominator() const{ return denominator; }
	operator double() const{ //隐式类型转换操作符,将Rational转换为double
		return static_cast<double>(numerator) / denominator;
	}
	/*friend ostream& operator<<(ostream& os, Rational& r){
		os << r.numerator << '/' << r.denominator;
		return os;
	}*/
private:
	int numerator;
	int denominator;
};

int main(){
	Rational oneHalf(1, 2);
	cout << oneHalf << endl;

	system("pause");
	return 0;
}

上述例子中不注释掉friend ostream&operator<<(ostream& os, Rational& r)函数的运行结果:


上述例子中注释掉friend ostream&operator<<(ostream& os, Rational& r)函数的运行结果:

上述例子中如果Rational类存在friendostream& operator<<(ostream& os, Rational& r)函数,cout << oneHalf << endl;语句就会调用该函数,但当不存在friend ostream&operator<<(ostream& os, Rational& r)函数时,Rational类存在operator double() const这个隐式类型转换操作符,它就将oneHalf隐式转换为double,cout <<oneHalf << endl;调用也能成功。这说明了隐式类型转换操作符的缺点:可能导致错误(非预期)的函数被调用。

解决方法:以功能对等的另一个函数取代类型转换操作符。

#include<iostream>
using namespace std;
//参见http://blog.csdn.net/ruan875417/article/details/47260827条款24的例子
class Rational{
public:
	Rational(int n = 0, int d = 1) :numerator(n), denominator(d){}//可以把int转换为Rational
	int getNumerator() const{ return numerator; }
	int getDenominator() const{ return denominator; }
	double asdouble() const{ //将Rational转换为double
		return static_cast<double>(numerator) / denominator;
	}
private:
	int numerator;
	int denominator;
};

int main(){
	Rational oneHalf(1, 2);
	//cout << oneHalf << endl;//错误,Rational没有operator<<
	cout << oneHalf.asdouble() << endl;//正确

	system("pause");
	return 0;
}

二、单自变量constructors完成的隐式转换例子:

template<class T>
class Array{
public:
	Array(int lowBound, int highBound);
	Array(int size);
	T& operator[](int index);
	...
};

bool operator == (const Array<int>& lhs, const Array<int>& rhs);
Array<int> a(10);
Array<int> b(10);
....
for (int i = 0; i<10; ++i)
    if (a == b[i])//此处a应该是a[i]才对
	    do something;
    else
        do something else;

上述例子中对于if (a == b[i])语句,希望编译器报错,但却能顺利编译通过,因为b[i]可以通过Array(int size)函数转换为Array<int> object,会产生类似以下代码:

for (int i = 0; i<10; ++i)
	if (a == static_cast< Array<int> >(b[i]);

此行为还非常没效率,因为每次走过这个循环,必须产生和销毁一个临时的Array<int> objec。

解决方法:

a、用关键字explicit。只要将constructors声明为explicit,编译器便不能因隐式类型转换的需要而调用它们。

explicit Array(int size);//注意,使用explicit

b、C++中有一条规则:没有任何一个转换程序可以内含一个以上的“用户定制转换行为”。即编译器不能两次调用隐式类型转换函数。所以可以这样做:

template<class T>
class Array{
public:
	class ArraySize{
	public:
		AraaySize(int numElements) : theSize(numElements) {}
		int size() const { return theSzie; }
	private:
		int theSize;
	};

	Array(int lowBound, int highBound);
	Array(ArraySize size);//注意这个新声明
	...
};

上述例子仍然可以用一个int自变量构造起一个Array对象,还能阻止你希望避免的类型转换动作。类似ArraySize这样的classes,往往被称为proxy classes,因为它的每一个对象都是为了其他对象而存在的,好像其他对象的代理人一般。


条款6:区别increment/decrement操作符的前置(prefix)和后置(postfix)形式

1、C++中允许++和--操作符的两种形式(前置式和后置式)拥有重载能力。重载函数是以参数类型来区分的,然而不论是++还是--操作符的前置式或后置式都没有参数,为了区分这两种不同的操作,只好让后置式有一个int自变量,并且在它被调用时,编译器默认给该int指定一个0值。

例子:

#include<iostream>
using namespace std;

class UPInt{     //unlimited precision int
public:
	UPInt(int i) :value(i){}
	UPInt(UPInt& upint) :value(upint.value){}

	UPInt& operator++(); //前置式
	const UPInt operator++(int); //后置式
	UPInt& operator--(); //前置式
	const UPInt operator--(int);//后置式

	friend ostream& operator<<(ostream& os,const UPInt& upint);
private:
	int value;
};
//前置式:累加后取出
UPInt& UPInt::operator++(){//返回一个引用
	this->value += 1;
	return *this;
}
//后置式:取出后累加
const UPInt UPInt::operator++(int){//返回一个const对象
	UPInt oldValue = *this;
	++(*this);//后置式increment和decrement 操作符的实现应以其前置式兄弟为基础。
	return oldValue;
}
//前置式:减后取出
UPInt& UPInt::operator--(){//返回一个引用
	this->value -= 1;
	return *this;
}
//后置式:取出后减
const UPInt UPInt::operator--(int){//返回一个const对象
	UPInt oldValue = *this;
	--(*this);//后置式increment和decrement 操作符的实现应以其前置式兄弟为基础。
	return oldValue;
}
ostream& operator<<(ostream& os,const UPInt& upint){
	os << upint.value;
	return os;
}

int main(){
	UPInt i = 5;
	cout << i++ << endl;//5
	cout << ++i << endl;//7
	cout << i-- << endl;//7
	cout << --i << endl;//5

	system("pause");
	return 0;
}

2、后置式操作符并未动用其参数,其参数的唯一目的是为了区别前置式和后置式而已。

3、后置式操作符必须返回一个const对象的原因是如果不是const对象,以下调用将合法:

UPInt i = 5;
i++++;

即调用i.operator++(0).operator++(0);

不欢迎上述调用的理由有两个:a、它和内建类型的行为不一致;b、其行为非你所预期,第二个operator++所改变的对象是第一个operator++返回的对象,而不是原对象。i只是被累加一次,违反直觉。

4、相较于前置式操作符,后置式操作符效率会更低,因为后置式需要产生一个临时对象来存储原对象的值,然后再返回原对象的值,会发生构造和析构。所以处理用户定制类型时,应该尽可能使用前置式操作符。

5、确定后置式操作行为和前置式的行为一致的原则是后置式increment和decrement 操作符的实现应以其前置式兄弟为基础。


版权声明:本文为博主原创文章,未经博主允许不得转载。

转载于:https://www.cnblogs.com/ruan875417/p/4785427.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值