1.前言
紧接前面章节的序列化容器,本章开始讲解STL中的关联式容器。
关联式容器与上面的序列式容器最大的区别就是,序列式容器是按顺序的存储所有元素,访问元素的方式是通过下标,或遍历。
而关联式容器则采用给每一个值分配一个键,我们访问元素的方式就是直接通过键,就能得到它的值,总结来说就是,关联式容器存储的是一个键值对,而序列式容器只存储值。
2.pair
在了解各个关联式容器之前, 我们必须来看一看各个容器存储的基本单元:键值对
,即这里介绍的pair
。
它的使用方法非常简单:
#include<iostream>
#include<utility> //pair所在头文件
using namespace std;
int main() {
pair<int, string> p;
p.first = 10;
p.second = "csdn";
}
首先我们需要理解一下它的实现过程。
因为它是用来存储两个变量的,所以它的模板参数有两个,分别指定即可,其中第一个为键 的类型,第二个为值的类型,我这里指定的键为int
,值为string
类型。
然后我们就可以直接通过它的first
属性与second
属性来分别给键与值进行赋值。
这基本就是它的所有用法,但如果在容器中添加一个pair
元素,每次都这样写的话,还是有点麻烦的。
所以可以直接初始化:
pair<int, string> p{10,"www.kucoding.com"};
为了更加便捷的生成一个pair
,还提供了一个函数来生成pair
:
make_pair<int, string>(10, "www.kucoding.com");
它可以直接返回一个生成好的pair
,类型为int
与string
组成的键值对。
但还是太长了, 所以我们还可以进一步简化:
make_pair(10, "www.kucoding.com");
你可以不用填入两个模板参数,它可以根据你填入的10
与字符串"www.kucoding.com"
自动推断出类型,使用起来更加简单!
3.map
这个容器应该是我们最常用的容器之一,它的特点就是键不可重复,也不能被修改,并且默认会根据你的键值进行升序排序
。
首先是声明:
#include<iostream>
#include<map>
using namespace std;
int main() {
map<int, string> m; //空map
map<int, string> m{{10,"www.kucoding.com"},{20,"www.kucoding.com"}}; //用初始值进行初始化
}
因为map
是键值对,所以一个元素通过{}
来表示,里面两个值分别为键与值,多个键值对则用,
分隔。
然后是添加:
map<int, string> m; //空map
m.insert(make_pair(10, "www.kucoding.com")); //1.通过函数生成pair
pair<int, string> p(20, "www.kucoding.com");//2.通过pair直接构造
m.insert(p);
m.insert({30,"www.kucoding.com"}); //3.直接通过值默认构造出一个pair
m[40] = "www.kucoding.com";//4.直接通过访问对应的键,不存在则创建,存在则修改
m.emplace(50, "www.kucoding.com"); //5.通过传递参数构造一个pair
auto beg = m.begin();
m.emplace_hint(beg,60,"www.kucoding.com"); //6.在指定迭代器处插入数据
最简单的肯定是第四种了,它的功能就是不存在则创建,存在则修改:
m[40] = "www.kucoding.com";//4.直接通过访问对应的键,不存在则创建,存在则修改
但就插入值的效率而言,第3种insert
更高,因为这种重载运算符的方法会先在内部构造一个带键但无值的pair
,然后再将对应的值赋值给pair
的值。
但如果是更新已有的数据,那么这种方法效率会更高,因为insert
需要先构造一个pair
完整对象,才能进行更新数据。
其次简单的便是第3种,但需要你有一定的理解,不然可能有点看不懂。
而第二种又比较繁琐,所以第一种用的就比较多,当然如果你能完全理解这种语句,那么使用第三种是完全没有问题的,而且还可以一次插入多个值。
注意一般带有emplace
的函数效率都更高,这里同理。
接着就是访问:
map<int, string> m{ {1,"kucoding1"},{2,"kucoding2"} }; //初始化2个键值对
cout << m[1] << endl;
cout << m.at(2) << endl;
map<string, int> m1{ {"kucoding1",1},{"kucoding2",2} }; //初始化2个键值对
cout << m1["kucoding1"] << endl;
访问一般就直接通过键来访问即可。
我这里特意将键值顺序调换了一下,就是为了让你能够理解用键来访问值的含义,
而at
函数与前面一样,就是用来专门访问值的,如果不存在则抛出异常。
当然你可以获取整个键值对
,而不是像上面只能获取值
:
map<int, string> m{ {1,"csdn1"},{2,"csdn2"} }; //初始化2个键值对
auto it = m.find(1); //查找该值对应的键值对,返回其迭代器
cout << it->first<<endl;
cout << it->second<<endl;
find
函数通过键来查找,得到对应键值对的迭代器,你就可以用像指针一样的方式来访问该键值对的键与值,如果没有找到,那么它会返回结束迭代器,即end
函数函数的值。
还有遍历:
map<int, string> m{ {1,"csdn1"},{2,"csdn2"} }; //初始化2个键值对
for (auto i = m.begin(); i !=m.end(); i++) //通过迭代器遍历
{
cout << i->first << endl;
cout << i->second << endl;
}
for (auto& i : m) { //高级遍历
cout << i.first;
cout << i.second;
}