比较运算符

来自cppreference.com
< cpp‎ | language


 
 
C++ 语言
 
 

比较参数。

运算符名    语法    可重载 原型示例(对于 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 T::operator<=>(const U& b) const;[1] operator<=>(const T& a, const U& b);[1]
注解
  • 内建运算符返回 bool,所以大多数用户定义重载也返回 bool 以使用户定义运算符能以与内建运算符相同的方式使用。然而,在用户定义运算符重载中,任何类型都可用作返回类型(包括 void)。
  • U 可以是包括 T 在内的任何类型
  1. 1.0 1.1 Roperator<=> 的返回类型(见下文

双路比较

双路比较运算符表达式的形式为

关系运算符
左操作数 < 右操作数 (1)
左操作数 > 右操作数 (2)
左操作数 <= 右操作数 (3)
左操作数 >= 右操作数 (4)
相等性运算符
左操作数 == 右操作数 (5)
左操作数 != 右操作数 (6)
1)左操作数 小于右操作数 时返回 true,否则返回 false
2)左操作数 大于右操作数 时返回 true,否则返回 false
3)左操作数 小于或等于右操作数 时返回 true,否则返回 false
4)左操作数 大于或等于右操作数 时返回 true,否则返回 false
5)左操作数 等于右操作数 时返回 true,否则返回 false
6)左操作数 不等于右操作数 时返回 true,否则返回 false

内建双路比较运算符

这些内建运算符都会对左操作数 和右操作数 应用左值到右值转换数组到指针转换函数到指针转换如果左操作数 和右操作数 在转换前都具有数组类型,那么这种比较被弃用。 (C++20 起)

这些内建运算符的结果都是 bool 纯右值。

内建算术比较

如果两个操作数具有算术或(有作用域或无作用域)枚举类型,那么对两个操作数实施一般算术转换。转换之后进行值的比较:

#include <iostream>
 
int main()
{
    static_assert(sizeof(unsigned char) < sizeof(int),
                  "不能正确地比较有符号值与较小的无符号值");
    int a = -1;
    int b = 1;
    unsigned int c = 1;
    unsigned char d = 1;
 
    std::cout << std::boolalpha
              << "比较两个有符号值:\n"
                 " -1 == 1 ? " << (a == b) << "\n"
                 " -1 <  1 ? " << (a <  b) << "\n"
                 " -1 >  1 ? " << (a >  b) << "\n"
                 "比较有符号与无符号值:\n"
                 // 可能会产生“符号不同”警告信息
                 " -1 == 1 ? " << (a == c) << "\n"
                 // 可能会产生“符号不同”警告信息
                 " -1 <  1 ? " << (a <  c) << "\n"
                 // 可能会产生“符号不同”警告信息
                 " -1 >  1 ? " << (a >  c) << "\n"
                 "比较有符号与较小的无符号值:\n"
                 " -1 == 1 ? " << (a == d) << "\n"
                 " -1 <  1 ? " << (a <  d) << "\n"
                 " -1 >  1 ? " << (a >  d) << '\n';
}

输出:

比较两个有符号值:
 -1 == 1 ? false
 -1 <  1 ? true
 -1 >  1 ? false
比较有符号与无符号值:
 -1 == 1 ? false
 -1 <  1 ? false
 -1 >  1 ? true
比较无符号值和较小的有符号值:
 -1 == 1 ? false
 -1 <  1 ? true
 -1 >  1 ? false

内建指针相等性比较

相等性运算符 ==!= 的转换后的操作数也可以具有 std::nullptr_t 类型、 (C++11 起)指针类型或成员指针类型。

内建指针相等性比较有三个结果:相等、不相等和未指定。相等性运算符对于内建指针相等性比较产生的结果见下表:

 pq 的比较结果  表达式产生的值
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); // 未指定
 
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 起)

内建指针关系比较

关系运算符 ><>=<= 的转换后的操作数也可以具有(非成员)指针类型。

对不相等的两个指针 pq 的内建指针关系比较有三个结果:p 大于 qq 大于 p和未指定。关系运算符对于内建指针关系比较产生的结果见下表:

 pq 的比较结果  表达式产生的值
 p > q   p < q   p >= q   p <= q 
相等 false false true true
p 大于 q true false true false
q 大于 p false true false true
未指定 未指定的 bool

