stl容器使用中的经验(四)--关联容器实现自己的比较类型及比较函数在等值的情况下一定要返回false

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之后,得到了一个包含有相等值的区间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值