前面我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,如交换⼀下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的
关联式容器也是⽤来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是⾮线性结构, 两个位置有紧密的关联关系,交换⼀下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。
链接:https://legacy.cplusplus.com/reference/set/
set的声明如下,T就是set底层关键字的类型
set默认要求T⽀持⼩于⽐较,如果不⽀持或者想按⾃⼰的需求⾛可以⾃⾏实现仿函数传给第⼆个模
版参数
set底层存储数据的内存是从空间配置器申请的,如果需要可以⾃⼰实现内存池,传给第三个参
数。
⼀般情况下,我们都不需要传后两个模版参数。
set底层是⽤红⿊树(下一篇博客重点介绍)实现,增删查效率是O ( logN ) ,迭代器遍历是⾛的搜索树的中序,所以是有序的。
前⾯部分我们已经了解了vector/list等容器的使⽤,STL容器接⼝设计,⾼度相似,所以这⾥我们
就不再⼀个接⼝⼀个接⼝的介绍,⽽是直接带着⼤家看⽂档,挑⽐较重要的接⼝进⾏介绍。
template < class T , // set::key_type/value_type class Compare = less<T>, // set::key_compare/value_compare class Alloc = allocator<T> // set::allocator_type > class set;
set的构造我们关注以下⼏个接⼝即可。
set的⽀持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是⼆叉搜索树,迭代器遍历⾛的中序;⽀持迭代器就意味着⽀持范围for,set的iterator和const_iterator都不⽀持迭代器修改数据,修改关键字数据,破坏了底层搜索树的结构。
// empty (1) ⽆参默认构造 explicit set ( const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()); // range (2) 迭代器区间构造 template < class InputIterator > set (InputIterator first, InputIterator last, const key_compare& comp = key_compare (), const allocator_type& = allocator_type ()); // copy (3) 拷⻉构造 set ( const set& x); // initializer list (5) initializer 列表构造 set (initializer_list<value_type> il, const key_compare& comp = key_compare (), const allocator_type& alloc = allocator_type ()); // 迭代器是⼀个双向迭代器 iterator -> a bidirectional iterator to const value_type // 正向迭代器 iterator begin (); iterator end (); // 反向迭代器 reverse_iterator rbegin (); reverse_iterator rend ();
set的增删查关注以下⼏个接⼝即可:
Member types key_type -> The first template parameter (T) value_type -> The first template parameter (T) // 单个数据插⼊,如果已经存在则插⼊失败 pair<iterator, bool > insert ( const value_type& val); // 列表插⼊,已经在容器中存在的值不会插⼊ void insert (initializer_list<value_type> il); // 迭代器区间插⼊,已经在容器中存在的值不会插⼊ template < class InputIterator > void insert (InputIterator first, InputIterator last); // 查找 val ,返回 val 所在的迭代器,没有找到返回 end() iterator find ( const value_type& val); // 查找 val ,返回 Val 的个数 size_type count ( const value_type& val) const ; // 删除⼀个迭代器位置的值 iterator erase (const_iterator position); // 删除 val , val 不存在返回 0 ,存在返回 1 size_type erase ( const value_type& val); // 删除⼀段迭代器区间的值 iterator erase (const_iterator first, const_iterator last); // 返回⼤于等 val 位置的迭代器 iterator lower_bound ( const value_type& val) const ; // 返回⼤于 val 位置的迭代器 iterator upper_bound ( const value_type& val) const ;
multiset和set的使⽤基本完全类似,主要区别点在于multiset⽀持值冗余,那么
insert/find/count/erase都围绕着⽀持值冗余有所差异,具体参看下⾯的样例代码理解。
# include <iostream> # include <set> using namespace std; int main () { // 相⽐ set 不同的是, multiset 是排序,但是不去重 multiset< int > s = { 4 , 2 , 7 , 2 , 4 , 8 , 4 , 5 , 4 , 9 }; auto it = s. begin (); while (it != s. end ()) { cout << *it << " " ; ++it; } cout << endl; // 相⽐ set 不同的是, x 可能会存在多个, find 查找中序的第⼀个 int x; cin >> x; auto pos = s. find (x); while (pos != s. end () && *pos == x) { cout << *pos << " " ; ++pos; } cout << endl; // 相⽐ set 不同的是, count 会返回 x 的实际个数 cout << s. count (x) << endl; // 相⽐ set 不同的是, erase 给值时会删除所有的 x s. erase (x); for ( auto e : s) { cout << e << " " ; } cout << endl; return 0 ; }
链接:https://legacy.cplusplus.com/reference/map/
map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key⽀持小于比较,如果不⽀持或者需要的话可以自行实现仿函数传给第⼆个模版参数,map底层存储数据的内存是从空间配置器申请的。⼀般情况下,我们都不需要传后两个模版参数。map底层是用红黑树(后面博客会出现的)实现,增删查改效率是 O ( logN ) ,迭代器遍历是⾛的中序,所以是按key有序顺序遍历的。
template < class Key , // map::key_type class T , // map::mapped_type class Compare = less<Key>, // map::key_compare class Alloc = allocator<pair< const Key,T> > // map::allocator_type > class map;
map底层的红⿊树节点中的数据,使⽤pair<Key, T>存储键值对数据。
typedef pair< const Key, T> value_type; template < class T1 , class T2 > struct pair { typedef T1 first_type; typedef T2 second_type; T1 first; T2 second; pair (): first ( T1 ()), second ( T2 ()) {} pair ( const T1& a, const T2& b): first (a), second (b) {} template < class U, class V> pair ( const pair<U,V>& pr): first(pr.first), second(pr.second) {} }; template < class T1 , class T2 > inline pair<T1,T2> make_pair (T1 x, T2 y) { return ( pair <T1,T2>(x,y) ); }
map的构造我们关注以下⼏个接⼝即可。
map的⽀持正向和反向迭代遍历,遍历默认按key的升序顺序,因为底层是⼆叉搜索树,迭代器遍历⾛的中序;⽀持迭代器就意味着⽀持范围for,map⽀持修改value数据,不⽀持修改key数据,修改关键字数据,破坏了底层搜索树的结构
// empty (1) ⽆参默认构造 explicit map ( const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()); // range (2) 迭代器区间构造 template < class InputIterator > map (InputIterator first, InputIterator last, const key_compare& comp = key_compare (), const allocator_type& = allocator_type ()); // copy (3) 拷⻉构造 map ( const map& x); // initializer list (5) initializer 列表构造 map (initializer_list<value_type> il, const key_compare& comp = key_compare (), const allocator_type& alloc = allocator_type ()); // 迭代器是⼀个双向迭代器 iterator -> a bidirectional iterator to const value_type // 正向迭代器 iterator begin (); iterator end (); // 反向迭代器 reverse_iterator rbegin (); reverse_iterator rend ();
map的增删查关注以下⼏个接⼝即可:
map增接⼝,插⼊的pair键值对数据,跟set所有不同,但是查和删的接⼝只⽤关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代还可以修改value
Member types key_type -> The first template parameter (Key) mapped_type -> The second template parameter (T) value_type -> pair < const key_type,mapped_type> // 单个数据插⼊,如果已经 key 存在则插⼊失败 ,key 存在相等 value 不相等也会插⼊失败 pair<iterator, bool > insert ( const value_type& val); // 列表插⼊,已经在容器中存在的值不会插⼊ void insert (initializer_list<value_type> il); // 迭代器区间插⼊,已经在容器中存在的值不会插⼊ template < class InputIterator > void insert (InputIterator first, InputIterator last); // 查找 k ,返回 k 所在的迭代器,没有找到返回 end() iterator find ( const key_type& k); // 查找 k ,返回 k 的个数 size_type count ( const key_type& k) const ; // 删除⼀个迭代器位置的值 iterator erase (const_iterator position); // 删除 k , k 存在返回 0 ,存在返回 1 size_type erase ( const key_type& k); // 删除⼀段迭代器区间的值 iterator erase (const_iterator first, const_iterator last); // 返回⼤于等 k 位置的迭代器 iterator lower_bound ( const key_type& k); // 返回⼤于 k 位置的迭代器 const_iterator lower_bound ( const key_type& k) const ;
前⾯我提到map⽀持修改mapped_type 数据,不⽀持修改key数据,修改关键字数据,破坏了底层搜索树的结构。
map第⼀个⽀持修改的⽅式时通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map还有⼀个⾮常重要的修改接⼝operator[],但是operator[]不仅仅⽀持修改,还⽀持插⼊数据和查找数据,所以他是⼀个多功能复合接⼝需要注意从内部实现⻆度,map这⾥把我们传统说的value值,给的是T类型,typedef为mapped_type。⽽value_type是红⿊树结点中存储的pair键值对值。⽇常使⽤我们还是习惯将这⾥的T映射值叫做value。
Member types key_type -> The first template parameter (Key) mapped_type -> The second template parameter (T) value_type -> pair < const key_type,mapped_type> // 查找 k ,返回 k 所在的迭代器,没有找到返回 end() ,如果找到了通过 iterator 可以修改 key 对应的 mapped_type 值 iterator find ( const key_type& k); // ⽂档中对 insert 返回值的说明 // The single element versions (1) return a pair , with its member pair::first set to an iterator pointing to either the newly inserted element or to the element with an equivalent key in the map . The pair::second element in the pair is set to true if a new element was inserted or false if an equivalent key already existed. // insert 插⼊⼀个 pair<key, T> 对象 // 1 、如果 key 已经在 map 中,插⼊失败,则返回⼀个 pair<iterator,bool> 对象,返回 pair 对象 first 是 key 所在结点的迭代器, second 是 false // 2 、如果 key 不在在 map 中,插⼊成功,则返回⼀个 pair<iterator,bool> 对象,返回 pair 对象 first 是新插⼊ key 所在结点的迭代器, second 是 true // 也就是说⽆论插⼊成功还是失败,返回 pair<iterator,bool> 对象的 first 都会指向 key 所在的迭 代器 // 那么也就意味着 insert 插⼊失败时充当了查找的功能,正是因为这⼀点, insert 可以⽤来实现 operator[] // 需要注意的是这⾥有两个 pair ,不要混淆了,⼀个是 map 底层红⿊树节点中存的 pair<key, T> ,另 ⼀个是 insert 返回值 pair<iterator,bool> pair<iterator, bool > insert ( const value_type& val); mapped_type& operator [] ( const key_type& k); // operator 的内部实现 mapped_type& operator [] ( const key_type& k) { // 1 、如果 k 不在 map 中, insert 会插⼊ k 和 mapped_type 默认值,同时 [] 返回结点中存储 mapped_type 值的引⽤,那么我们可以通过引⽤修改返映射值。所以 [] 具备了插⼊ + 修改功能 // 2 、如果 k 在 map 中, insert 会插⼊失败,但是 insert 返回 pair 对象的 first 是指向 key 结点的 迭代器,返回值同时 [] 返回结点中存储 mapped_type 值的引⽤,所以 [] 具备了查找 + 修改的功能 pair<iterator, bool > ret = insert ({ k, mapped_type () }); iterator it = ret.first; return it->second; }
multimap和map的使⽤基本完全类似,主要区别点在于multimap⽀持关键值key冗余,那么
insert/find/count/erase都围绕着⽀持关键值key冗余有所差异,这⾥跟set和multiset完全⼀样,⽐如find时,有多个key,返回中序第⼀个。其次就是multimap不⽀持[],因为⽀持key冗余,[]就只能⽀持插⼊了,不能⽀持修改。
结束语 set和map的使用总结完了,他们底层都是红黑树,后面详细介绍 OK,感谢观看!!!