C++primer第五版第十一章学习笔记

练习11.1:描述map和vector的不同。

vector这种顺序容器,元素在其中按顺序存储,每个元素都有唯一对应的位置编号,所有操作都是按编号进行的。例如,获取元素,插入删除元素,遍历元素。底层的数据结构是数组、链表,简单但能保证上述操作的高效。对于依赖值的元素访问,例如查找给定值,在这种数据结构上的实现是要通过遍历完成的,效率不佳。

而map这种关联容器,就是为了高效实现“按值访问元素”这类操作而设计的。为了达到这一目的,容器中的元素是按关键字值存储的,关键字值与元素数据建立起对应关系。底层数据结构是红黑树、哈希表等,可高效实现按关键字值查找、添加、删除元素等操作。

练习11.2:分别给出最适合使用list,vector,deque,map以及set的例子。

若元素很小(例如int),大致数量预先可知,在程序运行过程中不会剧烈变化,大部分情况下只在末尾添加或删除需要频繁访问任意位置的元素,则vector可带来最高的效率。若需要频繁在头部和尾部添加或删除元素,则deque是最好的选择。

如果元素较大(如大的类对象),数量预先不知道,或是程序运行过程中频繁变化,对元素的访问更多是顺序访问全部或很多元素,则list很适合。

map适合对一些对象按它们的某个特征进行访问的情形。set就是集合类型,当需要保存特定的值集合(通常是满足/不满足某种要求的值的集合),用set最为方便。

练习11.3:编写你自己的单词计数程序。

#include <iostream>

#include <fstream>
#include <map>
#include <string>
#include <algorithm>
using std::cout;
using std::endl;
using std::ifstream;
using std::map;
using std::string;

int main(int argc, char *argv[])
{
	ifstream in(argv[1]);
	if(!in){
		cout<<"Open input file failed."<<endl;
		exit(1);
	}

	map<string, size_t> word_count;
	string word;
	while(in>>word)
		++word_count[word];
	for(const auto &w : word_count)
		cout<<w.first<<" occurs "<<w.second<<((w.second>1)? "times" : "time" )<<endl;
	return 0;
}
练习11.4:扩展你的程序,忽略大小写和标点。例如,“example.”、“example,”和“Example”应该递增相同的计数器。

#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <algorithm>
using std::cout;
using std::endl;
using std::ifstream;
using std::map;
using std::string;

int main(int argc, char *argv[])
{
	ifstream in(argv[1]);
	if(!in){
		cout<<"Open input file failed."<<endl;
		exit(1);
	}

	map<string, size_t> word_count;
	string word;
	string punct=",.";

	while(in>>word){
		auto it=word.find_first_of(punct);
		if (it!=string::npos)  
			word.erase(it);
		for(auto &c:word)
			c=tolower(c);
		++word_count[word];
	}

	for(const auto &w : word_count)
		cout<<w.first<<" occurs "<<w.second<<((w.second>1)? "times" : "time" )<<endl;
	return 0;
}
练习11.5:解释map和set的区别。你如何选择使用哪个?

当需要查找给定值所对应的数据时,应使用map,其中保存的是<关键字,值>对,按关键字访问值。

如果只需判定给定值是否存在时,应使用set,它只是简单的值的集合。


练习11.6:解释set和list的差别。你该如何选择使用哪个?

两者都可以保存元素集合。如果只需要顺序访问这些元素,或是按位置访问元素,那么应使用list。如果需要快速判定是否有元素等于给定值,则应使用set。


练习11.7:定义一个map,关键字是家庭的姓,值是一个vector,保存家庭中孩子们的名。编写代码、实现添加新的家庭以及向已有家庭中添加新的孩子。

#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <algorithm>
using std::cout;
using std::endl;
using std::string;
using std::vector;
using std::map;

void add_family(map<string, vector<string>> &families, const string &family)
{
	if(families.find(family)==families.end())
		families[family]=vector<string> ();
}

void add_child(map<string, vector<string>> &families,const string &family, const string &child)
{
	families[family].push_back(child);
}

