一、什么是关联式容器
STL中的部分容器,像 vector、list、deque、forward_list 等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它和序列式容器有什么区别?
关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。
二、键值对
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量 key 和 value,key 代表键,value 表示与 key 对应的信息。
STL 中关于键值对的定义:
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)
{}
};
三、map
1. 在 map 中,键值 key 通常用于排序和惟一地标识元素,而值 value 中存储与此键值 key 关联的内容。键 key 和值 value 的类型可能不同,并且在 map 的内部,key 与 value 通过成员类型 value_type 绑定在一起,为其取别名称为 pair:
typedef pair value_type。
2. 在内部,map中的元素总是按照键值key进行比较排序的。(默认升序)
3. map 支持下标访问符,即在[]中放入 key ,就可以找到与 key 对应的 value 。
4. map 的底层是红黑树来实现的。
首先我们查看一下 map 的模板参数声明:
1. key: 键值对中key的类型
2. T: 键值对中value的类型
3. Compare: 比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)
4. Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器。
map 的迭代器和其他容器的迭代器用法一样,这里不做说明,我们可以利用迭代器去遍历 map:(该遍历函数后面会用到)
template<class K,class V>
void print(map<K,V>& m)
{
for (auto it = m.begin(); it != m.end(); it++)
{
cout << (*it).first << "--->" << (*it).second << " ";
}
cout << endl;
}
map的默认成员函数(构造、拷贝构造、赋值运算符重载):
函数声明 | 功能 |
| 构造一个空的map |
| 用[first,last)区间中元素来构造map |
map (const map& x) | 拷贝构造 |
| 赋值运算符重载 |
除了上述几种构造函数之外,map 还支持(c++11)使用花括号的方式来初始化构造。
void testmap1()
{
map<int, string> m1;
map<int, string> m2{
{ 4, "four" }, { 2, "two" }, { 3, "three" },
{ 1, "one" },{ 5, "five" },{ 2,“two” }
};
map<int, string> m3(m2);
map<int, string> m4(m2.begin(), m2.end());
m1 = m2;
print(m1);
print(m2);
print(m3);
print(m4);
}
程序运行结果如下:
我们可以看到通过上述几种方式都可以初始化构造或者赋值 map。可以看到 map 是有序的,默认升序,而且键 key 唯一,没有重复。
operator[]
operator[] 的原理:
用<key, T()>构造一个键值对,然后调用 insert() 函数将该键值对插入到 map 中。如果 key 已经存在,插入失败,insert 函数返回该 key 所在位置的迭代器,如果 key 不存在,插入成功,insert 函数返回新插入元素所在位置的迭代器。
operator[] 函数最后将 insert 返回值键值对中的 value 返回。
例如:
1. 假设现在有一个 map<int,string> m; 我们执行语句 m[2]; 即调用 insert() 将 <2,""> 插入到 map 中。
2. 假设现在有一个 map<int,string> m; 我们执行语句 m[2]="two"; 即调用 insert() 将 <2,"">插入到 map 中,然后 operator[] 返回 value的引用,再将 "two" 赋值给 value 的引用。如果,我们此时执行语句 cout<<m[2]<<endl; 首先调用 insert(),插入失败,返回 value值 "two"。
在元素访问时,有一个与 operator[] 类似的操作 at() (该函数不常用)函数,都是通过 key 找到与 key 对应的 value 然后返回其引用,不同的是:当 key 不存在时,operator[] 用默认 value 与 key 构造键值对,然后插入,返回该默认 value,at() 函数直接抛异常。
void testmap2()
{
map<int, string> m1{
{ 4, "four" }, { 2, "two" }, { 3, "three" },
{ 1, "one" }, { 5, "five" }
};
m1[6];
m1[7] = "seven";
//m1.at(8); 程序抛异常 直接崩掉
print(m1);
}
程序运行结果:
map 中的增删查改
函数声明 | 功能 |
| 插入键值对 val。返回值也是键值对:iterator代表插入键值对的迭代器,bool代表是否插入成功 |
| 在map中插入[first, last)区间中的元素 |
| 在position位置插入值为val的键值对,返回该键值对在map中的位置,注意:元素不一定必须插在position位置,该位置只是一个参考。 |
void erase ( iterator position ) | 删除position位置的元素 |
size_type erase ( const key_type& x ) | 删除键为 x的元素 |
void erase ( iterator first, iterator last ) | 删除[first, last)区间中的元素 |
void swap( map<Key,T,Compare,Allocator>& mp ) | 交换两个map |
size_type count ( const key_type& x ) const | 返回 key 值为x的元素个数,因此该函数的返回值要么为0,要么为1,因此也可以用该函数来检测一个key是否在map中 |
void clear ( ) | 将map 中元素清空 |
iterator find ( const key_type& x ) | 找到返回该元素的位置的迭代器,否则返回end |
void testmap3()
{
map<int, string> m1;
map<int, string> m2{
{ 1, "one" }, { 4, "four" }
};
m1.insert(pair<int, string>(3, "three"));
m1.insert(make_pair(2, "two"));
m1.insert(m1.find(1), make_pair(5, "five"));
m1.find(6);
m2.insert(m1.begin(), m1.end());
print(m1);
print(m2);
m1.erase(5);
m1.erase(m1.find(2));
print(m1);
m1.swap(m2);
print(m1);
print(m2);
}
程序运行结果:
四、multimap
multimap 的使用和 map基本一样,唯一不同的地方是 multimap 中key可以重复,我们来看一段代码简单了解即可:
void testmultimap()
{
multimap<int, string> m1{
{ 4, "four" }, { 2, "two" }, { 3, "three" },
{ 2, "two" }, { 5, "five" }, { 5, "five" }, { 1, "one" }
};
multimap<int, string>::iterator it = m1.begin();
while (it != m1.end())
{
cout << (*it).first << "-->" << (*it).second << " ";
it++;
}
cout << endl;
}
程序运行结果: