比较运算符

来自cppreference.com
< cpp‎ | language

比较参数。

运算符名 语法 可重载 原型示例(对 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);
注解
  • 内建运算符返回 bool,所以大多数用户定义重载也返回 bool以使用户定义运算符能以与内建运算符相同的方式使用。然而,在用户定义运算符重载中,任何类型都可用作返回类型(包含 void)。
  • T2 可以是包括 T 在内的任何类型

双路比较

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

左操作数 < 右操作数 (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()
{
    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 起)
2) 若一个操作数为空指针常量,而另一个为指针,则合成类型正是该指针类型
3) 若两个操作数分别为
  • 指向 cv1 void 的指针,和
  • 指向 cv2 T 的指针,其中 T 为对象类型或 void
则合成类型为“指向 cv12 void 的指针”,其中 cv12cv1cv2 的合并
4) 若两个操作数的类型分别为
  • 指向(可能 cv 限定的)T1 的指针 P1,和
  • 指向(可能 cv 限定的)T2 的指针 P2
且如果 T1T2 相同或是 T2 的基类,则合成指针类型为 P1P2cv 结合类型。否则,如果 T2T1 的基类,则合成指针类型为 P2P1cv 结合类型。
5) 若两个操作数的类型分别为
  • 指向 T1 的成员,其类型为(可能 cv 限定的)U1,的指针 MP1,和
  • 指向 T2 的成员,其类型为(可能 cv 限定的)U2,的指针 MP2
且如果 T1T2 相同或派生自 T2,则合成指针类型为 MP1MP2cv 结合类型。否则,如果 T2 派生自 T1,则合成指针类型为 MP2MP1cv 结合类型。
6) 若操作数 P1P2 的类型具有相同层数的多层混合指针和成员指针类型,而仅在任意层上的 cv 限定性有别,则合成指针类型为 P1P2cv 结合类型

在上面的定义中,两个指针类型 P1P2cv 结合类型,是与 P1 拥有相同的层数且每层具有与 P1 相同类型的类型 P3,其中每层上的 cv 限定性设定如下:

a) 除顶层之外的每层,其 cv 限定性为该层上 P1P2 的 cv 限定性的合并
b) 如果有任何层上得到的 cv 限定性与同一层上 P1P2 的 cv 限定性不同,则对顶层和此层之间的每一层添加 const

例如,void*const int* 的合成指针类型为 const void*int**const int** 的合成指针类型为 const int* const*。注意,在 CWG1512 的解决方案之前无法比较 int**const int**

除上述之外,函数指针和指向 noexcept 函数的指针(只要函数类型相同)的合成指针类型为函数指针。

(C++17 起)

注意这意味着任何指针都能与 void* 进行比较。

两个(转换后的)对象指针的比较结果定义如下:

1) 若两个指针指向同一数组的不同元素,或同一数组的不同元素内的子对象,则指向拥有较高下标的元素的指针比较大于另一指针。换言之,比较指针的结果与比较其所指向的元素下标结果相同。
2) 若一个指针指向数组元素,或数组元素的子对象,而另一指针指向数组末尾元素后一位置,则后者比较大于前者。指向单个对象的指针被当做指向单元素数组的指针:&obj+1 比较大于 &obj
3) 若在非联合体类类型的对象内,两个指针指向拥有相同成员访问 (C++23 前)非零大小 (C++20 起)不同非静态数据成员,或(递归地)指向这种成员的子对象或数组元素,则指向较后声明的成员的指针比较大于另一指针。换言之,三个成员访问模式的每一者中的类成员按声明顺序置于内存中。

两个(转换后的)指针的相等性比较的结果定义如下:

1) 若两个指针均为空指针值,则它们比较相等
2) 若两个指针均为函数指针且指向同一函数,则它们比较相等
3) 若两个指针均为对象指针且表示相同地址,则它们比较相等(这包括两个指向同一联合体的不同非静态成员的指针,指向标准布局结构体与其首成员的指针,由 reinterpret_cast 关联的指针等)
4) 所有其他指针的比较不相等。

两个(转换后的)成员指针的比较结果定义如下:

1) 若两个成员指针均为空成员指针值,则它们比较相等
2) 否则,若两个成员指针之一为空成员指针值,则它们比较不相等。
3) 否则,若其中之一为指向虚成员函数的指针,则结果未指明。
4) 否则,当且仅当以一个其关联类类型的假想对象对它们解引用时,它们会指代同一最终派生对象或同一子对象的相同成员,两个成员指针相等。
5) 否则它们的比较不相等。


若指针 p 比较等于指针 q,则 p<=qp>=q 都产出 truep<qp>q 都产出 false

若指针 p 比较大于指针 q,则 p>=qp>qq<=pq<p 都产出 truep<=qp<qq>=pq>p 都产出 false

若未指明两个指针的比较大于或比较相等,则其比较结果未指明。其结果可以是不确定的,并且甚至不必在程序的同一次执行中,在拥有相同操作数的相同表达式的多次求值之间保持一致:

int x, y;
 
bool f(int* p, int* q) { return p < q; }
 
assert(f(&x, &y) == f(&x, &y)); // 在遵从标准的实现中可能引发断言

针对用户定义运算符的重载决议中,对于(包括枚举类型的)每个提升后算术类型 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 的类型 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::sortstd::max_elementstd::map 等都对此有所要求。

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

三路比较

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

左操作数 <=> 右操作数

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

  • 左操作数 < 右操作数(a <=> b) < 0
  • 左操作数 > 右操作数(a <=> b) > 0
  • 而若 左操作数右操作数 相等/等价则 (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 的底层类型再对转换后的操作数应用 <=> 的结果。

若至少一个操作数为指针或成员指针,则按需应用数组到指针转换、派生类到基类指针转换、函数指针转换和限定性转换,以将它们转换为同一指针类型,且结果指针类型为对象指针类型,则 p <=> q 返回 std::strong_ordering 类型的纯右值:

  • p == q 则为 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 // (res == 0)
        std::cout << "-0 与 0 相等";
}

输出:

-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_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 中移除)
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
自动生成基于用户定义的 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 仅就指针算术的目的认为非数组对象属于拥有一个元素的数组 该规则亦适用于比较

参阅

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

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
? :

特殊运算符

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 起)