int main(int argc, char *argv[])
{
	map<string, vector<string>> families;
	add_family(families, "Zhang");
	add_child(families, "Zhang", "San");
	add_child(families, "Zhang", "Gang");
	add_child(families, "Li", "Si");
	add_family(families, "Li");
	for(auto f: families){
		cout<<f.first<<":";
		for(auto c: f.second)
			cout<<c<<" ";
		cout<<endl;
	}
	return 0;
}

练习11.8:编写一个程序,在一个vector而不是set中保存不重复的单词。使用set的优点是什么?

#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <algorithm>
using std::cout;
using std::endl;
using std::vector;
using std::string;
using std::ifstream;

string &trans(string &s)
{
	for(int p=0; p<s.size(); ++p){
		if(s[p]>='A'&&s[p]<='Z')
			s[p]-='A'-'a';
		else if(s[p]==','||s[p]=='.')
			s.erase(p,1);
	}
	return s;
}

int main(int argc, char *argv[])
{
	ifstream in(argv[1]);
	if(!in){
		cout<<"Open input file failed."<<endl;
		exit(1);
	}

	vector<string> unique_word;
	string word;
	while(in>>word)
	{
		trans(word);
		if(find(unique_word.begin(), unique_word.end(), word)==unique_word.end())
			unique_word.push_back(word);
	}

	for(const auto &w: unique_word)
		cout<<w<<" ";
	cout<<endl;

	return 0;
}

练习11.9:定义一个map,将单词与一个行号的list关联,list中保存的是单词所出现的行号。

#include <iostream>
#include <fstream>
#include <map>
#include <list>
#include <string>
#include <algorithm>
#include <sstream>
using std::cout;
using std::endl;
using std::ifstream;
using std::istringstream;
using std::map;
using std::list;
using std::string;

string &trans(string &s)
{
	for(int p=0; p<s.size(); ++p){
		if(s[p]>='A'&&s[p]<='Z')
			s[p]-='A'-'a';
		else if(s[p]==','||s[p]=='.')
			s.erase(p,1);
	}
	return s;
}

int main(int argc, char *argv[])
{
	ifstream in(argv[1]);
	if(!in)
	{
		cout<<"Open input file failed."<<endl;
		exit(1);
	}

	map<string, list<int>> word_lineno;
	string line;
	string word;
	int lineno=0;
	while(getline(in,line)){
		++lineno;
		istringstream l_in(line);
		while(l_in>>word){
			trans(word);
			word_lineno[word].push_back(lineno);
		}
	}

	for(const auto &w:word_lineno){
		cout<<w.first<<" is in: ";
		for(auto const &i: w.second)
			cout<<i<<" ";
		cout<<endl;
	}

	return 0;
}

练习11.10:可以定义一个vector<int>::iterator到int的map吗?list<int>::iterator到int的map呢?对于两种情况,如果不能,解释为什么。

由于有序容器要求关键字类型必须支持比较操作<,因此map<vector<int>::iterator, int>是可以的,因为vector的迭代器支持比较操作。而map<list<int>::iterator ,int>是不行的,因为list的元素不是连续存储,其迭代器不支持比较操作。


练习11.11:不使用decltype重新定义bookstores.

typedef bool (*pf) (const Sales_data &, const Sales_data &);
multiset<Sales_data, pf> bookstore(compareTsbn)

练习11.12:编写程序,读入string和int序列,将每个string和int序列存入一个pair中,pair保存在一个vector中。

#include <iostream>
#include <string>
#include <vector>
#include <utility>
#include <fstream>
#include <algorithm>
using std::cout;
using std::endl;
using std::vector;
using std::string;
using std::pair;
using std::ifstream;

int main(int argc, char *argv[])
{
	ifstream in(argv[1]);
	if(!in){
		cout<<"Open input file failed."<<endl;
		exit(1);
	}

	vector<pair<string, int>> data;
	string s;
	int v;
	while(in>>s&&in>>v)
		data.push_back(pair<string,int>(s,v));

	for(const auto &d: data)
		cout<<d.first<<" "<<d.second<<endl;

	return 0;
}

