最近在学习STL,在前期分析了vector和list的源码,印象很深,感觉对自己以后的代码之路产生了很多改变,今天,介绍下STL当中的几个关联式容器。本博客所有论述的都为SGI版本的STL。
关联容器是通过键存取和读取元素、顺序容器通过元素在容器中的位置顺序存储和访问元素。因此,关联容器不提供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,
所以在介绍容器之前你首先应该有红黑树的知识,不熟悉可以翻一下前面的博客,对平衡树方面都有论述!
我们首先来看set,重点来熟悉一下set的接口。
这些接口当中,很多我们使用很简单,比如像是关于容量的接口。
在这我们说下insert。
pairbool> 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()不是你的有效空间,防止对它进行访问引起不必要的错误。
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当中,它返回的数目就会是其他了。
其他的不是太为常用,如果想要自己研究看文档就好了,我就介绍这些。
map而言,map所使用的结构是key—value。在这里就是一个pair。
pair第一个元素是key,第二个元素是value。
template<typename K,typename V>
struct pair
{
K key;
V value;
}
每一个节点都是这样的一个结构体,然后存在一棵红黑树当中的。
同样的,对于一些很简单看文档就能了解的我们就不多说了,关注一些比较有太大区别的。
map的insert实现,因为保存的是每一个pair这样的结构体,所以如果要插入,你也应该插入这样的结构体才对。
我们先来看库所提供的接口:
pairbool> 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是一样的。
对于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[],所以如果你要修改,那么就必须采用迭代器,然后修改的方式了。