如果转换后的左操作数 和右操作数 都是(非成员)指针,那么对转换后的两个操作数应用指针转换函数指针转换 (C++17 起)限定性转换,以获得它们的合成指针类型。具有合成指针类型的两个指针按以下方式进行比较:

  • 如果两个指针比较相等,或者相等性比较结果未指定,那么关系比较结果与相等性比较结果相同。
  • 否则(两个指针比较不相等),如果至少有一个指针不是指向对象的指针,那么比较结果未指定。
  • 否则(两个指针都指向对象),那么结果按以下规则规定的偏序进行定义:
  • 给定某个数组的两个不同的成员 highlow,其中 high 的下标高于 low,如果一个指针指向 high(或 high 的某个子对象)而另一个指针指向 low(或 low 的子对象),那么前者比较大于后者。
  • 如果一个指针指向某个数组的元素 elem(或 elem 的某个子对象)而另一个指针是相同数组的尾后指针,那么尾后指针比较大于另一指针。
  • 如果一个指针指向一个完整对象,基类子对象或成员子对象 obj(或 obj 的某个子对象),而另一个指针是 obj 的尾后指针,那么尾后指针比较大于另一指针。

  • 如果在非联合体类类型的对象内,两个指针指向拥有相同成员访问 (C++23 前)非零大小 (C++20 起)不同的非静态数据成员,或(递归地)指向这种成员的子对象或数组元素,那么指向较后声明的成员的指针比较大于另一指针。
  • 否则比较结果未指定。

指针全序

每个程序中都有一个由实现定义的指针严格全序。该全序与上述偏序保持一致:原来未指定的结果改由实现定义,其他结果保持不变。

在以下情况以严格全序进行指针比较:

(C++14 起)
(C++20 起)

重载

针对用户定义运算符的重载决议中,对于(包括枚举类型的)每个提升后算术类型 LR,下列函数签名参与重载决议:

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 (C++11 起)的类型 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 << "指向数组元素的指针:\n"
              << "p1 == p2? " << (p1 == p2) << '\n'
              << "p1 <  p2? " << (p1 <  p2) << '\n';
 
    Foo f;
    int* p3 = &f.n1;
    int* p4 = &f.n2;
    std::cout << "指向类成员的指针:\n"
              << "p3 == p4? " << (p3 == p4) << '\n'
              << "p3 <  p4? " << (p3 <  p4) << '\n';
 
    Union u;
    int* p5 = &u.n;
    double* p6 = &u.d;
    std::cout << "指向联合体成员的指针:\n"
              << "p5 == (void*)p6? " << (p5 == (void*)p6) << '\n'
              << "p5 <  (void*)p6? " << (p5 <  (void*)p6) << '\n';
}

输出:

指向数组元素的指针:
p1 == p2? false
p1 <  p2? true
指向类成员的指针:
p3 == p4? false
p3 <  p4? true
指向联合体成员的指针:
p5 == (void*)p6? true
p5 <  (void*)p6? false

三路比较

三路比较运算符表达式的形式为

左操作数 <=> 右操作数

表达式返回一个对象,使得

  • 如果 a < b,那么 (a <=> b) < 0
  • 如果 a > b,那么 (a <=> b) > 0
  • 而如果 ab 相等/等价,那么 (a <=> b) == 0

如果操作数之一是 bool 类型而另一个不是,那么程序非良构。

如果两个操作数均具有算术类型,或一个具有无作用域枚举类型而另一个具有整数类型,那么对各操作数应用一般算术转换,然后

  • 如果需要进行除了从整数类型到浮点类型之外的窄化转换,那么程序非良构。
  • 否则,如果各操作数均具有整数类型,那么运算符产出 std::strong_ordering 类型的纯右值:
  • 如果两个操作数算术上相等则为 std::strong_ordering::equal
  • 如果第一操作数算术上小于第二个则为 std::strong_ordering::less
  • 否则为 std::strong_ordering::greater
  • 否则,操作数均具有浮点类型,而运算符产出 std::partial_ordering 类型的纯右值。表达式 a <=> b 产出
  • 如果 a 小于 b 则为 std::partial_ordering::less
  • 如果 a 大于 b 则为 std::partial_ordering::greater
  • 如果 a 等价于 b 则为 std::partial_ordering::equivalent-0 <=> +0 为等价),
  • 否则为 std::partial_ordering::unorderedNaN <=> 任何值 为无序)。

如果两个操作数都具有相同的枚举类型 E,那么运算符产出将各操作数转换到 E 的底层类型再对转换后的操作数应用 <=> 的结果。

如果至少一个操作数是指针或成员指针,那么按需应用数组到指针转换指针转换限定性转换,以将它们转换到它们的合成指针类型

