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

STL 中的自定义比较、相等和等价

一、简介

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

有如下的代码:

cpp 复制代码
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。第一个版本:

cpp 复制代码
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,它操作排序元素:

cpp 复制代码
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 中的灵活性和重要性,它可以帮助我们定制算法的行为,满足各种不同的需求。

相关推荐
Hugo_McQueen4 分钟前
pWnos1.0 靶机渗透 (Perl CGI 的反弹 shell 利用)
linux·服务器·网络安全
金灰10 分钟前
HTML5--裸体回顾
java·开发语言·前端·javascript·html·html5
爱上语文13 分钟前
Java LeetCode每日一题
java·开发语言·leetcode
XY.散人23 分钟前
初识Linux · 文件(1)
linux·运维·服务器
Манго нектар40 分钟前
JavaScript for循环语句
开发语言·前端·javascript
程序猿小D1 小时前
第二百六十九节 JPA教程 - JPA查询OrderBy两个属性示例
java·开发语言·数据库·windows·jpa
阿华的代码王国1 小时前
【JavaEE】——文件IO的应用
开发语言·python
satan–01 小时前
R语言的下载、安装及环境配置(Rstudio&VSCode)
开发语言·windows·vscode·r语言
良月澪二1 小时前
CSP-S 2021 T1廊桥分配
算法·图论
叶北辰CHINA2 小时前
nginx反向代理,负载均衡,HTTP配置简述(说人话)
linux·运维·nginx·http·云原生·https·负载均衡