C++ Primer第十一章①
C++ Primer
关联容器
前面我们学的都是顺序容器,顺序容器中的元素是按它们在容器中的位置来保存和访问的。接下来这一章我们要学习关联容器:关联容器中的元素是按关键字来保存和访问的。
关联容器的很多行为与顺序容器相同,但那些不同之处反映了关键字的作用。
使用关联容器
大多数程序员都很熟悉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"}
}
multi差别
vector<int> ivec = {1, 1, 2, 2};
set<int> iset(ivec.cbegin(), ivec.cend());
multiset<int> miset(ivec.cbegin(), ivec.cend());
cout << iset.size() << endl; //2
cout << miset.size() << endl; //4
关键字类型的要求
关键字就是键,值就是值,它俩一起就是键值对,对于键的要求就是:键必须定义元素比较的方法,这个要求很好理解嘛。我们前面用到的键都是string或int之类的,元素比较的方法都已经帮我们写好了,所以我们可以很轻松地不用管,接下来就要介绍,如果键的类型是自定义的一些数据类型,那我们在定义比较方法的时候有哪些注意的呢?
必须定义一个严格弱序(strict weak ordering),什么意思呢,它必须具备如下性质:
- 若k1<=k2,则k2不能<=k1 (这个跟我们数学学的不一样啊)
- 若k1<=k2, 且k2<=k3,则k1<=k3,这个好理解,传递性
- 若k1不小于等于k2, k2也不小于等于k1,则k1 == k2
接下来我们就来举个例子。定义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类型
接下来介绍一个map的好基友,pair,我们会经常用到它,它定义在头文件utility中。 一个pair保存两个数据成员:
pair<string, string> a;
pair<string, size_t> b;
pair<string, vector<int>> c; //反正什么都能装,类似容器
pair的默认构造函数对数据成员进行值初始化,我们也可以提供初始化器来显式初始化:
pair<string, string> d{"张无忌", "赵敏"};
pair有个很特殊的规定它很大方,它的数据成员都是public的,而且两个成员分别命名为first和second,这样就方便所有人来访问,我家大门常打开。
cout << d.first << d.second << endl;
关于pair上的操作以后碰到了再解释,比较简单的。
创建返回值为pair对象的函数
pair<string, int> process(vector<string> &v)
{
if(v.empty()){ return {v.back(), v.back().szie(); } //列表初始化,
else {return pair<string, int>();} //调用默认构造函数构造临时对象返回
}
#C++工程师#