您的位置:首页 > 编程语言 > C语言/C++

《C++ Primer》习题参考答案:第11章 - 关联容器

2020-03-02 04:53 831 查看

欢迎关注WX公众号:【程序员管小亮】

专栏C++学习笔记

《C++ Primer》学习笔记/习题答案 总目录

——————————————————————————————————————————————————————

📚💻 Cpp-Prime5 + Cpp-Primer-Plus6 源代码和课后题

第11章 - 关联容器

练习11.1

描述

map
vector
的不同。

解:

学习关联容器,理解与顺序容器的不同,最关键的是理解其基础的数据结构,随后它所表现出来的一些性质就很自然能够理解了。

两类容器的根本差别在于,顺序容器中的元素是“顺序”存储的(链表容器中的元素虽然不是在内存中“连续”存储的,但仍然是按“顺序”存储的)。理解“顺序”的关键,是理解容器支持的操作形式以及效率。

对于

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

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

练习11.2

分别给出最适合使用

list
vector
deque
map
以及
set
的例子。

解:

  • 若元素很小(例如
    int
    ),大致数量预先可知,在程序运行过程中不会剧烈变化, 大部分情况下只在末尾添加或删除需要频繁访问任意位置的元素,则
    vector
    可带来最高的效率。
  • 若需要频繁在头部和尾部添加或删除元素,则
    deque
    是最好的选择。
  • 如果元素较大(如大的类对象),数量预先不知道,或是程序运行过程中频繁变化,对元素的访问更多是顺序访问全部或很多元素,则
    list
    很适合。
  • map
    很适合对一些对象按它们的某个特征进行访问的情形。典型的例如按学生的名字来查询学生信息,即可将学生名字作为关键字,将学生信息作为元素值,保存在
    map
    中。
  • set
    ,顾名思义,就是集合类型。当需要保存特定的值集合——通常是满足/不满足某种要求的值集合,用
    set
    最为方便。
  • 练习11.3

    编写你自己的单词计数程序。

    解:

    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <map>
    #include <fstream>          // file I/O support
    #include <cstdlib>          // support for exit()
    using namespace std;
    int main()
    {
    char filename[20];
    cout << "Enter name of data file: ";
    cin.getline(filename, 20);
    ifstream inFile;        // object for handling file input
    inFile.open(filename);  // associate inFile with a file
    if (!inFile.is_open())  // failed to open file
    {
    cout << "Could not open the file " << filename << endl;
    cout << "Program terminating.\n";
    // cin.get();    	// keep window open
    exit(EXIT_FAILURE);
    }
    map<string, size_t> word_count;
    string word;
    while (inFile >> word)
    {
    ++word_count[word];
    }
    for (const auto &w : word_count){
    cout << w.first << "出现了" << w.second << "次" << endl;
    }
    inFile.close();         // finished with the file
    
    system("pause");
    return 0;
    }


    练习11.4

    扩展你的程序,忽略大小写和标点。例如,“example.”、“example,” 和 “Example” 应该递增相同的计数器。

    解:

    编写函数

    trans
    ,将单词中的标点去掉,将大写都转换为小写。

    具体方法是:

    遍历字符串,对每个字符首先检查是否是大写(

    ASCII
    值在
    A
    Z
    之间),若是,将其转换为小写(减去
    A
    ,加上
    a
    );否则,检查它是否带标点,若是,将其删除。最终,将转换好的字符串返回。

    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <map>
    #include <fstream>          // file I/O support
    #include <cstdlib>          // support for exit()
    using namespace std;
    string &trans(string &s){
    for (int p = 0; p < s.size(); p++){
    if (s

    >= 'A' && s[p] <= 'Z') s[p] -= ('A' - 'a'); else if (s[p] == ',' || s[p] == '.') s.erase(p, 1); } return s; } int main() { char filename[20]; cout << "Enter name of data file: "; cin.getline(filename, 20); ifstream inFile; // object for handling file input inFile.open(filename); // associate inFile with a file if (!inFile.is_open()) // failed to open file { cout << "Could not open the file " << filename << endl; cout << "Program terminating.\n"; // cin.get(); // keep window open exit(EXIT_FAILURE); } map<string, size_t> word_count; string word; while (inFile >> word) { ++word_count[trans(word)]; } for (const auto &w : word_count){ cout << w.first << "出现了" << w.second << "次" << endl; } inFile.close(); // finished with the file system("pause"); return 0; }

    [p]

    练习11.5

    解释

    map
    set
    的区别。你如何选择使用哪个?

    解:

    • 当需要查找给定值所对应的数据时,应使用

      map
      ,其中保存的是
      <
      关键字,值对,按关键字访问值。

    • 如果只需判定给定值是否存在时,应使用

      set
      ,它是简单的值的集合。

    练习11.6

    解释

    set
    list
    的区别。你如何选择使用哪个?

    解:

    两者都可以保存元素集合。

    • 如果只需要顺序访问这些元素,或是按位置访问元素,那么应使用
      list
    • 如果需要快速判定是否有元素等于给定值,则应使用
      set

    练习11.7

    定义一个

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

    解:

    map
    的关键字类型是
    string
    ,值类型是
    vector<string>

    我们定义函数

    add family
    添加一个家庭,注意,必须先检查是否已有这个家庭,若不做这个检查,则可能将已有家庭的孩子名字清空(如
    main
    函数中的王姓家庭的添加顺序)。若确实还没有这个家庭,则创建一个空的
    vector<string>
    ,表示这个家庭的孩子名字列表。

    函数

    add_child
    向一个已有家庭添加孩子的名字:首先用
    []
    运算符取出该家庭的
    vector
    ,然后调用
    push back
    将名字追加到
    vector
    末尾。

    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <map>
    #include <vector>
    using namespace std;
    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(){
    map<string, vector<string>>families;
    add_family(families, "张");
    add_child(families, "张", "强");
    add_child(families, "张", "刚");
    add_child(families, "王", "五");
    add_family(families, "王");
    for (auto f : families){
    cout << f.first << "家的孩子:";
    for (auto c : f.second)
    cout << c << " ";
    cout << endl;
    }
    system("pause");
    return 0;
    }

    练习11.8

    编写一个程序,在一个

    vector
    而不是一个
    set
    中保存不重复的单词。使用
    set
    的优点是什么?

    解:

    使用

    vector
    保存不重复单词,需要用
    find
    查找新读入的单词是否已在
    vector
    中,若不在(返回尾后迭代器),才将单词加入
    vector
    。而使用
    set
    ,检查是否重复的工作是由
    set
    模板负责的,程序员无须编写对应代码,程序简洁很多。

    更深层次的差别,

    vector
    是无序线性表,
    find
    查找指定值只能采用顺序查找方式,所花费的时间与
    vector.size()
    呈线性关系。而
    set
    是用红黑树实现的,花费的时间与
    vector.size()
    呈对数关系。当单词数量已经非常多时,
    set
    的性能优势是巨大的。当然,
    vector
    也不是毫无用处。它可以保持单词的输入顺序,而
    set
    则不能,遍历
    set
    ,元素是按值的升序被遍历的。

    vector
    版本:

    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <vector>
    #include <fstream>          // file I/O support
    #include <cstdlib>          // support for exit()
    using namespace std;
    string &trans(string &s){
    for (int p = 0; p < s.size(); p++){
    if (s

    >= 'A' && s[p] <= 'Z') s[p] -= ('A' - 'a'); else if (s[p] == ',' || s[p] == '.') s.erase(p, 1); } return s; } int main() { char filename[20]; cout << "Enter name of data file: "; cin.getline(filename, 20); ifstream inFile; // object for handling file input inFile.open(filename); // associate inFile with a file if (!inFile.is_open()) // failed to open file { cout << "Could not open the file " << filename << endl; cout << "Program terminating.\n"; // cin.get(); // keep window open exit(EXIT_FAILURE); } vector<string> unique_word; string word; while (inFile >> 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 << endl; } cout << endl; inFile.close(); // finished with the file system("pause"); return 0; }

    [p]
    set
    版本:

    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <set>
    #include <fstream>          // file I/O support
    #include <cstdlib>          // support for exit()
    using namespace std;
    string &trans(string &s){
    for (int p = 0; p < s.size(); p++){
    if (s

    >= 'A' && s[p] <= 'Z') s[p] -= ('A' - 'a'); else if (s[p] == ',' || s[p] == '.') s.erase(p, 1); } return s; } int main() { char filename[20]; cout << "Enter name of data file: "; cin.getline(filename, 20); ifstream inFile; // object for handling file input inFile.open(filename); // associate inFile with a file if (!inFile.is_open()) // failed to open file { cout << "Could not open the file " << filename << endl; cout << "Program terminating.\n"; // cin.get(); // keep window open exit(EXIT_FAILURE); } set<string> unique_word; string word; while (inFile >> word) { trans(word); if (find(unique_word.begin(), unique_word.end(), word) == unique_word.end()) unique_word.insert(word); } for (const auto &w : unique_word){ cout << w << endl; } cout << endl; inFile.close(); // finished with the file system("pause"); return 0; }

    [p]

    练习11.9

    定义一个

    map
    ,将单词与一个行号的
    list
    关联,
    list
    中保存的是单词所出现的行号。

    解:

    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <set>
    #include <map>
    #include <list>
    #include <sstream>
    #include <fstream>          // file I/O support
    #include <cstdlib>          // support for exit()
    using namespace std;
    string &trans(string &s){
    for (int p = 0; p < s.size(); p++){
    if (s

    >= 'A' && s[p] <= 'Z') s[p] -= ('A' - 'a'); else if (s[p] == ',' || s[p] == '.') s.erase(p, 1); } return s; } int main() { char filename[20]; cout << "Enter name of data file: "; cin.getline(filename, 20); ifstream inFile; // object for handling file input inFile.open(filename); // associate inFile with a file if (!inFile.is_open()) // failed to open file { cout << "Could not open the file " << filename << endl; cout << "Program terminating.\n"; // cin.get(); // keep window open exit(EXIT_FAILURE); } map<string, list<int>> word_lineno; string line; string word; int lineno = 0; while (getline(inFile,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 << "所在行"; for (const auto &i : w.second) cout << i << " "; cout << endl; } inFile.close(); // finished with the file system("pause"); return 0; }

    [p]

    练习11.10

    可以定义一个

    vector<int>::iterator
    int
    map
    吗?
    list<int>::iterator
    int
    map
    呢?对于两种情况,如果不能,解释为什么。

    解:

    由于有序容器要求关键字类型必须支持比较操作

    <

    • 因此
      map<vector<int>::iterator, int>ml;
      是可以的,因为
      vector
      的迭代器支持比较操作。
    • map<list<int>::iterator, int>m2;
      是不行的,因为
      list
      的元素不是连续存储,其迭代器不支持比较操作。

    练习11.11

    不使用

    decltype
    重新定义
    bookstore

    解:

    首先用

    typedef
    定义与
    compareIsbn
    相容的函数指针类型,然后用此类型声明
    multiset
    即可。

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

    练习11.12

    编写程序,读入

    string
    int
    的序列,将每个
    string
    int
    存入一个
    pair
    中,
    pair
    保存在一个
    vector
    中。

    解:

    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <utility>
    #include <vector>
    #include <fstream>          // file I/O support
    #include <cstdlib>          // support for exit()
    using namespace std;
    int main()
    {
    char filename[20];
    cout << "Enter name of data file: ";
    cin.getline(filename, 20);
    ifstream inFile;        // object for handling file input
    inFile.open(filename);  // associate inFile with a file
    if (!inFile.is_open())  // failed to open file
    {
    cout << "Could not open the file " << filename << endl;
    cout << "Program terminating.\n";
    // cin.get();    	// keep window open
    exit(EXIT_FAILURE);
    }
    vector<pair<string, int>> data;
    string s;
    int v;
    while (inFile >> s && inFile >> v)
    {
    data.push_back(pair<string, int>(s, v));
    }
    for (const auto &d : data){
    cout << d.first << " " << d.second << endl;
    }
    inFile.close();         // finished with the file
    
    system("pause");
    return 0;
    }

    练习11.13

    在上一题的程序中,至少有三种创建

    pair
    的方法。编写此程序的三个版本,分别采用不同的方法创建
    pair
    。解释你认为哪种形式最易于编写和理解,为什么?

    解:

    vec.push_back(make_pair(str, i));
    vec.push_back({ str, i });
    vec.push_back(pair<string, int>(str, i));

    使用花括号的初始化器最易于理解和编写。

    练习11.14

    扩展你在11.2.1节练习中编写的孩子姓达到名的

    map
    ,添加一个
    pair
    vector
    ,保存孩子的名和生日。

    解:

    在本题中,我们将家庭的姓映射到孩子信息的列表,而不是简单的孩子名字的列表。因此,将在

    vector
    中的元素类型声明为
    pair<string, string>
    ,两个
    string
    分别表示孩子的名字和生日。在添加孩子信息时,用列表初始化创建名字和生日的
    pair
    ,添加到
    vector
    中即可。

    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <map>
    #include <vector>
    using namespace std;
    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(){
    map<string, vector<pair<string, string>>> families;
    add_family(families, "张");
    add_child(families, "张", "强", "1970-1-1");
    add_child(families, "张", "刚", "1980-1-1");
    add_child(families, "王", "五", "1990-1-1");
    add_family(families, "王");
    for (auto f : families){
    cout << f.first << "家的孩子:";
    for (auto c : f.second)
    cout << c.first << "(生日" << c.second << "),";
    cout << endl;
    }
    system("pause");
    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
    迭代器编写一个表达式,将一个值赋予一个元素。

    解:

    解引用关联容器的迭代器,得到的是

    value_type
    的值的引用。因此对
    map
    而言,得到的是一个
    pair
    类型的引用,其
    first
    成员保存
    const
    的关键字,
    second
    成员保存值。因此,通过迭代器只能修改值,而不能改变关键字。

    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<const Sales_data, pf>::iterator it = bookstore.begin();
    

    练习11.20

    重写11.1节练习的单词计数程序,使用

    insert
    代替下标操作。你认为哪个程序更容易编写和阅读?解释原因。

    解:

    • 使用

      insert
      操作的方式是:构造一个
      pair
      (单词,1),用
      insert
      将其插入容器,返回一个
      pair
      。若单词已存在,则返回
      pair
      second
      成员为
      false
      ,表示插入失败,程序员还需通过返回
      pair
      first
      成员(迭代器)递增已有单词的计数器。判断单词是否已存在,并进行相应操作的工作完全是由程序员负责的。

    • 使用下标操作的方式是:以单词作为下标获取元素值,若单词已存在,则提取出已有元素的值;否则,下标操作将

      pair
      (单词,0)插入容器,提取出新元素的值。单词是否已存在的相关处理完全是由下标操作处理的,程序员不必关心,直接访问提取出的值就行了。

    显然,对于单词计数问题来说,下标操作更简洁易读。

    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <map>
    #include <fstream>          // file I/O support
    #include <cstdlib>          // support for exit()
    using namespace std;
    int main()
    {
    char filename[20];
    cout << "Enter name of data file: ";
    cin.getline(filename, 20);
    ifstream inFile;        // object for handling file input
    inFile.open(filename);  // associate inFile with a file
    if (!inFile.is_open())  // failed to open file
    {
    cout << "Could not open the file " << filename << endl;
    cout << "Program terminating.\n";
    // cin.get();    	// keep window open
    exit(EXIT_FAILURE);
    }
    map<string, size_t> word_count;
    string word;
    while (inFile >> word)
    {
    auto ret = word_count.insert({ word, 1 });
    if (!ret.second)
    ++ret.first->second;
    }
    for (const auto &w : word_count){
    cout << w.first << "出现了" << w.second << "次" << endl;
    }
    inFile.close();         // finished with the file
    
    system("pause");
    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.22

    给定一个

    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 <string>
    #include <algorithm>
    #include <map>
    using namespace std;
    void add_child(multimap<string, string> &families,
    const string &family, const string &child){
    families.insert({ family, child });
    }
    int main(){
    multimap<string, string> families;
    add_child(families, "张", "强");
    add_child(families, "张", "刚");
    add_child(families, "王", "五");
    for (auto f : families){
    cout << f.first << "家的孩子:" << f.second << endl;
    }
    system("pause");
    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;

    解:

    对于

    m
    ,"0”表示“关键字0”。而对于
    v
    ,“0”表示“位置0”。

    v
    中已有不少于一个元素,即,存在“位置0”元素,则下标操作提取出此位置的元素(左值),赋值操作将其置为1。而
    map
    的元素是
    pair
    类型,下标提取的不是元素,而是元素的第二个成员,即元素的值。

    v
    尚为空,则下标提取出的是一个非法左值(下标操作不做范围检查),向其赋值可能导致系统崩溃等严重后果。

    练习11.26

    可以用什么类型来对一个

    map
    进行下标操作?下标运算符返回的类型是什么?请给出一个具体例子——即,定义一个
    map
    ,然后写出一个可以用来对
    map
    进行下标操作的类型以及下标运算符将会返会的类型。

    解:

    map
    进行下标操作,应使用其
    key_type
    ,即关键字的类型。

    而下标操作返回的类型是

    mapped_type
    ,即关键字关联的值的类型。

    示例如下:

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

    练习11.27

    对于什么问题你会使用

    count
    来解决?什么时候你又会选择
    find
    呢?

    解:

    find
    查找关键字在容器中出现的位置,而
    count
    则还会统计关键字出现的次数。因此,当我们希望知道(允许重复关键字的)容器中有多少元素的关键字与给定关键字相同时,使用
    count

    当我们只关心关键字是否在容器中时,使用

    find
    就足够了。特别是,对于不允许重复关键字的关联容器,
    find
    count
    的效果没有什么区别,使用
    find
    就可以了。或者,当我们需要获取具有给定关键字的元素(而不只是统计个数)时,也需要使用
    find

    find
    和下标操作有一个重要区别,当给定关键字不在容器中时,下标操作会插入一个具有该关键字的元素。因此,当我们想检查给定关键字是否存在时,应该用
    find
    而不是下标操作。

    练习11.28

    对一个

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

    解:

    find
    返回一个迭代器,指向具有给定关键字的元素(若不存在则返回尾后迭代器),因此其返回类型是容器的迭代器。

    // map类型
    map<string, vector<int>> m;
    // 保存find返回结果的变量
    map<string, vector<int>>::iterator iter;

    练习11.29

    如果给定的关键字不在容器中,

    upper_bound
    lower_bound
    equal_range
    分别会返回什么?

    解:

    lower_bound
    返回第一个具有给定关键字的元素,
    upper_bound
    则返回最后一个具有给定关键字的元素之后的位置。即,这两个迭代器构成包含所有具有给定关键字的元素的范围。若给定关键字不在容器中,两个操作显然应构成一个空范围,它们返回相当的迭代器,指出关键字的正确插入位置——不影响关键字的排序。如果给定关键字比容器中所有关键字都大,则此位置是容器的尾后位置
    end

    equal_range
    返回一个
    pair
    ,其
    first
    成员等价于
    lower_bound
    返回的迭代器,
    second
    成员等价于
    upper_bound
    返回的迭代器。因此,若给定关键字不在容器中,
    first
    second
    都指向关键字的正确插入位置,两个迭代器构成一个空范围。

    练习11.30

    对于本节最后一个程序中的输出表达式,解释运算对象

    pos.first->second
    的含义。

    解:

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

    练习11.31

    编写程序,定义一个作者及其作品的

    multimap
    。使用
    find
    multimap
    中查找一个元素并用
    erase
    删除它。确保你的程序在元素不在
    map
    中时也能正常运行。

    解:

    将数据插入

    multimap
    ,需使用
    insert
    操作。

    multimap
    中查找具有给定关键字的元素,有几种方法:

    • 使用
      find
      只能查找第一个具有给定关键字的元素,要找到所有具有给定关键字的元素,需编写循环;
    • lower_bound
      upper_bound
      配合使用,可找到具有给定关键字的元素的范围;
    • equal_range
      最为简单,一次即可获得要查找的元素范围。将找到的范围传递给
      erase
      ,即可删除指定作者的所有著作。

    为了解决元素不在

    multimap
    中的情况,首先检查
    equal_range
    返回的两个迭代器,若相等(空范围),则什么也不做。范围不为空时,才将迭代器传递给
    erase
    ,删除所有元素。

    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <map>
    using namespace std;
    void remove_author(multimap<string, string> &books,
    const string &author){
    auto pos = books.equal_range(author);
    if (pos.first == pos.second)
    cout << "没有" << author << "这个作者" << endl << endl;
    else
    books.erase(pos.first, pos.second);
    }
    void print_books(multimap<string, string> &books){
    cout << "当前书目包括:" << endl;
    for (auto &book : books)
    cout << book.first << ",《" << book.second << "》" << endl;
    cout << endl;
    }
    int main(){
    multimap<string, string> books;
    books.insert({ "Barth, John", "Sot-Weed Factor" });
    books.insert({ "Barth, John", "Lost in the Funhouse" });
    books.insert({ "金庸", "射雕英雄传" });
    books.insert({ "金庸", "天龙八部" });
    print_books(books);
    remove_author(books, "张三");
    remove_author(books, "Barth, John");
    print_books(books);
    system("pause");
    return 0;
    }

    练习11.32

    使用上一题定义的

    multimap
    编写一个程序,按字典序打印作者列表和他们的作品。

    解:

    multimap
    的数据结构是红黑树,它维护了元素的关键字的默认序。例如,对字符串关键字(作者),红黑树会维护它们的字典序。当我们遍历
    multimap
    (如遍历
    [begin(), end())
    ,或更简单地使用范围
    for
    )时,就是按关键字的字典序来访问元素。

    因此,上一题的

    print_books
    实际上已经实现了按字典序打印作者列表和他们的作品。

    但是,当我们要求的不是关键字的默认序(运算符

    <
    定义的顺序)时,就要复杂一些。由于
    sort
    算法要求给定的两个迭代器是随机迭代器,关联容器的迭代器不符合这一要求,所以不能直接对其使用
    sort
    算法。其实这不难理解,关联容器的根本特征就是维护了关键字的默认序,从而实现了按关键字的插入、删除和查找。是不可能通过
    sort
    使其内部元素呈现出另外一种顺序的。只有本身不关心元素值的顺序容器,才可能随意安排元素顺序(位置)。我们可以在定义
    multimap
    时使用自己定义的比较操作所定义的关键字的序,而不是使用
    <
    定义的序,但这只是令
    multimap
    以另外一种序来维护关键字,仍然不可能在使用
    multimap
    的过程中来改变关键字顺序。为此,我们只能将
    multimap
    中的元素拷贝到一个顺序容器(如
    vector
    )中,对顺序容器执行
    sort
    算法,来获得关键字的其他序。

    练习11.33

    实现你自己版本的单词转换程序。

    解:

    #include <map>
    #include <vector>
    #include <iostream>
    #include <fstream>
    #include <string>
    #include <stdexcept>
    #include <sstream>
    using namespace std;
    map<string, string> buildMap(ifstream &map_file)
    {
    map<string, string> trans_map;   // holds the transformations
    string key;    // a word to transform
    string value;  // phrase to use instead
    // read the first word into key and the rest of the line into value
    while (map_file >> key && getline(map_file, value))
    if (value.size() > 1) // check that there is a transformation
    trans_map[key] = value.substr(1); // skip leading space
    else
    throw runtime_error("no rule for " + key);
    return trans_map;
    }
    
    const string &
    transform(const string &s, const map<string, string> &m)
    {
    // the actual map work; this part is the heart of the program
    auto map_it = m.find(s);
    // if this word is in the transformation map
    if (map_it != m.cend())
    return map_it->second; // use the replacement word
    else
    return s;              // otherwise return the original unchanged
    }
    
    // first argument is the transformations file;
    // second is file to transform
    void word_transform(ifstream &map_file, ifstream &input)
    {
    auto trans_map = buildMap(map_file); // store the transformations
    
    // for debugging purposes print the map after its built
    cout << "Here is our transformation map: \n\n";
    for (auto entry : trans_map)
    cout << "key: " << entry.first
    << "\tvalue: " << entry.second << endl;
    cout << "\n\n";
    
    // do the transformation of the given text
    string text;                    // hold each line from the input
    while (getline(input, text)) {  // read a line of input
    istringstream stream(text); // read each word
    string word;
    bool firstword = true;      // controls whether a space is printed
    while (stream >> word) {
    if (firstword)
    firstword = false;
    else
    cout << " ";  // print a space between words
    // transform returns its first argument or its transformation
    cout << transform(word, trans_map); // print the output
    }
    cout << endl;        // done with this line of input
    }
    }

    练习11.34

    如果你将

    transform
    函数中的
    find
    替换为下标运算符,会发生什么情况?

    解:

    如前所述,

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

    练习11.35

    buildMap
    中,如果进行如下改写,会有什么效果?

    trans_map[key] = value.substr(1);
    //改为
    trans_map.insert({key, value.substr(1)});

    解:

    map
    中没有给定关键字时,
    insert
    操作与
    下标操作+赋值
    操作的效果类似,都是将关键字和值的
    pair
    添加到
    map
    中。

    但当

    map
    中已有给定关键字,也就是新的转换规则与一条已有规则要转换同一个单词时,两者的行为是不同的。

    • 下标操作会获得具有该关键字的元素(也就是已有规则)的值,并将新读入的值赋予它,也就是用新读入的规则覆盖了容器中的已有规则。
    • insert
      操作遇到关键字已存在的情况,则不会改变容器内容,而是返回一个值指出插入失败。

    因此,当规则文件中存在多条规则转换相同单词时,

    下标+赋值
    的版本最终会用最后一条规则进行文本转换,而
    insert
    版本则会用第一条规则进行文本转换。

    练习11.36

    我们的程序并没检查输入文件的合法性。特别是,它假定转换规则文件中的规则都是有意义的。如果文件中的某一行包含一个关键字、一个空格,然后就结束了,会发生什么?预测程序的行为并进行验证,再与你的程序进行比较。

    解:

    此题有误,书中程序已经处理了这种情况。

    buildMap
    函数中,当循环中读入要转换的单词和转换的内容后,会检查是否存在转换的内容(
    value.size()>1
    ),若不存在,则抛出一个异常。

    练习11.37

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

    解:

    无序版本通常性能更好,使用也更为简单。有序版本的优势是维护了关键字的序。

    当元素的关键字类型没有明显的序关系,或是维护元素的序代价非常高时,无序容器非常有用。但当应用要求必须维护元素的序时,有序版本就是唯一的选择。

    练习11.38

    unordered_map
    重写单词计数程序和单词转换程序。

    解:

    对单词计数程序仅有的两处修改是将包含的头文件

    map
    改为
    unordered_map
    ,以及将
    word_count
    的类型由
    map
    改为
    unordered map

    尝试编译、运行此程序,你会发现,由于无序容器不维护元素的序,程序的输出结果与第3题的输出结果的顺序是不同的。

    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <map>
    #include <fstream>          // file I/O support
    #include <cstdlib>          // support for exit()
    using namespace std;
    int main()
    {
    char filename[20];
    cout << "Enter name of data file: ";
    cin.getline(filename, 20);
    ifstream inFile;        // object for handling file input
    inFile.open(filename);  // associate inFile with a file
    if (!inFile.is_open())  // failed to open file
    {
    cout << "Could not open the file " << filename << endl;
    cout << "Program terminating.\n";
    // cin.get();    	// keep window open
    exit(EXIT_FAILURE);
    }
    map<string, size_t> word_count;
    string word;
    while (inFile >> word)
    {
    ++word_count[word];
    }
    for (const auto &w : word_count){
    cout << w.first << "出现了" << w.second << "次" << endl;
    }
    inFile.close();         // finished with the file
    
    system("pause");
    return 0;
    }


    单词转换程序的修改类似。由于程序中不再有元素内容的顺序输出,因此输出结果与有序版本没有什么不同。

    #include <unordered_map>
    #include <vector>
    #include <iostream>
    #include <fstream>
    #include <string>
    #include <stdexcept>
    #include <sstream>
    using namespace std;
    unordered_map<string, string> buildMap(ifstream &map_file)
    {
    unordered_map<string, string> trans_map;   // holds the transformations
    string key;    // a word to transform
    string value;  // phrase to use instead
    // read the first word into key and the rest of the line into value
    while (map_file >> key && getline(map_file, value))
    if (value.size() > 1) // check that there is a transformation
    trans_map[key] = value.substr(1); // skip leading space
    else
    throw runtime_error("no rule for " + key);
    return trans_map;
    }
    
    const string &
    transform(const string &s, const unordered_map<string, string> &m)
    {
    // the actual map work; this part is the heart of the program
    auto map_it = m.find(s);
    // if this word is in the transformation map
    if (map_it != m.cend())
    return map_it->second; // use the replacement word
    else
    return s;              // otherwise return the original unchanged
    }
    
    // first argument is the transformations file;
    // second is file to transform
    void word_transform(ifstream &map_file, ifstream &input)
    {
    auto trans_map = buildMap(map_file); // store the transformations
    
    // for debugging purposes print the map after its built
    cout << "Here is our transformation map: \n\n";
    for (auto entry : trans_map)
    cout << "key: " << entry.first
    << "\tvalue: " << entry.second << endl;
    cout << "\n\n";
    
    // do the transformation of the given text
    string text;                    // hold each line from the input
    while (getline(input, text)) {  // read a line of input
    istringstream stream(text); // read each word
    string word;
    bool firstword = true;      // controls whether a space is printed
    while (stream >> word) {
    if (firstword)
    firstword = false;
    else
    cout << " ";  // print a space between words
    // transform returns its first argument or its transformation
    cout << transform(word, trans_map); // print the output
    }
    cout << endl;        // done with this line of input
    }
    }
    • 点赞 1
    • 收藏
    • 分享
    • 文章举报
    我是管小亮 博客专家 发布了216 篇原创文章 · 获赞 4537 · 访问量 65万+ 他的留言板 关注
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: