set & 比较器
目前知道3种比较器
- 模板库的比较器cppReference
- 可调用类
- 重载比较运算符
本文要探讨的是,使用set和multiset中,以及在两者中使用自定义比较器需要注意的问题
set和multiset
key=value
写在前面的总结
set的作用是为某类对象维护一个排序,set中的元素根据所要管理的对象构造(且被const修饰),但并不是那个对象本身
当对象发生改变时,需要更新set,以保持该排序(而更新意味着重新构造set元素,并插入)
自定义比较器的语义:
- 有个判断条件就相当于有几个“key”
- key的优先级基于if分支的先后顺序
- erase会根据传入的元素,依次判断key,删除匹配成功的元素
erase只会删除所有key都匹配成功的元素,否则删除失败- 若要使用多个判断条件,必须有一个条件能够唯一区分元素(id)
对象变动时,更新set的注意事项:
- 首先erase旧的元素
- 对对象进行变动操作
- 将新对象重新insert入set
元素是const修饰的
位于set中的元素的值是不能更改的,但是可以在容器中插入或删除
比较抽象,看代码;
可以看到,下面代码,在元素插入到set中后
改变之前对象的内容,并不会影响到容器中的值
在multiset中也一样

此时,如果使用erase()来删除对象,set将会删除元素失败
可以看到,t1没有被删除;

如果此时将t1插入容器,容器中甚至会出现两个t1

如何修改set中元素
如果需要修改对象的值,正确的做法如下
- 先从set/multiset中移除对应元素
- 修改对象
- 重新将对象插入容器中
这样做之后,可以得到正确的结果

更改不涉及比较条件的属性
更改不涉及比较条件的对象中的属性,不会对容器中对象的查找造成影响
在Test对象中加入var2属性,对其进行更改后,不会影响对其erase

自定义比较器的注意事项
在使用自定义比较器之后,key=value的概念就会凸显出来
因为自定义的比较条件,就是key,就是value
什么意思呢?
意思就是,如果是set使用了该比较器,如果有多个元素满足该条件,那么只会有一个元素被插入
而如果是multiset使用了该比较器,那么erase时,所有元素都会被erase掉
因此,如果想要在set和multiset中存储多个值相同的元素(又不想要以上两个副作用的);那么就需要多个条件来区分元素
乍一看,又要值相同又要区分元素;似乎与
key=value的概念相悖其实不是,比如要在set和multiset中存储对象时(当然这种情况也可以使用map)
按照我的总结来说,判断条件就是key,就是value;因此元素靠判断条件来区分
说来总归抽象,直接看代码
比较条件
首先定义一个Object类,重载了其小于运算符(set按该比较操作的结果进行排序)
Object的对象有id和一个数组
然后定义了一个管理器,使用set保存Object对象
管理器的目标是,按照升序(数组大的往后排)对元素进行排序
struct Object
{
int _id;
vector<int> members;
public:
Object(int id, int c); // 赋予id;往members插入c个元素
bool operator<(const Object& a) const
{...}
};
typedef std::set<Object> ObjectMgr;
// typedef std::multiset<Object> ObjectMgr; 同时讨论multiset
ObjectMgr objectMgr;
// 构造7个object,并插入管理器
Object o1(1, 4);
Object o2(2, 2);
Object o3(3, 1);
Object o4(4, 6);
Object o5(5, 6);
Object o6(6, 5);
Object o7(7, 2);
objectMgr.insert(o1);
objectMgr.insert(o2);
objectMgr.insert(o3);
objectMgr.insert(o4);
objectMgr.insert(o5);
objectMgr.insert(o6);
objectMgr.insert(o7);
// 遍历管理器,查看结果
cout << "size: " << objectMgr.size() << endl;
for(auto it = objectMgr.begin(); it != objectMgr.end(); ++it)
cout << "ID: " << (it->_id) << " num: " << it->members.size() << endl;
下面来看看不同的比较条件,在两种容器的insert和erase中,会带来什么不同的结果
只使用size()
只按照数组size进行排序
bool Object::operator<(const Object& a) const{
return members.size() < a.members.size();
}
set
从输出结果可以看到,id = 5 | 7的元素没有插入
因为之前已经插入过members.size(),与它们相同的元素
而现在,members.size()作为判断条件,正是元素的key
而set中元素的key唯一,因此只有id = 2 | 4的元素被插入

multiset
multiset则不一样;可以看到,所有object都插入到了multiset中
同理,现在members.size()成为了multiset中元素的key

当执行erase()操作时,猜猜会发生什么?是的,所有members.size()相同的元素都会被删除
可以看到,数组大小与o7相同的o2也从multiset中删除了

在set中使用<= || >=
不要在set中使用=作为判断条件,因为这本身不符合语义;并且会导致bug
虽然使用=,可以让值(从而键)相同的元素可以在set中同时存在
但无法删除元素
加入_id
正像之前所说的,若想不出现o5\o7没有插入set;o7\o2同时被删除的情况
就需要加入新的判断条件,这里加入_id
bool Object::operator<(const Object& a) const{
if(members.size() < a.members.size())
return ture;
else if(members.size() > a.members.size())
return false; // 第一个判断条件
else
return _id < a._id; // 第二个判断条件
}
第一个判断条件要写全,也就是
> <两种情况;否则第一个条件会被降级
例如现在若b.size()>c.size() && b._id < c._id;那么b会排c的前面;这和初衷相悖
现在,元素的key变成了一个二元组{members.size(), _id}
只要members.size() != && _id !=,就可以区分两个元素
set
现在,set和multiset插入的结果都一样了
实现了插入“某个值”相同的元素

multiset
而现在,multiset的erase也不会同时删除o2、o7了

自定义比较器
仿函数
仿函数,即重载函数调用运算符()的类;是一种可调用对象
需要注意的事项
- 有两个形参
- 需要const修饰
struct Comp{
bool operator()(const T& a, const T& b) {...}
};
使用仿函数作为比较器
std::set<T, Comp> container;
重载比较运算符
重载比较运算符的语义是,与同类对象进行比较,因此,只需要一个参数
需要注意的点:
- 参数只有一个
- 两个const修饰符
class T{
public:
bool operator<(const T& a) const {...}
};
使用类自身的<运算符作为比较器
std::set<T> container;
常见错误
仿函数参数没有使用const修饰

重载比较运算符时,没有用const修饰函数,编译报错

重载比较运算符时,没有用const修饰参数,编译报警

弄混仿函数与比较运算符;误将类中的函数调用运算符作为比较器
编译报警,可以运行;但是会发现元素并没有排序


浙公网安备 33010602011771号