EdwardWong
2022/12/13阅读:50主题:姹紫
STL关联式容器之set
set
容器概述
set
容器也属于关联式容器,和map
、multimap
容器不同,使用set
容器存储的各个键值对,要求键key
和value
必须相等。
{<'a', 1>, <'b', 2>, <'c', 3>}
{<'a', 'a'>, <'b', 'b'>, <'c', 'c'>}
显然,第一组数据中各键值对的键和值不相等,而第二组中各键值对的键和值对应相等。对于 set
容器来说,只能存储第 2
组键值对,而无法存储第1
组键值对.
基于 set
容器的这种特性,当使用 set
容器存储键值对时,只需要为其提供各键值对中的 value
值(也就是 key
的值)即可。仍以存储上面第 2
组键值对为例,只需要为 set 容器提供 {'a','b','c'}
,该容器即可成功将它们存储起来。
map
、multimap
容器都会自行根据键的大小对存储的键值对进行排序,set
容器也会如此,只不过 set
容器中各键值对的键 key
和值 value
是相等的,根据 key
排序,也就等价为根据 value
排序。
使用 set
容器存储的各个元素的值必须各不相同。更重要的是,从语法上讲 set
容器并没有强制对存储元素的类型做 const
修饰,即 set
容器中存储的元素的值是可以修改的。但是,C++
标准为了防止用户修改容器中元素的值,对所有可能会实现此操作的行为做了限制,使得在正常情况下,用户是无法做到修改 set
容器中元素的值的。
切勿尝试直接修改
set
容器中已存储元素的值,这很有可能破坏set
容器中元素的有序性,最正确的修改set
容器中元素值的做法是:先删除该元素,然后再添加一个修改后的元素。
set
容器定义于<set>
头文件,并位于 std
命名空间中。因此如果想在程序中使用 set
容器,该程序代码应先包含如下语句:
#include <set>
using namespace std;
set
容器的类模板定义如下:
template < class T, // 键 key 和值 value 的类型
class Compare = less<T>, // 指定 set 容器内部的排序规则
class Alloc = allocator<T> // 指定分配器对象的类型
> class set;
创建set
容器
-
调用默认构造函数,创建空的 set
容器
std::set<std::string> myset;
由此就创建好了一个 set
容器,该容器采用默认的std::less<T>
规则,会对存储的 string
类型元素做升序排序。注意,由于 set
容器支持随时向内部添加新的元素,因此创建空 set
容器的方法是经常使用的。
-
set
类模板还支持在创建set
容器的同时进行初始化
std::set<std::string> myset{"http://c.biancheng.net/java/","http://c.biancheng.net/stl/","http://c.biancheng.net/python/"};
由此即创建好了包含 3
个 string
元素的 myset
容器。由于其采用默认的 std::less<T>
规则,因此其内部存储 string
元素的顺序如下所示:

3.set
类模板中还提供拷贝构造函数和移动构造函数,可以实现在创建新set
容器的同时,将已有set
容器中存储的所有元素全部复制到新的set
容器中。
std::set<std::string> copyset(myset);
//等同于
//std::set<std::string> copyset = myset
该行代码在创建 copyset
容器的基础上,还会将 myset
容器中存储的所有元素,全部复制给 copyset
容器一份。
set<string> retSet() {
std::set<std::string> myset{ "http://c.bianche ng.net/java/","http://c.biancheng.net/st l/","http://c.biancheng.net/python/" };
return myset;
}
std::set<std::string> copyset(retSet());
//或者
//std::set<std::string> copyset = retSet();
由于 retSet()
函数的返回值是一个临时 set
容器,因此在初始化 copyset
容器时,其内部调用的是 set
类模板中的移动构造函数,而非拷贝构造函数.
无论是调用复制构造函数还是调用拷贝构造函数,都必须保证这
2
个容器的类型完全一致。
-
将 set
容器中的部分元素初始化set
容器
std::set<std::string> myset{ "http://c.biancheng.net/java/","http://c.biancheng.net/stl/","http://c.biancheng.net/python/" };
std::set<std::string> copyset(++myset.begin(), myset.end());

-
采用默认的 std::less<T>
规则,借助set
类模板定义中第2个参数,可以手动修改set
容器中的排序规则。
std::set<std::string,std::greater<string> > myset{
"http://c.biancheng.net/java/",
"http://c.biancheng.net/stl/",
"http://c.biancheng.net/python/"};
通过选用 std::greater<string>
降序规则,myset
容器中元素的存储顺序为:

set
容器包含的成员方法

#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
//创建空set容器
std::set<std::string> myset;
//空set容器不存储任何元素
cout << "1、myset size = " << myset.size() << endl;
//向myset容器中插入新元素
myset.insert("http://c.biancheng.net/java/");
myset.insert("http://c.biancheng.net/stl/");
myset.insert("http://c.biancheng.net/python/");
cout << "2、myset size = " << myset.size() << endl;
//利用双向迭代器,遍历myset
for (auto iter = myset.begin(); iter != myset.end(); ++iter) {
cout << *iter << endl;
}
return 0;
}

