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

来源:互联网 发布:esp8266与51单片机 编辑:程序博客网 时间:2024/05/22 03:20

条款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转换为Rationalint getNumerator() const{ return numerator; }int getDenominator() const{ return denominator; }operator double() const{ //隐式类型转换操作符,将Rational转换为doublereturn 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转换为Rationalint getNumerator() const{ return numerator; }int getDenominator() const{ return denominator; }double asdouble() const{ //将Rational转换为doublereturn 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 intpublic: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;//5cout << ++i << endl;//7cout << i-- << endl;//7cout << --i << endl;//5system("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 操作符的实现应以其前置式兄弟为基础。


0 0