今天是第十一篇笔记,主要内容是关联容器。关联容器的涉及到的内容相比顺序容器是很少的,所以篇幅也是比较短小的。但是一些细节方面的问题还是比较晦涩的,经过一番挣扎后,我还是决定先放弃比较困难的部分。好读书不求甚解从某些层面来说也许是好事,有些东西真的需要一定基础才能理解透彻。在本节中,书上对无序容器的介绍很少,尤其是哈希相关的内容,基本就是一笔带过。
有错误 请指正 谢谢
关联容器中的元素按照关键字来保存和访问,顺序容器中的元素是按照它们在容器中的位置来顺序保存和访问的。关联容器支持高效的关键字查找和访问。
map 和 set。map也叫关联数组和映射类型,set称为集合类型。可以参考其中的数学意思。
map是关键字-值,成对存在,但是set中关键字就是值,是以单个元素的形式存在。
主要的异同有三点:
1.是map或是set. 2.是否允许重复关键字(multi 前缀)。3.是否有序(unordered前缀)。
在无需容器中,元素是依靠哈希函数进行组织的。
头文件主要区分在无序和有序,同关键字是否重复无影响。有序容器的头文件是map&set.无序是对应的unordered_map&unordered_set.
map:要同时指明关键字类型和值类型,即同时在尖括号中提供两种类型信息。
set:只需要指明关键字类型。
都存在默认构造函数,即允许创建空容器。
初始化有两种:拷贝或者范围初始化.范围一般是指花括号列表或者迭代器范围。拷贝是指想匹配的容器之间的互相拷贝初始化。
multimap/multiset:主要区别在于此两种是允许关键字重复的。带multi前缀的是允许重复关键字的。
有序关联容器中对其关键字类型有要求:必须是定义了元素的比较方法,不然如何区分有序。默认是使用<进行比较,但是可以自定义操作进行替代。
使用自定义关键字类型的比较函数用来组织容器中的元素,这个时候比较操作也成为类型的一部分,这个时候需要在方括号中提供自定义操作。
mutimap book(Compare);
这个地方使用的是函数指针类型,是可调用类型之一。在实例化的时候需要加上相应的函数名,此时的函数名自动转换为指针类型。
pair的英文解释是一对。是一种标准库类型,定义在头文件utility中,pair中的数据成员都是public的。
示例:
pair spair{"Max","Cy"};
其中两个成员分别被命名为first和second成员。我们可以通过.运算符进行访问:
cout<
make_pair()函数:用给定的数据构建一个pair,其中类型可以由值类型提供。
auto pa=make_pair("Max","Cy");//可以推断出类型。
key_type; //关键字类型。
value_type; //值类型。
mapped_type;//map特有类型。同key_type构成了一对。
解引用其迭代器会得到一个值类型,即上面提到的value_type.
key_type类型是const的,所以set的迭代器是const的。所以对其进行写的操作是无意义的。
遍历关联容器:支持begin和end成员,输出是按字典序排列的,因为一开始的时候就用了<进行比较。
关联容器的insert成员可以添加一个元素或者一个范围。当添加重复关键字的时候,对不允许关键字重复的容器来说,是无影响的。
当接受一个范围(迭代器范围或者初始值列表)时,不允许关键字重复时,只会插入第一个带此关键字的元素。
当map中使用insert插入元素时,会返回一个pair类型,可以用来检测是否插入成功。first成员是一个迭代器,指向插入的元素。second成员是bool用来表示是否插入成功。
c.erase(k);删除关键字为k的元素,返回删除元素的个数。
c.erase(p);删除迭代器p指向的元素,返回指向下一个元素的迭代器。
c.erase(b,e); 删除迭代器b,e之间的元素,返回迭代器e.注意区间是左闭合。
只有map和unordered_map提供了下标操作。至于允许关键字重复的容器,使用关键字下标会有冲突。
map的下标操作有两种:类似数组的和at函数。
map的下标接受一个关键字,返回一个值类型,若此关键字不存在则会插入此不存在的关键字,并且对值执行值认初始化。
因为下标可能插入元素,故只能对非const的map执行此操作。
但是使用at函数的时候不会出现此状况,当关键字不存在的时候会抛出一个异常。
提供了多种访问元素的方法:
c.find(k);
c.count(k);
c.lower_bound(k);
c.upper_bound(k);
c.equal_range(k);
具体的含义解释可以参考书籍或者谷歌上的资料。
不再使用<来组织元素,而是通过使用一个哈希函数和一个关键字类型上的==运算符。
管理桶:无序容器在存储上组织为一个桶,每个桶保存0个或者多个元素。无序容器使用一个哈希函数将元素映射到桶。为了访问一个元素,首先计算元素的哈希值,它指出应该搜索哪个桶。容器将具有一个特定哈希值的元素都存储到一个桶中。
无序容器对关键字类型的要求:
默认情况下使用==运算符比较元素,同时也会使用一个hash
定义了8个容器类型,区分点在三个维度上:1.是map or set.2.是否有multi.3.是否有unordered.
有序容器使用比较函数来比较关键字,从而元素是有序的。
无序容器使用hash
无论是有序还是无序的,相同关键字的元素的都是相邻存储的。
11.1
不同:最显著的区别就是map之中存在一种映射关系,而是vector却没有。
11.2
1.最适合用list:需要在任何位置插入元素。
2.最适合用vector:只需要在链表尾部进行操作。
3.最适合用deque:需要在头尾操作。
4.最适合用map:两种类型之间存在映射关系。
5.最适合用set:用于排除某种情况,需要对关键字进行高效查找。
11.3
#include
#include
11.4
#include
#include
11.5
map是映射类型,而set是集合类型。
选择的时候依据自己的数据之间时候存在默认关系。
11.6
set是一种集合类型,list是双向链表,数据之间是串起来的。选择依据是是否要进行插入删除等操作。
11.7
#include
#include
11.8
set自身带有不重复的特点,即使重复的单词被添加进去也不会影响到set.
#include
#include
#include
using namespace std;
int main(){
vector svec = { "aa", "bb", "cc" };
string word;
auto f = [&](){cout << "Enter strings " << endl;
cin >> word;
return word != "@q"; };
while (f()){
for (auto x : svec){
if (x == word)
cout << "excluded!" << endl;
else
svec.push_back(word);
}
}
system("pause");
return 0;
}
11.9
map> map_var;
map> map_var;//也许用size_t更好一点。
11.10
取决于迭代器是否定义了<操作。vector的迭代器定了此操作,但是list没有定义。
11.11
使用函数指针即可。
bool(*compreIsbn)(const Sales_data& lhs, const Sales_data& rhs) = compreIsbn;
11.12
#include
#include
#include
#include
#include
11.13
#include
#include
#include
#include
#include
11.14
#include
#include
#include
#include
#include
11.15
key_type: int;
mapped_type: vector;
value_type: pair>;
11.16
#include
11.17
第二个不合法。因为其未定义push_back操作。
11.18
const_iterator.
11.19
using compare=bool (*)(const Sales_data& lhs,const Sales_data& rhs);
multiset::iterator iter=bookstore.begin();
11.20
#include
#include
#include
#include
11.21
同上面函数功能等价,统计单词个数,只是写的比较简便。
11.22
argument:pair>;
return type: pair
11.23
#include
#include
#include
#include
#include
11.24
在原先是空的map中插入一个关键字是0,值为的1的元素。
11.25
报错。应为容器为空,不可以使用下标运算符。
11.26
//下标类型:key_type;
//返回类型:mapped_type.
#include
11.27
统计出现的次数用count.
查找是否存在用find。
11.28
#include
11.29
lower_bound可能指向尾后迭代器或者返回一个指向大于给定元素的迭代器。
upper_bound如果容器中没有大于给定值的元素,那么会返回尾后迭代器,否则返回一个指向大于给定值的迭代器。
equal_range可能会返回一个pair类型,first和second成员都是尾后迭代器。
11.30
因为返回的是pair类型,pos.first表示返回的pair中的第一个迭代器。然后迭代器用箭头运算符符访问map中pair的元素。
11.31
#include
11.32
#include
11.33
#include
#include
#include
11.34
如果用下标:使用at函数,会抛出异常;使用[]下标,那么会把不存在的元素插进map中。
以上两种行为都不符合自己的要求。
11.35
使用下标会把最后一个匹配的插入进去;
使用insert函数会把第一个匹配的插入进去。
11.36
没有任何问题。关键字和空字符之间形成了一种映射关系。
测试的时候只需要在转换规则里面添加一行就可以了。
11.37
书本搬运计划:
1.在关键字类型没用明显的序关系的时候。
2.某些情况下,维护元素的代价非常高。
有序的优势当然是有顺序。
11.38
#include
#include
#include
#include
using namespace std;
int main(){
unordered_map word_count;
string word;
while (cin>>word){
++word_count[word];
}
for (const auto& x : word_count){
cout << x.first << "occurs " << x.second << ((x.second > 1) ? " times " : " time " )<< endl;
}
return 0;
}
单词转换程序留待上面的空缺习题一起解决。
看了好多关于学习C++的评价,都说是一种自虐和舔伤口的行为。在开始看C++ Primer这本书的时候,当时也是信心慢慢,可是看到后面越发觉得艰难,所以我决定改变下计划,先把简单的看完,然后集中精力攻克复杂知识。另外给大家发个福利,是大神做的答案然后在git上公开的。另外这位大神在豆瓣上建立了讨论组,大家搜一下就可以看见了。
答案链接。
End