【C++学习】标准库:关联容器(总结C++primer第十一章)

关联容器

  • 关联容器中的元素按关键字来保存和访问

  • 标准库提供8个关联容器,mapmultimap定义在头文件map中,setmultiset定义在头文件set中,无序容器则定义在头文件unordered_map和unordered_set中。

    按关键字有序保存元素
    map关联数组;保存关键字-值对,关键字不可重复出现。
    set关键字即值,即只保存关键字的容器,关键字不可重复出现。
    multimap关键字可重复出现的map
    multiset关键字可重复出现的set
    无序集合
    unordered_map用哈希函数组织的map
    unordered_set用哈希函数组织的set
    unordered_multimap哈希函数组织的map,关键字可重复出现
    unordered_multiset哈希函数组织的set,关键字可重复出现

使用关联容器

  • 使用map

    //可以直接列表初始化
    map<string, size_t> word_count = {{"a", 1}, {"b", 2}...};
    
     // 也可以先初始化空map,再添加键和值
    map<string, size_t> word_count; 
    string word;
    while (cin >> word)
        ++word_count[word];   
    
  • 使用set

    // 单词计数程序,排除部分单词
    map<string, size_t> word_count;  
    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()) // 检查单词是否在忽略集中,find返回一个迭代器,在set中指向该关键字,否则返回尾后迭代器。这里,排除在exclude里面的单词。
            ++word_count[word];
    

关联容器概述

  • 定义关联容器定义一个map时,必须既指明关键字类型又指明值类型而定义一个set时,只需指明关键字类型,因为set中没有值

    map<string, size_t> word_count;  // string到size_t的空map,关键字是string类型,值是size_t类型。
    set<string> exclude = {"The", "But", "And", "Or", "An", "A", 
                           "the", "but", "and", "or", "an", "a"};
    map<string, string> authors = { {"Joyce", "James"},
                                    {"Austen", "Jane"},
                                    {"Dickens", "Charles"} };
    // 初始化muitimap或multiset
     
    // 定义一个有20个元素的vector,保存0到
    vector<int> ivec;
    for (vector<int>::size_type i = 0; i != 10; ++i) {
        ivec.push_back(i);
        ivec.push_back(i);           // 每个数重复保存一次
    }
    // iset包含来自ivec的不重复的元素;miset包含所有20个元素
    set<int> iset(ivec.cbegin(), ivec.cend());            // ivec初始化iset,iset也只有10个元素,即ivec中的不同元素
    multiset<int> miset(ivec.cbegin(), ivec.cend());      // ivec初始化miset,miset有20个元素
    cout << ivec.size() << endl;
    cout << iset.size() << endl;
    cout << miset.size() << endl;
    
  • 关键字类型的要求:对于有序关联容器,关键字类型必须定义元素比较的方法,即传递给排序算法的可调用对象必须满足与关联容器中关键字一样的类型要求。

  • pair类型

    • pair标准库类型定义再头文件utility中;

    • 一个pair保存两个数据成员;

    • pair类似容器,是一个用来生成特定类型的模板;

      pair<string, string> anon;                  // 保存两个string
      pair<string, size_t> word_count;            // 保存一个string和一个size_t
      pair<string, vector<int>> line;             // 保存string和vector<int>
      pair<string, string> author{"James", "Joyce"};     // 保存两个string
       
      for (const auto &w : word_count)
          cout << w.first << "occurs" << w.second << ((w.second > 1) ? "times" : "time") << endl;
      
    • 创建pair对象的函数

      pair<string, int>
      process(vector<string> &v) {
          if (!v.empty())
              return {v.back(), v.back().size()};        // 列表初始化
          else
              return pair<string, int>();                // 隐式构造返回值
      }
       
      // 用make_pair生成pair对象
      if (!v.empty())
              return make_pair(v.back(), v.back().size());
      

