CH11 关联容器

Posted Emma_U

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CH11 关联容器相关的知识,希望对你有一定的参考价值。

关联容器与顺序容器有着根本的不同:关联容器中的元素是按关键字来保存和访问的,而顺序容器是按它们在容器中的位置来顺序保存和访问的。两个主要的关联容器:map和set

map 中的元素的是一个key-value对:关键字是用来索引值关联的数据。set:每个关键字值包含一个关键字。

关联容器类型:

map 保存key-value  map<key,value>

set 保存关键字key set<key>

multimap 关键字可以重复出现的map

multiset 关键字可以重复出现的set

unordered_map 用哈希函数组织的map

unordered_set 用哈希函数组织的set

unordered_mulitimap 

unordered_multiset 

使用map

一个经典的使用关联数组的例子是单词计数程序:

 

map<string, size_t> word_count;//string到size_t的空map
    string word;
    while (cin >> word)
        ++word_count[word];//获取word的计数器并将其加1
    for (const auto &w : word_count)
        cout << w.first << " occurs " << w.second
        << ((w.second > 1) ? " times" : " time") << endl;

此程序中关键字是string类型,值是size_t类型。while循环每次从标准输入读取一个单词,如果word不在map中,word_count下标运算符创建一个新元素,其关键字是word,值是0,不管元素是否是新创建的,都将其值加1。使用for循环对map进行遍历。从map中提取一个元素时,会得到一个pair类型的对象

使用set

上个程序可以使用set保存想要忽略的单词,只统计不在set中的单词

map<string, size_t> word_count;//string到size_t的空map
    set<string> exclude = { "The","But","And","Or","An","A",
                           "the","but","and","or","an","a" };
    string word;
    while (cin >> word)
        if(exclude.find(word)==exclude.end())
        ++word_count[word];//获取word的计数器并将其加1
    for (const auto &w : word_count)
        cout << w.first << " occurs " << w.second
        << ((w.second > 1) ? " times" : " time") << endl;

11.1

vector是顺序容器,顺序容器中的元素是“顺序”存储的(链表中的元素在内存中是“连续”存储的)。对于vector这样是顺序容器,每个元素有唯一对应的位置编号,所有操作都是按位置进行的,底层的数据结构是数组、链表,对于插入、删除、遍历等操作高效,但是对于查找给定元素的效率不高

而map这种容器,是“按值访问元素”的,容器中的元素是按关键字值存储的,关键字值与元素数据建立起对应关系。底层数据结构是红黑树、哈希表等,可高效实现按关键字查找元素等操作

11.2

若元素很小,大致数量预先可知,在程序运行过程中不会剧烈变化,大部分情况下只在末尾添加或删除需要频繁访问的元素,则vector的效率较高。若是头部和末尾需要添加或删除,则deque较好。

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

map很适合一些按元素的某个特点进行访问。如查询学生信息

set 当需要保存特定值的集合。

11.3

上述程序,

11.4

 

 1 #include <iostream>
 2 #include <map>
 3 #include <algorithm>
 4 #include <fstream>
 5 #include <string>
 6 #include <cctype>
 7 
 8 using namespace std;
 9 
10  //void ignore_some_char(string& str)
11 string& ignore_some_char(string& str)
12 {
13      string::size_type i;
14      for (string::size_type i = 0; i < str.size(); i++)
15      {
16          if (str[i] >= A&&str[i] <= Z)
17              // str[i].tolower(str[i]);
18             // tolower(str[i]);
19              str[i] += a - A;
20          else if (ispunct(str[i]))
21              str.erase(i, 1);
22              
23              
24     }
25      return str;
26 }
27 
28 int main(int argc, char* argv[])
29 {
30     ifstream infile(argv[1]);
31     if (!infile)
32         cerr << "can not open the file1!" << endl;
33     string word;
34     map<string, size_t> word_count;
35     while (infile >> word)
36     {
37         ignore_some_char(word);
38         ++word_count[word];
39         
40     }
41     for (auto w : word_count)
42         cout << w.first << " occurs " << w.second
43         << ((w.second > 1) ? " times " : " time ") << endl;
44 
45     
46     system("pause");
47     return 0;
48 }

 定义关联容器:

关联容器初始化:

每个关联容器都定义了一个默认构造函数,他定义了一个指定类型的空容器。也可以将关联容器初始化为另一个通类型容器的拷贝,后是从一个值范围来初始化关联容器

set<string> exclude = { "The","But","And","Or","An","A",
                           "the","but","and","or","an","a" };
