c++基础(五)——关联容器

1.关联容器

  关联容器中的元素时按照关键字来保存和访问的,与之相对的,顺序容器中的元素时按它们在容器中的位置来顺序保存和访问的。两个主要关联容器是 map 和 set。标准库提供了8个关联容器,这8个容器间的不同体现在
三个维度上:

  • 或是一个 map 或是一个 set
  • 或者要求不重复关键字,或者允许重复关键字
  • 按顺序保存元素,或无序保存

2.使用关联容器

  • map
  • 1 //统计每个单词在输入中出现的次数
    2 map<string, size_t> word_count;    //string到size_t的空map
    3 string word;
    4 while (cin >> word)
    5     ++word_count[word];    //提取word的计数器并将其加1
    6 for(const auto &w : word_count)    //对map中的每个元素
    7     //打印结果
    8     cout << w.first << " occurs " << w.second
    9          << ((w.second > 1)? " times ": "time") << endl;
  • set
  • 1 //统计输入中每个单词出现的次数
    2 map<string, size_t> word_count;    //string到size_t的空map
    3 set<string> exclude = {"The", "But", "And", "Or", "An", "A",
    4                "the", "but", "and", "or", "an", "a"};
    5 string word;
    6 while (cin >> word)
    7     //只统计不在exclude中的单词
    8     if (exclude.find(word) == exclude.end())
    9         ++word_count[word];    //获取并递增word的计数器

3.关联容器概述

  关联容器不支持顺序容器的位置相关操作,例如push_front或push_back。原因是关联容器中元素是根据关键字存储的,这些操作对关联容器没有意义。关联容器的迭代器都是双向的

  • 初始化 multimap 和 multiset。一个map或set中的关键字必须是唯一的,容器multimap和multiset没有此限制,它们允许多个元素具有相同的关键字。
  •  1 //定义一个有20个元素的vector,保存0到9每个整数的两个拷贝
     2 vector<int> ivec;
     3 for (vector<int>::size_type i = 0; i != 10; ++i){
     4     ivec.push_back(i);
     5     ivec.push_back(i);    //每个数重复保存一次
     6 }
     7 //iset包含来自ivec的不重复的元素,miset包含所有20个元素
     8 set<int> iset(ivec.begin(), ivec.end());
     9 multiset<int> miset(ivec.begin(), ivec.end());
    10 cout << ivec.size() << endl;    //打印出20
    11 cout << iset.size() << endl;    //打印出10
    12 cout << miset.size() << endl;    //打印出20

 3.1 set,map自定义函数排序

  关联容器对其关键字类型有一些限制。对于有序容器,关键字类型必须定义元素比较的方法。默认情况下,标准库使用关键字类型<运算符来比较两个关键字。

  用来组织一个容器中元素的操作的类型也是该容器类型的一部分。为了指定使用自定义的操作,必须在定义关联容器类型时提供此操作的类型使用方法如下:用尖括号指定要定义的那种类型的容器,自定义的操作类型必须在尖括号中紧跟元素类型给出。例如:我们不能直接定义一个Sales_data的multiset,因为Sales_data没有<运算符。因此需要定义一个compareIsbn:

1 bool compareIsbn (const Sales_data &lhs, const Sales_data &rhs)
2 {
3     return lhs.isbn() < rhs.isbn();
4 }

  为了使用自己定义的操作,在定义multiset时我们必须提供两个类型:关键字类型Sales_data,以及比较操作类型——应该是一种函数指针类型,可以指向compareIsbn。

1 //bookstore中多条记录可以有相同的ISBN
2 //bookstore中的元素以ISBN的顺序进行排列
3 multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);

  此处,我们使用decltype来指出自定义操作的类型。记住,当用decltype来获得一个函数指针类型时,必须加上一个*来指出我们要使用一个给定函数类型的指针;用compareIsbn来初始化bookstore对象,这表示当我们想bookstore添加元素时,通过调用compareIsbn来为这些元素排序。

  下面举两个实例:均按照降序进行排列,容器默认是使用“<”。

 1 #include <set>
 2 #include <map>
 3 #include <vector>
 4 #include <string>
 5 #include <iostream>
 6 using namespace std;
 7 
 8 class st {
 9 public:
10     int id;
11     string name;
12     st(int id, string name);
13     ~st() {};
14 };
15 
16 st::st(int id, string name) :id(id), name(name) {
17 
18 }
19 
20 bool compareST(const int &s, const int &r) {
21     return s > r;
22 }
23 
24 bool compareST2(const st *s, const st *r) {
25     return s->id > r->id;
26 }
27 
28 int main()
29 {
30     /// map
31     st *st1 = new st(3, "st1");
32     st *st2 = new st(1, "st2");
33     st *st3 = new st(2, "st3");
34     
35     map<int, st*, decltype(compareST)*> temp(compareST);
36     temp.insert(make_pair(st1->id, st1));
37     temp.insert(make_pair(st2->id, st2));
38     temp.insert(make_pair(st3->id, st3));
39 
40     for (auto item : temp) {
41         cout << item.first << "	" << item.second->name << endl;
42     }
43 
44     cout << "========================" << "
";
45 
46     /// set
47     set<st*, decltype(compareST2)*> temp2(compareST2);
48     temp2.insert(st1);
49     temp2.insert(st2);
50     temp2.insert(st3);
51 
52     for (auto item : temp2) {
53         cout << item->id << "	" << item->name << endl;
54     }
55     
56     getchar();
57     return 0;
58 }

