初识STL——set,multiset,map,multimap

来源:互联网 发布:redis数据库设计java 编辑:程序博客网 时间:2024/06/05 04:48

最近在学习STL,在前期分析了vector和list的源码,印象很深,感觉对自己以后的代码之路产生了很多改变,今天,介绍下STL当中的几个关联式容器。本博客所有论述的都为SGI版本的STL。

1.什么是关联式容器


关联容器是通过键存取和读取元素、顺序容器通过元素在容器中的位置顺序存储和访问元素。因此,关联容器不提供front、push_front、pop_front、back、push_back以及pop_back,此外对于关联容器不能通过容器大小来定义,因为这样的话将无法知道键所对应的值什么。

关联式容器分为set(集合)和map(映射表)两大类,还有拓展的multiset(多键集合)和multimap(多键映射表)。这些容器的底层实现都是使用了红黑树来实现的。

另外,STL还提供了hash_table(散列表),hash_set(散列集合),hash_map(散列映射表),hash_multiset(散列多键集合),hash_multimap(散列多键映射表)。这些都是利用hash表实现的。
这里写图片描述
总共9个容器。

另外来说,在C++11当中,新的标准重新定义了4个无序的关联式容器,就是unordered_map,unordered_set,unordered_multiset,unordered_multimap,

所以在介绍容器之前你首先应该有红黑树的知识,不熟悉可以翻一下前面的博客,对平衡树方面都有论述!

2.set


我们首先来看set,重点来熟悉一下set的接口。

这里写图片描述

这些接口当中,很多我们使用很简单,比如像是关于容量的接口。

在这我们说下insert。

pair<iterator,bool> insert ( const value_type& x );           iterator insert ( iterator position, const value_type& x );template <class InputIterator>      void insert ( InputIterator first, InputIterator last );

这里面的pair就是一个key-value的结构体。这个结构体的定义是这样的:

template<typename K,typename V>struct pair{    K first;    V value;}

在这里因为value是不存在的。所以返回以后的first就是迭代器,value就是bool类型。

我们可以通过测试:

    std::set<int> num;    cout << num.empty() << endl;    num.insert(1);    num.insert(5);    pair<set<int>::iterator,int > ret1= num.insert(23);    num.insert(ret1.first, 12);    pair<set<int>::iterator, int >ret = num.insert(5);

在这,我们的insert有两种情形,一种插入的是不存在set中的,另外一种是存在set中的,对于不存在的,返回的pair是返回插入的迭代器,bool返回true,对于存在的,返回的pair是返回已经存在的迭代器,bool返回false。

然后再来看erase,

  void erase ( iterator position );size_type erase ( const key_type& x );     void erase ( iterator first, iterator last );

erase函数返回一个size_type,其实就是你的bool,如果你的这个是存在的,那么返回就是true,如果不存在,那么就返回false。

剩下的两个没有太大难处,一个需要讨论的是find。

对于find,

iterator find ( const key_type& x ) const;

我们需要注意,find返回的是const的迭代器,如果找到了,就返回x所在的迭代器,如果没有找到,那么就返回的是end()。在我们输出这些上,要特别注意这一点,因为end()不是你的有效空间,防止对它进行访问引起不必要的错误。

3.multiset

multiset是一个多键集合,对于这个多键集合而言,就是调用了底层实现红黑树的不同的一个insert,在set中是如果insert,先进行查找,这个时候如果存在那么就结束查找,返回pair的boolfalse。现在是如果存在,那么就继续插入,至于插入红黑树的那一边,其实都是一样的,对于它的性能是一样的。
这里写图片描述

对于multiset而言来说,改变的是insert。

iterator insert ( const value_type& x );iterator insert ( iterator position, const value_type& x );template <class InputIterator>    void insert ( InputIterator first, InputIterator last );

在插入的时候返回的是一个迭代器,因为可以出现重复的key。

erase

void erase ( iterator position );size_type erase ( const key_type& x );     void erase ( iterator first, iterator last );

erase所操作的是删除了所有相同键值的节点。这里返回的size_type其实就是删除的重复的个数。如果不存在那么返回的当然是0。

find

iterator find ( const key_type& x ) const;

和set一样,返回迭代器,这里如果存在一样的,那么只是返回第一个迭代器。

在这里说一下count

