比较运算符
比较参数。
运算符名称 | 语法 | 可重载 | 原型示例(对于 class T) | |
---|---|---|---|---|
类定义内部 | 类定义外部 | |||
等于 | a == b
|
是 | bool T::operator==(const U& b) const; | bool operator==(const T& a, const U& b); |
不等于 | a != b
|
是 | bool T::operator!=(const U& b) const; | bool operator!=(const T& a, const U& b); |
小于 | a < b
|
是 | bool T::operator<(const U& b) const; | bool operator<(const T& a, const U& b); |
大于 | a > b
|
是 | bool T::operator>(const U& b) const; | bool operator>(const T& a, const U& b); |
小于或等于 | a <= b
|
是 | bool T::operator<=(const U& b) const; | bool operator<=(const T& a, const U& b); |
大于或等于 | a >= b
|
是 | bool T::operator>=(const U& b) const; | bool operator>=(const T& a, const U& b); |
三路比较 (C++20) | a <=> b
|
是 | R T::operator<=>(const U& b) const;[1] |
R operator<=>(const T& a, const U& b);[1] |
|
目录 |
[编辑] 双路比较
双路比较运算符表达式具有以下形式
[编辑] 关系运算符
左操作数 < 右操作数 |
(1) | ||||||||
左操作数 > 右操作数 |
(2) | ||||||||
左操作数 <= 右操作数 |
(3) | ||||||||
左操作数 >= 右操作数 |
(4) | ||||||||
[编辑] 相等运算符
左操作数 == 右操作数 |
(5) | ||||||||
左操作数 != 右操作数 |
(6) | ||||||||
[编辑] 内建双路比较运算符
对于内建双路比较运算符,左值到右值转换、数组到指针转换(C++26 前)和函数到指针转换会应用于左操作数和右操作数。
如果应用这些转换前 左操作数 和 右操作数 都具有数组类型,那么比较是被弃用的。 |
(C++20 起) (直到 C++26) |
对于内建关系运算符,如果其中一个操作数是指针,则对另一个操作数执行数组到指针转换。 对于内建相等运算符,如果其中一个操作数是指针或空指针常量,则对另一个操作数执行数组到指针转换。 |
(C++26 起) |
对于内建双路比较运算符,结果是 bool 纯右值。
[编辑] 内建算术比较
如果转换后的操作数都具有算术类型或枚举类型(有作用域或无作用域),则对两个操作数执行常规算术转换。在转换后比较值。
#include <iostream> int main() { static_assert(sizeof(unsigned char) < sizeof(int), "Cannot compare signed and smaller unsigned properly"); int a = -1; int b = 1; unsigned int c = 1; unsigned char d = 1; std::cout << std::boolalpha << "Comparing two signed values:\n" " -1 == 1 ? " << (a == b) << "\n" " -1 < 1 ? " << (a < b) << "\n" " -1 > 1 ? " << (a > b) << "\n" "Comparing signed and unsigned:\n" // may issue different-signedness warning: " -1 == 1 ? " << (a == c) << "\n" // may issue different-signedness warning: " -1 < 1 ? " << (a < c) << "\n" // may issue different-signedness warning: " -1 > 1 ? " << (a > c) << "\n" "Comparing signed and smaller unsigned:\n" " -1 == 1 ? " << (a == d) << "\n" " -1 < 1 ? " << (a < d) << "\n" " -1 > 1 ? " << (a > d) << '\n'; }
输出
Comparing two signed values: -1 == 1 ? false -1 < 1 ? true -1 > 1 ? false Comparing signed and unsigned: -1 == 1 ? false -1 < 1 ? false -1 > 1 ? true Comparing signed and smaller unsigned: -1 == 1 ? false -1 < 1 ? true -1 > 1 ? false
[编辑] 内建指针相等比较
相等运算符 ==
和 !=
转换后的操作数也可以有std::nullptr_t 类型、(C++11 起)指针类型或成员指针类型。
内建指针相等比较有三种可能的结果:相等、不相等和未指明。相等运算符对内建指针相等比较产生的值如下表所示:
比较结果 p 和 q 的 |
产生的值 | |
---|---|---|
p == q | p != q | |
相等 | true | false |
不相等 | false | true |
未指定 | 未指明的 bool 值 |
如果转换后的 左操作数 和 右操作数 中至少有一个是指针,则对两个转换后的操作数执行指针转换、函数指针转换(C++17 起)和限定转换,以将它们转换为复合指针类型。这两个复合指针类型的指针按如下方式比较:
- 如果一个指针表示一个完整对象的地址,而另一个指针
- 表示另一个不同的非数组完整对象的尾后地址,或
- 表示另一个不同的完整数组对象的最后一个元素之后一个位置的地址,
- 则比较的结果是未指明的。
- 否则,如果两个指针都是空指针,都指向同一个函数,或都表示相同的地址(即,它们指向同一个对象,或都是同一个对象的尾后指针),那么它们比较为相等。
- 否则,指针比较为不相等。
如果转换后的 左操作数 和 右操作数 中至少有一个是成员指针,则对两个转换后的操作数执行成员指针转换、函数指针转换(C++17 起)和限定转换,以将它们转换为复合指针类型。这两个复合指针类型的成员指针按如下方式比较:
- 如果两个成员指针都是空成员指针值,它们比较为相等。
- 如果两个成员指针中只有一个是空成员指针值,它们比较为不相等。
- 如果其中任一是虚成员函数的指针,结果是未指明的。
- 如果一个指向类
C1
的成员,而另一个指向另一个不同的类C2
的成员,且两者都不是对方的基类,那么结果是未指明的。 - 如果两者都指向同一个联合体的(可能不同的)成员,它们比较为相等。
- 否则,如果通过一个假想的关联类类型的对象进行间接访问时,两个成员指针会引用同一个最终派生对象的同一个成员或同一个子对象,那么它们比较为相等,否则它们比较为不相等。
struct P {}; struct Q : P { int x; }; struct R : P { int x; }; int P::*bx = (int(P::*)) &Q::x; int P::*cx = (int(P::*)) &R::x; bool b1 = (bx == cx); // unspecified 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
两个类型为 std::nullptr_t 的操作数,或者一个操作数为 std::nullptr_t 类型而另一个为空指针常量的操作数,比较为相等。 |
(C++11 起) |
[编辑] 内建指针关系比较
关系运算符 >
、<
、>=
和 <=
转换后的操作数也可以有指针类型。
内建指针关系比较对不相等的指针 p 和 q 有三种可能的结果:p 更大,q 更大和未指明。关系运算符对内建指针关系比较产生的值如下表所示:
比较结果 p 和 q 的 |
产生的值 | |||
---|---|---|---|---|
p > q | p < q | p >= q | p <= q | |
相等 | false | false | true | true |
p 更大 | true | false | true | false |
q 更大 | false | true | false | true |
未指定 | 未指明的 bool 值 |
如果转换后的 左操作数 和 右操作数 都是指针,则对两个转换后的操作数执行指针转换、函数指针转换(C++17 起)和限定转换,以将它们转换为复合指针类型。这两个复合指针类型的指针按如下方式比较:
- 如果指针比较为相等或相等比较结果是未指明的,则关系比较结果属于同一类别。
- 否则(指针比较为不相等),如果任一指针不是对象指针,则结果是未指明的。
- 否则(两个指针都指向对象),结果是根据与以下规则一致的偏序来定义的:
- 给定一个数组的两个不同元素 high 和 low,使得 high 的下标高于 low,如果一个指针指向 high(或 high 的子对象),另一个指针指向 low(或 low 的子对象),则前者比较为大于后者。
- 如果一个指针指向数组的一个元素 elem(或 elem 的子对象),而另一个指针是同一数组的尾后指针,则尾后指针比较为大于另一个指针。
- 如果一个指针指向一个完整对象、一个基类子对象或一个成员子对象 obj(或 obj 的子对象),而另一个指针是 obj 的尾后指针,则尾后指针比较为大于另一个指针。
[编辑] 指针全序
在每个程序中,都存在一个由实现定义的指针严格全序。这个严格全序与上面描述的偏序是一致的:未指明的结果变为由实现定义,而其他结果保持不变。
在以下情况下会应用严格全序的指针比较:
- 调用 std::less、std::greater、std::less_equal 和 std::greater_equal 的指针类型特化的 operator()。
|
(C++14 起) |
|
(C++20 起) |
[编辑] 重载
在针对用户定义运算符的重载决议中,对于每一对提升后的算术类型 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); |
||
对于每种类型 MP
(它是成员对象指针或成员函数指针或 std::nullptr_t(C++11 起)),以下函数签名参与重载决议:
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 << "Pointers to array elements:\n" << "p1 == p2? " << (p1 == p2) << '\n' << "p1 < p2? " << (p1 < p2) << '\n'; Foo f; int* p3 = &f.n1; int* p4 = &f.n2; std::cout << "Pointers to members of a class:\n" << "p3 == p4? " << (p3 == p4) << '\n' << "p3 < p4? " << (p3 < p4) << '\n'; Union u; int* p5 = &u.n; double* p6 = &u.d; std::cout << "Pointers to members of a union:\n" << "p5 == (void*)p6? " << (p5 == (void*)p6) << '\n' << "p5 < (void*)p6? " << (p5 < (void*)p6) << '\n'; }
输出
Pointers to array elements: p1 == p2? false p1 < p2? true Pointers to members of a class: p3 == p4? false p3 < p4? true Pointers to members of a union: p5 == (void*)p6? true p5 < (void*)p6? false
三路比较三路比较运算符表达式具有以下形式:
该表达式返回一个对象,使得:
如果其中一个操作数的类型为 bool 而另一个不是,则程序非良构。 如果两个操作数都具有算术类型,或者一个操作数具有无作用域枚举类型而另一个具有整数类型,则对操作数应用常规算术转换,然后
如果两个操作数具有相同的枚举类型 如果至少有一个操作数是对象指针或成员指针,则对两个操作数应用数组到指针转换、指针转换和限定转换,以将它们转换为复合指针类型。 对于转换后的指针操作数 p 和 q,p <=> q 返回一个 std::strong_ordering 类型的纯右值:
否则,程序格式错误。
重载在针对用户定义运算符的重载决议中,对于指针或枚举类型
其中 运行此代码 #include <compare> #include <iostream> int main() { double foo = -0.0; double bar = 0.0; auto res = foo <=> bar; if (res < 0) std::cout << "-0 is less than 0"; else if (res > 0) std::cout << "-0 is greater than 0"; else if (res == 0) std::cout << "-0 and 0 are equal"; else std::cout << "-0 and 0 are unordered"; } 输出 -0 and 0 are equal |
(C++20 起) |
[编辑] 注意
因为比较运算符是左结合的,所以表达式 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; maybe warning << ((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 等。
指向同一类的不同非静态数据成员的指针的比较结果意味着,在三种成员访问模式中的每一种中,(C++23 前)非静态数据成员在内存中的位置按其声明顺序排列。
尽管比较随机来源(例如,并非都指向同一数组的成员)的指针的结果是未指定的,但许多实现为指针提供了严格全序,例如,如果它们被实现为连续虚拟地址空间内的地址。那些不提供严格全序的实现(例如,指针的某些位不是内存地址的一部分而必须在比较时被忽略,或者需要额外的计算,或者指针和整数之间不是一一对应的关系),会为指针提供 std::less 的特化,以保证这种全序关系。这使得可以将所有随机来源的指针作为键用在标准关联容器中,如 std::set 或 std::map。
对于同时满足 EqualityComparable 和 LessThanComparable 的类型,C++ 标准库区分了相等性 (equality) 和等价性 (equivalence)。相等性是表达式 a == b 的值,而等价性是表达式 !(a < b) && !(b < a) 的值。
指针与空指针常量之间的比较已通过 N3624 中包含的 CWG 问题 583 的解决方案移除。
void f(char* p) { if (p > 0) { /*...*/ } // Error with N3624, compiled before N3624 if (p > nullptr) { /*...*/ } // Error with N3624, compiled before N3624 } int main() {}
可以为类类型自动生成三路比较,详见默认比较。
如果两个操作数都是数组,则三路比较是非法的。
unsigned int i = 1; auto r = -1 < i; // existing pitfall: returns ‘false’ auto r2 = -1 <=> i; // Error: narrowing conversion required
特性测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__cpp_impl_three_way_comparison |
201907L |
(C++20) | 三路比较(编译器支持) |
__cpp_lib_three_way_comparison |
201907L |
(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) |
比较两个 durations (函数模板) |
(C++11)(C++11)(C++20 中移除)(C++11)(C++11)(C++11)(C++11)(C++20) |
比较两个时间点 (函数模板) |
(在 C++20 中移除) |
比较两个 scoped_allocator_adaptor 对象(函数模板) |
(在 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++11)(C++11)(C++20 中移除)(C++11)(C++20 中移除)(C++11)(C++20 中移除)(C++11)(C++20 中移除)(C++11)(C++20 中移除)(C++20) |
按字典序比较两个 array 的值(函数模板) |
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) |
按字典序比较两个 deque 的值(函数模板) |
(C++11)(C++11)(C++20 中移除)(C++11)(C++20 中移除)(C++11)(C++20 中移除)(C++11)(C++20 中移除)(C++11)(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++11起)(C++11起)(C++20中移除) |
比较 unordered_map 中的值 (函数模板) |
(C++11起)(C++11起)(C++20中移除) |
比较 unordered_multimap 中的值 (函数模板) |
(C++11起)(C++11起)(C++20中移除) |
比较 unordered_set 中的值 (函数模板) |
(C++11起)(C++11起)(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 中移除) |
比较两个复数或一个复数和一个标量 (函数模板) |
比较两个 valarray 或一个 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 1596 | C++98 | 非数组对象仅在指针算术中 被认为属于大小为一的数组 |
此规则也 应用于比较 |
CWG 1598 | C++98 | 指向不同类成员的两个指针,且 两个类都不是对方的基类,它们不会比较为相等, 即使所指向成员的偏移量可能相同 |
结果为 未指定 在这种情况下 |
CWG 1858 | C++98 | 不清楚指向同一联合体中不同成员的 两个成员指针 是否像指向同一成员一样比较为相等 |
在这种情况下它们 比较为相等 |
CWG 2419 | C++98 | 指向非数组对象的指针仅在通过 & 获取时,才在指针比较中被视为 指向大小为 1 的数组的第一个元素 |
适用于所有指向 非数组对象的指针 |
CWG 2526 | C++98 | 指向 void 的指针和函数指针的关系比较(> , >= , < , <= )的定义被 N3624 移除了 |
恢复 |
CWG 2796 | C++17 | 在内建指针关系比较中,函数指针转换 未对转换后的指针操作数执行 |
在这种情况下 执行这些转换 |
[编辑] 参阅
常见运算符 | ||||||
---|---|---|---|---|---|---|
赋值 | 递增 递减 |
算术 | 逻辑 | 比较 | 成员 访问 |
其他 |
a = b |
++a |
+a |
!a |
a == b |
a[...] |
函数调用 a(...) |
逗号 a, b | ||||||
条件 a ? b : c | ||||||
特殊运算符 | ||||||
static_cast 将一种类型转换为另一种相关类型 |
C 文档中关于比较运算符的内容
|