set 容器定义于<set>头文件,并位于 std 命名空间中。如果想在程序中使用 set 容器,该程序代码应先包含如下语句:

1
2
#include <set>
using namespace std;

set 容器的类模板定义如下:

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

注意,由于 set 容器存储的各个键值对,其键和值完全相同,也就意味着它们的类型相同,因此 set 容器类模板的定义中,仅有第 1 个参数用于设定存储数据的类型。

对于 set 类模板中的 3 个参数,后 2 个参数自带默认值,且几乎所有场景中只需使用前 2 个参数,第 3 个参数不会用到。

🐢set容器

和 map、multimap 容器不同,使用 set 容器存储的各个键值对,要求键 key 和值 value 必须相等。

举个例子,如下有 2 组键值对数据:

1
2
{<'a', 1>, <'b', 2>, <'c', 3>}
{<'a', 'a'>, <'b', 'b'>, <'c', 'c'>}

显然,第一组数据中各键值对的键和值不相等,而第二组中各键值对的键和值对应相等。对于 set 容器来说,只能存储第 2 组键值对,而无法存储第一组键值对。

基于 set 容器的这种特性,当使用 set 容器存储键值对时,只需要为其提供各键值对中的 value 值(也就是 key 的值)即可。仍以存储上面第 2 组键值对为例,只需要为 set 容器提供 {‘a’,’b’,’c’} ,该容器即可成功将它们存储起来。

🐢set的简介

  • set是一个集合容器,其中所包含的元素是唯一的,集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。

  • set采用Post not found: 程序员内功/算法与数据结构/数据结构/红黑树变体的数据结构实现,红黑树属于平衡二叉树。在插入操作和删除操作上比vector快。

  • set不可以直接存取元素。(不可以使用at.(pos)与[]操作符)。

  • multiset与set的区别:set支持唯一键值,每个元素值只能出现一次;而multiset中同一值可以出现多次。

  • </p>不可以直接修改set或multiset容器中的元素值,因为该类容器是自动排序的。如果希望修改一个元素值,必须先删除原有的元素,再插入新的元素。

🐢创建set容器

常见的创建set容器的方法,大致有以下5种:

1)默认构造函数

调用默认构造函数,创建空的set容器。set对象的默认构造

1
2
3
set<int> setInt;       //一个存放int的set容器。
set<float> setFloat; //一个存放float的set容器。
set<string> setString; //一个存放string的set容器。

由此就创建好了一个 set 容器,该容器采用默认的std::less<T>规则,会对存储的 string 类型元素做升序排序。注意,由于 set 容器支持随时向内部添加新的元素,因此创建空 set 容器的方法是经常使用的。

2)创建并初始化

1
2
3
4
5
set<string> myBlogSet{
"http://fole-del.github.io",
"http://fole-del.gitee.io",
"http://www.mingsrc.work"
};

由此即创建好了包含 3 个 string 元素的 myBlogSet容器。由于其采用默认的 std::less 规则,因此其内部存储 string 元素的顺序如下所示:

http://fole-del.gitee.io
http://fole-del.github.io
http://www.mingsrc.work

3)拷贝构造函数

使用set类模板提供的拷贝构造函数,在第2种myBlog的基础上,执行如下代码:

1
2
3
std::set<std::string> copyBlogSet(myBlogSet);
//等同于
//std::set<std::string> copyBlogSet = myBlogSet

该行代码在创建 copyBlogSet 容器的基础上,还会将myBlogSet容器中存储的所有元素,全部复制给copyBlogSet容器一份。

除此之外,C++11标准还为set类模板新增了移动构造函数,其功能是实现创建新 set 容器的同时,利用临时的 set 容器为其初始化。比如:

1
2
3
4
5
6
7
8
9
set<string> retBlogSet() {
std::set<std::string> myBlogSet{ "http://fole-del.github.io",
"http://fole-del.gitee.io",
"http://www.mingsrc.work"};
return myBlogSet;
}
std::set<std::string> copyBlogSet(retBlogSet());
//或者
//std::set<std::string> copyBlogSet = retBlogSet();

4) 使用已有set的部分初始化set

在第 3 种方式的基础上,set 类模板还支持取已有 set 容器中的部分元素,来初始化新 set 容器。例如:

1
2
3
4
set<string> myBlogSet{"http://fole-del.github.io",
"http://fole-del.gitee.io",
"http://www.mingsrc.work"};
std::set<std::string> copyBlogSet(++myBlogSet.begin(), myBlogSet.end());

由此初始化的copyset容器,其内部有如下2两个字符串:

http://fole-del.github.io
http://www.mingsrc.work

5)修改set的排序规则

以上几种方式创建的 set 容器,都采用了默认的std::less<T>规则。其实,借助 set 类模板定义中第 2 个参数,我们完全可以手动修改 set 容器中的排序规则。比如:

1
2
3
set<string,greater<string> > myBlogSet{"http://fole-del.github.io",
"http://fole-del.gitee.io",
"http://www.mingsrc.work"};
http://www.mingsrc.work
http://fole-del.github.io
http://fole-del.gitee.io
# 🐢set容器成员方法
下表C++ set 容器常用成员方法
成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
find(val) 在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(val) 返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(val) 返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(val) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 set 容器中存有元素的个数。
max_size() 返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
insert() 向 set 容器中插入元素。
erase() 删除 set 容器中存储的元素。
swap() 交换 2 个 set 容器中存储的所有元素。这意味着,操作的 2 个 set 容器的类型必须相同。
clear() 清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。
emplace() 在当前 set 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。
emplace_hint() 在本质上和 emplace() 在 set 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。
count(val) 在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。

🐢set的操作

set的插入

set.insert(elem); //在容器中插入元素。

Set集合的元素排序

set > setIntA; //该容器是按升序方式排列元素。

set> setIntB; //该容器是按降序方式排列元素。

set 相当于 set>。

less与greater中的int可以改成其它类型,该类型主要要跟set容纳的数据类型一致。

注意点1: less<>与greate< >是什么?

注意点2:如果set<>不包含int类型,而是包含自定义类型,set容器如何排序?

要解决如上两个问题,需要了解容器的函数对象,也叫伪函数,英文名叫functor。

下面将讲解什么是functor以及用法。

使用stl提供的函数对象

set> setIntB;

setIntB.insert(3);

setIntB.insert(1);

setIntB.insert(5);

setIntB.insert(2);

此时容器setIntB就包含了按顺序的5,3,2,1元素

函数对象functor的用法

尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。

functor,翻译成函数对象,伪函数,算符,是重载了“()”操作符的普通类对象。从语法上讲,它与普通函数行为类似。

greater<>与less<>就是函数对象。

下面举出greater的简易实现原理。

1
2
3
4
5
6
7
8
class greater
{
public:
bool operator()(const int& iLeft, const int& iRight)
{
return iLeft > iRight; //如果是实现less<int>的话,这边是写return (iLeft<iRight);
}
};

容器就是调用函数对象的operator()方法去比较两个值的大小。

仿函数练习

学生包含学号,姓名属性,现要求任意插入几个学生对象到set容器中,使得容器中的学生按学号的升序排序。

解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<iostream>
#include<set>
using namespace std;
//学生类
class CStudent
{
public:
CStudent(int iID, string strName)
{
m_iID = iID;
m_strName = strName;
}
//private:
int m_iID; //学号
string m_strName; //姓名


};
ostream& operator<<(ostream& os, CStudent& stu) {
os << stu.m_iID << " ";
for (auto it : stu.m_strName)
os << it;
return os;
}
//为保持主题鲜明,本类不写拷贝构造函数,本类也不需要写拷贝构造函数。但大家仍要有考虑拷贝构造函数的习惯。
//函数对象
struct StuFunctor
{
bool operator()(const CStudent& stu1, const CStudent& stu2) const
{
return (stu1.m_iID < stu2.m_iID);
}
};

int main()
{
set<CStudent, StuFunctor> setStu;
setStu.insert(CStudent(3, "小张"));
setStu.insert(CStudent(1, "小李"));
setStu.insert(CStudent(5, "小王"));
setStu.insert(CStudent(2, "小刘"));
for (auto i : setStu)
{
cout << i << endl;
}
return 0;
}

程序运行结果:

1 小李
2 小刘
3 小张
5 小王
## set对象的拷贝构造与赋值 set(const set &st); //拷贝构造函数 set& operator=(const set &st); //重载等号操作符 set.swap(st); //交换两个集合容器 ## set的大小 set.size(); //返回容器中元素的数目 set.empty();//判断容器是否为空 ## set的删除 set.clear(); //清除所有元素 set.erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。 set.erase(beg,end); //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。 set.erase(elem); //删除容器中值为elem的元素。 ## set的查找 set.find(elem); //查找elem元素,返回指向elem元素的迭代器。 set.count(elem); //返回容器中值为elem的元素个数。对set来说,要么是0,要么是1。对multiset来说,值可能大于1。 set.lower_bound(elem); //返回第一个>=elem元素的迭代器。 set.upper_bound(elem); // 返回第一个>elem元素的迭代器。 set.equal_range(elem); //返回一对迭代器,这两个迭代器分别用于发现set中其键大于指定键的第一个元素,以及集中其键等于或大于指定键的第一个元素。 以上函数返回两个迭代器,而这两个迭代器被封装在pair中。 # 💻程序演示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main(){
//创建空set容器
std::set<std::string> myBlogSet;
//空set容器不存储任何元素
cout << "1、myBlogSet size = " << myset.size() << endl;
//向myBlogSet容器中插入新元素
myset.insert("http://www.mingsrc.work");
myset.insert("http://fole-del.github.io");
myset.insert("http://fole-del.gitee.io");
cout << "2、myBlogSet size = " << myBlogSet.size() << endl;
//利用双向迭代器,遍历myBlogSet
for (auto iter = myBlogSet.begin(); iter != myBlogSet.end(); ++iter) {
cout << *iter << endl;
}
return 0;
}
程序的执行结果为:
1、myBlogSet size = 0
2、myBlogSet size = 3
http://fole-del.gitee.io
http://fole-del.github.io
http://www.mingsrc.work