std::set与std::multiset使用总结

Set和Multiset

  Set和Multiset都会根据特定的排序准则,自动将元素排序,两者不同在于multiset允许元素重复,而set不允许元素重复。
std::set与std::multiset使用总结_第1张图片
  Set和Multiset的使用均需要包含头文件

	#include

  在这个头文件中,上述两个类型都被定义为std命名空间中的class template:

template<
    typename Key,
    typename Compare = std::less<Key>,
    typename Allocator = std::allocator<Key>
> class set;

template<
    typename Key,
    typename Compare = std::less<Key>,
    typename Allocator = std::allocator<Key>
> class multiset;

  必须是能够比较的对象,才能作为set或者multiset的元素(即所谓的comparable)。排序准则默认为less——这是个函数对象,以operator<对元素进行比较。第三个参数指定内存模型,默认是allocator,由C++标准库提供。
注意——
  所谓的“排序准则”,必须定义strick weak ordering,其定义如下:
1. 必须是非对称的(anisymmertric)
  对operator< 而言,如果x < y为true,那么y < x 必为false。
  对判别式 op()而言,如果op(x,y)为ture,那么op(y,x)必须为false。
2. 必须是可传递的(transitive)
  即,如果x 3. 必须是非自反的(irreflexive)
  即,x < x永远为false
4. 必须具有等效传递性(transitivity of equivalence)
  即,如果a等于b且b等于c,那么a必然等于c。


  规则1~4的目的是保证set和multiset元素能够区分lessequal的关系,即<和=的关系。而区分两者的关系则是为了辨别元素是否重复——如果两个元素x和y,x   Multiset的想等元素排序是稳定排序,因此C++11保证插入和删除的相对位置的稳定性。

Set和Multiset的能力

  与所有的关联式容器类似,set和multiset通常以平衡二叉树完成——通常是红黑树,红黑树在改变元素数量和元素搜索方面具有出色的性能,它保证节点安插最多做两个重新链接的动作,而到达某一元素的最长路径的深度,最多是最短路径深度的两倍。
  set和multiset具有自动排序的功能,自动排序使得平衡二叉树在查找元素时具有卓越的效率。但同时也带来一种限制——set和multiset无法直接改变元素的值,因为这样会打乱原本的顺序。
  set和multiset本身并不提供任何操作函数可以直接访问元素,从迭代器的角度而言,元素的值是常量。因此,要改变元素的值,只能删除再插入。

  std::set与std::multiset使用总结_第2张图片

Set和Multiset的操作函数

创建、复制和析构函数

  set的构造函数和析构函数,如下表所示——

序号 操作 效果
1 set c Default构造函数,产生一个空set,没有任何元素
2 set c 建立一个空的set,并以op为排序规则
3 set c(c2)
set c=c2
Copy构造函数,建立c2同型set并成为c2的一份副本,该复制是深度复制
4 set c(rv)
set c=rv
Move构造函数,rv是一个set右值引用,那么这里的构造函数是一个Move构造函数,建立一个新的set,取右值内容(C++11新特性)
5 set c(beg,end) 建立一个set,并以迭代器所指向的区间[beg,end)作为元素值,该构造函数支持从其他容器类型接收元素
6 set c(beg,end,op) 建立一个set,并以迭代器所指向的区间[beg,end)作为元素值,同时以op作为排序准则
7 set c(initlist)
set c=initlist
建立一个set,以初值列initlist元素为初值(C++11新特性)
8 c.~set() 销毁所有元素,释放内存

  其中,上表中set的形式可以置换为以下任意一种——

序号 set 效果
1 set 使用默认排序准则,以Elem作为元素类型的set
2 set 使用op作为排序准则,以Elem作为元素类型的set
3 multiset 使用默认排序规则,以Elem作为元素类型的multiset
4 multiset 使用op作为排序准则,以Elem作为元素类型的multiset
#include
#include
using namespace std;
int main(){
	cout << "multiset:" << endl;
	multiset<string> ms{ "B","C","A","A","A" };
	for (auto&it : ms) {
		cout << it << " ";
	}
	cout << endl << "set:" << endl;
	set<string> s{ "B","C","A","A" };
	for (auto&it : s) {
		cout << it << " ";
	}
	return 0;
}

