笔记会持续更新,有错误的地方欢迎指正,谢谢!
前言:前面学的都是顺序容器,顺序容器中的元素是按它们在容器中的位置来保存和访问的。接下来这一章学习关联容器:关联容器中的元素是按关键字来保存和访问的。
关联容器与顺序容器的不同之处反映了关键字的作用。
**关联容器分类:**set还是map、关键字是否重复、关键字是否有序。
使用关联容器
大多数程序员都很熟悉vector这一套,但很多人可能从未用过关联容器,接下来让大家看看关联容器在某些场合是多么牛逼。
使用map
我们要统计每个单词在输入中出现的次数:
map<string, size_t> word_count;
string word;
while(cin >> word)
{
++word_count[word];//[]优先级比++高
//你还能用顺序容器写出更简单的程序吗?
}
上一个程序的一个合理拓展是:忽略常见单词,如the,and等,我们可以用set来保存想忽略的单词:
map<string, size_t> word_count;
set<string> exclude = {"the", "and", "but"};
while(cin >> word)
{
if(exclude.find(word) == exclude.end())
{
++word_count[word];
}
}
关联容器概述
关联容器都支持一般的普通容器操作,但是不支持顺序容器的位置相关操作,例如push_front,因为关联容器中元素是根据关键字存储的。
定义关联容器
set<string> exclude = {"1", "2"};
map<string, string> authors = {{"a", "haha"},{"b", "hehe"}}
关键字类型的要求
就是键值对,要求是:必须定义元素比较的方法,这个要求很好理解嘛。我们前面用到的键都是string或int之类的,元素比较的方法都已经默认有了。接下来就要介绍,如果键的类型是自定义的,那我们如何定义比较方法呢?
必须定义一个严格弱序(strict weak ordering)。
接下来我们就来举个例子。定义Sales_data的multiset,显然我们不能直接这样写:
multiset<Sales_data> a;
//错的,因为Sales_data没有定义<运算符。
所以,我们要自定义一个比较操作,这个函数其实我们之前写过:
bool compareIsbn(const Sales_data &a, const Sales_data &b)
{
return a.isbn() < b.isbn(); //这里能用<,还是因为isbn()返回类型是string,string有<运算符。
}
好了,现在已经自定义好比较操作了,那我们怎么让编译器知道它:
multiset<Sales_data,decltype(compareIsbn)*>bookstore(compareIsbn);
我们来仔细看看分析:
- 我们在定义multiset时就要在键后面加上比较操作类型,这个比较操作类型是函数指针,指向我们定义的比较操作函数。
- 后面bookstore加括号,意思是我们要调用compareIsbn来使bookstoe排序,什么意思呢?就是当我们添加元素时,用compareIsbn来为这些元素排序。这个小括号里的compareIsbn类型是函数指针,别忘了我们在使用函数名的时候,它会自动转化为指针。
- 那么问题来了,为什么前面那个compareIsbn还要加*表示指针呢?因为decltype比较特殊啊,它得出的类型是函数类型,所以要加*号表示指针。
pair类型
pair是map的好基友,我们会经常用到它,它定义在头文件utility中。 一个pair保存两个数据成员:
pair<string, string> a;
pair<string, size_t> b;
pair<string, vector<int>> c; //反正什么都能装,类似容器。
pair的默认构造函数对数据成员进行值初始化,我们也可以提供初始化器来显式初始化:
pair<string, string> d{"张无忌", "赵敏"};
//可用花括号包围的初始化器来返回pair类型的对象。
pair有个很特殊的规定它很大方,它的数据成员都是public的,而且两个数据成员分别命名为first和second。方便所有人来访问。
cout << d.first << d.second << endl;
创建返回值为pair对象的函数:
pair<string, int> process(vector<string> &v)
{
if(v.empty()){ return {v.back(), v.back().size(); } //v不为空时,列表初始化。可用花括号包围的初始化器来返回pair类型的对象。
else {return pair<string, int>();} //v为空时,调用默认构造函数初始化。
}