C++ Primer 笔记+习题解答(十一)

今天是第十一篇笔记,主要内容是关联容器。关联容器的涉及到的内容相比顺序容器是很少的,所以篇幅也是比较短小的。但是一些细节方面的问题还是比较晦涩的,经过一番挣扎后,我还是决定先放弃比较困难的部分。好读书不求甚解从某些层面来说也许是好事,有些东西真的需要一定基础才能理解透彻。在本节中,书上对无序容器的介绍很少,尤其是哈希相关的内容,基本就是一笔带过。

有错误 请指正 谢谢

1.引言:

  • 关联容器和顺序容器有着根本差异:

关联容器中的元素按照关键字来保存和访问,顺序容器中的元素是按照它们在容器中的位置来顺序保存和访问的。关联容器支持高效的关键字查找和访问。

  • 关联容器分为两类:

map 和 set。map也叫关联数组和映射类型,set称为集合类型。可以参考其中的数学意思。

map是关键字-值,成对存在,但是set中关键字就是值,是以单个元素的形式存在。

  • 新版标准库提供了8个关联容器:

主要的异同有三点:

1.是map或是set. 2.是否允许重复关键字(multi 前缀)。3.是否有序(unordered前缀)。

在无需容器中,元素是依靠哈希函数进行组织的。

头文件主要区分在无序和有序,同关键字是否重复无影响。有序容器的头文件是map&set.无序是对应的unordered_map&unordered_set.

2.使用关联容器:

  • 1.map:关键字-值,存在一种映射关系。 set :关键字即是值,也叫集合类型。
  • 2.凡是顺序容器中位置相关的操作,在关联容器中都是不支持的,意义不大。因为关联容器中同位置无关。
  • 3.关联容器中的迭代器都是双向的。
  • 4.定义关联容器:

map:要同时指明关键字类型和值类型,即同时在尖括号中提供两种类型信息。

set:只需要指明关键字类型。

都存在默认构造函数,即允许创建空容器。

初始化有两种:拷贝或者范围初始化.范围一般是指花括号列表或者迭代器范围。拷贝是指想匹配的容器之间的互相拷贝初始化。

multimap/multiset:主要区别在于此两种是允许关键字重复的。带multi前缀的是允许重复关键字的。

  • 2.1关键字类型的要求:

有序关联容器中对其关键字类型有要求:必须是定义了元素的比较方法,不然如何区分有序。默认是使用<进行比较,但是可以自定义操作进行替代。

使用自定义关键字类型的比较函数用来组织容器中的元素,这个时候比较操作也成为类型的一部分,这个时候需要在方括号中提供自定义操作。

<span style="font-size:18px;"><span style="font-size:18px;">mutimap<Sales_data,decltype(Compare)*> book(Compare);                                                                                </span></span>
<span style="font-size:18px;"><span style="font-size:18px;">这个地方使用的是函数指针类型,是可调用类型之一。在实例化的时候需要加上相应的函数名,此时的函数名自动转换为指针类型。</span></span>

  • 2.2 pair类型:

pair的英文解释是一对。是一种标准库类型,定义在头文件utility中,pair中的数据成员都是public的。

示例:

<span style="font-size:18px;"><span style="font-size:18px;">pair<string,string> spair{"Max","Cy"};</span></span>
其中两个成员分别被命名为first和second成员。我们可以通过.运算符进行访问:
<span style="font-size:18px;"><span style="font-size:18px;">cout<<spair.first<<endl;</span></span>
make_pair()函数:用给定的数据构建一个pair,其中类型可以由值类型提供。
<span style="font-size:18px;"><span style="font-size:18px;">auto pa=make_pair("Max","Cy");//可以推断出类型。</span></span>

3.关联容器的操作:

<span style="font-size:18px;"><span style="font-size:18px;">key_type;  //关键字类型。
value_type; //值类型。
mapped_type;//map特有类型。同key_type构成了一对。</span></span>

  • 3.1关联容器的迭代器:

解引用其迭代器会得到一个值类型,即上面提到的value_type.

