文章目录
- [第一章 C++20核心语法特性](#第一章 C++20核心语法特性)
-
- [1.5 三路比较运算符 (<=>)](#1.5 三路比较运算符 (<=>))
-
- [1.5.1 实现原理](#1.5.1 实现原理)
- [1.5.2 使用举例](#1.5.2 使用举例)
-
- [示例1: 自动生成比较运算符](#示例1: 自动生成比较运算符)
- 示例2:自定义比较逻辑
- 示例3:浮点数陷阱
本文记录C++20新特性之三路比较运算符 (<=>)。
第一章 C++20核心语法特性
1.5 三路比较运算符 (<=>)
在C++11/14/17中,如果定义一个支持所有比较运算符的类,需要手动重载6个运算符,如下:
cpp
struct Point {
int x, y;
bool operator==(const Point& other) const { return x == other.x && y == other.y; }
bool operator!=(const Point& other) const { return !(*this == other); }
bool operator<(const Point& other) const {
if (x < other.x) return true;
if (other.x < x) return false;
return y < other.y;
}
bool operator>( const Point& other) const { return other < *this; }
bool operator<=(const Point& other) const { return !(*this > other); } // 不大于
bool operator>=(const Point& other) const { return !(*this < other); } // 不小于
};
void test()
{
Point p1{ 10,20 };
Point p2{ 15,25 };
if (p1 < p2)
{
std::cout << "p1 小于 p2" << std::endl;
}
if (p1 != p2)
{
std::cout << "p1 不等于 p2" << std::endl;
}
/*
p1 小于 p2
p1 不等于 p2
*/
}
C++20引入了三路比较运算符,让上面的代码实现更简洁。
1.5.1 实现原理
三路比较运算符 <=> 返回的不是 bool,而是一个表示"顺序关系"的对象。
这三种顺序关系如下:
1 std::strong_ordering,表示强序。如果 a == b,则它们在任何情况下都是可互换的。
适用:int, char, 指针等。
值:less, equal, greater。
2 std::weak_ordering:表示弱序。如果 a == b(等价),它们可能仍然有区别(例如大小写不敏感的字符串比较,"A" 和 "a" 等价,但它们的数据不同)。
适用:大小写不敏感字符串。
值:less, equivalent, greater。
3 std::partial_ordering,表示偏序。某些值可能无法比较(例如浮点数中的 NaN,它既不小于、也不大于、也不等于任何数)。
适用:float, double。
值:less, equivalent, greater, unordered。
编译器的重写规则:当写a < b时,编译器会尝试查找 operator<,如果没有找到,会查找 operator<=>,将代码重写为 (a<=> b) < 0
1.5.2 使用举例
示例1: 自动生成比较运算符
下面Point类支持比较运算符,可以直接operator<=>:
cpp
class Point
{
public:
int x;
int y;
// 一行代码,自动生成 == , != , < , <= , > , >= 运算符重载
auto operator<=>(const Point& other) const = default;
};
void test()
{
Point p1{ 10,20 };
Point p2{ 15,25 };
if (p1 < p2) // 编译器自动转换为 (p1 <=> p2) < 0
{
std::cout << "p1 小于 p2" << std::endl;
}
if (p1 != p2) // 编译器自动转换为 (p1 <=> p2) != 0
{
std::cout << "p1 不等于 p2" << std::endl;
}
}
示例2:自定义比较逻辑
假设我们有一个类,比较string类型的name, 并忽略大小写。
cpp
struct User
{
int id;
std::string name;
std::weak_ordering operator<=>(const User& obj) const
{
// 逐字符比较,忽略大小写
std::string s1 = name;
std::string s2 = obj.name;
for (auto& c : s1) c = std::tolower(c);
for (auto& c : s2) c = std::tolower(c);
// string 的 <=> 返回 strong_ordering,我们可以隐式转换为 weak_ordering
if (auto cmp = s1 <=> s2; cmp != 0)
{
return cmp;
}
return std::weak_ordering::equivalent;
}
// 自定义了 <=>后,默认显示
bool operator==(const User& obj) const
{
return (*this <=> obj) == 0;
}
};
void test()
{
User u1{ 1, "Alice" };
User u2{ 99, "alice" };
if (u1 == u2)
{
std::cout << "相等" << endl;
}
// 相等
}
示例3:浮点数陷阱
由于浮点数存在 NaN,只能使用 partial_ordering。
cpp
struct Data {
double value;
// 必须使用 partial_ordering,因为 double 只能偏序比较
std::partial_ordering operator<=>(const Data& other) const {
return value <=> other.value;
}
bool operator==(const Data&) const = default;
};
void test()
{
Data d1{ 3.14 };
Data d2{ 2.71 };
Data d3{ std::nan("") }; // NaN 值
if (auto cmp = d1 <=> d2; cmp == std::partial_ordering::greater)
{
std::cout << "d1 大于 d2" << std::endl;
}
if (auto cmp = d1 <=> d3; cmp == std::partial_ordering::unordered)
{
std::cout << "d1 和 d3 无法比较 (NaN)" << std::endl;
}
/*
d1 大于 d2
d1 和 d3 无法比较 (NaN)
*/
}