对于转换后的操作数 pqp <=> q 返回 std::strong_ordering 类型的纯右值:

  • 如果它们比较相等则为 std::strong_ordering::equal
  • 如果 q 比较大于 p 则为 std::strong_ordering::less
  • 如果 p 比较大于 q 则为 std::strong_ordering::greater
  • 如果未指明这些指针值的比较,那么结果未指明。

否则程序非良构。

重载

针对用户定义运算符的重载决议中,对于指针或枚举类型 T,下列函数签名参与重载决议:

R operator<=>(T, T);

其中 R 是上文所定义的顺序类别类型。

#include <compare>
#include <iostream>
 
int main()
{
    double foo = -0.0;
    double bar = 0.0;
 
    auto res = foo <=> bar;
 
    if (res < 0)
        std::cout << "-0 小于 0";
    else if (res > 0)
        std::cout << "-0 大于 0";
    else if (res == 0)
        std::cout << "-0 与 0 相等";
    else
        std::cout << "-0 与 0 无序";
}

输出:

-0 与 0 相等
(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;可能会有警告
        << ((a < b) < c) << '\n' // true
        << (a < (b < c)) << '\n' // false
        << ((a < b) && (b < c)) << '\n'; // false
}

用户定义的 operator< 的一项常见要求是严格弱序。尤其是用比较 (Compare) 类型进行工作的标准算法和容器,如 std::sortstd::max_elementstd::map 等,都对此有所要求。

指向相同类的不同成员的指针的比较结果说明了三个成员访问模式的每一者中的 (C++23 前)类成员按声明顺序置于内存中。

尽管未指明对随机来源(例如不都指向同一数组中的成员)的指针比较的结果,许多实现都提供指针的严格全序,例如它们被实现为连续的虚拟地址空间中的地址。不这样做的实现(例如并非指针的所有位都是内存地址的一部分而在比较中必须被忽略,或要求进行额外的计算否则指针与整数并非一对一关系),为指针提供具有这项保证的 std::less 的特化。这使得程序可以将随机来源的所有指针都用作标准关联容器(如 std::setstd::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() {}

类类型可以自动生成三路比较,见默认比较

如果两个运算数都是数组,那么三路比较非良构。

unsigned int i = 1;
auto r = -1 < i;    // 既存陷阱:返回 false
auto r2 = -1 <=> i; // 错误:要求窄化转换
功能特性测试 标准 功能特性
__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_conditionerror_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> 的公开成员函数)
与另一个 unique_ptrnullptr 进行比较
(函数模板)
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20)
与另一个 shared_ptrnullptr 进行比较
(函数模板)
(C++20 中移除)
比较 std::functionnullptr
(函数模板)
(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...> 的公开成员函数)
比较底层 std::type_info 对象
(std::type_index 的公开成员函数)
(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20)
以字典序比较两个字符串
(函数模板)
(C++20 中移除)
本地环境对象之间的相等性比较
(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 中移除)
比较两个复数,或一个复数与一个标量
(函数模板)
比较两个 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 定义
基于用户定义的 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 N3624 移除了指向 void 的指针和函数指针的关系比较(>>=<<=)的定义 恢复原本定义
CWG 2796 C++17 在内建指针关系比较中不会对转换后的指针操作数进行函数指针转换 此时会进行转换

参阅

常见运算符
赋值 自增/自减 算术 逻辑 比较 成员访问 其他

a = b
a += b
a -= b
a *= b
a /= b
a %= b
a &= b
a |= b
a ^= b
a <<= b
a >>= b

++a
--a
a++
a--

+a
-a
a + b
a - b
a * b
a / b
a % b
~a
a & b
a | b
a ^ b
a << b
a >> b

!a
a && b
a || b

a == b
a != b
a < b
a > b
a <= b
a >= b
a <=> b

a[b]
*a
&a
a->b
a.b
a->*b
a.*b

函数调用
a(...)
逗号
a, b
条件
a ? b : c
特殊运算符

static_cast 转换一个类型为另一相关类型
dynamic_cast 在继承层级中转换
const_cast 添加或移除 cv 限定符
reinterpret_cast 转换类型到无关类型
C 风格转换static_castconst_castreinterpret_cast 的混合转换一个类型到另一类型
new 创建有动态存储期的对象
delete 销毁先前由 new 表达式创建的对象,并释放其所拥有的内存区域
sizeof 查询类型的大小
sizeof... 查询形参包的大小(C++11 起)
typeid 查询类型的类型信息
noexcept 查询表达式是否能抛出异常(C++11 起)
alignof 查询类型的对齐要求(C++11 起)