key_type类型是const的,所以set的迭代器是const的。所以对其进行写的操作是无意义的。

遍历关联容器:支持begin和end成员,输出是按字典序排列的,因为一开始的时候就用了<进行比较。

  • 3.2添加元素:

关联容器的insert成员可以添加一个元素或者一个范围。当添加重复关键字的时候,对不允许关键字重复的容器来说,是无影响的。

当接受一个范围(迭代器范围或者初始值列表)时,不允许关键字重复时,只会插入第一个带此关键字的元素。

当map中使用insert插入元素时,会返回一个pair类型,可以用来检测是否插入成功。first成员是一个迭代器,指向插入的元素。second成员是bool用来表示是否插入成功。

  • 3.3删除成员:

<span style="font-size:18px;"><span style="font-size:18px;">c.erase(k);删除关键字为k的元素,返回删除元素的个数。
c.erase(p);删除迭代器p指向的元素,返回指向下一个元素的迭代器。
c.erase(b,e); 删除迭代器b,e之间的元素,返回迭代器e.注意区间是左闭合。</span></span>

  • 3.4 map的下标操作:

只有map和unordered_map提供了下标操作。至于允许关键字重复的容器,使用关键字下标会有冲突。

map的下标操作有两种:类似数组的和at函数。

map的下标接受一个关键字,返回一个值类型,若此关键字不存在则会插入此不存在的关键字,并且对值执行值认初始化。

因为下标可能插入元素,故只能对非const的map执行此操作。

但是使用at函数的时候不会出现此状况,当关键字不存在的时候会抛出一个异常。

  • 3.5访问元素:

提供了多种访问元素的方法:

c.find(k);
c.count(k);
c.lower_bound(k);
c.upper_bound(k);
c.equal_range(k);
具体的含义解释可以参考书籍或者谷歌上的资料。

4.无序容器:

不再使用<来组织元素,而是通过使用一个哈希函数和一个关键字类型上的==运算符。

管理桶:无序容器在存储上组织为一个桶,每个桶保存0个或者多个元素。无序容器使用一个哈希函数将元素映射到桶。为了访问一个元素,首先计算元素的哈希值,它指出应该搜索哪个桶。容器将具有一个特定哈希值的元素都存储到一个桶中。

无序容器对关键字类型的要求:

默认情况下使用==运算符比较元素,同时也会使用一个hash<key_type>类型对象来生成每个元素的哈希值。

5.总结:

定义了8个容器类型,区分点在三个维度上:1.是map or set.2.是否有multi.3.是否有unordered.

有序容器使用比较函数来比较关键字,从而元素是有序的。

无序容器使用hash<key_type> 和一个==运算符来组织元素。

无论是有序还是无序的,相同关键字的元素的都是相邻存储的。

6.习题解答:

11.1

<span style="font-size:18px;"><span style="font-size:18px;">不同:最显著的区别就是map之中存在一种映射关系,而是vector却没有。</span></span>
11.2
<span style="font-size:18px;"><span style="font-size:18px;">1.最适合用list:需要在任何位置插入元素。
2.最适合用vector:只需要在链表尾部进行操作。
3.最适合用deque:需要在头尾操作。
4.最适合用map:两种类型之间存在映射关系。
5.最适合用set:用于排除某种情况,需要对关键字进行高效查找。</span></span>
11.3
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main(){
	map<string, size_t> word_count;
	string word;
	while (cin >> word)
		++word_count[word];
	for (const auto& x : word_count)
		cout << x.first << " " << x.second << " " << endl;
	system("pause");
	return 0;
}</span></span>
11.4
<span style="font-size:18px;"><span style="font-size:18px;">#include <iostream>
#include <map>
#include <string>
#include <cctype>
#include <set>
using namespace std;
int main(){
	map<string, size_t> word_count;
	set<string> exclude = { ",", "." };
	string word;
	while (cin >> word){
		word[0]=tolower(word[0]);
		if (exclude.find(word) == exclude.end()){
			if (ispunct(word[word.size() - 1]))
				word = word.erase(word.size() - 1, 1);
			++word_count[word];
		}
	}
	for (const auto& x : word_count)
		cout << x.first << " " << x.second << " " << endl;
	system("pause");
	return 0;
}<span style="white-space:pre">																	</span>//具体的思路是是同一化,不管大写小写全部转换成一个格式。然后检测末尾是否是标点符号,是的则删去。</span></span>
11.5
<span style="font-size:18px;"><span style="font-size:18px;">map是映射类型,而set是集合类型。
选择的时候依据自己的数据之间时候存在默认关系。</span></span>
11.6
<span style="font-size:18px;"><span style="font-size:18px;">set是一种集合类型,list是双向链表,数据之间是串起来的。选择依据是是否要进行插入删除等操作。</span></span>

