1.3、第三次尝试:全局operator+
隐式转换允许你使用operator+成员函数给SpreadsheetCell对象加上int与double。然而,这个操作符不是双向的,如下代码所示:
aThirdCell = myCell + 5.6;
aThirdCell = myCell + 4;
aThirdCell = 5.6 + myCell; // FAILS TO COMPILE!
aThirdCell = 4 + myCell; // FAILS TO COMPILE!
当SpreadsheetCell对象在操作符左侧的时候可以很好地进行隐式转换,但是当在右边的时候就不灵光了。另外假定是双向的,其实也有问题。问题就是operator+成员函数必须在SpreadsheetCell对象上进行调用,该对象必须在operator+的左手边。c++语言就是这么定义的。所以是没有办法让operator+成员函数按那种方式工作的。
然而,如果把类内的operator+成员函数用全局的operator+函数替换,不与任何特定对象紧密绑定在一起,就可以解决这个问题了。函数看起来像这样:
SpreadsheetCell operator+(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs)
{
return SpreadsheetCell { lhs.getValue() + rhs.getValue() };
}
需要在模块接口文件中声明该操作符并export出来:
export class SpreadsheetCell
{
/* Omitted for brevity */
};
export SpreadsheetCell operator+(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs);
现在所有的四个前面的相加操作都可以按你想要的结果工作了。
aThirdCell = myCell + 5.6; // works fine
aThirdCell = myCell + 4; // works fine
aThirdCell = 5.6 + myCell; // works fine
aThirdCell = 4 + myCell; // works fine
你可能会想,如果按下面的代码写会有什么结果:
aThirdCell = 4.5 + 5.5;
它会正常编译并执行,但是不会调用你写的operator+。它会执行正常的double相加4.5与5.5,结果是下面的中间语句:
aThirdCell = 10;
为了使这个赋值语句好好干活,应该在右手边有一个SpreadsheetCell对象。编译器发现有一个非显式的用户定义的构造函数使用一个double参数,会使用这个构造函数隐式地将double值转换成一个临时的SpreadsheetCell对象,然后会调用赋值操作符。
2、重载算术操作符
现在你理解了怎么写operator+,那剩下的基本算术操作术就很直接了。下面是+,-,*,与/的声明,可以用<op>来替换+,-,*,与/,结果就是四个函数。也可以重载%,但是对于保存在SpreadsheetCell中的double值是讲不通的,没必要凑数了。
export class SpreadsheetCell { /* Omitted for brevity */ };
export SpreadsheetCell operator<op>(const SpreadsheetCell& lhs,
const SpreadsheetCell& rhs);
operator-与operator*的实现与operator+的实现类似,所以就不赘述了。对于operaotr/,稍有点儿不同,记住要检查是否被零除。如果检测到被零除要抛出一个例外:
SpreadsheetCell operator/(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs)
{
if (rhs.getValue() == 0) {
throw invalid_argument { "Divide by zero." };
}
return SpreadsheetCell { lhs.getValue() / rhs.getValue() };
}
c++不要求在operator*真正实现乘,在operator/中实现除,等等。你可以在operator/中实现乘,在operator/中实现除,等等。然而,那样会非常地令人迷惑,没有道理这么做啊。只要有可能,在实现中用通常的用过的操作符的意思就行了。
注意:在c++中,不要改变操作的运算优先级。例如,*与/要比+与-早检查。一旦运算的优先级定了,用户定义的操作符唯一需要做的事就是去实现。c++也不允许你发明新的操作符号或者改变操作符的参数数量。我们以后还会再详细讨论操作符重载。
2.1、重载算术缩写操作符
除了简单的算术操作符,c++提供了像+=与-=这样的缩写操作符。你可能想当然地认为在类中写了operator+,它就会提供operator+=。对不起,你没有这么走运。你不得不显式地重载缩写算术操作符。这些操作符与基本的算术操作述不同,它们改变了操作符左手边的对象而不是生成一个新对象。第二个细微的不同是,与赋值操作符相似,它们产生了一个指向被改变对象的引用结果。
算术缩写操作符总是要求左手边的类的对象,所以要把它们写成成员函数,而不是全局函数。下面是SpreadsheetCell类的定义:
export class SpreadsheetCell
{
public:
SpreadsheetCell& operator+=(const SpreadsheetCell& rhs);
SpreadsheetCell& operator-=(const SpreadsheetCell& rhs);
SpreadsheetCell& operator*=(const SpreadsheetCell& rhs);
SpreadsheetCell& operator/=(const SpreadsheetCell& rhs);
// Omitted for brevity
}
operator+=的实现,其它的类似。
SpreadsheetCell& SpreadsheetCell::operator+=(const SpreadsheetCell& rhs)
{
set(getValue() + rhs.getValue());
return *this;
}
缩写的算术操作符是基本算术与赋值操作符的组合。有了前面的定义,你可以写出下面的代码:
SpreadsheetCell myCell { 4 }, aThirdCell{ 2 };
aThirdCell -= myCell;
aThirdCell += 5.4;
但是,你不能写出下面这样的代码(有这种想法也是很奇葩!)
5.4 += aThirdCell;
注意:当同时有正常的与缩写版本的某个操作符时,推荐使用正常的那个而不是缩写版本的,以避免代码重复。
下面是一个例子:
SpreadsheetCell operator+(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs)
{
auto result{ lhs }; // Local copy
result += rhs; // Forward to +=()
return result;
}