C++ STL 中的自定义比较:深入理解相等和等价

一、简介

本文主要讨论了在 STL 中使用自定义比较函数,以及比较操作中的相等和等价概念。

有如下的代码:

std::vector< std::pair<int, std::string> > v1 = ... // v1 填充数据
std::vector< std::pair<int, std::string> > v2 = ... // v2 填充数据
std::vector< std::pair<int, std::string> > results;

std::sort(v1.begin(), v1.end());
std::sort(v2.begin(), v2.end());

std::set_difference(v1.begin(), v1.end(),
                    v2.begin(), v2.end(),
                    std::back_inserter(result),
                    compareFirst);

这里有两个由两个排序向量 v1v2 表示的数据集,对其应用 std::set_differencestd::set_difference 将其输出写入 resultsstd::back_inserter 确保所有输出都被 push_backresults 中。

但是,有一个特殊之处:使用 std::set_difference 时提供了一个自定义比较运算符 compareFirst

默认情况下,std::set_difference 使用 std::pair 上的默认比较来比较元素(它比较 pair 的第一个和第二个元素),而在这里希望使用 compareFirst 仅根据第一个元素比较 paircompareFirst 不在 STL 中,因此需要自己实现它。

在开始实现之前,已经得到一个有趣的结论。即使 std::set_difference 要求其输入已排序,也可以使用它(或任何针对排序元素的算法)基于与用于排序的比较器不同的比较器(称之为 C),前提是元素也根据此比较器 C 排序。例如,在例子中,使用一个 std::set_difference,它根据 pair 的第一个元素进行比较,尽管这些 pair 已经根据其第一个和第二个元素进行了排序。但由于它们必须按第一个元素排序,所以这样做完全没问题。

现在实现一下 compareFirst。第一个版本:

bool compareFirst(const std::pair<int, std::string>& p1, const std::pair<int, std::string>& p2)
{
    return p1.first == p2.first; // 不是最终代码,存在bug!
}

实际上,这个实现根本不会给出预期的结果。但为什么呢?毕竟,set_difference 应该检查给定元素是否等于另一个集合中的另一个元素,对吧?

至少可以说,这看起来很不自然。

二、STL 的排序部分

这部分包括关联容器(std::mapstd::multimapstd::setstd::multiset),因为它们的元素已排序。

一些算法也属于此类别,因为它们假设它们操作的元素已排序:例如 std::set_differencestd::includesstd::binary_search

三、STL 的未排序部分

这部分包括序列容器(std::vectorstd::liststd::dequestd::string),因为它们的元素不一定排序。

属于此类别的算法是不需要其元素排序的算法,例如 std::equalstd::countstd::find

四、比较元素

在 C++ 中有两种表达“a 与 b 相同”的方式:

  • 自然方式:a == b。这称为相等。相等基于 operator==
  • 另一种方式:a 不小于 b 并且 b 不小于 a,所以 !(a<b) && !(b<a)。这称为等价。等价基于 operator<

然后自然会产生关于等价性的两个问题。

等价与相等有什么不同?

对于像 int 这样的简单类型,实际上对于实践中的大多数类型来说,等价性确实与相等性相同。但有一些特别的类型,它们的等价性与相等性不同,例如:不区分大小写的字符串。

为什么用如此牵强的方式表达一件简单的事情?

当算法比较集合中的元素时,很容易理解必须只有一种比较它们的方式(拥有多个比较器很麻烦,并且会造成不一致的风险)。因此,需要在基于 operator==operator< 进行比较之间做出选择。

在 STL 的排序部分,已经做出了选择:根据排序的定义,元素必须使用 operator<(或自定义的 (operator<) 类函数)进行比较。另一方面,未排序部分没有这个限制,可以使用自然的 operator==

五、实现比较器

STL 的未排序部分使用 operator== 来执行比较,而排序部分使用 operator<。自定义比较运算符必须遵循此逻辑。

现在了解了如何为 std::set_difference 实现自定义运算符 compareFirst,它操作排序元素:

bool compareFirst(const std::pair<int, std::string>& p1, const std::pair<int, std::string>& p2)
{
    return p1.first < p2.first; // 正确的,与 STL 兼容的代码。
}

六、总结

本文深入探讨了 STL 中自定义比较函数的使用,以及比较操作中相等和等价的概念。

  • STL 被分为排序部分和非排序部分,分别使用 operator<operator== 进行比较。
  • 自定义比较函数必须遵循 STL 的比较规则,才能与排序部分的算法兼容。
  • 相等和等价在大多数情况下是相同的,但对于一些特殊类型,例如不区分大小写的字符串,它们可能存在差异。

理解这些概念对于高效使用 STL 至关重要。例如,在使用 std::set_difference 等算法时,需要根据数据的排序方式和比较需求,选择合适的自定义比较函数,才能获得预期的结果。

此外,本文还强调了自定义比较函数在 STL 中的灵活性和重要性,它可以帮助我们定制算法的行为,满足各种不同的需求。

在这里插入图片描述

  • 30
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值