【C++】map和set使用

发布于:2024-10-18 ⋅ 阅读:(7) ⋅ 点赞:(0)

前言

有了前面搜索二叉树的基础,那么这篇博客对于map和set两个容器就很好理解使用,让我们来看看map和set到底有什么特性吧

💓 个人主页:小张同学zkf

⏩ 文章专栏:C++

若有问题 评论区见📝

🎉欢迎大家点赞👍收藏⭐文章

目录

1.序列式容器和关联式容器 

2.set系列的使用

2.1set和multiset参考文档

2.2set类的介绍

2.3set的构造和迭代器

2.4set的增删查

 2.5multiset和set的差异

3.map系列的使用

3.1map和multimap参考文档

3.2map类的介绍

3.3pair类型介绍

3.4map的构造

3.5map的增删查

3.6map的数据修改

3.7multimap和map的差异


1.序列式容器和关联式容器 

前面我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,如交换⼀下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的
关联式容器也是⽤来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是⾮线性结构, 两个位置有紧密的关联关系,交换⼀下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。

2.set系列的使用

2.1set和multiset参考文档

链接:https://legacy.cplusplus.com/reference/set/


2.2set类的介绍

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;

2.3set的构造和迭代器

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 ();

2.4set的增删查

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 ;

 2.5multiset和set的差异

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 ;
}

3.map系列的使用

3.1map和multimap参考文档

链接:https://legacy.cplusplus.com/reference/map/ 


3.2map类的介绍

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;

3.3pair类型介绍

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) );
}

3.4map的构造

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 ();

3.5map的增删查

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 ;

3.6map的数据修改

前⾯我提到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;
}

3.7multimap和map的差异

multimap和map的使⽤基本完全类似,主要区别点在于multimap⽀持关键值key冗余,那么
insert/find/count/erase都围绕着⽀持关键值key冗余有所差异,这⾥跟set和multiset完全⼀样,⽐如find时,有多个key,返回中序第⼀个。其次就是multimap不⽀持[],因为⽀持key冗余,[]就只能⽀持插⼊了,不能⽀持修改。

结束语

set和map的使用总结完了,他们底层都是红黑树,后面详细介绍

OK,感谢观看!!!