关联容器操作

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

    key_type此容器类型的关键字类型
    mapped_type每个关键字关联的类型,只适用于map
    value_type对于set,与key_type相同,对于map,为pair<const key_type, mapped_type>
    • 由于不能改变一个元素的关键字,因此这些pair的关键字部分是const的。
  • 关联容器迭代器

    • 解引用一个关联容器迭代器时,得到一个类型为容器的value_type的值的引用;

      // 获得指向word_count中一个元素的迭代器,*map_it是指向一个pair<const string, size_t>对象的引用
      auto map_it = word_count.begin();    
      cout << map_it -> first;             // 打印此元素的关键字       
      cout << " " << map_it -> second      // 打印此元素的值
      map_it -> first = "new key";         // 错误!关键字是const的
      ++map_it -> second;                  // 正确:可以通过迭代器改变元素
      
    • set的迭代器是const的,即set的关键字是只读的。

    • 遍历关联容器:

      auto map_it = word_count.cbegin(); // 获得一个指向首元素的迭代器
      while (map_it != word_count.cend()) {
          cout << map_it -> first << " occurs " << map_it -> second << " times " <<endl; // 解引用
          ++map_it; // 递增迭代器,移动到下一个元素
      }
      
    • **关联容器和算法:**通常不对关联容器使用泛型算法。在实际编程中,如果真的要对一个关联容器使用算法,要么将它当作一个源序列,要么当成一个目的位置。

  • 添加元素

    操作名称相关描述
    c.insert(v)如果c不是multi,返回的是一个pairfirst是指向插入关键字的迭代器,指向的是一个map<t1,t2>::iteratorsecond是一个布尔值,为真则进行插入,反之,则没有进行插入。
    c.emplace(args))如果是multi,那么就只是返回一个指向新元素的迭代器。
    c.insert(b.e)b,e表示的是一个c::value_type的迭代器范围。返回void
    c.insert({})返回的是void
    c.insert(p,v)指定位置进行插入,返回同insert(v)
    c.emplace(p.args)同上
    • 关联容器的insert成员向容器添加一个元素或一个元素范围。由于map和set包含不重复的关键字,因此插入一个已经存在的元素对容器没有任何影响。

      vector<int> ivec = {2, 4, 6, 8, 2, 4, 6, 8};    // ivec有8个元素
      set<set> set2;                                  // 空集合
      set2.insert(ivec.cbegin(), ivec.cend());       // set2有4个元素
      set2.insert({1, 3, 5, 7, 1, 3, 5, 7});          // set2有8个元素
       
      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));
      
  • 删除元素

    删除操作作用
    c.erase(k)从c中删除每个关键字为k的元素,返回一个size_type值,指出删除元素的数量
    c.erase§从c中删除迭代器p指向的元素,p须指向真实元素,返回指向p后元素的迭代器
    c.erase(b,e)删除迭代器对b和e所表示的范围中的元素,返回e
  • map的下标操作

  • map的下标运算符接受一个索引,获取与此关键字相关联的值。与其他下标运算符不同的是,如果关键字不在map中,会为它创建一个元素并插入到map中,关联值将进行值初始化。

```c++
map<string, size_t> word_count;
word_count["Anna"] = 1;    // 插入一个关键字为Anna的元素,关键值进行值初始化;然后将1赋予它
```
  • 与vector与string不同,map的下标运算符返回的类型与解引用map迭代器得到的类型不同

  • 操作名称相关描述
    c[k]k是关键字,如存在返回mapped_type,若不存在,创建并进行值初始化。
    c.at(k)返回mapped_type会进行参数检查,如果k不存在,抛出out_of_range
  • 访问元素

    操作描述
    c.find(k)有返回指向第一个关键字为k的迭代器,没有返回end
    c.count(k)返回关键字k出现的次数。
    c.lower_bound(k)返回第一个不小于k的元素的迭代器
    c.upper_bound(k)返回第一个大于k的元素的迭代器
    c.equal_range(k)返回与匹配的迭代器范围,是一个pair类型的对象。注意,first是指向第一个与k匹配的元素的迭代器,second是指向与k匹配的最后一个元素的后一个元素。

无序容器

  • 无序关联容器使用哈希函数和关键字类型的==运算符;

  • 无序容器的底层实现:

    • 通过哈希函数计算哈希值;
    • 性能取决于哈希函数的选择,解决冲突的办法。
  • 无序容器的操作:管理桶

    • 无序容器在组织上为一组桶,每个桶保存零个或多个元素,无序容器使用一个哈希函数将元素映射到桶,容器将具有一个特定哈希值的所有元素都保存在相同的桶里

    • 为访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶,当一个桶保存多个元素时,需要顺序搜索这些元素来进行查找。

      操作名称相关描述
      桶接口
      c.bucket_count()返回的是容器正在使用的桶的数目
      c.max_bucket_count()容器最多能容纳桶的数量
      c.bucket_size(n)第n个桶里面的元素的个数
      c.bucket(k)关键字为k所在的桶
      桶迭代器
      local_iterator访问桶的迭代器
      const _local_iteratorconst版本的桶迭代器
      c.begin(n),c.end(n)桶n的首和尾后迭代器
      c.cbegin(),c.cend(n)返回的是const_local_iterator
      哈希策略
      c.load_factor()当前每一个桶的平均元素数量,返回的是float
      c.max_load_factor()c的最大平均元素数量。返回的是float,注意c会在需要时,增加新的桶,使得load_factor <= max_load_factor
      c.rehash(n)重组存储,使得bucket_count >= n,并且bucket_count > size/max_load_count
      c.reverse(n)重新存储,使得c可以保存n个元素而不需要rehash
  • 无序容器对关键字的要求

    • 使用hash<key_type>类型对象来为每一个元素通过哈希函数生成哈希值;

    • 标准库为内置类型(指针,一些标准库类型,string,智能指针)提供了hash模板。也就是说这些类型可以直接作为key_type

    • 对于自定义的类型我们需要自己进行构造hash模板,或者在构造容器时使用重载的版本:

      {
          size_t hasher(const Sales_data &sd)
          {
              return hash<string>()(sd.isbn());//通过这个作为它的哈希值。注意哈希值的返回值是size_t
          }
      }
      
    • 由以上的重载操作,我们可以对自定义类型构造无序容器:

      {
          using sd_multiset = unordered_multiset<Sales_data,decltype(hasher)*,decltype(eqOp)*>;
          sd_multiset bookStore(42,hasher,eqOp);//第一个参数表示的是桶的数量。第二个和第三个表示hash函数和比较操作。
          
          //如果我们的类定义了==
          // 我们可以只对hasher进行重载。
          unordered_multiset<f,decltype(fhasher)*>fset(10,fhasher);
      }
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值