比较运算符
比较参数。
运算符名 | 语法 | 可重载 | 原型示例(对于 class T) | |
---|---|---|---|---|
类内定义 | 类外定义 | |||
等于 | a == b
|
是 | bool T::operator==(const T2& b) const; | bool operator==(const T& a, const T2& b); |
不等于 | a != b
|
是 | bool T::operator!=(const T2& b) const; | bool operator!=(const T& a, const T2& b); |
小于 | a < b
|
是 | bool T::operator<(const T2& b) const; | bool operator<(const T& a, const T2& b); |
大于 | a > b
|
是 | bool T::operator>(const T2& b) const; | bool operator>(const T& a, const T2& b); |
小于或等于 | a <= b
|
是 | bool T::operator<=(const T2& b) const; | bool operator<=(const T& a, const T2& b); |
大于或等于 | a >= b
|
是 | bool T::operator>=(const T2& b) const; | bool operator>=(const T& a, const T2& b); |
三路比较(C++20) | a <=> b
|
是 | /*R*/ T::operator<=>(const T2& b) const; | /*R*/ operator<=>(const T& a, const T2& b); |
|
双路比较
双路比较运算符表达式的形式为
左操作数 < 右操作数
|
(1) | ||||||||
左操作数 > 右操作数
|
(2) | ||||||||
左操作数 <= 右操作数
|
(3) | ||||||||
左操作数 >= 右操作数
|
(4) | ||||||||
左操作数 == 右操作数
|
(5) | ||||||||
左操作数 != 右操作数
|
(6) | ||||||||
所有情况下,对于内建运算符,左操作数 和 右操作数 在应用左值到右值、数组到指针和函数到指针标准转换后,必须具有下列类型之一
- 算术或枚举类型(见下文的算术比较运算符)
- 指针类型(见下文的指针比较运算符)
如果两个运算数在应用这些转换前均具有数组类型,那么这种比较被弃用。 (C++20 起)
任何情况下,结果都是 bool 纯右值。
算术比较运算符
如果两个操作数具有算术或(有作用域或无作用域)枚举类型,那么遵循算术运算符的规则,对两个操作数实施一般算术转换。转换之后进行值的比较:
示例
#include <iostream> int main() { std::cout << std::boolalpha; int n = -1; int n2 = 1; std::cout << " -1 == 1? " << (n == n2) << '\n' << "比较两个有符号值:\n" << " -1 < 1? " << (n < n2) << '\n' << " -1 > 1? " << (n > n2) << '\n'; unsigned int u = 1; std::cout << "比较有符号与无符号值:\n" << " -1 < 1? " << (n < u) << '\n' << " -1 > 1? " << (n > u) << '\n'; static_assert(sizeof(unsigned char) < sizeof(int), "不能正确地比较有符号值与较小的无符号值"); unsigned char uc = 1; std::cout << "比较无符号值和较小的有符号值:\n" << " -1 < 1? " << (n < uc) << '\n' << " -1 > 1? " << (n > uc) << '\n'; }
输出:
-1 == 1? false 比较两个有符号值: -1 < 1? true -1 > 1? false 比较有符号与无符号值: -1 < 1? false -1 > 1? true 比较无符号值和较小的有符号值: -1 < 1? true -1 > 1? false
指针比较运算符
比较运算符能用于比较两个指针。
只有相等性运算符(operator== 与 operator!=)能用于比较下列指针对:
- 两个成员指针
- 一个空指针常量与一个指针或成员指针
|
(C++11 起) |
首先,对两个操作数应用指针转换(参数是成员指针时就是成员指针转换)、函数指针转换 (C++17 起)和限定性转换,以获得合成指针类型,规则如下:
1) 如果两个操作数都是空指针常量,那么合成指针类型是 std::nullptr_t
|
(C++11 起) |
- 指向 cv1 void 的指针,和
- 指向 cv2
T
的指针,其中T
是对象类型或 void,
- 指向(可有 cv 限定的)
T1
的指针P1
,和 - 指向(可有 cv 限定的)
T2
的指针P2
,
T1
与 T2
相同或是 T2
的基类,那么合成指针类型是 P1
与 P2
的 cv 结合类型。否则,如果 T2
是 T1
的基类,那么合成指针类型是 P2
与 P1
的 cv 结合类型。- 指向
T1
的成员,它的类型是(可有 cv 限定的)U1
,的指针MP1
,和 - 指向 T2 的成员,它的类型是(可有 cv 限定的)
U2
,的指针MP2
,
T1
与 T2
相同或派生自 T2
,那么合成指针类型是 MP1
与 MP2
的 cv 结合类型。否则,如果 T2
派生自 T1
,那么合成指针类型是 MP2
与 MP1
的 cv 结合类型。P1
和 P2
的类型具有相同层数的多层混合指针和成员指针类型,而仅在任意层上的 cv 限定性不同,那么合成指针类型是 P1
与 P2
的 cv 结合类型在上面的定义中,两个指针类型 P1
和 P2
的 cv 结合类型,是与 P1
拥有相同的层数且每层具有与 P1
相同类型的类型 P3
,其中每层上的 cv 限定性设定如下:
P1
与 P2
的 cv 限定性的合并P1
或 P2
的 cv 限定性不同,那么对顶层和此层之间的每一层添加 const。例如,void* 与 const int* 的合成指针类型是 const void*。int** 与 const int** 的合成指针类型是 const int* const*。注意,在解决 CWG 问题 1512(N3624)之前无法比较 int** 和 const int**。
除上述之外,函数指针和指向 noexcept 函数的指针(只要函数类型相同)的合成指针类型是函数指针。 |
(C++17 起) |
注意这意味着任何指针都能与 void* 进行比较。
两个(转换后的)对象指针的比较结果定义如下:
两个(转换后的)指针的相等性比较的结果定义如下:
reinterpret_cast
关联的指针等)两个(转换后的)成员指针的比较结果定义如下:
C1
的成员而另一成员指针表示不同类 C2
的成员,且 C1
和 C2
都不是对方的基类,那么结果未指明。
struct A {}; struct B : A { int x; }; struct C : A { int x; }; int A::*bx = (int(A::*)) &B::x; int A::*cx = (int(A::*)) &C::x; bool b1 = (bx == cx); // 未指明
struct B { int f(); }; struct L : B {}; struct R : B {}; struct D : L, R {}; int (B::*pb)() = &B::f; int (L::*pl)() = pb; int (R::*pr)() = pb; int (D::*pdl)() = pl; int (D::*pdr)() = pr; bool x = (pdl == pdr); // false bool y = (pb == pl); // true
如果指针 p 比较等于指针 q,那么 p <= q 和 p >= q 都会得到 true 而 p < q 和 p > q 都会得到 false。
如果指针 p 比较大于指针 q,那么 p >= q、p > q、q <= p 和 q < p 都会得到 true 而 p <= q、p < q、q >= p 和 q > p 都会得到 false。
如果未指明两个指针的比较大于或比较相等,那么它们的比较结果未指明。该结果可以不确定,并且甚至不必在程序的同一次执行中,在拥有相同操作数的相同表达式的多次求值之间保持一致:
int x, y; bool f(int* p, int* q) { return p < q; } assert(f(&x, &y) == f(&x, &y)); // 在遵从标准的实现中可能会引发断言
在针对用户定义运算符的重载决议中,对于(包括枚举类型的)每个提升后算术类型 L
和 R
,下列函数签名参与重载决议:
bool operator<(L, R); |
||
bool operator>(L, R); |
||
bool operator<=(L, R); |
||
bool operator>=(L, R); |
||
bool operator==(L, R); |
||
bool operator!=(L, R); |
||
对于每个对象指针或函数指针的类型 P
,下列函数签名参与重载决议:
bool operator<(P, P); |
||
bool operator>(P, P); |
||
bool operator<=(P, P); |
||
bool operator>=(P, P); |
||
bool operator==(P, P); |
||
bool operator!=(P, P); |
||
对于每个是成员对象指针、成员函数指针或 std::nullptr_t 的类型 MP
,下列函数签名参与重载决议:
bool operator==(MP, MP); |
||
bool operator!=(MP, MP); |
||
示例
#include <iostream> struct Foo { int n1; int n2; }; union Union { int n; double d; }; int main() { std::cout << std::boolalpha; char a[4] = "abc"; char* p1 = &a[1]; char* p2 = &a[2]; std::cout << "指向数组元素的指针:p1 == p2 " << (p1 == p2) << ", p1 < p2 " << (p1 < p2) << '\n'; Foo f; int* p3 = &f.n1; int* p4 = &f.n2; std::cout << "指向类成员的指针:p3 == p4 " << (p3 == p4) << ", p3 < p4 " << (p3 < p4) << '\n'; Union u; int* p5 = &u.n; double* p6 = &u.d; std::cout << "指向联合体成员的指针:p5 == (void*)p6 " << (p5 == (void*)p6) << ", p5 < p6 " << (p5 < (void*)p6) << '\n'; }
输出:
指向数组元素的指针:p1 == p2 false, p1 < p2 true 指向类成员的指针:p3 == p4 false, p3 < p4 true 指向联合体成员的指针:p5 == (void*)p6 true, p5 < p6 false
注解
因为这些运算符从左到右组合,所以表达式 a < b < c 会被解析为 (a < b) < c,而不是 a < (b < c) 或 (a < b) && (b < c)。
#include <iostream> int main() { int a = 3, b = 2, c = 1; std::cout << std::boolalpha << (a < b < c) << '\n' // true;可能会有警告 << ((a < b) < c) << '\n' // true << (a < (b < c)) << '\n' // false << ((a < b) && (b < c)) << '\n'; // false }
对用户定义的 operator< 的一项常见要求是严格弱序。尤其是,用比较 (Compare) 类型进行工作的标准算法和容器如 std::sort、std::max_element、std::map 等都对此有所要求。
尽管未指明对随机来源(例如不都指向同一数组中的成员)的指针比较的结果,许多实现都提供指针的严格全序,例如它们被实现为连续的虚拟地址空间中的地址。不这样做的实现(例如并非指针的所有位都是内存地址的一部分而在比较中必须被忽略,或要求进行额外的计算否则指针与整数并非一对一关系),为指针提供具有这项保证的 std::less 的特化。这使得程序可以将随机来源的所有指针都用作标准关联容器(如 std::set 或 std::map)的键。
对于同时满足可相等比较 (EqualityComparable) 和可小于比较 (LessThanComparable) 的类型,C++ 标准库在相等(即表达式 a == b 的值),和等价(即表达式 !(a < b) && !(b < a) 的值)之间做出区别。
N3624 中包含的 CWG 问题 583 的解决方案移除了指针与空指针常量间的比较。
void f(char * p) { if (p > 0) { /*...*/ } // 用 N3624 出错, N3624 前可编译 if (p > nullptr) { /*...*/ } // 用 N3624 出错, N3624 前可编译 } int main() {}
三路比较三路比较运算符表达式的形式为
表达式返回一个对象,使得
如果操作数之一是 bool 类型而另一个不是,那么程序非良构。 如果两个操作数均具有算术类型,或一个具有无作用域枚举类型而另一个具有整数类型,那么对各操作数应用一般算术转换,然后
如果两个操作数都具有相同的枚举类型 如果至少一个操作数是指针或成员指针,那么按需应用数组到指针转换、派生类到基类指针转换、函数指针转换和限定性转换,以将它们转换到同一指针类型,且结果指针类型是对象指针类型,那么 p <=> q 返回 std::strong_ordering 类型的纯右值:
否则程序非良构。 在针对用户定义运算符的重载决议中,对于指针或枚举类型
其中 示例运行此代码 输出: -0 与 0 相等 注解类类型可以自动生成三路比较,见默认比较。 如果两个运算数都是数组,那么三路比较非良构。 unsigned int i = 1; auto r = -1 < i; // 既存陷阱:返回 false auto r2 = -1 <=> i; // 错误:要求窄化转换
|
(C++20 起) |
标准库
标准库中的许多类都重载了比较运算符。
(C++20 中移除) |
检查对象是否指代相同类型 ( std::type_info 的公开成员函数) |
(C++20 中移除)(C++20 中移除)(C++20) |
比较两个 error_code (函数) |
(C++20 中移除)(C++20 中移除)(C++20) |
比较 error_condition 和 error_code (函数) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
按字典序比较 pair 中的值 (函数模板) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
按字典顺序比较 tuple 中的值 (函数模板) |
(C++20 中移除) |
比较其内容 ( std::bitset<N> 的公开成员函数) |
(C++20 中移除) |
比较两个分配器实例 ( std::allocator<T> 的公开成员函数) |
(C++20 中移除)(C++20) |
与另一个 unique_ptr 或 nullptr 进行比较 (函数模板) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
与另一个 shared_ptr 或 nullptr 进行比较 (函数模板) |
(C++20 中移除) |
比较 std::function 和 nullptr (函数模板) |
(C++11)(C++11)(C++20 中移除)(C++11)(C++11)(C++11)(C++11)(C++20) |
比较两个时长 (函数模板) |
(C++11)(C++11)(C++20 中移除)(C++11)(C++11)(C++11)(C++11)(C++20) |
比较两个时间点 (函数模板) |
(C++20 中移除) |
比较两个 scoped_allocator_adaptor 实例 ( std::scoped_allocator_adaptor<OuterAlloc,InnerAlloc...> 的公开成员函数) |
(C++20 中移除)(C++20) |
比较底层 std::type_info 对象 ( std::type_index 的公开成员函数) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
以字典序比较两个字符串 (函数模板) |
(C++20 中移除) |
locale 对象之间的相等性比较 ( std::locale 的公开成员函数) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
按照字典顺序比较 array 中的值 (函数模板) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
按照字典顺序比较 deque 中的值 (函数模板) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
按照字典顺序比较 forward_list 中的值 (函数模板) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
按照字典顺序比较 list 中的值 (函数模板) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
按照字典顺序比较 vector 中的值 (函数模板) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
按照字典顺序比较 map 中的值 (函数模板) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
按照字典顺序比较 multimap 中的值 (函数模板) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
按照字典顺序比较 set 中的值 (函数模板) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
按照字典顺序比较 multiset 中的值 (函数模板) |
(C++20 中移除) |
比较 unordered_map 中的值 (函数模板) |
(C++20 中移除) |
比较 unordered_multimap 中的值 (函数模板) |
(C++20 中移除) |
比较 unordered_set 中的值 (函数模板) |
(C++20 中移除) |
比较 unordered_multiset 中的值 (函数模板) |
按照字典顺序比较 queue 中的值 (函数模板) | |
按照字典顺序比较 stack 中的值 (函数模板) | |
比较迭代器底层迭代器 (函数模板) | |
(C++11)(C++11)(C++20 中移除)(C++11)(C++11)(C++11)(C++11)(C++20) |
比较迭代器底层迭代器 (函数模板) |
(C++20 中移除) |
比较两个 istream_iterator (函数模板) |
(C++20 中移除) |
比较两个 istreambuf_iterator (函数模板) |
(C++20 中移除) |
比较两个复数,或一个复数与一个标量 (函数模板) |
比较两个 valarrays,或比较一个 valarray 和一个值 (函数模板) | |
(C++11)(C++11)(C++20 中移除) |
比较两个伪随机数引擎的内部状态 (函数) |
(C++11)(C++11)(C++20 中移除) |
比较两个分布对象 (函数) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
比较一个 sub_match 与另一 sub_match 、字符串或字符 (函数模板) |
(C++20 中移除) |
以字典序比较两个匹配结果的值 (函数模板) |
(C++20 中移除) |
比较两个 regex_iterator ( std::regex_iterator<BidirIt,CharT,Traits> 的公开成员函数) |
(C++20 中移除) |
比较两个 regex_token_iterator ( std::regex_token_iterator<BidirIt,CharT,Traits> 的公开成员函数) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
比较两个 thread::id 对象 (函数) |
命名空间 std::rel_ops 提供了泛型运算符 !=、>、<= 及 >=
在标头
<utility> 定义 | |
在命名空间
std::rel_ops 定义 | |
(C++20 中弃用) |
自动生成基于用户定义的 operator== 和 operator< 的比较运算符 (函数模板) |
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 583 (N3624) |
C++98 | 所有六个比较运算符都能用来比较指针和空指针常量 | 只有相等性运算符可以 |
CWG 661 | C++98 | 未指明算术比较的实际语义(例如 1 < 2 会生成 true 还是 false )
|
指明语义 |
CWG 879 | C++98 | 指向函数类型或 void 的指针缺少内建比较 | 指明如何比较这些指针 |
CWG 1512 (N3624) |
C++98 | 合成指针类型的规则不完整,从而不允许 int** 与 const int** 间的比较 | 使之完整 |
CWG 1596 | C++98 | 仅就指针算术的目的认为非数组对象属于拥有一个元素的数组 | 该规则也适用于比较 |
CWG 1598 | C++98 | 两个成员指针在两个成员所属类不同且都不是对方的基类时 不会比较相等,即使此时被指向的成员的偏移量可能相等 |
此时结果未指明 |
CWG 1858 | C++98 | 表示相同联合体的不同成员的两个成员指针不明确 是否会如同它们表示相同的成员那样比较相等 |
此时它们比较相等 |
CWG 2419 | C++98 | 只有通过 & 获取的到非数组对象的指针才会在指针比较中被视为到大小为 1 的数组的首元素的指针 |
应用到所有到非数组对象的指针 |
CWG 2526 | C++98 | N3624 移除了指向 void 的指针和函数指针的关系比较(> 、>= 、< 和 <= )的定义
|
恢复原本定义 |
参阅
- 运算符优先级
- 运算符重载
- 比较 (Compare) (具名要求)
常见运算符 | ||||||
---|---|---|---|---|---|---|
赋值 | 自增/自减 | 算术 | 逻辑 | 比较 | 成员访问 | 其他 |
a = b |
++a |
+a |
!a |
a == b |
a[b] |
函数调用 |
a(...) | ||||||
逗号 | ||||||
a, b | ||||||
条件 | ||||||
a ? b : c | ||||||
特殊运算符 | ||||||
static_cast 转换一个类型为另一相关类型 |