STL源码剖析---关联式容器map
什么是map
map是STL中关联式容器的一种,所谓关联式容器,元素是按关键字来保存和访
问的,而序列式容器中的元素则是按它们在容器中的位置来顺序保存和访问的。
本片博客的主角map中,存储的不是一般的数据,而是一个个的键值对,我们称
之为pair,pair键值对由两部分组成:键值key和实值value。key与value之间具
有一一映射的关系。通常在查找过程中,我们通常都是通过key值来找value值。
这也满足了关联式容器的定义,通过key值关键字来保存和访问。
现实中有很多这种数据需要我们用map来存储,比如一个人与他的身份证号。
字典当中英文单词与中文含义等等。
map的底层是有由红黑树实现的(与set / multimap / multiset相同)。所以这里提几
个值得注意的点。
- 用map查找数据的效率是O(logn),算是一种比较快的方法。(但还是略逊unordered_map一筹,人家底层是哈希表,查询是O(1),这儿就不多介绍了)
- 因为红黑树的实质是平衡二叉搜索树,所以map中插入的数据会自动排序。且根据你创建的map的模板参数来决定是升序排列还是降序排列,默认是升序排列。
- 我们通过key值比较来确认每一个pair的存储位置,所以,在map中存储的数据必须能够比较大小。
- 我们不能够修改map中的每个pair的键值key,这是因为红黑树是搜索树,且按照key值排列的,修改key值会破坏整个红黑树的结构。但是修改pair中的实值value是被允许的。
- map中键值不能够重复,这也是他和multimap的唯一区别.
下面是map的STL源码结构:
template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
public:
// typedefs:
typedef Key key_type;//键值key
typedef Key value_type;//实值value(其实底层红黑树中key和value都是key值,
//只是取名不同)
typedef Compare key_compare;//比较器,默认传的是less,
//也可以显示的传函数指针或仿函数。
typedef Compare value_compare;
private:
typedef rb_tree<key_type, value_type,
identity<value_type>, key_compare, Alloc> rep_type;
rep_type t; // red-black tree representing set
};
map中存储的键值对
上面已经说过,map中存储的键值对叫pair,那么我们来看看源码中pair的结构
是什么样的:
template<class _T1, class _T2>
struct pair
{
typedef _T1 first_type; /// @c first_type is the first bound type
typedef _T2 second_type; /// @c second_type is the second bound type
_T1 first; //注意,他是pulbic
_T2 second;
pair() : first(T1()) , second(T2()) //构造一个key和value都为空的pair
{}
pair(const T1& a,const T2& b): first(a),second(b)//构造一个键值为a,
//实值为b的pair
{}
}
可以看到,pair中存储的是一个个first和second就对应着我们这里说的key和value。
map的迭代器
以下是map的迭代器的常用操作:
map的迭代器就是红黑树的迭代器,红黑树迭代器和list的迭代器有某些相同的
性质:当用户对map(list)进行了增加和删除操作后,所有的迭代器都依然有
效。
当然,被删除的结点的迭代器是个例外。
map的常用操作
at
|
查找具有指定键值的元素。 |
begin | 返回一个迭代器,此迭代器指向映射中的第一个元素。 |
cbegin | 返回一个常量迭代器,此迭代器指向映射中的第一个元素。 |
cend | 返回一个超过末尾常量迭代器。 |
clear | 清除映射的所有元素。 |
count | 返回映射中其键与参数中指定的键匹配的元素数量。 |
crbegin | 返回一个常量迭代器,此迭代器指向反向映射中的第一个元素。 |
crend | 返回一个常量迭代器,此迭代器指向反向映射中最后一个元素之后的位置。 |
emplace | 将就地构造的元素插入到映射。 |
emplace_hint | 将就地构造的元素插入到映射,附带位置提示。 |
empty | 如果映射为空,则返回 true。 |
end | 返回超过末尾迭代器。 |
equal_range | 返回一对迭代器。 此迭代器对中的第一个迭代器指向 map 中其键大于指定键的第一个元素。 此迭代器对中的第二个迭代器指向 map 中其键等于或大于指定键的第一个元素。 |
erase | 从指定位置移除映射中的元素或元素范围。 |
find | 返回一个迭代器,此迭代器指向映射中其键与指定键相等的元素的位置。 |
get_allocator | 返回用于构造映射的 allocator 对象的副本。 |
insert | 将元素或元素范围插入到映射中的指定位置。 |
key_comp | 返回用于对映射中的键进行排序的比较对象副本。 |
lower_bound | 返回一个迭代器,此迭代器指向映射中其键值等于或大于指定键的键值的第一个元素。 |
max_size | 返回映射的最大长度。 |
rbegin | 返回一个迭代器,此迭代器指向反向映射中的第一个元素。 |
rend | 返回一个迭代器,此迭代器指向反向映射中最后一个元素之后的位置。 |
size | 返回映射中的元素数量。 |
swap | 交换两个映射的元素。 |
upper_bound | 返回一个迭代器,此迭代器指向映射中其键值大于指定键的键值的第一个元素。 |
value_comp | 检索用于对映射中的元素值进行排序的比较对象副本。 |
shrink_to_fit | 放弃额外容量。 |
size | 返回vector元素个数 |
swap | 交换两个向量的元素。 |
以上就是map的所有常见操作。这里唯独要对insert和operator[ ]进行进一步的说明。
先来看insert的原型:
//指定元素插入 返回值为pair 第二个参数bool值可以判断元素是否插入成功
pair<iterator,bool> insert (const value_type& val);
//指定位置插入,但位置可能是不合适的,会导致插入失败,插入成功返回新位置的迭代器
iterator insert (iterator position, const value_type& val);
//指定迭代器区间插入
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
这里我们要说的是第一种插入方式,直接给一个val作为参数,插入则返回一个
pair,pair的第一个参数是迭代器,倘若本次插入成功,则返回新插入位置的迭
代器,倘若插入失败,那么就说明该值已经在map中,直接返回val所在位置的
迭代器,而第二个参数bool则说明是否插入成功,1标识插入成功,0标识插入
失败。
有了这个函数,我们就可以来讲一讲operator[ ]了,map中的operator[ ]让我们
能够像数组那样,通过key值(可以理解成数组里的下标值),找到value值(可
以理解成a[ i ])。
那operator[ ]的底层原理是:
- 先构建一个pair<key,T()>的插入对象。
- 用上面的插入函数尝试插入到map中。
- 倘若插入成功,则返回新插入位置的迭代器和 1 ,operator[ ]返回此迭代器中second成员(也就是value)的引用。
- 糖果插入失败,则说明map中已经有以key为键值的pair对象了,则返回该pair的迭代器和 0,operator[ ]返回该迭代器中的second成员(也就是value)的引用。
再来看看底层operator[ ]的实现源码:
T& operator[](const key_type& k)
{
return (*((insert(value_type(k, T()))).first)).second;
}
一下子可能难以理解,我们拆分着看就很容易能理解它的思路了:
- insert( value_type( k, T() ) ): 插入以k和T()构成的pair的键值对,我们将这部分结果记作x。
- *( (x).first ) : x的first是指向pair迭代器,我们将他解引用后的结果记作y
- return (y).second; y的second就是value了。
举例:
#include <string>
#include <map>
void TestMap()
{
// 构造一个空的map,此时m中一个元素都没有
map<string, string> m;
/*
operator[]的原理是:
用<key, T()>构造一个键值对,然后调用insert()函数将该键值对插入到map中
如果key已经存在,插入失败,insert函数返回该key所在位置的迭代器
如果key不存在,插入成功,insert函数返回新插入元素所在位置的迭代器
operator[]函数最后将insert返回值键值对中的value返回
*/
// 将<"apple", "">插入map中,插入成功,返回value的引用,将“苹果”赋值给该引用结果,
// 即修改与"apple"对应的value""为"苹果"
m["apple"] = "苹果";
// 将<"apple", "">插入map中,插入失败,将<"apple", "苹果">中的"苹果"返回
cout << m["apple"] << endl;
cout << m.size() << endl;
// “banan不在map中,该函数抛异常”
m.at("banan");
}
最后再说一点,operator[ ]一定会返回键值为key的键值对中value的引用,但是
他无法分清是原来就有还是新插入的,所以跟他类似的有一个at(),功能和他一
样,但是at()在key值不在的时候会抛异常。
代码演练
#include<iostream>
#include<map>
using namespace std;
void printMap(map<string, string>& m)
{
map<string, string>::iterator e = m.begin();
while (e != m.end())
{
cout << ((*e).first).c_str() << " ->>> " << e->second.c_str() << endl;
e++;
}
}
int main()
{
map<string, string> m ;
m["苹果"] = "apple";
m["香蕉"] = "banana";
//m["梨"] = "pear";
m.insert(pair<string, string>("不行", "no"));
m.insert(make_pair("你好", "hello"));
m.erase("香蕉");
auto it1 = m.find("你好") ;;
cout << "查找成功: " << it1->first.c_str() <<" ->>> "<< it1->second.c_str() << endl;
printMap(m);
system("pause");
return 0;
}