std::set与std::multiset使用总结_第3张图片
  上述代码中,使用range-based for循环对set/multiset的所有元素进行遍历,显然——
  1.set/multiset具有自动排序功能;
  2.set不允许重复元素,尽管使用初值列初始化有重复元素,但是即不生效,也不报错。

非更易型操作(Nonmodifying Operating)

  Set也提供元素比较、查询大小等操作——

序号 操作 效果
1 c.empty() 容器为空返回true,不为空返回false,相当于size()==0
2 c.size() 返回当前元素的个数
3 c.max_size() 返回元素个数之最大可能量
4 c1==c2 对每个元素调用c1==c2,全部相等返回true
5 c1!=c2 只要有一个元素相等,返回true,相当于!(c1==c2)
6 c1>c2,c1>=c2,c1 同上,依次类推
7 c.key_comp() 返回比较准则
8 c.value_comp() 返回针对value的比较准则,但实际上,Set中并没有Key-Value之说,所以,此处的value比较准则与key比较准则相同(Map中是不同的)

  set的元素比较只适用于类型相同的元素,这里的类型相同包括元素类型和排序准则,在set中,排序准则是作为类型看待的,因此不同排序类型的set无法比较,比如——

	std::set<float> c1;
	std::set<float,std::greater<float>> c2;
	if(c1==c2)// 编译错误
	{
		...
	}

特殊的查找函数

  Set和Multiset在元素查找方面具有优化设计,因此提供了特殊的查找函数,

序号 set 效果
1 c.count(val) 返回元素为val的个数
2 c.find(val) 返回元素为val的第一个位置,注意这里返回的是一个迭代器
3 c.lower_bound(val) 返回val第一个可插入的位置,即第一个元素值<=val的位置
4 c.upper_bound(val) 返回val最后一个可插入的位置,即第一个元素值
5 c.equal_range(val) 返回val可以被插入的第一个位置和最后一个位置的范围

  通过一个例子暂时其查找函数的能力——

	multiset<int> ms{ 1,1,1,2,2,2,3,3,4,5,6,7,8 };
	cout << "ms.count(1) = " << ms.count(1) << endl;
	multiset<int>::iterator it1 = ms.find(2);
	cout << "ms.find(2) :" << endl << *it1 << endl;
	pair<multiset<int>::iterator,multiset<int>::iterator> it2 = ms.equal_range(3);
	cout << "ms.equal_range(3) :" << endl << *it2.first << " " << *it2.second << endl;

std::set与std::multiset使用总结_第4张图片
  其中,除了count是返回int型之外,其余的查找函数返回的都是set::iterator迭代器,其指向的位置是已构建的平衡二叉树的位置,而不是初始化时的位置。

赋值(Assignment)

  Set只提供任何容器都提供的基本赋值操作:

序号 操作 效果
1 c = c2 将c2的全部元素赋值给c
2 c = rv 将rv右值语义所有元素以Move assign的方式赋值给c(C++11新特性)
3 c = initlist 将初值列所有元素赋值给c(C++11新特性)
4 c1.swap(c2)
swap(c1,c2)
置换c1和c2的数据

迭代器函数(Iterator Function)

 Set和Multiset不提供元素访问,所以只能采用range-based for循环,迭代器的使用就尤为常见:

