Set和Multiset的基本能力
set和multiset会根据特定的排序准则,自动将元素排序。两者不同之处在于multiset允许元素重复而set不允许
在使用set和multiset之前,必须先包含头文件<set>
以上者两个类型都被定义为命名空间std内的class template:
namespace std{
template<typename T,typename Compare =less<T>,typename Allocator =allocator<T>>
class set;
template<typename T,typename Compare =less<T>,typename Allocator =allocator<T>>
class multiset;
只要是可依据某排序准则被比较的任意类型T都可以成为set或multiset的元素类型。第二个实参默认为less<T>函数对象,以operator <进行元素比较。第三个实参默认为allocator,由C++标准库提供,定义内存模型。
所谓"排序准则",必须定义strict weak ordering,其意义如下:
必须是非对称的:对于operator <而言,如果x<y为true,则y<x为false
必须是可传递的:对于operator <而言,如果x<y为true且y<z为true,则x<z为true
必须是非自反的:对于operator <而言,如果x<x永远为false
必须有等效传递性:如果a等于b且b等于c,那么a必然等于c
这个"强弱规则"告诉我们必须要区分less和equal。一个像operator <=并不满足这个条件。
根据这些性质,排序准则也被用来检查等效性。也就是说,两个元素如果没有任何一个小于另一个,则它们被视为重复。
Multiset的等效元素的次序是随机但稳定的。因此C++11保证安插和抹除动作都会保存等效元素的相对次序。
Set和Multiset的能力
和所有标准的关联式容器类似,set和multiset通常以平衡二叉树完成--C++standard并未规定。(事实上,这两个容器通常以红黑树实现)
自动排序的主要优点在于令二叉树用于查找元素时拥有良好性能(即AVL树的优点)。
但是自动排序造成一个重要限制:你不能直接改变元素值(改变const key的值),因为这个会打乱原本正确的顺序。
因此你如果要改变元素的值,必须先删除旧元素的,再插入新元素。
Set和Multiset的操作函数
创建,复制和销毁(Create,Copy,and Destroy)
set c//Default构造函数,建立一个空的容器
set c(op)//建立一个空的容器,以op作为排序准则
set c(c2)//Copy构造函数,将相同类型c建立为c2的一份拷贝,所有元素均为拷贝
set c=c2//Copy构造函数,将相同类型c建立为c2的一份拷贝,所有元素均为拷贝
set c(rv)//Move构造函数,取rv的内容建立c的内容(类型相同)
set c=rv//Move构造函数,取rv的内容建立c的内容(类型相同)
set c(beg,end)//以区间[begin,end)内元素为初值,建立一个容器
set c(beg,end,op)//以区间[begin,end)内元素为初值,并以op为排序准则,建立一个容器
set c(initlist)//以初始化列表的元素为初值,建立一个容器
set c=initlist//以初始化列表的元素为初值,建立一个容器
c.~set()//销毁所有元素,释放内存
其中set可被视为以下形式:
set<T>//一个set,以less<>为排序准则
set<T,op>//一个set,以op()为排序准则
multiset<T>//一个multiset,以less<>为排序准则
multiset<T,op>//一个multiset,以op()为排序准则
有两种方式可以定义排序准则:
以template参数定义之。例如:
std::set<int,std::greater<int>>coll;
这种情况下排序准则就是类型的一部分。于是类型系统确保"只有排序准则相同的容器才能被合并"。
以构造函数参数定义之。这种情况下,同一个类型可以运用不同的排序准则,而排序准则的初始值或状态也可以不同。如果运行期才获得排序准则,而且需要用到不同的排序准则,这一方式可以排上用场。
以下是运行期确定排序准则的例子:
#include<iostream>
#include<set>
using namespace std;
class Runtimecmp{ //自定义一个函数对象,用于比较,通过枚举类的切换,得到运行期判断
public:enum cmp_mode{normal,reverse};
private:cmp_mode mode;
public:
Runtimecmp(cmp_mode m=normal):mode(m){}
template<typename T>
bool operator ()(const T&t1,const T&t2)const{
return mode==normal?t1<t2:t2<t1;
}
bool operator ==(const Rumtimecmp&rc){
return mode==rc.mode;
}
};
using IntSet=set<int,Rumtimecmp>;
int main(){
IntSet coll1={4,7,5,1,6,2,5};
cout<<"coll1: ";
for(auto it:coll1)cout<<it<<" ";
cout<<endl;
Rumtimecmp reverse_order(Rumtimecmp::reverse);
IntSet coll2(reverse_order);
coll2={4,7,5,1,6,2,5};
cout<<"coll2: ";
for(auto it:coll2)cout<<it<<" ";
cout<<endl;
coll1=coll2; //这个赋值要注意,如果排序准则不相同,不能直接赋值
coll1.insert(3);
cout<<"coll1: ";
for(auto it:coll1)cout<<it<<" ";
cout<<endl;
}
程序结果输出如下:
coll1: 1 2 4 5 6 7
coll2: 7 6 5 4 2 1
coll1: 7 6 5 4 3 2 1
在这个程序中,class Runtimecmp提供了一种泛化的能力:允许在运行期间对任何数据类型指定排序准则。其default构造函数设定以升序进行排序,用到是默认值normal。它也允许你使用Rumtimecmp::reverse作为构造函数实参,导致以降序排序。
如果用户没有提供特定的排序准则,就采用默认准则--函数对象less<>,less<>通过operator <对元素进行排序。
此时两元素的相等性检验如下:
if(!(elem1<elem2||elem2<elem1))
这样有三个好处:
只需传递一个实参作为排序准则
不必针对元素类型提供operator ==
这样的相等性判断可能会消耗较多时间,因为可能需要两次比较。
非更易型操作(Nonmodifying Operation)
c.key_comp()//返回比较准则
c.value_comp()//返回针对value的"比较准则"(和key_comp相同)
c.empty() //返回是否容器为空(相当于size()==0但也许较快)
c.size() //返回元素的数量
c.max_size() //返回元素个数之最大可能量
c1==c2 //返回c1是否等于c2
c1!=c2 //返回c1是否不等于c2
c1>c2 //返回c1是否大于c2
c1<c2 //返回c1是否小于c2
c1>=c2 //返回c1是否大等于c2
c1<=c2 //返回c1是否小等于c2
注:容器的比较只适用于类型相同的容器。如果元素和排序准则不同的时进行比较,在编译期会产生类型方面的错误。
std::set<float>c1;
std::set<float,std::greater<float>>c2;
if(c1==c2) //ERROR:类型不同
比较动作是以"字典顺序"进行比较某个容器是否小于另一个容器。
特殊的查找函数(Special Search Operation)
set和multiset在元素快速查找方面有优化设计,提供了同名STL算法的特殊版本,可以获得对数复杂度。
c.count(val)//返回元素值为val的个数
c.find(val)//返回元素第一个值为val的位置,找不到就返回end()
c.lower_bound(val)//返回元素值>=val的第一个元素的位置(第一个可安插位置)
c.upper_bound(val)//返回元素值>val的第一个元素的位置(最后一个可安插位置)
c.equal_range(val)//返回元素值==val的区间,(第一个和最后一个可安插位置)
赋值(Assignment)
c=c2 //将c2的全部元素赋值给c
c=rv //将rv的所有元素都已move assign的方式给予c
c=initlist //将初始值列表的所有元素赋值给c
c1.swap(c2) //置换c1和c2的值
swap(c1,c2) //置换c1和c2的值
这些操作函数,赋值操作两端的容器必须具有相同类型。尽管"比较准则"本身可能不同,但其类型必须相同。如果准则不同,准则本身也会联同容器一并被赋值或交换。
迭代器相关函数(Iterator Function)
c.begin() //返回一个指向首处 bidirectional iterator
c.cbegin() //返回一个const bidirectional iterator
c.end() //返回一个指向末尾下一个位置的 bidirectional iterator
c.cend() //返回一个指向末尾下一个位置的const bidirectional iterator
c.rbegin() //返回一个reverse bidirectional iterator,指向末尾的下一个位置
c.rend() //返回一个reverse bidirectional iterator,指向首处的位置
c.crend() //返回一个const reverse bidirectional iterator,指向首处的位置
c.crbegin() //返回一个const reverse bidirectional iterator,指向末尾的下一个位置
这里的迭代器类型为双向迭代器。对于只接受随机访问迭代器的STL算法,该容器便无福消受了。重要的是,由于所有的元素都被视为常量,因此你无法调用任何更易型算法。如果你想移除其中的元素,你只能使用它们所提供的成员函数。
元素的安插和移除(Inserting and Removing)
c.insert(elem)//安插一个elem的拷贝,返回新元素的位置
c.insert(pos,elem)//插入elem的一个拷贝,并且返回新元素的位置(pos只是起到了提示和加快速度的参数)
c.insert(beg,end)//插入[begin,end)区间的拷贝,无返回值
c.insert(initlist)//插入初始化列表的拷贝,无返回值
c.emplace(args...)//插入以args作为初值的元素,并且返回新元素的位置
c.erase(pos)//移除pos位置上的元素,无返回值
c.erase(val)//移除值为val的所有元素,返回被移除元素的个数
c.erase(beg,end)//移除区间[beg,end)内的所有元素,无返回值
c.clear()//移除所有元素,将容器清空
注意,用以安插元素的函数:insert()和emplace(),返回类型不尽相同。
set提供以下接口:
pair<iterator,bool> insert(const value_type& val);
iterator insert(const_iterator pos,const value_type& val);
template<typename...args>
pair<iterator,bool>emplace(Args&&...args);
multiset提供以下接口:
iterator insert(const value_type& val);
iterator insert(const_iterator pos,const value_type& val);
template<typename...args>
boolemplace(Args&&...args);
原因是:multiset允许元素重复,但是set不允许。如果往set中插入的元素set中已经有了的话,安插动作宣告失败。
几个注意事项:
面对以迭代器为元素的set,调用erase可能会造成歧义,为了这个因素,C++11提出修正,提供重载函数erase(iterator)和erase(const_iterator)。
multiset的insert(),emplace()和erase()操作函数都会保留等价元素的相对次序。自C++11起,调用insert(val)和emplace(args...),新元素保证会安插于等价元素所形成的区间的末端。
异常处理(Exception Handling)
面对多重元素安插动作,"保持元素次序"这一条件会造成"异常抛出时必须完全恢复原状变得不切实际"。因此,只有"单一元素安插动作"才支持"要么成功要么无效"的操作原则。
Set和Multiset运用实例
#incldue<iostream>
#incldue<set>
#incldue<algorithm>
#incldue<iterator>
using namespace std;
int main(){
set<int,greater<int>>coll1;
coll1.insert({4,3,5,1,6,2});
coll1.insert(5);
for(int elem:coll1){
cout<<elem<<' ';
}
cout<<endl;
auto status=coll1.insert(4);
if(status.second){
cout<<"4 inserted as element "
<<distance(coll1.begin(),status.first)+1<<endl;
}
else cout<<"4 already exists"<<endl;
//将coll1其中的元素拷贝给新的排序新标准coll2容器
set<int>coll2(coll1.cbegin(),coll1.cend());
copy(coll2.cbegin(),coll2.cend(),ostream_iterator<int>(cout," ");
cout<<endl;
coll2.erase(coll2.begin(),coll2.find(3));
int num;
num=coll2.erase(5);
cout<<num<<" element(s) removed"<<endl;
copy(coll2.cbegin(),coll2.cend(),ostream_iterator<int>(cout," ");
cout<<endl;
}
输出结果:
6 5 4 3 2 1
4 already exists
1 2 3 4 5 6
1 element(s) removed
3 4 6
如果是multiset那么输出结果会发生一点改变:
6 5 4 3 2 1
4 already exists
1 2 3 4 5 6
1 element(s) removed
3 4 6