【C++基础】第七十七课:[重载运算与类型转换]算术和关系运算符

重载算术和关系运算符

Posted by x-jeff on July 14, 2023

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.算术和关系运算符

通常情况下,我们把算术和关系运算符定义成非成员函数以允许对左侧或右侧的运算对象进行转换。因为这些运算符一般不需要改变运算对象的状态,所以形参都是常量的引用。

算术运算符通常会计算它的两个运算对象并得到一个新值,这个值有别于任意一个运算对象,常常位于一个局部变量之内,操作完成后返回该局部变量的副本作为其结果。如果类定义了算术运算符,则它一般也会定义一个对应的复合赋值运算符。此时,最有效的方式是使用复合赋值来定义算术运算符:

1
2
3
4
5
6
7
//假设两个对象指向同一本书
Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs)
{
	Sales_data sum = lhs; //把lhs的数据成员拷贝给sum
	sum += rhs; //将rhs加到sum中
	return sum;
}

2.相等运算符

通常情况下,C++中的类通过定义相等运算符来检验两个对象是否相等。也就是说,它们会比较对象的每一个数据成员,只有当所有对应的成员都相等时才认为两个对象相等。

1
2
3
4
5
6
7
8
bool operator==(const Sales_data &lhs, const Sales_data &rhs)
{
	return lhs.isbn() == rhs.isbn() && lhs.units_sold == rhs.units_sold && lhs.revenue == rhs.revenue;
}
bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
	return !(lhs == rhs);
}

3.关系运算符

定义了相等运算符的类也常常(但不总是)包含关系运算符。特别是,因为关联容器和一些算法要用到小于运算符,所以定义operator<会比较有用。

通常情况下关系运算符应该:

  1. 定义顺序关系,令其与关联容器中对关键字的要求一致;并且
  2. 如果类同时也含有==运算符的话,则定义一种关系令其与==保持一致。特别是,如果两个对象是!=的,那么一个对象应该<另外一个。

尽管我们可能会认为Sales_data类应该支持关系运算符,但事实证明并非如此。

一开始我们可能会认为应该像compareIsbn那样定义<,该函数通过比较ISBN来实现对两个对象的比较。然而,尽管compareIsbn提供的顺序关系符合要求1,但是函数得到的结果显然与我们定义的==不一致,因此它不满足要求2。

对于Sales_data的==运算符来说,如果两笔交易的revenue和units_sold成员不同,那么即使它们的ISBN相同也无济于事,它们仍然是不相等的。如果我们定义的<运算符仅仅比较ISBN成员,那么将发生这样的情况:两个ISBN相同但revenue和units_sold不同的对象经比较是不相等的,但是其中的任何一个都不比另一个小。然而实际情况是,如果我们有两个对象并且哪个都不比另一个小,则从道理上来讲这两个对象应该是相等的。

基于上述分析我们也许会认为,只要让operator<依次比较每个数据元素就能解决问题了,比方说让operator<先比较isbn,相等的话继续比较units_sold,还相等再继续比较revenue。

然而,这样的排序没有任何必要。根据将来使用Sales_data类的实际需要,我们可能会希望先比较units_sold,也可能希望先比较revenue。有的时候,我们希望units_sold少的对象“小于”units_sold多的对象;另一些时候,则可能希望revenue少的对象“小于”revenue多的对象。

因此对于Sales_data类来说,不存在一种逻辑可靠的<定义,这个类不定义<运算符也许更好。

如果存在唯一一种逻辑可靠的<定义,则应该考虑为这个类定义<运算符。如果类同时还包含==,则当且仅当<的定义和==产生的结果一致时才定义<运算符。