序号 操作 效果
1 c.begin() 返回一个bidirectional iterator指向第一个元素
2 c.end() 返回一个bidirectional iterator指向的之后一个元素
3 c.cbegin() 返回一个const bidirectional iterator指向的第一个元素(C++11新特性
4 c.cend() 返回一个const bidirectional iterator指向的最后一个元素(C++11新特性
5 c.rbegin() 返回一个反向迭代器(reverse iterator)指向的第一个元素
6 c.rend() 返回一个reverse iterator指向的最后一个元素
7 c.crbegin() 返回一个const reverse iterator指向的第一个元素(C++新特性
8 c.crend() 返回一个const reverse iterator指向的最后一个元素(C++11新特性
	
	multiset<string> ms{ "apple","xiaomi","huawei","vivo","oppo" };
	// 遍历set常用range-based for循环
	for (auto&elem : ms) {
		cout << elem << " ";
	}
	cout << endl;
	// 或者使用迭代器
	for (auto it = ms.cbegin(); it != ms.cend(); it++) {
		cout << *it << " ";
	}

在这里插入图片描述
  对Set和Multiset而言,这里的迭代器只能是双向迭代器,因此,对于那些只能够支持“随机访问迭代器”的STL算法(如random shuffling),Set和Multiset无福消受。
  更重要的是,从迭代器的角度而言,Set和Multiset的元素都被视为常量,这是因为改变其元素值会打乱既有顺序,因此无法通过迭代器改变其元素值,也无法对Set和Multiset采用任何易更型算法,只能通过其提供的成员函数来改变其元素值。

插入和移除(Inserting and Removing)

  Set的所有插入和移除操作如下表所示——。

序号 操作 效果
1 c.insert(val) 插入val元素,并返回新元素的位置,不论是否成功
2 c.insert(pos,val) 在iterator指向的pos位置的前方插入一个元素val的副本,并返回新元素的位置(pos只是一个提示,该提示恰当会加快查找过程)
3 c.insert(beg,end) 插入区间[beg,end)内所有元素的副本,无返回值
4 c.insert(initilist) 插入initilist的所有元素的副本,无返回值(C++11新特性)
5 c.emplace(args…) 插入一个以args为初值的元素,并返回新元素的位置,无论是否成功(C++11新特性)
6 c.emplace_hint(pos,args…) 插入一个以args为初值的元素,并返回新元素的位置,pos是个位置提示,如果恰当,将大大提高插入效率
7 c.erase(val) 移除与val相等的所有元素,返回被移除的个数
8 c.erase(pos) 移除迭代器iterator指向的元素,无返回值
9 c.erase(beg,end) 移除区间[beg,end)内的所有元素,无返回值
10 c.clear() 移除所有元素,并将容器清空

  下面通过一段程序展示set插入和删除的若干能力——

	multiset<int> ms{ 1,2,3,4,5,6 };
	ms.insert({ 7,8,9,10 });
	for (auto it : ms) {
		cout << it << " ";
	}

在这里插入图片描述
  初值列(initialization list) 是C++11的重要新特性,无论是初始化、赋值、插入,都变得十分简单。
  注意,用以插入元素的函数insert和emplace,对于基本类型,两者没有太大区别,emplace不支持初值列,因此只能一个一个插入,而对于对象,insert是复制对象,调用的是复制构造函数,而emplace调用的是默认构造函数(或对应的自定义构造函数)。
  此外,由于Set不允许元素重复,而Multiset可以,两者的insert函数有所不同——

	// Set提供如下接口
	pair<iterator,bool> insert(const value_type&val);
	iterator 			insert(const_iterator posHint,const value_type&val);
	template<typename args>
	pair<iterator,bool> emplace(args);
	template<typename args>
	iterator emplace_hint(const_iterator posHint,const value_type&val);
	// Multiset 提供如下接口
	iterator			insert(const value_type&val);
	iterator 			insert(const_iterator posHint,const value_type&val);
	template<typename args>
	iterator emplace(args);
	template<typename args>
	iterator emplace_hint(const_iterator posHint,const value_type&val);

  两者区别在于,Set不支持重复元素,因此返回的是一个pair组织起来的两个值,其中pair.second表示是否插入成功,pair.first表示插入元素之后的位置(如果值相同,则返回相同元素的位置)。
  同时,C++11保证,Multiset的insert、emplace和erase操作都会保证元素间的相对次序,插入元素都会被放到既有等值元素的末尾。

异常处理

  Set和Multiset是以节点为基础的容器,如果节点构建失败,容器保持原样。此外,由于(元素)析构函数通常不抛出异常,因此移除元素不能失败。
  对于单一元素的插入操作,能够保证其“要么成功,要么没有任何作用”的操作原则,但是对于多重元素的插入操作,必须保证其排序准则不抛出异常,才能保证其达到这一点。

你可能感兴趣的:(C/C++)