set & 比较器

目前知道3种比较器

  1. 模板库的比较器cppReference
  2. 可调用类
  3. 重载比较运算符

本文要探讨的是,使用set和multiset中,以及在两者中使用自定义比较器需要注意的问题

set和multiset

key=value

写在前面的总结
set的作用是为某类对象维护一个排序,set中的元素根据所要管理的对象构造(且被const修饰),但并不是那个对象本身
当对象发生改变时,需要更新set,以保持该排序(而更新意味着重新构造set元素,并插入)
自定义比较器的语义:

  1. 有个判断条件就相当于有几个“key”
  2. key的优先级基于if分支的先后顺序
  3. erase会根据传入的元素,依次判断key,删除匹配成功的元素
    erase只会删除所有key都匹配成功的元素,否则删除失败
  4. 若要使用多个判断条件,必须有一个条件能够唯一区分元素(id)

对象变动时,更新set的注意事项:

  1. 首先erase旧的元素
  2. 对对象进行变动操作
  3. 将新对象重新insert入set

元素是const修饰的

位于set中的元素的值是不能更改的,但是可以在容器中插入或删除

比较抽象,看代码;

可以看到,下面代码,在元素插入到set中后

改变之前对象的内容,并不会影响到容器中的值

在multiset中也一样

此时,如果使用erase()来删除对象,set将会删除元素失败

可以看到,t1没有被删除;

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

如何修改set中元素

如果需要修改对象的值,正确的做法如下

  1. 先从set/multiset中移除对应元素
  2. 修改对象
  3. 重新将对象插入容器中

这样做之后,可以得到正确的结果

更改不涉及比较条件的属性

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

自定义比较器的注意事项

在使用自定义比较器之后,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了

自定义比较器

仿函数

仿函数,即重载函数调用运算符()的类;是一种可调用对象
需要注意的事项

  1. 有两个形参
  2. 需要const修饰
struct Comp{
    bool operator()(const T& a, const T& b) {...}
};

使用仿函数作为比较器

std::set<T, Comp> container;

重载比较运算符

重载比较运算符的语义是,与同类对象进行比较,因此,只需要一个参数

需要注意的点:

  1. 参数只有一个
  2. 两个const修饰符
class T{
public:
    bool operator<(const T& a) const {...}
};

使用类自身的<运算符作为比较器

std::set<T> container;

常见错误

仿函数参数没有使用const修饰
image

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

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

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

posted @ 2024-04-17 23:00  lifeAddicted  阅读(98)  评论(0)    收藏  举报