map<string, string> authors = { {"Joyce","James"},{"Austen","Jane"} };

初始化器必须能转换为容器中元素的类型。

如果说map的key与value是一对一映射的,那么multimap是一对多映射的。

11.5

需要访问关键字对应的数据时使用map,只需要访问关键字是否存在时使用set

11.6

顺序访问使用list,非顺序访问使用set

11.7

 1 #include <iostream>
 2 #include <map>
 3 #include <string>
 4 #include <vector>
 5 #include <algorithm>
 6 
 7 using namespace std;
 8 
 9 void add_family(map<string, vector<string>>&families, const string&last_name)
10 {
11     if (families.find(last_name) == families.end())
12         families[last_name] = vector<string>();
13 }
14 
15 void add_child(map<string, vector<string>>& families, const string& first_name,
16     const string& last_name)
17 
18 {
19     families[last_name].push_back(first_name);
20 }
21 int main(int argc, char* argv)
22 {
23     map<string, vector<string>> families;
24     add_family(families, "James");
25     add_child(families, "Joyce", "James");
26     add_child(families, "Austen", "Jane");
27     add_child(families, "Dicken", "Charles");
28     for (auto f : families)
29     {
30         cout << "the child of " << f.first << ": ";
31             for (auto c : f.second)
32                 cout << c << " ";
33         cout << endl;
34     }
35     system("pause");
36     return 0;
37 }

其实,11,-12行可以直接使用

families[last_name];

因为关联容器定义了一个默认构造函数,当该家庭存在时,vector不会有变化,当该家庭不存在时,map会构造一个该关键字的对象,并初始化为空。这也是add_child没有检查家庭是否存在的原因

关键字类型必须定义元素比较的方法。默认情况,标准库使用关键字类型的<运算符比较两个关键字。

也可以提供自己定义的操作代替关键字上的<运算符,所提供的操作必须在关键字类型上定义一个严格弱序。在实际编程中,如果一个类型定义了“行为正常”的<运算符,则它可以用作关键字类型

11.9

 

 1 #include <iostream>
 2 #include <list>
 3 #include <map>
 4 #include <string>
 5 #include <fstream>
 6 #include <algorithm>
 7 #include <sstream>
 8 
 9 using namespace std;
10 
11 string transform(string& word)
12 {
13     for (string::size_type i = 0; i != word.size(); i++)
14     {
15         if (word[i] >= A&&word[i] <= Z)
16             tolower(word[i]);
17         /*else if (ispunct(word[i]))
18             word.erase(i, 1);*/
19 
20 
21     }
22     return word;
23 }
24 
25 int main(int argc, char* argv[])
26 {
27     ifstream infile(argv[1]);
28     if (!infile)
29         cerr << "can not open the file!" << endl;
30     map<string, list<int>> word_lineNo;
31     string word;
32     string line;
33     int lineNo = 0;
34     while (getline(infile, line))
35     {
36         lineNo++;
37         istringstream in_line(line);
38         while (in_line >> word)
39         {
40             transform(word);
41             word_lineNo[word].push_back(lineNo);
42         }
43     }
44 
45     for (auto &w : word_lineNo)
46     {
47         cout << "the line NO. of " << w.first << " is ";
48         for (auto &i : w.second)
49             cout << i << " ";
50         cout << endl;
51     }
52         
53 
54     system("pause");
55     return 0;
56 }

 

11.10

 

map<vector<int>::iterator, int>it_map;//合法的。map对关键字类型默认支持<运算符,vector的迭代器支持<操作
map<list<int>::iterator, int>il_map;//非法的。因为list的迭代器不支持<操作。因为list元素的存储只是在内存上是连续的,不是像vector中元素那样连续的

pair类型

pair类型定义在头文件utility中,一个pair保存两个数据成员。

 

 

    pair<string, string> anon;//保存两个string
    pair<string, size_t> word_cnt;//保存一个string和一个size_t
    pair<string, vector<int>>line;//保存一个string和一个vector<int>

 

pair默认构造函数会对数据成员进行默认值初始化,anon被初始化为包含两个空字符串的pair,line初始化为保存一个string和一个vector<int>的pair

也可以定义时就为每个数据成员提供初值

pair<string, string> author{ "James","Joyce" };

pair的成员是public的,分别命名为first和second。

创建pair对象的函数

