习题11.1:描述map和vector的不同。
答:(标准)
两种容器的根本差别是:
顺序容器中的元素是按“顺序”存储的。对于vector这样的顺序容器,每个元素有唯一对应的位置编号。所有的操作都是按位置(编号)进行的。
而map这种关联容器,是为了高效实现“按值访问元素”这类操作而设计的。容器中的元素是按关键字值来存储的,关键字值和元素数据建立起对应关系。底层数据结构是红黑树,哈希表等,可高效地实现按关键字查找 添加 删除元素等操作。
习题11.2:分别找出最适合使用list vector deque map 和 set 的例子。
(标准)
答:
若元素很小,大致数量预先可知,在程序运行过程中不会剧烈变化,大部分情况下只在末尾添加 删除元素,需要频繁访问任意位置的元素,则vector可带来最高的效率。若需要在头部和尾部频繁地删除和添加元素,则deque是最好的选择。
如果元素较大,数量预先不知道,或是程序运行过程中频繁变化,对元素的访问更多是顺序访问全部或者很多元素,则list很适合。
map很适合对一些对象按他们的某个特征进行访问的情形。典型的例如按学生的名字来查询学生信息,即可将学生名字作为关键字,将学生信息作为元素值,保存在map中。
set就是集合类型。当需要保存特定的值的集合——通常是满足或不满足某种要求的集合,用set最为方便。
习题11.3:编写你自己的单词计数程序。
#include
#include
#include
#include
#include
#include
当需要查找给定值对应的数据时,应使用map
如果只需判定给定值是否存在时,应使用set
练习11.6:解释set和list的差别。
如果只需要顺序访问这些元素,或者是按位置访问这些元素,使用list
如果需要快速判定是否有元素等于给定 值,则应该使用set
练习11.7:定义一个map,关键字是家庭的姓,值是一个vector,保存家庭中孩子(们)的名。编写代码,实现向新的家庭以及向已有家庭添加新的孩子。
#include
#include
练习11.8:编写一个程序,在一个vector而不是一个set中保存不重复的单词。使用set的优点是什么?
#include
#include
#include
#include
using namespace std;
int main()
{
vector svec;
string word;
ifstream fin;
fin.open( "T10_9_data.txt" );
if( !fin ){
cerr << "打开文件失败!";
return -1;
}
while( fin >> word ){
auto iter = find( svec.cbegin(), svec.cend(), word );
if( iter == svec.cend() )//word不在vector中
svec.push_back( word );
else
continue;
}
fin.close();
sort( svec.begin(), svec.end() );
cout << "容器中的元素为:" << endl;
for( const auto &s : svec )
cout << s << " ";
cout << endl;
return 0;
}
#include
#include
#include
using namespace std;
int main()
{
set uni_gather;
string word;
ifstream fin;
fin.open( "T10_9_data.txt" );
if( !fin ){
cerr << "打开文件失败!";
return -1;
}
while( fin >> word )
uni_gather.insert( word );
fin.close();
cout << "容器中的元素为:" << endl;
for( const auto &s : uni_gather )
cout << s << " ";
cout << endl;
return 0;
}
而set是红黑树实现的,花费时间与size的大小成对数关系。当单词数量非常多,set的优势尤其明显。
练习11.9:定义一个map,将单词与一个行号的list关联,list中保存的是单词所出现的行号。
#include
#include
#include
#include
答:
由于有序容器要求关键字类型必须支持比较操作 < ,因此:
前者可以,后者不行。
练习11.11:不适用decltype重新定义bookstore 。
我想到了可以使用类型别名。
typedef bool (*pf) ( const Sales_data &lhs, const Sales_data &rhs );
multiset
练习11.12:编写程序,读入string和int的序列,将每个string和int存入一个pair中,pair保存在一个vector中。
略了。
练习11.13:上一题的程序中,至少有三种创建pair的方法。编写此程序的三个版本分别采用不同的方法创建pair。解释你认为哪种形式更易于编写和理解?为什么?
列表初始化的方式更简洁和容易理解。
#include
#include
mapped_type 是 vector
key_type是int
value_type是pair
练习11.16:使用一个map迭代器编写一个表达式,将一个值赋予一个元素。
略了。
值得注意的是不能修改关键字的值,只能修改其对应的值。
练习11.17:假定c是一个string的multiset,v是一个string的vector,解释下面的调用。指出每个调用是否合法。
set的迭代器是const的。因此只允许访问set中的元素,而不能改变set。
所以前两个调用非法。
后两个调用合法。
练习11.18:写出第382页循环中map_it的类型,不要使用auto或decltype 。
map
练习11.19:定义一个变量,通过对11.2.2节中的名为bookstore的multiset调用begin()来初始化这个变量。写出变量的类型,不要使用auto或decltype。
typedef bool (*pf) ( const Sales_data &, const Sales_data & );
multiset
multiset
练习11.20:重写11.1节练习的单词计数程序,使用insert代替下标操作。你认为哪个程序更容易编写和阅读?解释原因。
#include
#include
#include
练习11.21:假定word_count是一个string 到 size_t 的map ,word是一个string , 解释下面循环的作用。
while( cin >> word )
++word_count.insert( { word, 0 } ).first->second;
对每个从cin读入的word进行操作,先向word_count插入关键字word及对应的值0;然后返回迭代器,通过迭代器的first成员指向关键字word的元素,解引用迭代器,对给定关键字的值进行前置递增。
练习11.22:给定一个map
参数类型 pair
返回类型pair
练习11.23: 11.2.1节练习中的map以孩子的姓为关键字,保存它们的名的vector,用multimap重写此map。
#include
#include
map
m[0] = 1;
先创建一个关键字为0,值为0的pair类元素。然后将该关键字的对应的值赋值为1。
练习11.25:对比下面程序与上一题的程序。
vector
v[0] = 1;
v是一个空的容器,用下标访问它的非法的位置0的元素会引发错误造成系统崩溃。
练习11.26:可以用什么类型来对一个map进行下标操作?下标运算符返回的类型是什么?请给出一个例子——即,定义一个map,然后写出一个可以用来对map进行下标操作的类型以及下标运算符将会返回的类型。
PS:通常,解引用一个迭代器所返回的类型与下标运算符返回的类型是一样的。但对map则不然。
解引用一个迭代器会得到一个value_type对象,而下标运算符会得到一个mapped_type对象。
而对map进行下标操作,应使用value_type类型。
练习11.27:对于什么问题你会使用count来解决?什么时候是又会选择find呢?
find查找关键字在容器中出现的位置,而count则还会统计关键字出现的次数。
当我们只关心关键字是否在容器中时,使用find就足够了。特别是不允许重复关键字的关联容器,find和count的效果没什么区别,使用find即可。
练习11.28:对一个string到 int的vector 的map,定义并初始化一个变量来保存在其上调用find所返回的结果。
find返回一个迭代器。
变量的定义及初始化:
map
练习11.29:如果给定的关键字不在容器中,upper_bound、lower_bound和equal_range分别会返回什么?
uppper_bound返回一个迭代器,指向第一个大于给定的关键字的元素。若关键字不在容器中,返回尾后迭代器。
lower_bound返回一个迭代器,指向第一个不小于给定关键字的元素(第一个安全插入点)。若不在容器中,返回尾后迭代器。
equal_range返回一个迭代器pair,表示给定关键字的范围。若不存在给定关键字,则pair的两个成员都等于尾后迭代器。
练习11.30:对于本节的最后一个程序中的输出表达式,解释运算对象pos.first->second的含义。
pos是一个迭代器的pair,pos.first是一个指向第一个具有给定关键字的元素的迭代器。
对其使用->second,解引用得到一个pair,再访问它的second的成员。
按照题意:
pos.first得到一个指向给定作者名字的pair。
->解引用得到该pair,它的first成员是作者的名字,second成员是作者的著作名字。
练习11.31:编写程序,定义一个作者及其作品的multimap。使用find在multimap中查找一个元素并erase删除它。确保你的程序在元素不在map中时也能正常运行。
#include
#include
在multimap中,作者的名字本来就已经默认按照字典顺序排序。
难点是:使得相同的作者(相同关键值)的作品名字也按字典顺序输出。也就是在插入进关联容器时,就已经按关键字及其键值排好序。
如果是map还好办,使用map
但是现在是要使用multimap
我的思路是:自定义这个multimap的比较函数,但是遇到困难:这个函数应该是接受两个const string&,也就是multimap 的两个元素各自的关键字。那么我如何通过这个两个关键字,来比较这两个关键字映射的值呢?这里没想出来。
另一个想法是:照常向multimap添加元素。添加完后,遍历每个元素,对每个相同关键字对应的值存储到一个vector中进行sort排序再输出。这种方法不难实现但是有点舍近求远。个人不是很喜欢这种做法。
这题先搁置在这儿吧,如果有大神可以跟我说说你们的想法吗?
练习11.33:实现你自己版本的单词转换程序。
先把要求写在下面捋捋思路:
功能:给定一个string,将它转换成另一个string。
输入两个文件。第一个文件是:转换规则。第二个文件:用来转换的文本。
转换规则格式:第一个单词是被替换的string,这一行后面的所有单词替换第一个单词。
为了简单起见,我的转换规则和待转换文本和课本的相同。
#include
#include
如果关键字不在关联容器中,下标运算符会创建一个pair(进行值初始化),将其插入到容器中。对于单词转换程序,这可能会导致转换规则的改变,发生不在期望之内的错误转换。
练习11.35:在buildMap中,如果进行如下改写,会有什么效果?
trans_map[ key ] = value.substr( 1 );
改为 trans_map.insert( { key, value.substr( 1 ) } );
(1)第一种情况,
如果key在容器中,会将key原来对应的值改变为value.substr(1);
如果key不在容器中,则会构造一个关键字为key,值为默认初始化的pair插入到容器中,然后再将key对应的值改变为value.substr(1)。
(2)改写后的第二种情况:
如果key不在容器中,会构造一个关键字为key,值为value.substr(1) 的pair插入到容器中。
但如果key已经在容器中,insert则会什么都不做。
练习11.36:我们的程序并没有检查输入文件的合法性。特别是,它假定转换规则文件中的规则都是有意义的。如果文件中的某一行包含一个关键字、一个空格,然后就结束了,会发生什么?预测程序的行为并进行验证,再与你的程序进行比较。
书中的程序已经检查了输入文件的合法性。
如果出现上述的文件,会抛出一个异常。throw runtime_error( "no rule for " + key );
练习11.37:一个无序容器与其有序版本相比有何优势?有序版本有何优势?
(标准)
答:
无序版本通常性能更好,使用也更为简单。有序版本的优势是维护了关键字的序。
当元素的关键字类型没有明显的序关系,或是维护元素的序的代价非常高时,无序容器非常有用。
但当应用要求必须维护元素的序时,有序版本就是唯一的选择。
练习11.38:用unordered_map重写单词计数程序和单词转换程序。
两题都是直接把对应的map改成unoredered_map即可。
代码都不用贴了感觉。