比较运算符

来自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 在内的任何类型

双路比较

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

lhs < rhs (1)
lhs > rhs (2)
lhs <= rhs (3)
lhs >= rhs (4)
lhs == rhs (5)
lhs != rhs (6)
1)lhs 小于 rhs 则返回 true,否则返回 false
2)lhs 大于 rhs 则返回 true,否则返回 false
3)lhs 小于或等于 rhs 则返回 true,否则返回 false
4)lhs 大于或等于 rhs 则返回 true,否则返回 false
5)lhs 等于 rhs 则返回 true,否则返回 false
6)lhs 不等于 rhs 则返回 true,否则返回 false

所有情况下,对于内建运算符,lhsrhs 在应用左值到右值数组到指针函数到指针标准转换后,必须具有下列类型之一

  • 算术或枚举类型(见下文的算术比较运算符)
  • 指针类型(见下文的指针比较运算符)

若两个运算数在应用这些转换前均具有数组类型,则这种比较被弃用。 (C++20 起)

任何情况下,结果均为 bool 纯右值。

算术比较运算符

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

示例

#include <iostream>
int main()
{
    std::cout << std::boolalpha;
    int n = -1;
 
    int n2 = 1;
    std::cout << " -1 == 1? " << (n == n2) << '\n'
              << "Comparing two signed values:\n"
              << " -1  < 1? " << (n < n2) << '\n'
              << " -1  > 1? " << (n > n2) << '\n';
 
    unsigned int u = 1;
    std::cout << "Comparing signed and unsigned:\n"
              << " -1  < 1? " << (n < u) << '\n'
              << " -1  > 1? " << (n > u) << '\n';
 
    unsigned char uc = 1;
    std::cout << "Comparing signed and smaller unsigned:\n"
              << " -1  < 1? " << (n < uc) << '\n'
              << " -1  > 1? " << (n > uc) << '\n';
}

输出:

 -1 == 1? false
Comparing two signed values:
 -1  < 1? true
 -1  > 1? false
Comparing signed and unsigned:
 -1  < 1? false
 -1  > 1? true
Comparing signed and smaller unsigned:
 -1  < 1? true
 -1  > 1? false

指针比较运算符

比较运算符可用于比较二个指针(或成员指针,但仅对于 operator==operator!=),或一个(成员) (C++14 起)指针和一个空指针常量,或两个空指针常量(但要至少一个为 std::nullptr_t:NULL 和 NULL 的比较遵循算术比较规则) (C++14 前)

首先,对两个操作数应用指针转换(若参数为成员指针则为成员指针转换)、函数指针转换 (C++17 起)限定性转换,以获得合成指针类型,规则如下

1) 若两个操作数均为空指针常量,则合成指针类型为 std::nullptr_t
2) 若一个操作数为空指针常量,而另一个为指针,则合成类型正是该指针类型
3) 若一个操作数为指向 cv1 void 的指针,而另一个为(对于某类型 T)指向 cv2 T 的指针,其中 T 为对象类型或 void,则合成类型为“指向 cv12 void 的指针”,其中 cv12cv1cv2 的合并
4) 若两个操作数均为指向带有不同 cv 限定的同一类型的指针,则合成类型为指向同一类型的指针并带有两个实参 cv 限定性的合并 cv 限定性。
(C++14 前)
4) 若操作数的类型分别为指向(可能 cv 限定)T1 的指针 P1,和指向(可能 cv 限定)T2 的指针 P2,且若 T1 与 T2 相同或是 T2 的基类,则合成指针类型为 P1 与 P2 的 cv 结合类型。否则,若 T2 为 T1 的基类,则合成指针类型为 P2 与 P1 的 cv 结合类型。
5) 若操作数的类型分别为指向(可能 cv 限定)U1 类型的 T1 的成员指针,和指向(可能 cv 限定)U2 类型的 T2 的成员指针,且若 T1 与 T2 相同或派生于 T2,则合成指针类型为 MP1 和 MP2 的 cv 结合类型。否则,若 T2 派生于 T1,则合成指针类型为 MP2 与 MP1 的 cv 结合类型。
6) 若操作数 P1 和 P2 的类型具有相同层数的多层混合指针和成员指针类型,而仅在任何层上的 cv 限定性有别,则合成指针类型为 P1 与 P2 的 cv 结合类型

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

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

例如,void*const int* 的合成指针类型为 const void*int**const int** 的合成指针类型为 const int* const*。注意,C++14 之前无法比较 int**const int**

(C++14 起)

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

(C++17 起)

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

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

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

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

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)); // 在遵从标准的实现中可能引发断言

针对用户定义运算符的重载决议中,对于(包括枚举类型的)每个提升后算术类型 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);

