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);
这里有两个由两个排序向量 v1
和 v2
表示的数据集,对其应用 std::set_difference
。std::set_difference
将其输出写入 results
,std::back_inserter
确保所有输出都被 push_back
到 results
中。
但是,有一个特殊之处:使用 std::set_difference
时提供了一个自定义比较运算符 compareFirst
。
默认情况下,std::set_difference
使用 std::pair
上的默认比较来比较元素(它比较 pair
的第一个和第二个元素),而在这里希望使用 compareFirst
仅根据第一个元素比较 pair
。compareFirst
不在 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::map
、std::multimap
、std::set
、std::multiset
),因为它们的元素已排序。
一些算法也属于此类别,因为它们假设它们操作的元素已排序:例如 std::set_difference
、std::includes
或 std::binary_search
。
三、STL 的未排序部分
这部分包括序列容器(std::vector
、std::list
、std::deque
和 std::string
),因为它们的元素不一定排序。
属于此类别的算法是不需要其元素排序的算法,例如 std::equal
、std::count
或 std::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 中的灵活性和重要性,它可以帮助我们定制算法的行为,满足各种不同的需求。