一、前言
和 map、multimap 容器不同,使用 set 容器存储的各个键值对,要求键 key 和值 value 必须相等。如下:
{<'a', 'a'>, <'b', 'b'>, <'c', 'c'>}
不支持存储键和值不相等的键值对。因此在使用set容器时,只需要为其提供各键值对中的value值即可。set容器和map容器一样,会自行根据键的大小对存储的键值对进行排序,在set容器中,实际上也是根据value值进行排序。
set 容器存储的各个元素的值必须各不相同。需要注意的是:在语法上set容器并没有强制对存储元素的类型做const修饰,因此set容器中存储的元素的值是可以修改的。但是,C++ 标准为了防止用户修改容器中元素的值,对所有可能会实现此操作的行为做了限制,使得在正常情况下,用户是无法做到修改 set 容器中元素的值的。
注意:直接修改set容器中的已存储的元素的值,可能会破坏set容器中元素的有序性。最正确的修改 set 容器中元素值的做法是:先删除该元素,然后再添加一个修改后的元素。使用set容器,需要
#include <set>
1、set容器类模板的定义
template < class T, // 键 key 和值 value 的类型
class Compare = less<T>, // 指定 set 容器内部的排序规则
class Alloc = allocator<T> // 指定分配器对象的类型
> class set;
因为键和值的类型是一样的,因此这里只有3个参数,大多数情况下,主需要用到其中的前2个参数。
二、创建set容器的方法
常见的创建 set 容器的方法,大致有以下 5 种。
①std::set<std::string> myset;
②std::set<std::string> myset{"1","2","3"};
③std::set<std::string> copyset(myset);
④std::set<std::string> myset{"1","2","3"};
std::set<std::string> copyset(++myset.begin(), myset.end());
⑤std::set<std::string,std::greater<string> > myset{"1","2","3"};
①默认构造函数,创建空的set容器,会对存储的 string 类型元素做升序排序
②在创建 set 容器的同时,对其进行初始化
③通过拷贝(复制)构造函数,实现在创建新 set 容器的同时,将已有 set 容器中存储的所有元素全部复制到新 set 容器中,等价于std::set<std::string> copyset = myset;C++ 11 标准还为 set 类模板新增了移动构造函数,其功能是实现创建新 set 容器的同时,利用临时的 set 容器为其初始化,如下:
set<string> retSet()
{
std::set<std::string> myset{ "1","2","3" };
return myset; //返回值是一个临时 set 容器,因此在初始化 copyset 容器时,其内部调用的是 set 类模板中的移动构造函数,而非拷贝构造函数。
}
std::set<std::string> copyset(retSet());
//等价于 std::set<std::string> copyset = retSet();
④set 类模板取已有 set 容器中的部分元素,来初始化新 set 容器
⑤、修改 set 容器中的排序规则方式
三、方法
这里不列举迭代器、插入和删除相关的方法。
方法 | 功能 |
---|---|
empty() | 若容器为空,则返回 true;否则 false |
size() | 返回当前 set 容器中存有元素的个数 |
max_size() | 返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同 |
count(val) | 在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1 |
1、实例
1)、创建set容器
方式1
std::set<std::string> myset;
std::cout << "myset size: " << myset.size() << std::endl;
方式2
std::set<std::string> myset1{
"who are you",
"i am lin",
"i am wu",
"i am ouyang"};
std::cout << "myset1 size: " << myset1.size() << std::endl;
std::set<std::string>::iterator setIter = myset1.begin();
for (; setIter != myset1.end(); setIter++)
{
std::cout << "myset1 :" << *setIter << std::endl;
}
方式3
std::set<std::string> myset2(myset1);
std::cout << "myset2 size: " << myset2.size() << std::endl;
setIter = myset2.begin();
for (; setIter != myset2.end(); setIter++)
{
std::cout << "myset2 :" << *setIter << std::endl;
}
移动构造函数方式
set<string> RemoveStructureSet()
{
std::set<std::string> myset{
"who are you",
"i am wen",
"i am li" };
return myset;
}
std::set<std::string> myset3(RemoveStructureSet());
//等价于 std::set<std::string> myset3 = RemoveStructureSet();
setIter = myset3.begin();
for (; setIter != myset3.end(); setIter++)
{
std::cout << "myset3 :" << *setIter << std::endl;
}
方式4
std::set<std::string> myset4(++myset.begin(), --myset.end());
setIter = myset4.begin();
for (; setIter != myset4.end(); setIter++)
{
std::cout << "myset4 :" << *setIter << std::endl;
}
方式5
std::set<std::string, std::greater<string> > myset5{
"who are you",
"i am lin",
"i am wu",
"i am ouyang" };
setIter = myset5.begin();
for (; setIter != myset5.end(); setIter++)
{
std::cout << "myset5 :" << *setIter << std::endl;
}
结果如下:
四、迭代器
set 容器类模板中未提供 at() 成员函数,也未对 [] 运算符进行重载。C++ STL 标准库为 set 容器配置的迭代器类型为双向迭代器,则set容器的迭代器支持++p、p++、--p、p--、*p 操作,并且 2 个双向迭代器之间做比较,只能使用 == 或者 != 运算符。
方法 | 功能 |
---|---|
begin() | 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器 |
end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器 |
rbegin() | 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器 |
rend() | 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。通常和 rbegin() 结合使用。如果 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 容器中各个元素是唯一的,因此该范围最多包含一个元素) |
以上成员函数返回的迭代器,指向的只是 set 容器中存储的元素,而不再是键值对。以上成员方法返回的迭代器,无论是 const 类型还是非 const 类型,都不能用于修改 set 容器中的值。
begin和end方法在这里不再说明。equal_range(val) 函数的返回值是一个 pair 类型数据,其包含 2 个迭代器,表示 set 容器中和指定参数 val 相等的元素所在的区域,但由于 set 容器中存储的元素各不相等,因此该函数返回的这 2 个迭代器所表示的范围中,最多只会包含 1 个元素。
1、实例
1)、find和count
std::set<std::string> myset1{
"who are you",
"i am lin",
"i am wu",
"i am ouyang"};
std::cout << "myset1 size: " << myset1.size() << std::endl;
std::set<std::string>::iterator setIter = myset1.begin();
for (; setIter != myset1.end(); setIter++)
{
std::cout << "myset1 :" << *setIter << std::endl;
}
//find
set<std::string>::iterator findIter = myset1.find("i am wu");
cout << "findIter: " << *findIter << endl;
//count
int nCount = myset1.count("i am wu");
cout << "nCount: " << nCount << endl;
2)、lower_bound和upper_bound
set<std::string>::iterator lowerIter = myset1.lower_bound("i am wu");
cout << "lowerIter: " << *lowerIter << endl;
set<std::string>::iterator upperIter = myset1.upper_bound("i am wu");
cout << "upperIter: " << *upperIter << endl;
3)、equal_range
pair<set<std::string>::iterator, set<std::string>::iterator> mypair = myset1.equal_range("i am wu");
set<std::string>::iterator pairIter1 = mypair.first;
cout << "pairIter1: " << *pairIter1 << endl;
set<std::string>::iterator pairIter2 = mypair.second;
cout << "pairIter2: " << *pairIter2 << endl;
结果如下:
五、插入数据
1、insert
1)直接将键的值插入set容器
格式 | 说明 |
---|---|
pair<iterator,bool> insert (const value_type& val); | 普通引用方式传参 |
pair<iterator,bool> insert (value_type&& val); | 右值引用方式传参 |
以上 2 种语法格式的 insert() 方法,返回的都是 pair 类型的值,其包含 2 个数据,一个迭代器和一个 bool 值:
当向 set 容器添加元素成功时,该迭代器指向 set 容器新添加的元素,bool 类型的值为 true;
如果添加失败,即证明原 set 容器中已存有相同的元素,此时返回的迭代器就指向容器中相同的此元素,同时 bool 类型的值为 false。
std::set<std::string> myset1;
//格式1
pair<set<std::string>::iterator, bool> ret;
string str = "i am lin";
ret = myset1.insert(str);
cout << "one iter->" << *(ret.first) << " , " << "bool = " << ret.second << endl;
//格式2
ret = myset1.insert("i am huang");
cout << "two iter->" << *(ret.first) << " , " << "bool = " << ret.second << endl;
//插入失败
ret = myset1.insert("i am huang");
cout << "failed iter->" << *(ret.first) << " , " << "bool = " << ret.second << endl;
std::cout << "myset1 size: " << myset1.size() << std::endl;
std::set<std::string>::iterator setIter = myset1.begin();
for (; setIter != myset1.end(); setIter++)
{
std::cout << "myset1 :" << *setIter << std::endl;
}
结果如下:
2)指定将新元素插入到 set 容器中的具体位置
格式 | 说明 |
---|---|
iterator insert (const_iterator position, const value_type& val); | 以普通引用的方式传递 val 值 |
iterator insert (const_iterator position, value_type&& val); | 以右值引用的方式传递 val 值 |
以上 2 种语法格式中,insert() 函数的返回值为迭代器:
当向 set 容器添加元素成功时,该迭代器指向容器中新添加的元素;
当添加失败时,证明原 set 容器中已有相同的元素,该迭代器就指向 set 容器中相同的这个元素。
std::set<std::string> myset1;
set<std::string>::iterator iter;
//格式1
string str = "i am lin";
iter = myset1.insert(myset1.begin(), str);
cout << "one insert iter->" << *iter << endl;
//格式2
iter = myset1.insert(myset1.end(), "i am huang");
cout << "two insert iter->" << *iter << endl;
std::cout << "myset1 size: " << myset1.size() << std::endl;
std::set<std::string>::iterator setIter = myset1.begin();
for (; setIter != myset1.end(); setIter++)
{
std::cout << "myset1 :" << *setIter << std::endl;
}
结果如下:
使用 insert() 方法将目标元素插入到 set 容器指定位置后,如果该元素破坏了容器内部的有序状态,set 容器还会自行对新元素的位置做进一步调整,即会判断有序性是否成立,不成立会调整键值对顺序。
3)向当前 set 容器中插入其它 set 容器指定区域内的所有元素
前提:2 个 set 容器存储的元素类型相同即可 | |
---|---|
template <class InputIterator> void insert (InputIterator first, InputIterator last); | 插入其它 set 容器指定区域内的所有元素 |
std::set<std::string> myset1{
"i am wang",
"i am wu",
"i am a",
"i am kai"};
std::set<std::string> myset2;
myset2.insert(++myset1.begin(), --myset1.end());
std::cout << "myset2 size: " << myset2.size() << std::endl;
std::set<std::string>::iterator setIter = myset2.begin();
for (; setIter != myset2.end(); setIter++)
{
std::cout << "myset2 :" << *setIter << std::endl;
}
结果如下:
4)一次性插入多个元素
void insert ( {E1, E2,...,En} ); |
std::set<std::string> myset1{
"i am wang",
"i am wu",
"i am a",
"i am kai"};
myset1.insert({ "i am ouyang","i am fang", "i am lu", "i am xu" });
std::cout << "myset1 size: " << myset1.size() << std::endl;
std::set<std::string>::iterator setIter = myset1.begin();
for (; setIter != myset1.end(); setIter++)
{
std::cout << "myset1 :" << *setIter << std::endl;
}
结果如下:
C++ 11 标准的 set 类模板中,还提供有另外 2 个成员方法,分别为 implace() 和 implace_hint() 方法,借助它们不但能实现向 set 容器添加新元素的功能,其实现效率也比 insert() 成员方法更高。
2、emplace
template <class... Args> pair<iterator,bool> emplace (Args&&... args); |
只需要传入构建新元素所需的数据即可,该方法可以自行利用这些数据构建出要添加的元素。另外,该方法的返回值类型为 pair 类型,其包含 2 个元素,一个迭代器和一个 bool 值:
当该方法将目标元素成功添加到 set 容器中时,其返回的迭代器指向新插入的元素,同时 bool 值为 true;
当添加失败时,则表明原 set 容器中已存在相同值的元素,此时返回的迭代器指向容器中具有相同键的这个元素,同时 bool 值为 false。
std::set<std::string> myset;
pair<set<std::string, std::string>::iterator, bool> ret = myset.emplace("i am lin");
cout << "ret.iter = < value: " << *(ret.first) << ", result: " << ret.second << ">" << endl;
结果如下:
3、emplace_hint
template <class... Args> iterator emplace_hint (const_iterator position, Args&&... args); |
和 emplace() 方法相比:
该方法需要额外传入一个迭代器,用来指明新元素添加到 set 容器的具体位置(新元素会添加到该迭代器指向元素的前面);
返回值是一个迭代器,而不再是 pair 对象。当成功添加元素时,返回的迭代器指向新添加的元素;反之,如果添加失败,则迭代器就指向 set 容器和要添加元素的值相同的元素。
std::set<std::string> myset{"i am wu","i am kai","i am yang"};
set<std::string>::iterator iter = myset.emplace_hint(++myset.begin(), "i am lin");
cout << "iter = < value: " << *iter << " >" << endl;
std::cout << "myset size: " << myset.size() << std::endl;
std::set<std::string>::iterator setIter = myset.begin();
for (; setIter != myset.end(); setIter++)
{
std::cout << "myset1 :" << *setIter << std::endl;
}
结果如下:
六、删除元素
删除 set 容器存储的元素,可以选择用 erase() 或者 clear() 成员方法。
1、erase方法
语法 | 说明 |
---|---|
size_type erase (const value_type& val); | 删除 set 容器中值为 val 的元素 |
iterator erase (const_iterator position); | 删除 position 迭代器指向的元素 |
iterator erase (const_iterator first, const_iterator last); | 删除 [first,last) 区间内的所有元素 |
①方法返回值是一个整数,表示成功删除的元素个数
②③方法返回值都是迭代器,其指向的是 set 容器中删除元素之后的第一个元素
注意:如果要删除的元素就是set容器最后一个元素,则erase()方法返回的迭代器就指向新 set 容器中最后一个元素之后的位置(等价于end()方法返回的迭代器)
2、clear
删除set容器中存储的所有元素,不需要参数,也没有返回值
因删除元素函数比较简单,这里就不写示例了
总结
1、不再以键值对的方式存储数据,因为 set 容器专门用于存储键和值相等的键值对,因此该容器中真正存储的是各个键值对的值(value)
2、set 容器在存储数据时,会根据各元素值的大小对存储的元素进行排序(默认做升序排序);
3、存储到 set 容器中的元素,虽然其类型没有明确用 const 修饰,但正常情况下它们的值是无法被修改的
4、set 容器存储的元素必须互不相等
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。