3.2 pair 类型

  它定义在头文件utility中。类似容器,pair是一个用来生成特定类的模板,与其他标准库类型不同,pair的数据成员是public的。两个成员分别命名为firstsecond,我们用普通的成员访问符号来访问它们。

  如下是一个错误写法:

1 map<int, string> temp;
2 pair<int, string> p(); // 显得不伦不类了。①不加括号,即采用第一种进行值初始化②采用(1,"test")利用第二种方式初始化
3 temp.insert(p);

  改正:

1 map<int, string> temp;
2 pair<int, string> p(1, "dd");
3 pair<int, string> p2;
4 temp.insert(p);
5 temp.insert(p2);
6 temp.insert(make_pair(1, "dd"));

3.3 关联容器的操作

  关联容器额外的类型别名:

  对于 set 类型,key_type 和 value_type 是一样的:set 中保存的就是关键字。

  在 map 中,元素是关键字-值对,即每一个元素是一个 pair 对象,包含一个关键字和一个关联的值,同时因为不能改变一个元素的关键字,因此这些 pair 的关键字部分是 const 的。

1 map<int, string>::key_type v; // v 是一个 int
2 map<int, string>::mapped_type v2; // v 是一个 string
3 set<string>::key_type s; // s 是一个 string
4 set<string>::value_type s2; // s 是一个 string 
5 map<int, string>::value_type v3;// 是一个 pair<int, string> 类型

 4. 关联容器迭代器

  map 中的 key 值是const的,不能够被改变。

1 map<int, string> m;
2 m.insert({ 1, "a" });
3 m.insert({ 2, "b" });
4 for (auto iter = m.begin(); iter != m.end(); ++iter) {
5    cout << iter->first << iter->second<<endl;
6 }

  set的迭代器是const的。set 中的值 也只能访问不能修改。

4.1 添加元素

  由于map和set包含不重复的关键字,因此插入一个已存在的元素对容器没有任何影响:

  注:insert有两个版本分别接受一对迭代器,或是一个初始化器列表,这两个版本的行为类似对应的构造函数。

1 vector<int> ivec = { 2, 4, 6, 8, 2,4, 6, 8 };
2 set<int> set2;
3 set2.insert(ivec.begin(), ivec.end());    //set2有4个元素

4.1.1 向 map 添加元素

1 //向word_count插入word的4中方法
2 word_count.insert({word, 1}); // 最简单
3 word_count.insert(make_pair(word, 1));
4 word_count.insert(pair<string, size_t>(word, 1));
5 word_count.insert(map<string, size_t>::value_type(word, 1)); //构造一个恰当的 pair 类型,并构造该类型的一个对象,插入到map中

4.1.2 检测 insert 的返回值

  insert(或emplace)返回的值依赖于容器类型和参数。对于不包含重复关键字的容器,添加单一元素的insert和emplace版本返回一个pair,告诉我们插入操作是否成功pair的first成员是一个迭代器,指向具有给定关键字的元素;second成员是一个bool值,指出元素是插入成功还是已经存在于容器中。

4.1.3 删除元素

  关联容器定义了三个版本的erase,与顺序容器一样,我们可以通过传递给erase一个迭代器或一个迭代器对来删除一个元素或一个元素范围;关联容器还提供一个额外的erase操作,它接受一个key_type参数。

4.1.4 map 的下标操作

  map和unordered_map支持下标运算和一个对应的 at 函数,但是 set 不支持,因为set中本身存放的就是 关键字。

 

  与其他下标运算符不同的是,但是如果关键字并不在map中,会为它创建一个元素并插入到map中,关联值将进行初始化。比如下面的例子:

1     map<int, int> m;
2     m.insert({ 1, 1 });
3     m.insert({ 2, 2 });
4 
5     cout << m.size() << endl; // 2
6     cout << m[3] << endl; // 0. key=3 的值不存在,插入并初始化
7     cout << m.size() << endl; // 3
8     cout << m[3] << endl; // 0    

 4.2 访问元素

  如果我们所关心的只不过是一个特定元素是否在容器中,可能find是最佳选择。

1 set<int> iset = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
2 iset.find(1);    //返回一个迭代器,指向key==1的元素
3 iset.find(11);    //返回一个迭代器,其值等于iset.end()
4 iset.count(1);    //返回1
5 iset.count(11);    //返回0

  

  map 中利用 find 代替下标操作。当我们不想改变 map 的时候,下标操作会带来麻烦。

1 if (word_count.find("foobar") == word_count.end())
2     cout << "foobar is not in the map " << endl;

  在multimap或multiset中查找元素。如果 multimap 中有多个元素具有给定的关键字,这些元素会在容器中相邻存储

1 string search_item("Alain de Botton");    //要查找的作者
2 auto entries = authors.count(search_item);    //元素的数量
3 auto iter = authors.find(search_item);    //此作者的第一本书
4 //用一个循环查找此作者的所有著作
5 while(entries){
6     cout << iter->second << endl;    //打印每个题目
7     ++iter;                //前进到下一本书
8     --entries;            //记录已经打印了多少本书
9 }

  

原文地址:https://www.cnblogs.com/KongHuZi/p/11366383.html