11.7

<span style="font-size:18px;">#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
int main(){
	map<string,vector<string>> famls;
	std::string lastName, chldName;
	cout << "Ente lastnames :";
	while (cin >> lastName&&lastName != "@q"){//这个地方要提供自己定义的结束标志,不要用ctrl+z.
		cout << "PLZ Enter children's name:\n";
		while (cin >> chldName && chldName != "@q")
			famls[lastName].push_back(chldName);
		cout << "Ente lastname :";
	}
	for (auto e : famls){
		cout << e.first << ":\n";
		for (auto c : e.second)
			cout << c << " ";
		cout << "\n";
	}
	return 0;
}</span>

11.8

<span style="font-size:18px;">set自身带有不重复的特点,即使重复的单词被添加进去也不会影响到set.
#include <iostream>
#include <vector>
#include<string>
using namespace std;
int main(){
    vector<string> 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;
}
</span>
11.9

<span style="font-size:18px;">map<string, list<int>>  map_var;
map<string,list<std::size_t>> map_var;//也许用size_t更好一点。 
</span>
11.10

<span style="font-size:18px;">取决于迭代器是否定义了<操作。vector的迭代器定了此操作,但是list没有定义。
</span>
11.11

<span style="font-size:18px;">使用函数指针即可。
bool(*compreIsbn)(const Sales_data& lhs, const Sales_data& rhs) = compreIsbn;
</span>
11.12

<span style="font-size:18px;">#include <string>
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <utility>
using namespace std;
int main(){
	vector<pair<string, int>> pvec;
	string word;
	int number;
	while (cin >> word >> number){
		auto temp=make_pair(word, number);
		pvec.push_back(temp);
	}
	for (const auto&x : pvec){
		cout << x.first << " " << x.second << endl;
	}
	system("pause");
	return 0;
}</span>
11.13

<span style="font-size:18px;">#include <string>
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <utility>
using namespace std;
int main(){
	vector<pair<string, int>> pvec;
	string word;
	int number;
	while (cin >> word >> number){
		//auto temp=make_pair(word, number);
		pair<string, int> temp{ word, number };
		//pair<string, int> temp = (word, number);
		pvec.push_back(temp);
	}
	for (const auto&x : pvec){
		cout << x.first << " " << x.second << endl;
	}
	system("pause");
	return 0;
}</span>

11.14

<span style="font-size:18px;">#include <vector>
#include <iostream>
#include <string>
#include <utility>
#include <map>
using namespace std;
int main(){
	map < string, vector<pair<string, string>>> family;
	string last_name, name, birthday;
	int number;
	cout << "Enter last name ." << endl;
	while (cin >> last_name){
		cout << "Enter names and birthday of children ." << endl;
		while (cin >> name >> birthday){
			family[last_name].push_back(make_pair(name, birthday));
			cout << "Continue or quit? 1 or 0 ." << "Enter your choice :";
			cin >> number;
			if (number == 0)
				break;
			else
				cout << "Continue to Enter information of children's name and birthday ." << endl;
		}
		cout << "Continue or quit ? 1 or 0 ." << "Enter your choice :";
		cin >> number;
		if (number == 0)
			break;
		else
			cout << "Continue to Enter information of children's last name ." << endl;
	}
	for (const auto& x : family){
		cout << x.first << endl;
		for (const auto&y : x.second)
			cout << y.first << " " << y.second << endl;
		cout << endl;
	}
	system("pause");
	return 0;
}</span>


