比较运算符
比较参数。
运算符名 | 语法 | 可重载 | 原型示例(对 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
|
是 | /*见说明*/ T::operator <=>(const T2 &b) const; | /*见说明*/ operator <=>(const T &a, const T2 &b); |
|
双路比较
双路比较运算符表达式的形式为
左操作数 < 右操作数
|
(1) | ||||||||
左操作数 > 右操作数
|
(2) | ||||||||
左操作数 <= 右操作数
|
(3) | ||||||||
左操作数 >= 右操作数
|
(4) | ||||||||
左操作数 == 右操作数
|
(5) | ||||||||
左操作数 != 右操作数
|
(6) | ||||||||
true
,否则返回 false
。true
,否则返回 false
。true
,否则返回 false
。true
,否则返回 false
。true
,否则返回 false
。true
,否则返回 false
。所有情况下,对于内建运算符,左操作数 和 右操作数 在应用左值到右值、数组到指针和函数到指针标准转换后,必须具有下列类型之一
- 算术或枚举类型(见下文的算术比较运算符)
- 指针类型(见下文的指针比较运算符)
若两个运算数在应用这些转换前均具有数组类型,则这种比较被弃用。 (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*。注意,在 CWG1512 的解决方案之前无法比较 int** 和 const int**。
除上述之外,函数指针和指向 noexcept 函数的指针(只要函数类型相同)的合成指针类型为函数指针。 |
(C++17 起) |
注意这意味着任何指针都能与 void* 进行比较。
两个(转换后的)对象指针的比较结果定义如下:
&obj+1
比较大于 &obj
。两个(转换后的)指针的相等性比较的结果定义如下:
reinterpret_cast
关联的指针等)两个(转换后的)成员指针的比较结果定义如下:
若指针 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 中包含的 CWG583 的解决方案移除了指针与空指针常量间的比较。
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++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 583 | C++98 C++11 |
所有六个比较运算符均能用于比较指针和 nullptr (C++11) 或另一空指针常量 (C++98)
|
仅允许相等性运算符 |
CWG 1512 | C++98 | 合成指针类型的规则不完整,从而不允许 int** 与 const int** 间的比较 | 使之完整 |
CWG 1596 | C++98 | 仅就指针算术的目的认为非数组对象属于拥有一个元素的数组 | 该规则亦适用于比较 |
参阅
- 运算符优先级
- 运算符重载
- 比较 (Compare) (具名要求)
常见运算符 | ||||||
---|---|---|---|---|---|---|
赋值 | 自增 自减 |
算术 | 逻辑 | 比较 | 成员访问 | 其他 |
a = b |
++a |
+a |
!a |
a == b |
a[b] |
a(...) |
特殊运算符 | ||||||
static_cast 转换一个类型为另一相关类型 |