set
容器迭代器
和 map
容器不同,**C++
STL
中的 set
容器类模板中未提供 at()
成员函数,也未对 []
运算符进行重载。因此,要想访问 set
容器中存储的元素,只能借助 set
容器的迭代器**。
C++ STL
标准库为 set
容器配置的迭代器类型为双向迭代器。这意味着,假设 p
为此类型的迭代器,则其只能进行++p
、p++
、--p
、p--
、*p
操作,并且 2
个双向迭代器之间做比较,也只能使用 ==
或者 !=
运算符。

以上成员函数返回的迭代器,指向的只是
set
容器中存储的元素,而不再是键值对。另外,以上成员方法返回的迭代器,无论是const
类型还是非const
类型,都不能用于修改set
容器中的值。
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
//创建并初始化set容器
std::set<std::string> myset{ "http://c.biancheng.net/java/",
"http://c.biancheng.net/stl/",
"http://c.biancheng.net/python/"
};
//利用双向迭代器,遍历myset
for (auto iter = myset.begin(); iter != myset.end(); ++iter) {
cout << *iter << endl;
}
return 0;
}

除此之外,如果只想遍历 set
容器中指定区域内的部分数据,则可以借助 find()
、lower_bound()
以及 upper_bound()
实现。通过调用它们,可以获取一个指向指定元素的迭代器。
需要特别指出的是,equal_range(val)
函数的返回值是一个 pair
类型数据,其包含 2
个迭代器,表示 set
容器中和指定参数 val
相等的元素所在的区域,但由于 set
容器中存储的元素各不相等,因此该函数返回的这 2
个迭代器所表示的范围中,最多只会包含 1
个元素。
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
//创建并初始化set容器
std::set<std::string> myset{ "http://c.biancheng.net/java/",
"http://c.biancheng.net/stl/",
"http://c.biancheng.net/python/"
};
set<string>::iterator iter = myset.find("http://c.biancheng.net/python/");
for (;iter != myset.end();++iter)
{
cout << *iter << endl;
}
return 0;
}

值得一提的是,虽然
C++ STL
标准中,set
类模板中包含lower_bound()
、upper_bound()
、equal_range()
这3
个成员函数,但它们更适用于multiset
容器,几乎不会用于操作set
容器。
set
中insert()
方法
-
给定目标元素的值, insert()
方法可将该元素添加到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
。
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
//创建并初始化set容器
std::set<std::string> myset;
//准备接受 insert() 的返回值
pair<set<string>::iterator, bool> retpair;
//采用普通引用传值方式
string str = "http://c.biancheng.net/stl/";
retpair = myset.insert(str);
cout << "iter->" << *(retpair.first) << " " << "bool = " << retpair.second << endl;
//采用右值引用传值方式
retpair = myset.insert("http://c.biancheng.net/python/");
cout << "iter->" << *(retpair.first) << " " << "bool = " << retpair.second << endl;
return 0;
}

-
insert()
还可以指定将新元素插入到set
容器中的具体位置,其语法格式如下:
//以普通引用的方式传递 val 值
iterator insert (const_iterator position, const value_type& val);
//以右值引用的方式传递 val 值
iterator insert (const_iterator position, value_type&& val);
以上 2
种语法格式中,insert()
函数的返回值为迭代器:
-
当向
set
容器添加元素成功时,该迭代器指向容器中新添加的元素 -
当添加失败时,证明原
set
容器中已有相同的元素,该迭代器就指向set
容器中相同的这个元素。
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
//创建并初始化set容器
std::set<std::string> myset;
//准备接受 insert() 的返回值
set<string>::iterator iter;
//采用普通引用传值方式
string str = "http://c.biancheng.net/stl/";
iter = myset.insert(myset.begin(),str);
cout << "myset size =" << myset.size() << endl;
//采用右值引用传值方式
iter = myset.insert(myset.end(),"http://c.biancheng.net/python/");
cout << "myset size =" << myset.size() << endl;
return 0;
}

注意,使用
insert()
方法将目标元素插入到set
容器指定位置后,如果该元素破坏了容器内部的有序状态,set
容器还会自行对新元素的位置做进一步调整。也就是说,**insert()
方法中指定新元素插入的位置,并不一定就是该元素最终所处的位置**。
-
insert()
方法支持向当前set
容器中插入其它set
容器指定区域内的所有元素,只要这2
个set
容器存储的元素类型相同即可
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
其中 first
和 last
都是迭代器,它们的组合 [first,last)
可以表示另一 set
容器中的一块区域,该区域包括 first
迭代器指向的元素,但不包含 last
迭代器指向的元素。
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
//创建并初始化set容器
std::set<std::string> myset{ "http://c.biancheng.net/stl/","http://c.biancheng.net/python/","http://c.biancheng.net/java/" };
//创建一个同类型的空 set 容器
std::set<std::string> otherset;
//利用 myset 初始化 otherset
otherset.insert(++myset.begin(), myset.end());
//输出 otherset 容器中的元素
for (auto iter = otherset.begin(); iter != otherset.end(); ++iter) {
cout << *iter << endl;
}
return 0;
}