练习11.13:在上一题的程序中,至少有三种创建pair的方法。编写此程序的三个版本,分别采用不同的方法创建pair。解释你认为哪种形式最易于编写和理解,为什么?

列表初始化:data.push_back({s,v});

还可以使用make_pair:data.push_back(make_pair(s,v)};

列表初始化最易于编写和理解。


练习11.14:扩展你在11.2.1节练习中编写的孩子姓到名的map,添加一个pair的vector,保存孩子的名字和生日。

#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <algorithm>
#include <utility>
using std::pair;
using std::cout;
using std::endl;
using std::string;
using std::vector;
using std::map;

void add_family(map<string, vector<pair<string, string>>> &families, const string &family)
{
	families[family];
}

void add_child(map<string, vector<pair<string, string>>> &families,const string &family, const string &child, const string &birthday)
{
	families[family].push_back({child,birthday});
}

int main(int argc, char *argv[])
{
	map<string, vector<pair<string, string>>> families;
	add_family(families, "Zhang");
	add_child(families, "Zhang", "San", "1997-10-01");
	add_child(families, "Zhang", "Gang","1993-09-12");
	add_child(families, "Li", "Si","1992-03-27");
	add_family(families, "Li");
	for(auto f: families){
		cout<<f.first<<":";
		for(auto c: f.second)
			cout<<c.first<<"(birthday"<<c.second<<"),";
		cout<<endl;
	}
	return 0;
}

练习11.15:对一个int到vector<int>的map,其mapped_type,key_type,value_type分别是什么?

mapped_type是vector<int>, key_type是int,value_type是pair<const int, vector<int>>。


练习11.16:使用一个map迭代器编写一个表达式,将一个值赋予一个元素。

map<int, int> m;
auto it=m.begin();
it->second=0;

