14-3 算术和关系运算符
14.3.0 基本介绍
通常情况下,我们把算术和关系运算符定义成非成员函数以允许对左侧或右侧的运算对象进行转换(参见14.1节,第492页)。
参数:常量引用。因为这些运算符一般不需要改变运算对象的状态,所以形参都是常量的引用。
返回值:值返回。算术运算符通常会计算它的两个运算对象并得到一个新值,这个值有别于任意一个运算对象,常常位于一个局部变量之内,操作完成后返回该局部变量的副本作为其结果。
如果类定义了算术运算符,则它一般也会定义一个对应的复合赋值运算符。此时,最有效的方式是使用复合赋值来定义算术运算符:
Sale_data operator+(const Sale_data &lhs, const Sale_data &rhs){
Sale_data sum = lhs;
sum += rhs;
return sum;
}
14.3.1 相等运算符
bool operator==(const Sale_data &lhs, const Sale_data &rhs){
return lhs.isbn() == rhs.isbn() &&
lhs.untis_sold == rhs.units_sold &&
lhs.revenue == rhs.revenue;
}
bool operator!=(const Sale_data &lhs, const Sale_data &rhs){
return !(lhs == rhs);
}
设计准则:
- 如果一个类含有判断两个对象是否相等的操作,则它显然应该把函数定义成operator==而非一个普通的命名函数:因为用户肯定希望能使用==比较对象,所以提供了==就意味着用户无须再费时费力地学习并记忆一个全新的函数名字。此外,类定义了==运算符之后也更容易使用标准库容器和算法。
- 如果类定义了operator==,则该运算符应该能判断一组给定的对象中是否含有重复数据。
- 通常情况下,相等运算符应该具有传递性,换句话说,如果a==b和 bq==c都为真,则a==c也应该为真。
- 如果类定义了 operator==,则这个类也应该定义operator!=。对于用户来说,当他们能使用==时肯定也希望能使用!=,反之亦然。
- 相等运算符和不相等运算符中的一个应该把工作委托给另外一个,这意味着其中一个运算符应该负责实际比较对象的工作,而另一个运算符则只是调用那个真正工作的运算符。
14.3.2 关系运算符
关联容器的使用需要类定义关系运算符,一些排序算法同理
关系运算符要满足两个要求:
1.定义顺序关系,令其与关联容器中对关键字的要求一致
2.如果类同时也含有==运算符的话,则定义一种关系令其与==保持一致。特别是,如果两个对象是!=的,那么一个对象应该<另外一个。
对Sale_data类关系运算符的探讨:
尽管我们可能会认为sales_data类应该支持关系运算符,但事实证明并非如此,其中的缘由比较微妙,值得读者深思。
一开始我们可能会认为应该像compareIsbn(参见11.2.2节,第379页)那样定义<,该函数通过比较isbn来实现对两个对象的比较。然而,尽管compareIsbn提供的顺序关系符合要求1,但是函数得到的结果显然与我们定义的==不一致,因此它不满足要求2。
对于sales_data的==运算符来说,如果两笔交易的revenue和units_sold成员不同,那么即使它们的ISBN相同也无济于事,它们仍然是不相等的。如果我们定义的<运算符仅仅比较ISBN成员,那么将发生这样的情况:两个ISBN相同但 revenue和units_sold不同的对象经比较是不相等的,但是其中的任何一个都不比另一个小。然而实际情况是,如果我们有两个对象并且哪个都不比另一个小,则从道理上来讲这两个对象应该是相等的。
而且,如果oerator<逐个比较每个成员的话,operator<没有实际意义,因为我们无法从中得到任何有用的信息
如果存在唯一一种逻辑可靠的<定义,则应该考虑为这个类定义<运算符。如果类同时还包含==,则当且仅当<的定义和==产生的结果一致时才定义<运算符。