size_type count ( const key_type& x ) const;

这个count在set和count中都存在,在set中,它只会返回 1或者0,但是在multiset当中,它返回的数目就会是其他了。

其他的不是太为常用,如果想要自己研究看文档就好了,我就介绍这些。

4.map

map而言,map所使用的结构是key—value。在这里就是一个pair。
pair第一个元素是key,第二个元素是value。

template<typename K,typename V>struct pair{    K key;    V value;}

每一个节点都是这样的一个结构体,然后存在一棵红黑树当中的。

这里写图片描述
同样的,对于一些很简单看文档就能了解的我们就不多说了,关注一些比较有太大区别的。

map的insert实现,因为保存的是每一个pair这样的结构体,所以如果要插入,你也应该插入这样的结构体才对。
我们先来看库所提供的接口:

pair<iterator,bool> insert ( const value_type& x );           iterator insert ( iterator position, const value_type& x );template <class InputIterator>               void insert ( InputIterator first, InputIterator last );

所以我们使用的时候,一定要按照规则来使用。
这个里面,你所构造的pair和insert返回的pair是两个东西,一个是向map中所要插入的结构体,一个是返回说明map当中是否存在你所要插入的键值相同的结构体。

    std::map<int,string > footballmap;    //插入的是一个pair类型的结构体,用后面()中的内容初始化构造对象。然后插入进去。    footballmap.insert(pair<int, string>(1, "西班牙"));    footballmap.insert(pair<int, string>(2, "德国"));    footballmap.insert(pair<int, string>(3, "葡萄牙"));    footballmap.insert(pair<int, string>(4, "意大利"));    footballmap.insert(pair<int, string>(5, "巴西"));    footballmap.insert(pair<int, string>(6, "法国"));    pair<map<int, string>::iterator, bool > a = footballmap.insert(pair<int, string>(1, "阿根廷"))

上述例子运行以后,你可以看到,插入的键值为1,这个时候你的value依然为“西班牙”,这是因为,在这个时候联想红黑树插入的原理,我们首先进行查找键值为1的节点,找到了节点,那么就返回一个pair结构体,pair结构体的first为键值为1的节点的迭代器,second是个bool,为true代表插入成功,false代表以及存在相同键值的节点 ,插入失败。

既然这样,我们不仅要想一下,我们如何来修改这个键值的value呢?

我们其实就可以通过insert的返回值来进行修改。上面a是一个保存了键值的迭代器,和bool值得一个pair结构体。

所以我们可以通过pair结构体修改value。

    a.first->second = "阿根廷";

这样就更改了键值为1的value。
或者我们还可以通过find函数先找到
find的接口:

iterator find ( const key_type& x );const_iterator find ( const key_type& x ) const;

find返回的是一个迭代器,这个迭代器我们可以理解为pair的一个指针,然后我们就可以通过这个指针来修改这个结构体中的内容。
比如:

    map<int, string>::iterator i = footballmap.find(1);    i->second = "中国";

这样我们也实现了修改。
当然,库中其实也为我们想到了这一点,给我们封装了一个接口函数,
它就是:operator[]

我们当然可以使用operator[]进行修改,operator []的内部实现就是

(*((this->insert(make_pair(x,T()))).first)).second

其实就是insert找到以后的,这个返回的pari的第一个迭代器的第二个元素,就是value。

这样我们就实现了修改了。

然后看下erase,erase一样,返回的依然是删除的个数,删除迭代器,和键值的节点。

 void erase ( iterator position );size_type erase ( const key_type& x );     void erase ( iterator first, iterator last );

其他的和上述的set,multiset是一样的。

5.multimap

对于multimap而言,其实和map的大部分也是一样的。
这里写图片描述
它可以允许重复的key出现。

比如

    multimap<int, string> nummap;    nummap.insert(pair<int, string >(1, "白菜"));    nummap.insert(pair<int, string >(1, "番茄"));    nummap.insert(pair<int, string >(1, "胡萝卜"));    nummap.insert(pair<int, string >(2, "菠菜"));    nummap.insert(pair<int, string >(3, "南瓜"));

这个里面就会储存相同键值为1的三个pair。

调用erase一样是删除所有相同键值的节点。

它没有operator[],所以如果你要修改,那么就必须采用迭代器,然后修改的方式了。

2 0