练习11.17:假定c是一个string的multiset,v是一个string的vector,解释下面的调用。指出每个调用是否合法:
copy(v.begin(), v.end(), inserter(c, c.end()));
copy(v.begin(), v.end(), back_inserter(c);
copy(c.begin(), c.end(), inserter(v, v.end()));
copy(c.begin(), c.end(), back_inserter(v);
set的迭代器是const的,因此只允许访问set中的元素,而不能改变set。与map一样,set的关键字也是const,因此不能通过迭代器来改变set重元素的值。因此,前两个调用试图将vector中的元素复制到set中,是非法的。而后两个调用将set中的元素复制到vector中,是合法的。

练习11.18:写出第382页循环中map_it的类型,不要使用auto或decltype.

pair<const string, size_t>::iterator


练习11.19:定义一个变量,通过11.2.2节中的名为bookstore的multiset的调用begin()来初始化这个变量。写出变量的类型,不要使用auto或decltype。

typedef bool (*pf) (const Sales_data &,  const Sales_data &);
multiset<Sales_data, pf> bookstore(compareIsbn);
pair<Sales_data, pf>::iterator=bookstore.begin();

练习11.20:重写11.1节练习的单词计数程序,使用inserter代替下标操作。你认为哪个程序更容易编写和阅读?解释原因。

#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <algorithm>
using std::cout;
using std::endl;
using std::ifstream;
using std::string;
using std::map;

int main(int argc, char *argv[])
{
	ifstream in(argv[1]);
	if(!in){
		cout<<"Open input file failed."<<endl;
		exit(1);
	}

	map<string, size_t> word_count;
	string word;
	while(in>>word){
		auto ret=word_count.insert({word, 1});
		if(!ret.second)
			++ret.first->second;
	}

	for(const auto &w : word_count)
		cout<<w.first<<" occurs "<<w.second<<" times."<<endl;

	return 0;
}

练习11.21:假定word_count是一个string到size_t的map,word是一个string,解释下面循环的作用:

while(cin>>word)
     ++word_count.insert({word,0}).first->second;
循环不断从标准输入读入单词(字符串),直至遇到文件结束或错误。每读入一个单词,构造pair{word,0},通过insert操作插入到word_count中。insert返回一个pair,其first成员是一个迭代器。若单词已存在容器中,它指向已有元素;否则,它指向新插入的元素。first会得到这个迭代器,指向word对应的元素。继续使用->second,可获得元素的值的引用,即单词的计数。若单词是新的,则其值为0,若已存在,则值为之前出现的次数。对其进行递增操作,即完成将出现次数加1.


练习11.2:给定一个map<string, vector<int>>,对此容器的插入一个元素的insert版本,写出其参数类型和返回类型。

参数类型是一个pair,first成员的类型是map的关键字类型string,second成员的类型是map的值类型vector<int>: pair<string, vector<int>>

返回类型也是一个pair,first成员的类型是map的迭代器,second成员的类型是布尔型:pair<map<string, vector<int>>::iterator, bool>


练习11.23:11.2.1节练习中的map以孩子的姓为关键字,保存他们的名的vector,用multimap重写此map。

#include <iostream>
#include <map>
#include <string>
#include <algorithm>
using std::cout;
using std::endl;
using std::string;
using std::multimap;

void add_child(multimap<string, string> &families, const string &family, const string &child)
{
	families.insert({family, child});
}

int main(int argc, char *argv[])
{
	multimap<string, string> families;
	add_child(families, "Zhang", "Qiang");
	add_child(families, "Zhang", "Gang");
	add_child(families, "Wang", "wu");

	for(auto f:families)
		cout<<f.first<<" "<<f.second<<endl;

	return 0;
}

练习11.24:下面的程序完成什么功能?

map<int, int> m;
m[0]=1;
若m中已有关键字0,下标操作提取出其值,赋值语句将其值置为1.

否则下标操作会创建一个pair(0,0),即关键字为0,值为0,将其插入到m中,然后提取其值,赋值语句将其置为1.


练习11.25:对比下面程序与上一题程序

vector<int> v;
v[0]=1;
若v中已有不少于一个元素,则下标操作提取出此位置的元素,赋值操作将其置为1;如v为空,则下标提取出的是一个非法左值,向其赋值可能会导致系统崩溃等严重后果。


练习11.26:可以用什么类型来对一个map进行下标操作?下标运算符返回的类型是什么?请给出一个具体的例子——即,定义一个map,然后写出一个可以用来对map进行下标操作的类型以及下标运算符将会返回的类型。
对map进行下标操作,应使用其key_type,即关键字的类型,而下标操作返回的类型是mapped_type,即关键字关联的值的类型。

例如:map类型:map<string, int> 用来进行下标操作的类型: string,下标操作返回的类型:int


练习11.27:对于什么问题你会使用count来解决?什么时候你又会选择find呢?

当希望知道容器中有多少元素与给定关键字相同时,使用count,只关心给定关键字是否在容器中时,使用find。


练习11.28:对于一个string到int的vector的map,定义并初始化一个变量来保存在其上调用find所返回的结果。

map<string, vector<int>> m;

map<string, vector<int>>::iterator iter;


练习11.29:如果给定关键字不在容器中,upper_bound、lower_bound和equal_range分别会返回什么?

若给定关键字不在容器中,则upper_bound和lower_bound返回相同的迭代器,指出关键字正确插入的位置,如果给定关键字比容器中所有关键字都大,则返回尾后迭代器。

若给定关键字不在容器中,则pair的first和second成员都指向关键字正确插入的位置,两个迭代器构成一个空范围。


练习11.30:对于本节最后一个程序的输出表达式,解释运算对象pos.first->second的含义。

equal_range返回一个pair,其first成员与lower_bound的返回结果相同,即指向容器中第一个具有给定关键值的元素。因此,对其解引用会得到一个value_type对象,即一个pair,其first为元素的关键字,即给定关键字,second为关键字关联的值。本题中,激活的给定作者的第一部著作的题目。


练习11.31:编写程序,定义一个作者及其作品的multimap。使用find在maultimap中查找一个元素并用erase删除它。确保你的程序在元素不在map中时也能正常运行。

#include <iostream>
#include <string>
#include <algorithm>
#include <map>
using std::cout;
using std::endl;
using std::string;
using std::multimap;

void remove_author(multimap<string, string> &books, const string &author)
{
	auto pos=books.equal_range(author);
	if(pos.first==pos.second)
		cout<<"No author "<<author<<endl;
	else
		books.erase(pos.first, pos.second);
}

void print_books(multimap<string, string> &books)
{
	for(auto &book: books)
		cout<<book.first<<", <"<<book.second<<">"<<endl;
	cout<<endl;
}

int main(int argc, char *argv[])
{
	multimap<string, string> books;
	books.insert({"Barth, John", "Sot-Weed Factor"});
	books.insert({"Barth, John", "Lost in the Funhouse"});
	books.insert({"Jin Yong", "Tian Long Ba Bu"});
	books.insert({"Jin Yong", "Bi Xue Jian"});
	print_books(books);
	remove_author(books, "Zhang San");
	remove_author(books, "Jin Yong");
	print_books(books);

	return 0;
}

练习11.3:实现你自己版本的单词转换程序。

#include <map>
#include <vector>
#include <iostream>
#include <fstream>
#include <string>
#include <stdexcept>
#include <sstream>
using std::map; 
using std::string; 
using std::vector;
using std::ifstream; 
using std::cout; 
using std::endl;
using std::getline; 
using std::runtime_error; 
using std::istringstream;

map<string, string> buildMap(ifstream &map_file)
{
    map<string, string> trans_map;   
    string key;    
	string value;  
		if (value.size() > 1) 
        	trans_map[key] = value.substr(1); 
		else
			throw runtime_error("no rule for " + key);
	return trans_map;
}

const string &
transform(const string &s, const map<string, string> &m)
{	
	auto map_it = m.find(s);
	if (map_it != m.cend()) 
		return map_it->second; 
    else
		return s; 
}

void word_transform(ifstream &map_file, ifstream &input)
{
	auto trans_map = buildMap(map_file); 
    cout << "Here is our transformation map: \n\n";
	for (auto entry : trans_map)
        cout << "key: "   << entry.first
             << "\tvalue: " << entry.second << endl;
    cout << "\n\n";

    string text;                    
    while (getline(input, text)) {  
        istringstream stream(text); 
        string word;
        bool firstword = true;     
        while (stream >> word) {
           if (firstword)
               firstword = false;
           else
               cout << " ";  
           
           cout << transform(word, trans_map); 
       }
        cout << endl;        
    }
}

int main(int argc, char **argv)
{
    if (argc != 3)
        throw runtime_error("wrong number of arguments");

    ifstream map_file(argv[1]); 
    if (!map_file)              
        throw runtime_error("no transformation file");
    
    ifstream input(argv[2]);    
    if (!input)                 
        throw runtime_error("no input file");

	word_transform(map_file, input);

    return 0;  
}

练习11.34:如果你将transform函数中的find替换为下标运算符,会发生什么情况?

find仅查找给定关键字在容器中是否出现,若容器中不存在给定关键字,它返回尾后迭代器。当关键字存在时,下标运算符的行为与find类似,但当关键字不存在时,它会构造一个pair,将其插入到容器中,对于单词转换程序来说,这不是我们所期望的。

练习11.35:在buildMap中如果进行如下改写,会有什么效果?

trans_form[key]=value.substr(1);

改为trans_map.insert({key, value.substr(1)})

当map中没有给定关键字时。insert和下标操作+赋值操作效果类似,都是将关键字和值的pair添加到map中。当map中已有给定关键字,下标操作会获得具有该关键字的元素的值,并将新读入的值赋予它,但insert操作遇到关键字已存在的情况,则不会改变容器内容,而使返回一个值指出茶如失败。当规则文件中存在多条规则转换相同单词时,下标+赋值的版本最终会用最后一条规则进行文本转换,而insert版本则会用第一条规则转换。


练习11.37一个无序容器与其有序版本相比有何优势?有序版本有何优势?

无序版本通常性能更好,使用也更为简单。有序版本的优势是维护了关键字的顺序。当元素的关键字类型没有明显的序关系,或是维护元素的序代价非常高时,无序容器非常有用。但当应用要求必须维护元素的序时,有序版本就是唯一的选择。

你可能感兴趣的:(C++Primer)