C++学习笔记----8、掌握类与对象(六)---- 操作符重载(3)

3、重载比较操作符

        在定义类时,比较操作符,>,<,<=,>=,==,和!=,是另一类有用的操作符。c++20标准对这些操作符带来了许多改变,添加了三向比较操作符,也叫做太空操作符,<=>。为了让你更感激c++20以来带来的改变,让我们首先从查看c++20之前必须怎么做,如果你的编译器还不支持三向比较操作符的话,你仍然需要做什么。

3.1、c++20之前重载比较操作符

        与基本算术操作符一样,6个c++20之前的比较操作符应该是全局函数,以便在左手边和右手边的操作符参数都可以使用隐式转换。比较操作符都返回布尔型。当然了,你可以改变返回值类型,但不推荐这么做。

        下面是声明,必须将<op>用==,<,>,<=,和>=替换,结果就是6个函数:

class SpreadsheetCell { /* Omitted for brevity */ };
bool operator<op>(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs);

        下面是operator==的定义,其它的类似。

bool operator==(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs)
{
    return (lhs.getValue() == rhs.getValue());
}

        注意:这些重载比较操作符比较double值。大部分情况下,在浮点型值上执行等于或不等于的检查不是一个好主意。应该使用epsilon检查,这超出了我们要讨论的范围。其实就是比较两个数值之间的差值,在一定范围内就认为是相等的,比如0.000001。

        对于拥有更多数据成员的类,比较每一个数据成员会比较痛苦。然而,如果实现了==和<,你就可以基于这两个写出剩余的比较操作符了。例如,下面是一个使用了operator<的operator>=的定义:

bool operator>=(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs)
{
    return !(lhs < rhs);
}

        可以使用这些操作符来比较SpredsheetCell与其它的SpreadsheetCell,也可以比较double与int:

if (myCell > aThirdCell || myCell < 10) {
    cout << myCell.getValue() << endl;
}

        可以看出来,需要写6个单独的函数来支持6个比较操作符,这还只是比较两个SpreadsheetCell。对于当前的6个实现了的比较函数,也能用double来比较SpreadsheetCell,因为double参数会隐式地转化为SpreadsheetCell。前面讨论过,这样的转换效率不高,因为要生成临时对象,与前面讨论的operator+一样,可以通过实现显式函数来比较double来避免。对于每一个操作符<op>,需要下面三个重载:

bool operator<op>(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs);
bool operator<op>(double lhs, const SpreadsheetCell& rhs);
bool operator<op>(const SpreadsheetCell& lhs, double rhs);

        如果想要支持所有的比较操作符,那就要写大量的重复代码了!

3.2、c++20之后重载比较操作符

        现在让我们换一下档,来看c++20及之后的版本带来了什么。从c++20开始,对于类的比较操作符的支持就简单多了。首先,现在实际上是推荐应用operator==作为类的一个成员函数而不是全局函数。记住添加上[[nodiscard]]属性是一个好主意,这校招操作符的结果就不会被忽略。下面是一个例子:

[[nodiscard]] bool operator==(const SpreadsheetCell& rhs) const;

        从c++20开始,单独的operator==重载使得下面的比较管用了:

if (myCell == 10) { println("myCell == 10"); }
if (10 == myCell) { println("10 == myCell"); }

        像10 == myCell这样的表达式现在现编译器重写成了myCell == 10,会调用operator==成员函数。还有,通过实现operator==,编译器自动添加了对于!=的支持;使用!=的表达式会被重写成使用==。

        接下来,要实现所有比较操作符的支持,只需要应用一个另外的重载操作符,operator<=>。一旦类有了operator==与<=>的重载,编译器就自动提供了对于所有6个比较操作符的支持!对于SpreadsheetCell类,operator<=>看起来像下面这样:

[[nodiscard]] std::partial_ordering operator<=>(const SpreadsheetCell& rhs) const;

        注意:编译器是不会依据<=>来重写==或!=比较操作符的。这样做的目的是为了避免性能问题,作为operator==的显式实现要比使用<=>效率更高。例如,使用std::string作参数,其operator==与!=的实现会首先检查要比较的两个string的长度。如果长度不同,operator==与!=会立马返回false或true,而不需要去检查每一个单独的字符。然而,operator<=>总是要比较每一个单独的字符,直到发现两个字符不一致。

        在SpreadsheetCell中保存的是double值。记住浮点类型的数只有部分顺序,所以其重载返回std::partial_ordering。其实现比较直接:

partial_ordering SpreadsheetCell::operator<=>(const SpreadsheetCell& rhs) const
{
    return getValue() <=> rhs.getValue();
}

        通过实现operator<=>,编译器自动提供了>,<,<=,和>=的支持,通过重写使用那些使用<=>操作符的表达式。例如,像myCell < aThirdCell的表达式会自动重写成像std::is_lt(myCell <=> aThirdCell)这样的,is_lt()是一个命名比较函数。

        所以,只要实现了operator==与operator<=>,SpreadsheetCell类支持所有的比较操作符:

	if (myCell < aThirdCell) { println("myCell < aThirdCell"); }
	if (aThirdCell < myCell) { println("aThirdCell < myCell"); }

	if (myCell <= aThirdCell) { println("myCell <= aThirdCell"); }
	if (aThirdCell <= myCell) { println("aThirdCell <= myCell"); }

	if (myCell > aThirdCell) { println("myCell> aThirdCell"); }
	if (aThirdCell > myCell) { println("aThirdCell> myCell"); }

	if (myCell >= aThirdCell) { println("myCell>= aThirdCell"); }
	if (aThirdCell >= myCell) { println("aThirdCell>= myCell"); }

	if (myCell == aThirdCell) { println("myCell == aThirdCell"); }
	if (aThirdCell == myCell) { println("aThirdCell == myCell"); }

	if (myCell != aThirdCell) { println("myCell != aThirdCell"); }
	if (aThirdCell != myCell) { println("aThirdCell != myCell"); }

        因为SpreadsheetCell类支持从double向SpreadsheetCell的隐式转换,像下面这样的比较也是支持的:

	if (myCell < 10) { println("myCell < 10"); }
	if (10 < myCell) { println("10 < myCell"); }
	if (10 != myCell) { println("10 != myCell"); }

        对于两个SpreadsheetCell对象的比较,编译器会用operator==与<=>重写这样的表达式,有选择性的交换参数的顺序。例如,10 < myCell首先会被重写成is_lt(10 <=> myCell),它是不会工作的,因为我们只有<=>作为一个参数的重载,意思是左手边的变量必须是一个SpreadsheetCell。注意到这一点,编译器就会尝试重写表达式为与is_gt(myCell <=> 10)等价,这就很好使了。

        与以前一样,如果你想避免隐式转换轻微的性能影响,可以提供double特定的重载。从c++20开始,这其实并不是很大量的工作。只需要提供两个额外的重载操作符作为成员函数:

[[nodiscard]] bool operator==(double rhs) const;
[[nodiscard]] std::partial_ordering operator<=>(double rhs) const;

        实现如下:

bool SpreadsheetCell::operator==(double rhs) const
{
    return getValue() == rhs;
}

partial_ordering SpreadsheetCell::operator<=>(double rhs) const
{
    return getValue() <=> rhs;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王俊山IT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值