11.15

<span style="font-size:18px;">key_type: int;
mapped_type: vector<int>;
value_type: pair<int,vector<int>>;</span>
11.16

<span style="font-size:18px;">#include <map>
#include <iostream>
using namespace std;
int main(){
	map<int, int> int_map = { 1, 1 };
	auto iter = int_map.begin();
	(*iter).second = 10;
	system("pause");
	return 0;
}</span>
11.17

<span style="font-size:18px;">第二个不合法。因为其未定义push_back操作。</span>
11.18

<span style="font-size:18px;">const_iterator.</span>
11.19

<span style="font-size:18px;">using compare=bool (*)(const Sales_data& lhs,const Sales_data& rhs);
multiset<Sales_data,compare>::iterator iter=bookstore.begin();</span>
11.20

<span style="font-size:18px;">#include <iostream>
#include <vector>
#include<string>
#include <map>
using namespace std;
int main(){
	map<string, size_t> smap;
	string word;
	auto func = [&]()->bool{
		cout << "Enter words or Enter quit to stop " << endl;
		cin >> word;
		return word != "quit";
	};
	while (func()){
		auto f = make_pair(word, 1);
		auto result = smap.insert(f);
		if (result.second == false)
			++((*result.first).second);
	}
	for (const auto& x : smap)
		cout << x.first << " " << x.second << endl;
	system("pause");
	return 0;
}
很明显下标版本更好接受一点。
</span>
11.21

<span style="font-size:18px;">同上面函数功能等价,统计单词个数,只是写的比较简便。</span>
11.22

<span style="font-size:18px;">argument:pair<string,vector<int>>;
return type: pair<map<string,vector<int>>::iterator,boo>;</span>

11.23

<span style="font-size:18px;">#include <vector>
#include <iostream>
#include <string>
#include <utility>
#include <map>
using namespace std;
int main(){
	multimap < string, vector<string>> family;
	string last_name, name;
	int number;
	cout << "Enter last name ." << endl;
	while (cin >> last_name){
		vector<string> temp;
		cout << "Enter names of children ." << endl;
		while (cin >> name){
			temp.push_back(name);
			cout << "More children ? 1(Y) or 0(N) ." << "Enter your choice :";
			cin >> number;
			if (number == 0)
				break;
			else
				cout << "Continue to Enter information of children's name ." << endl;
		}
		family.insert({ last_name, temp });
		cout << "More family ? 1(Y) or 0(N) ." << "Enter your choice :";
		cin >> number;
		if (number == 0)
			break;
		else
			cout << "Continue to Enter last name ." << endl;
	}
	for (const auto& x : family){
		cout << x.first<<":" << endl;
		for (const auto& y : x.second)
			cout << y << " ";
		cout << endl;
	}
	system("pause");
	return 0;
}//不得不牺牲美观,因为简化着写问题着实比较多。</span>
11.24

<span style="font-size:18px;">在原先是空的map中插入一个关键字是0,值为的1的元素。</span>
11.25

<span style="font-size:18px;">报错。应为容器为空,不可以使用下标运算符。</span>
11.26

<span style="font-size:18px;">//下标类型:key_type;
//返回类型:mapped_type.
#include <map>
#include <iostream>
using namespace std;
int main(){
	map<int, int> m_int = { { 1, 1 }, { 2, 4 }, { 3, 9 }, { 4, 16 } };
	cout << m_int[3] << endl;
	cout << typeid(m_int[3]).name() << endl;
	system("pause");
	return 0;
}</span>
11.27

<span style="font-size:18px;">统计出现的次数用count.
查找是否存在用find。
</span>
11.28

<span style="font-size:18px;">#include <map>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main(){
	map<string, vector<int>> m = { { "aa", { 1, 1 } } };
	auto result = m.find("aa");
	if (result == m.end())
		cout << "Not in the container " << endl;
	else
		cout << "In the container " << endl;
	system("pause");
	return 0;
}</span>
11.29