-
使用 insert()
方法一次向set
容器添加多个元素
void insert ( {E1, E2,...,En} );
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
//创建并初始化set容器
std::set<std::string> myset;
//向 myset 中添加多个元素
myset.insert({ "http://c.biancheng.net/stl/",
"http://c.biancheng.net/python/",
"http://c.biancheng.net/java/" });
for (auto iter = myset.begin(); iter != myset.end(); ++iter) {
cout << *iter << endl;
}
return 0;
}

emplace()
和emplace_hint()
方法
emplace()
方法的语法格式如下:
template <class... Args>
pair<iterator,bool> emplace (Args&&... args);
若 set
容器中存储的元素类型为自定义的结构体或者类,则在使用 emplace()
方法向容器中添加新元素时,构造新结构体变量(或者类对象)需要多少个数据,就需要为该方法传入相应个数的数据
该方法的返回值类型为 pair
类型,其包含 2
个元素,一个迭代器和一个 bool
值:
-
当该方法将目标元素成功添加到
set
容器中时,其返回的迭代器指向新插入的元素,同时bool
值为true
-
当添加失败时,则表明原
set
容器中已存在相同值的元素,此时返回的迭代器指向容器中具有相同键的这个元素,同时bool
值为false
。
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
//创建并初始化 set 容器
std::set<string>myset;
//向 myset 容器中添加元素
pair<set<string, string>::iterator, bool> ret = myset.emplace("http://c.biancheng.net/stl/");
cout << "myset size = " << myset.size() << endl;
cout << "ret.iter = <" << *(ret.first) << ", " << ret.second << ">" << endl;
return 0;
}

emplace_hint()
方法的功能和 emplace()
类似,其语法格式如下:
template <class... Args>
iterator emplace_hint (const_iterator position, Args&&... args);
和 emplace()
方法相比,有以下 2
点不同:
-
该方法需要额外传入一个迭代器,用来指明新元素添加到
set
容器的具体位置(新元素会添加到该迭代器指向元素的前面) -
返回值是一个迭代器,而不再是
pair
对象。当成功添加元素时,返回的迭代器指向新添加的元素;反之,如果添加失败,则迭代器就指向set
容器和要添加元素的值相同的元素。
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
//创建并初始化 set 容器
std::set<string>myset;
//在 set 容器的指定位置添加键值对
set<string>::iterator iter = myset.emplace_hint(myset.begin(), "http://c.biancheng.net/stl/");
cout << "myset size = " << myset.size() << endl;
cout << *iter << endl;
return 0;
}

set
删除数据
如果想删除 set
容器存储的元素,可以选择用 erase()
或者 clear()
成员方法。
set
类模板中,erase()
方法有 3
种语法格式,分别如下:
//删除 set 容器中值为 val 的元素
size_type erase (const value_type& val);
//删除 position 迭代器指向的元素
iterator erase (const_iterator position);
//删除 [first,last) 区间内的所有元素
iterator erase (const_iterator first, const_iterator last);
第 1
种格式的 erase()
方法,其返回值为一个整数,表示成功删除的元素个数;后 2
种格式的 erase()
方法,返回值都是迭代器,其指向的是 set
容器中删除元素之后的第一个元素。
注意,如果要删除的元素就是
set
容器最后一个元素,则erase()
方法返回的迭代器就指向新set
容器中最后一个元素之后的位置(等价于end()
方法返回的迭代器)
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
//创建并初始化 set 容器
std::set<int>myset{1,2,3,4,5};
cout << "myset size = " << myset.size() << endl;
//1) 调用第一种格式的 erase() 方法
int num = myset.erase(2); //删除元素 2,myset={1,3,4,5}
cout << "1、myset size = " << myset.size() << endl;
cout << "num = " << num << endl;
//2) 调用第二种格式的 erase() 方法
set<int>::iterator iter = myset.erase(myset.begin()); //删除元素 1,myset={3,4,5}
cout << "2、myset size = " << myset.size() << endl;
cout << "iter->" << *iter << endl;
//3) 调用第三种格式的 erase() 方法
set<int>::iterator iter2 = myset.erase(myset.begin(), --myset.end());//删除元素 3,4,myset={5}
cout << "3、myset size = " << myset.size() << endl;
cout << "iter2->" << *iter2 << endl;
return 0;
}

如果需要删除 set
容器中存储的所有元素,可以使用 clear()
成员方法。该方法的语法格式如下:
void clear();
显然,该方法不需要传入任何参数,也没有任何返回值。
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
//创建并初始化 set 容器
std::set<int>myset{1,2,3,4,5};
cout << "1、myset size = " << myset.size() << endl;
//清空 myset 容器
myset.clear();
cout << "2、myset size = " << myset.size() << endl;
return 0;
}
作者介绍