创建 set

首先是 set 容器的类模板定义。

template < class T,     // 键 key 和值 value 的类型
           class Compare = less<T>,  // 指定 set 容器内部的排序规则
           class Alloc = allocator<T>  // 指定分配器对象的类型
           > class set;

其中第一个参数表示 set 当中元素的类型,第二个参数则是 set 容器内部的排序规则,第三个参数可以忽略,一般用不到。

set33 种构造函数,可以应用在不同的场景当中,我们简单来列举一下。

第一种

set<string> st;

最常规的一种,没有任何参数,直接创建。

第二种

set<string> st{"good", "bad", "medium"};

直接通过花括号枚举我们要传入 set 的值。

第三种

set<string> st{"good", "bad", "medium"};
set<string> st2(st);

拷贝创建,从另外一个 set 当中拷贝元素。

除了这三种形式的构造函数之外,还可以利用 set 类模板的第二个参数,传入元素排序规则来影响 set 中元素的排序,这勉强也算是一种构造方法:

set<string, greater<string>> st{"good", "bad", "medium"};

我们不传入 greater 的排序结果是 badgoodmedium,当我们传入了这个参数之后,结果会变成:mediumgoodbad

这是因为我们传入的排序规则重新定义了元素的大小关系。

使用 set

创建完了 set 就需要使用,使用无非增删改查。

我们先来说说增,往 set 里添加元素的函数有好几个,我们一个一个来说。

关于 insert

insert 函数非常简单,就直接调用,往 set 里插入即可。

st.insert("hhh");

insert 还可以批量插入多个元素:

st.insert({"hhh", "wow"});

关于 emplace

emplace 函数的功能和 insert 一样,可以往 set 当中插入元素。它和 insert 最大的区别在于 emplace 传入的参数并不是要插入的元素,而是构造元素需要的参数。

我这么说估计有点难理解,其实很简单,我们来对比一下就知道了。

假设我们有一个 set 它的类型是结构体 PP,当中我们重载了它的比较算子,这个先忽略。

struct P {
 int x, y;
    P(int x, int y) : x(x), y(y){};
    bool operator<(const P b) const  {  
         return this->x < b.x;  
    } 
};

set<P> st;

如果我们要使用 insert 应该怎么操作呢?

P p{0, 3};
st.insert(p);

如果使用 emplace 函数呢,则是这样:

st.emplace(1, 23);

因为 emplace 的内部会替我们去调用结构体 PP 的构造函数,使用 112323 这两个参数构造出一个 PP 的实例来存入 set 当中。

使用 emplace 可以节省掉创建实例的一步,所以通常工程当中往往大量使用 emplace

emplace 函数返回的结果是一个 pairpair 的第一个元素是 set 的迭代器,表示插入的元素的位置,第二个值是一个 bool,表示是否插入成功。

关于 emplace_hint

emplace 函数的改进版,接受额外的参数表示插入 set 的位置。它的返回结果也有了一些变化,返回的是一个迭代器。

如果插入成功则返回新添加的元素,否则则指向 set 容器中和添加元素相同的元素。

使用 emplace_hint 会影响 set 中的有序性,一般不建议使用。

关于 erase

说完了插入再说说删除,在 set 当中删除的方法只有一个就是 erase,但是它却有好几种用法。

我们直接来看它的函数签名:

size_type erase (const value_type& val);
iterator erase (const_iterator position);
iterator erase (const_iterator first, const_iterator last);

第一种方法我们传入了一个 val 值,也就是我们要删除的元素。

第二种方法我们传入的是一个迭代器,它会删除迭代器指向的元素。第三种方法类似,只不过我们传入的是两个迭代器,表示一个范围,它会删除这个范围内所有的元素。

第一种方法的返回值是一个整数,表示删除的元素个数。后面两种返回的都是一个迭代器,指向删除元素后面一个位置。

关于 clear

清空set。

关于 find

set 中的查询函数,传入我们要查询的 value,返回一个迭代器。

set<string>::iterator it = st.find("good");

如果成功找到则返回指向该元素的迭代器,否则指向 end

关于count

同样是查询函数,只不过它返回的不再是迭代器,而是一个整数,表示查询到元素的个数。

int cnt = st.count("good");

lower_boundupper_bound

lower_boundupper_bound 严格也算是查询函数,只不过它们查询的范围。lower_bound 查询的是 set 当中第一个大于等于 val 的位置,而 upper_bound 查询的是 set 中第一个严格大于 val 的位置。

set<string>::iterator it_low = st.lower_bound("i");
set<string>::iterator it_up = st.upper_bound("i");

同样这两个函数返回的是一个迭代器。

关于 equal_range

这个函数返回的是一个 pair,它的第一个元素是 lower_bound 的结果,第二个元素是 upper_bound 的结果。

pair<set<string>::iterator, set<string>::iterator> ret = st.equal_range("i");

总结

到这里,关于 set 常用的方法基本上就都介绍完了,除此之外还有一些其他细枝末节的方法就不赘述了。比如像是 size()max_size() 等等,大家有用到去查询即可。