<span style="font-size:18px;">lower_bound可能指向尾后迭代器或者返回一个指向大于给定元素的迭代器。
upper_bound如果容器中没有大于给定值的元素,那么会返回尾后迭代器,否则返回一个指向大于给定值的迭代器。
equal_range可能会返回一个pair类型,first和second成员都是尾后迭代器。</span>
11.30

<span style="font-size:18px;">因为返回的是pair类型,pos.first表示返回的pair中的第一个迭代器。然后迭代器用箭头运算符符访问map中pair的元素。</span>
11.31

<span style="font-size:18px;">#include <map>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main(){
	map	<string, vector<string>> m_str = { { "Max", { "John,Carl,Bill" } } };
	m_str.insert({ "Max", { "JoJo,Car,BiBi" } });
	auto result=m_str.find("max");
	if (result != m_str.end())
		m_str.erase(result);
	else
		cout << "Wong hahah" <<endl;
	auto temp=m_str.find("Gate");//查找不存在的元素。
	if (temp != m_str.end())
		m_str.erase(temp);
	else
		cout << "Not in the container " << endl;
	system("pause");
	return 0;
}</span>
11.32

<span style="font-size:18px;">#include <map>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main(){
	multimap	<string, vector<string>> m_str = { { "Max", { "John,Carl,Bill" } } };
	m_str.insert({ "Max", { "JoJo,Car,BiBi" } });
	for (const auto& x : m_str){
		cout << x.first << " :" << endl;
		for (const auto& y : x.second)
			cout << y << " ";
		cout << endl;
	}
	system("pause");
	return 0;
}</span>

11.33

#include <fstream>
#include <iostream>
#include <map>
#include<string>
#include <sstream>
using namespace std;
//先把转换规则存入一个map中。
map<string, string> build_map(ifstream& ifs){
	string key, value;
	map<string, string> trans_map;
	while (ifs >> key&&getline(ifs, value))
		trans_map[key] = value.substr(1);//删除空格。
	return trans_map;
}
//要判断一个单词是否需要进行转换,判断的规则应该是把给定的单词同转换规则文件进行比较。
const string& transform(string& word, map<string, string>& m){
	//auto trans_map = build_map(ifs);
	auto result = m.find(word);
	if (result != m.end())
		word = m[word];
	return word;
}
//根据输入进行转换。
int main(){
	string word, temp;
	ifstream ifs("rule.txt");
	auto trans_map = build_map(ifs);
	while (getline(cin, word)){
		istringstream is(word);
		while (is >> temp)
			cout << transform(temp, trans_map) << " ";
		cout << endl;
	}
	system("pause");
	return 0;
}
11.34

如果用下标:使用at函数,会抛出异常;使用[]下标,那么会把不存在的元素插进map中。
以上两种行为都不符合自己的要求。
11.35

使用下标会把最后一个匹配的插入进去;
使用insert函数会把第一个匹配的插入进去。
11.36

没有任何问题。关键字和空字符之间形成了一种映射关系。
测试的时候只需要在转换规则里面添加一行就可以了。

11.37

<span style="font-size:18px;">书本搬运计划:
1.在关键字类型没用明显的序关系的时候。
2.某些情况下,维护元素的代价非常高。
有序的优势当然是有顺序。</span>
11.38

<span style="font-size:18px;">#include <unordered_map>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main(){
	unordered_map<string, size_t> 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;
}</span>
单词转换程序留待上面的空缺习题一起解决。

7.总结: 

    看了好多关于学习C++的评价,都说是一种自虐和舔伤口的行为。在开始看C++ Primer这本书的时候,当时也是信心慢慢,可是看到后面越发觉得艰难,所以我决定改变下计划,先把简单的看完,然后集中精力攻克复杂知识。另外给大家发个福利,是大神做的答案然后在git上公开的。另外这位大神在豆瓣上建立了讨论组,大家搜一下就可以看见了。

答案链接

End


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值