1、关联容器实现自己的比较类型
我们经常定义一个容器。
vector<int> vec;
其实,这个是下面函数的缩写。
vector<int, less<int>> vec;
vector<int, less<int>, allocator<int>> vec;
只不过是默认的,容器中指定了默认的比较大小的函数和分配子。当然这主要是对关联容器来说的,因为序列容器是有顺序的。
但是如果我们在容器中存入的是指针,并且对输出有一定的顺序要求,那么默认的比较函数可能会不能满足我们的需求。如果我们使用默认的less类型,则比较的是指针的大小,并不会是指针指向的内存的大小。
以 string*为例。假设我们需要输出的时候能够按照字符串的大小输出,则我们需要自己创建一个用来比较字符串的比较类型。
struct StringPtrLess : public binary_function<const string*, const string*, bool>
{
bool operator()(const string* p1, const string* p2)
{
return *p1 < *p2;
}
}
使用上面的StringPtrLess,我们就可以作为容器的比较类型:
typedef set<string*, StringPtrLess> StringPtrSet;
StringPtrSet sp;
for(StringPtrSet::const_iterator iter = sp.bengin(); iter != sp.end(); ++iter)
{
cout << **iter << endl;
}
上面针对每个元素的打印函数都要进行一次指针解引用,这在我们日常中是很容易忽略的一个问题,因此,针对该问题,我们可以提前进行指针的解引用。
void print(const string* p)
{
count << *p << endl;
}
for_each(sp.bengin(), sp.end(); print);
或者,我们可以写一个通用的解引用的类型,和transform函数一起使用。
struct Dereference{
template<typename T> const T& operator(const T* ptr) const
{
return *ptr;
}
};
transform(sp.bengin(), sp.end(), ostream_iterator<string>(cout, "\n"), Dereference())
上面的例子我们已经编写了定制化的比较类型。主要适用于元素是指针、引用、迭代器等的关联容器中。因为默认的比较类型会根据指针的大小去存储,而不是按照指针指向的实际内存的大小来存储。
但是,为什么不能直接用一个比较函数呢?而一定要比较类型。
bool stringLess(const string* p1, const string* p2)
{
return *p1 < *p2;
}
typedef set<string*, stringLess> StringSet;
上面的代码不能编译通过。主要是因为set模板的三个参数都是类型,并不是简单的函数。
我们由特例推一下普通化。
struct DereferenceLess
{
template<typename T> bool operator()(const T p1, const T p2)
{
return *p1 < *p2;
}
}
2、比较函数在等值的情况下一定要返回false
这个条款必须得在自己手动实现比较类型的时候才有用,如果选择使用默认的less做为比较函数,则不必进行特殊的考虑。
set<int, less_equal<int>> s;
s.insert(10);
上面的实例我们先创建了一个元素为int类型的集合容器,并且指定了元素之间用 "<="来排序。然后在容器中插入10;接下来我们继续往容器中插入10;也就是执行下面的代码:
s.insert(10);
会发生什么事情呢?
按照我们正常的思维和经验,这行代码会执行失败,容器中也就不会被插入有相同值的元素。因为我们默认的set创建时时这样子的。
set<int> s;
没有指定排序方式,也就是默认是以 less 排序的。
但是实际呢?
在进行第二次尝试insert(10) 的时候,执行比较类型函数进行比较大小。容器在其中找能够插入元素的位置,在遇到10之后,两者调用比较类型函数进行比较。
!(A1 <= A2) && !(A2 <= A1) ==> !true && !true
然后发现,这个值恒为false。
也就是说,在该容器看来,我们前后两次插入的 10 是不相等的,也就是不等价的。
也就意味着,第二个10会被插入到第一个10的旁边。这样我们就相当于破坏了容器原本的特性。任何一个比较函数,如果相等返回的都是true的话,也一定会造成现在的局面,比较函数的作用是返回值标明的是按照该函数定义的排列顺序。一个值是否在另一个值的前面。而相等的值从来是不会有前后顺序的。所以,对于相等的值,比较函数需要始终返回false。
对于 set map是这样的,但是对于multiset 和 multimap 这种可以包含重复的键值的容器,会不会有相同的问题呢?
multiset<int, less_equal<int>> s;
s.insert(10);
s.insert(10);
然后我们对该容器进行equal_range
操作。我们将得到一对迭代器。这一对迭代器定义了一个包含这两个值的区间。
但equal_range
并不指定一个包含相等值的区间,而是包含了一个等价值的区间。
#include<iostream>
#include<set>
#include<algorithm>
using namespace std;
int main()
{
multiset<int, less_equal<int> > s;
s.insert(10);
s.insert(10);
s.insert(11);
pair<multiset<int>::iterator, multiset<int>::iterator> range = equal_range(s.begin(), s.end(), 10);
for(multiset<int>::iterator it = range.first; it != range.second; ++it)
{
//cout << *it << " " ;
}
cout << *range.first << " " << *(++range.first) << " " << *range.second << endl; // 10 10 11
return 0;
}
结果却令人意外。也就是说,使用equal_range之后,得到了一个包含有相等值的区间。