对于每个对象指针或函数指针std::nullptr_t (C++14 前)的类型 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 << "Pointers to array elements: p1 == p2 " << (p1 == p2)
              << ", p1 < p2 "  << (p1 < p2) << '\n';
 
    Foo f;
    int* p3 = &f.n1;
    int* p4 = &f.n2;
    std::cout << "Pointers to members of a class: p3 == p4 " << (p3 == p4)
              << ", p3 < p4 "  << (p3 < p4) << '\n';
 
    Union u;
    int* p5 = &u.n;
    double* p6 = &u.d;
    std::cout << "Pointers to members of a union: p5 == (void*)p6 " << (p5 == (void*)p6)
              << ", p5 < 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 < p6 false

注解

因为这些运算符从左到右组合,故表达式 a<b<c 被分析为 (a<b)<c,而非 a<(b<c)(a<b)&&(b<c)

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

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

对于既可相等比较 (EqualityComparable) 可小于比较 (LessThanComparable) 的类型,C++ 标准库在相等(即表达式 a == b 的值),和等价(即表达式 !(a < b) && !(b < a) 的值)之间做出区别。

C++14 中移除了指针与空指针常量间的比较。

void f(char * p)
{
  if (p > 0) { ... } // C++98..C++11 中 OK,C++14 中不能编译
  if (p > nullptr) { ... } // C++11 中 OK,C++14 中不能编译
}

三路比较

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

lhs <=> rhs (1)

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

  • lhs < rhs(a <=> b) < 0
  • lhs > rhs(a <=> b) > 0
  • 而若 lhsrhs 相等/等价则 (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 是上文所定义的排序类别类型。

注解

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

如果两个运算数均为数组,则除了比较数组类型的类成员之外,三路比较均为非良构。

unsigned int i = 1;
auto r = -1 < i; // 既存陷阱:返回 false
auto r2 = -1 <=> i; // 错误:要求窄化转换


(C++20 起)

标准库

比较运算符为标准库中的许多类所重载。

(C++20 中移除)
检查对象是否指代相同类型
(std::type_info 的公开成员函数)
比较两个 error_code
(函数)
比较 error_condition 和 error_code
(函数)
按字典序比较 pair 中的值
(函数模板)
按字典顺序比较 tuple 中的值
(函数模板)
比较其内容
(std::bitset<N> 的公开成员函数)
(C++20 中移除)
比较两个分配器实例
(std::allocator<T> 的公开成员函数)
与另一个 unique_ptrnullptr 进行比较
(函数模板)
与另一个 shared_ptrnullptr 进行比较
(函数模板)
比较 std::functionnullptr
(函数模板)
比较两个时长
(函数模板)
比较两个时间点
(函数模板)
比较两个 scoped_allocator_adaptor 实例
(std::scoped_allocator_adaptor<OuterAlloc,InnerAlloc...> 的公开成员函数)
比较底层 std::type_info 对象
(std::type_index 的公开成员函数)
以字典序比较两个字符串
(函数模板)
locale 对象之间的相等性比较
(std::locale 的公开成员函数)
按照字典顺序比较 array 中的值
(函数模板)
按照字典顺序比较 deque 中的值
(函数模板)
按照字典顺序比较 forward_list 中的值
(函数模板)
按照字典顺序比较 list 中的值
(函数模板)
按照字典顺序比较 vector 中的值
(函数模板)
按照字典顺序比较 map 中的值
(函数模板)
按照字典顺序比较 multimap 中的值
(函数模板)
按照字典顺序比较 set 中的值
(函数模板)
按照字典顺序比较 multiset 中的值
(函数模板)
比较 unordered_map 中的值
(函数模板)
比较 unordered_multimap 中的值
(函数模板)
比较 unordered_set 中的值
(函数模板)
比较 unordered_multiset 中的值
(函数模板)
按照字典顺序比较 queue 中的值
(函数模板)
按照字典顺序比较 stack 中的值
(函数模板)
比较底层迭代器
(函数模板)
比较底层迭代器
(函数模板)
比较两个 istream_iterator
(函数模板)
比较两个 istreambuf_iterator
(函数模板)
比较两个复数,或一个复数与一个标量
(函数模板)
比较两个 valarrays,或比较一个 valarray 和一个值
(函数模板)
比较两个伪随机数引擎的内部状态
(函数)
比较两个分布对象
(函数)
比较两个 sub_match 对象
(函数模板)
以字典序比较两个匹配结果的值
(函数模板)
比较两个 regex_iterator
(std::regex_iterator<BidirIt,CharT,Traits> 的公开成员函数)
比较两个 regex_token_iterator
(std::regex_token_iterator<BidirIt,CharT,Traits> 的公开成员函数)
比较两个 thread::id 对象
(函数)

命名空间 rel_ops 提供了泛型运算符 !=><=>=

定义于头文件 <utility>
定义于命名空间 std::rel_ops
自动生成基于用户定义的 operator==operator< 的比较运算符
(函数模板)

参阅

运算符优先级

运算符重载

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

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