pair<string, int> process(vector<int>& v)
{
    //process
    if (!v.empty())
        return{ v.back(),v.back().size(); };//列表初始化
//return pair < string, vector<int>(v.back(), v.back().size());//早期版本
else return pair<string, int>();//默认构造函数 }

 11.12

 1 #include <iostream>
 2 #include <utility>
 3 #include <string>
 4 #include <vector>
 5 
 6 using namespace std;
 7 
 8 int main()
 9 {
10     vector<pair<string, int>> vp;
11     string str;
12     int ival;
13     while (cin >> str >> ival)
14         vp.push_back(pair<string, int>(str, ival));
15     //11.13 其它初始化pair方法
16     /*vp.push_back({ str,ival });
17     vp.push_back(make_pair(str, ival));*/
18     for ( auto &ip : vp)
19         cout << ip.first << " " << ip.second<<endl;
20     system("pause");
21     return 0;
22 }

11.14

 1 #include <iostream>
 2 #include <map>
 3 #include <utility>
 4 #include <vector>
 5 #include <string>
 6 #include <algorithm>
 7 
 8 
 9 using namespace std;
10 
11 void add_family(map<string, vector<pair<string, string>>>& family,const string& last_name)
12 {
13     family[last_name];
14 }
15 
16 void add_childInfo(map<string,vector< pair<string, string>>>& family,
17     const string& first_name, const string& last_name,const string& birth)
18 {
19     family[last_name].push_back({ first_name,birth });
20 }
21 
22 
23 int main(int argc, char*argv[])
24 {
25     map<string, vector<pair<string, string>>>family;
26     add_family(family, "James");
27     add_childInfo(family, "James", "Joyce", "1989-7-1");
28     add_childInfo(family, "Dickens", "Charles", "1979-3-4");
29     for (auto f : family)
30     {
31         cout << "the child of the family " << f.first << ":";
32         for (auto ch : f.second)
33             cout << "the birthay of " << ch.first << " is :" << ch.second << " ";
34         cout << endl;
35     }
36     
37     system("pause");
38     return 0;
39 }

关联容器操作

关联容器额外的类型别名

key_type                   此容器类型的关键字类型

mapped_type           每个关键字关联的类型,只适用于map

value_type               对于set,与key_type相同,对于map为pair<const key_type,mapped_type>

对于set类型,key_tyoe和value_type是一样的,map中是关键字-值对,每个元素是一个pair对象,并且pair的关键字部分是const的

    set<string>::value_type  v1;//v1是一个string
    set<string>::key_type v2;//v2是一个string
    map<string, int>::value_type v3;//v3是一个pair<const string,int>
    map<string, int>::key_type v4;//v4是一个string
    map<string, int>::mapped_type v5;//v5是一个int

 关联容器迭代器

解引用一个关联容器迭代器会得到一个类型为容器的value_type的值的引用。

auto map_it = word_count.begin();//*map_it 是一个pair<string,size_t>对象的引用
    cout << map_it->first;//输出此对象的第一个成员即关键字
    cout << " ";
    cout << map_it->second;//输出此对象的第二个成员,即此元素的值
    map_it->first = "new_key";//将map的关键字改为new_key,是错误的,因为pair的first成员是const的
    ++map_it->second;//正确,可以通过迭代器改变元素的值

Note:一个map的value_type是一个pair,可以改变pair的值,但不能改变关键字的值

set迭代器时const的

只能用set的迭代器来读取元素的值,不能修改

set<int> iset = { 0,1,2,3,4,5,6 };
    set<int>::iterator set_it = iset.begin();
    if (set_it != iset.end())
    //    *set_it = 42;//错误,set中的关键字是const的,不能修改
    cout << *set_it << endl;

遍历关联容器

map和set都支持begin和end操作。使用这些函数获取迭代器,用迭代器遍历容器。

auto map_it word_count.ebegin();
    while (map_it != word_count.cend())
    {
        cout << map_it->first << " occurs " << map_it->second << " times" << endl;
        ++map_it;
    }

11.15

    map<int, vector<int>>::mapped_type p1;//p1是一个vector<int>
    map<int, vector<int>>::key_type p2;//p2是一个int
    map<int, vector<int>>::value_type p3;//p3是一个pair<const int,vector<int>>

11.16

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

    map<int, int> m;
    auto map_it = m.begin();
    map_it->second = 1;

set的迭代器时const的,因此只允许访问,不能修改,所以不能通过迭代器修改set中的元素。所以前两个试图将vector中的元素复制到set中是错误的。后两个是将set中的元素复制到vector中,是合法的。

11.18

pair<const string, size_t>::iterator map_it;

 添加元素

insert 

c.insert(v) v 是value_type类型的对象,对于map和set,只有当元素的关键字不在c中时才插入,函数返回一个pair,包含一个迭代器,指向具有关键字的元素。

insert(p,v)将迭代器p作为一个提示,指出从哪里开始搜索新元素应存储的位置。返回一个迭代器,指向具有给定关键字的元素。

insert返回值:insert(或emplace)返回值依赖于容器类型和参数。对于不包含重复关键字的容器,添加但医院是的insert和emplace版本返回一个pair,给出插入操作是否成功,pair的first成员是一个迭代器,指向具有给定关键字的元素,second成员是一个bool值,指出元素是插入成功还是已经在容器中。如果关键字已在容器中,insert什么也不做,并返回bool值为false,如果关键字不存在,元素被插入,bool为true。

    vector<int> ivec = { 2,4,6,8,2,4,6,8 };
    set<int>s;//空set
    s.insert(ivec.begin(), ivec.end());//将vector中的元素插入set,现在set中元素为2,4,6,8
    s.insert({ 1,3,5,7,9,1,3,5,7,9 });//set中先在元素为1,2,3,4,5,6,7,8,9

对map进行insert操作,元素类型是pair,通常,对于想要插入的数据,并没有现成的pair对象,可以在insert的参数列表中创建一个pair:

//向word_count 插入word的4中方法
    word_count.insert({ word,1 });
    word_count.insert(make_pair(word, 1));
    word_count.insert(pair<string, size_t>(word, 1));
    word_count.insert(map<string, size_t>::value_type(word, 1));

11.20

使用insert的操作方式:构造一个pair(word,1)用insert将其插入到map中,返回一个pair,若word已存在,则pair的second成员为false,表示插入操作失败,程序员还需要通过返回的pair的first成员(迭代器)递增已有单词的计数器。这种方式程序员需要判断单词是否存在,并进行相应的后续操作

使用下标操作方式:已单词作为下标获取元素值,若单词已存在,则提取元素已有值,否则下标操作将pair(word,0)插入容器,提取元素值。单词是否存在的相关处理是由下标操作处理的,不需要程序员关心,直接访问提取的值就可以了

map<string, size_t>word_count;
    string word;
    while (cin >> word)
    {
        auto ret = word_count.insert({ word,1 });
        if (!ret.second)//程序员需要自己判断插入操作是否成功
            ++ret.first->second;
    }

 11.21

while循环从标准输入读取word,每读入一个word,insert会构造一个pair,其first成员是一个迭代器,若word已存在,指向该已存在的元素,否则,指向新插入的word元素。使用.first获得该迭代器,使用解引用操作->second ,获取单词的计数,若word是新的,则为0,否则对其进行递增操作。

11.22

        pair<string,vector<int>>//参数类型
        pair<map<string,vector<int>>::iterator,bool>//返回类型

11.23

只需将原来的map改为multimap

删除元素

c.erase(k)从从中删除每个关键字为k的元素,返回一个size_type的值,指出删除元素的数量。

c.erase(p) 从c删除迭代器p中所指定的元素,p必须指向c中一个真实元素,不能等于c.end()。返回一个指向p之后元素的迭代器,若p指向c中元素的尾元素,则返回c.end()

c.erase(b,e) 删除迭代器对b 和e所表示的范围中的元素,返回e

 

    word_count.erase(key_word);//删除关键字为key_word的值,返回的删除的key_word的数量
    word_count.erase(word_count.begin());//删除word_count的首元素,返回的是指向新的首元素的迭代器
    word_count.erase(word_count.begin(), word_count.end());//删除word_count中的所有元素,返回word_count.end()

 

map下标操作

map下标运算符接受一个索引(关键字)获取该关键字相关联的值,但是,如果该关键字关联的值不存在,map会创建一个与该关键字关联的值并插入到map中,对值进行值初始化,返回一个mapped_type 对象,与解引用迭代器不同,解引用迭代器返回的是value_type 对象

c[k]

c.at(k),通上述c[k],只是若关键字关联的值不在map中时不是添加到map中,而是抛出out_of_range异常

11.24

若m中已有关键字0,提取其关联的值,将其赋值为1,若没有,下标操作创建一个pair对象pair(0,0),插入到m中,然后将值赋值为1

11.25

若v不为空,则将v[0]赋值为1,若v为空,则赋值操作是非法的,可能会造成系统崩溃

11.26

下标操作的是关键字的类型,应该使用map的key_type

返回类型是关键字关联的值的类型,应使用map的mapped_type

map类型:map<string,size_t>

用来下标操作的类型是string,下标操作返回的类型是size_t

11.3.5 访问元素

        c.find(k)//返回一个迭代器,指向第一个关键字为k的元素,若k不在容器中返回尾后迭代器
        c.count(k)//返回关键字为k的元素的数量,对于不允许重复关键字的容器,返回的是0或1
        c.lower_bound(k)//返回一个迭代器,指向第一关键字为k的元素
        c.upper_bound(k)//返回一个迭代器,指向最后一个关键字为k的元素的之后的位置
        c.equal_range(k)//返回一个迭代器pair,表示关键字等于k的元素的范围,若k不存在,pair<c.end().c,end()>

给定一个从作者到其作品的映射,可以打印某一作者的所有作品,可以用上述方法分别实现

//使用find和count
    string search_item("Margret Machell");//要查找的作者
    auto entries = author.count(search_item);//返回该作者的作品的数量
    auto iter = author.find(search_item);//此作者的第一部作品
    while (entries)//while循环查找该作者的所有作品
    {
        cout << iter->second << endl;//输出每个作品
        ++iter;//指向下一部作品
        --entries;
    }
    //使用lower_bound 和upper_bound
    //beg和end分别返回的是指向该作者的第一部作品的迭代器和最后一部作品的迭代器的下一迭代器
    for (beg = author.lower_bound(search_item),
        end = author.upper_bound(search_item);
        beg != end; ++beg)
        cout << beg->second << endl;
    //使用eaual_range实现
    //pos是迭代器对,为指向该作者的作品的迭代器范围
    for (auto pos = author.equal_range(search_item);
        pos->first != pos->second; ++pos->first)
        cout << pos->first->second << endl;

11.27

find是查找关键字出现的位置,count是计算关键字出现的次数

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

11.29

upper_bound 和lower_bound返回一个不影响排序的关键字插入的位置。equral_range返回的一对迭代器都指向关键字可以插入的位置

pos是一个迭代器pair,其第一个成员是与lower_bound相同,指向的是关键字的位置,对其解引用得到的也是pair类型,是map的value_type类型,我们要求返回的是作品名称,所以需要对这个pair的second成员。

 11.33

 1 #include <iostream>
 2 #include <map>
 3 #include<fstream>
 4 #include <sstream>
 5 
 6 
 7 using namespace std;
 8 //读入给定文件,将文件中的内容建立起映射关系放入map中
 9 map<string, string>buildMap(ifstream& map_file)
10 {
11     map<string, string>trans_map;
12     string key;
13     string value;
14     while (map_file >> key&&getline(map_file, value))
15     {
16         if (value.size() > 1)
17             trans_map[key] = value.substr(1);
18         else
19             throw runtime_error("no rule for " + key);
20     }
21     return trans_map;
22 }
23 
24 //生成转换文本,对于给定的map,如果string在map的key中,就输出key关联的value
25 
26 const string& transform(const string& str, map<string, string>&m)
27 {
28     auto map_it = m.find(str);//查找str是否是map 的key
29     if (map_it != m.end())//如果是,输出对应的value
30         return map_it->second;
31     else
32         return str;//否则输出str
33 }
34 //接受转换规则文件和要转换的文件,输出转换后的结果
35 void word_trans(ifstream& map_file, ifstream& input)
36 {
37     auto trans_map = buildMap(map_file);//将规则文件保存到map中,形成映射关系
38     string text;
39     while (getline(input, text))//读取要求转换的文件
40     {
41         istringstream word_stream(text);
42         string word;
43         bool firstword = true;
44         while (word_stream >> word)
45         {
46             if (firstword)
47                 firstword = false;
48             else
49                 cout << " ";
50             cout << transform(word, trans_map);
51         }
52     }
53 }
54 int main(int argc, char*argv[])
55 {
56     if (argc != 3)
57         cerr << "there are two files :rule file and file needed to be transformed" << endl;
58     ifstream map_file(argv[1]);
59     if (!map_file)
60         cerr << "can not open the rule file " << endl;
61     ifstream input_file(argv[2]);
62         if (!input_file)
63             cerr << "can not open the file need to been transformed" << endl;
64         word_trans(map_file, input_file);
65     system("pause");
66     return 0;
67 }

 

以上是关于CH11 关联容器的主要内容,如果未能解决你的问题,请参考以下文章

Ch5 关联式容器(中)

ch11 持有对象

CH1 容器基础

《C++Primer(第5版)》第十一章笔记

将Android片段移动到不同的容器无法更改片段的容器ID

C++ Primer笔记11---chapter11 关联容器