【C++】《C++ Primer 》第十一章
第十一章 关联容器
- 关联容器和顺序容器的不同:关联容器中的元素时按照关键字来保存和访问的。
- 关联容器支持通过关键字来高效地查找和读取元素,基本的关联容器类型是 map和 set。
- 类型 map 和 multimap 定义在 头文件map 中;set 和 multimap 定义在 头文件set 中。 无序容器则定义在 unordered_map 和 unordered_set 中。
| 容器类型 | 解释 | 
|---|---|
| 有序存储 | |
| map | 关键数组:保存关键字-值对 | 
| set | 关键字即值,即只保存关键字的容器 | 
| multimap | 支持同一个键多次出现的map | 
| multiset | 支持同一个键多次出现的set | 
| 无序存储 | |
| unordered_map | 用哈希函数组织的map | 
| unordered_set | 用哈希函数组织的set | 
| unordered_multimap | 哈希组织的map,关键字可以重复出现 | 
| unordered_multiset | 哈希组织的set,关键字可以重复出现 | 
一、使用关联容器
1. 使用map
// 单词计数程序
map<string, size_t> word_cnt;
string word;
while(cin >> word) {
    ++word_cnt[word];
}
for(const auto &w: word_cnt) {
    cout << w.first << " occurs " << w.second << ((w.second > 1) ? "times": "time") << endl;
}
2. 使用set
// 单词计数程序
map<string, size_t> word_cnt;
set<string> exclude = {"The", "But", "And", "Or", "An"};
string word;
while(cin >> word) {
    if(exclude.find(word) == exclude.end()) {
        ++word_cnt[word];
    }
}
二、关联容器概述
- 关联容器的迭代器都是双向的。
1. 定义关联容器
- 需要指定元素类型。
- 列表初始化:
- map:map<string, int> word_count = {{"a", 1}, {"b", 2}};
- set:set exclude = {"the", "a"};
 
2. 关键字类型的要求
- 对于有序容器,关键字类型必须定义元素比较的方法。默认是<。
- 如果想传递一个比较的函数,可以这样定义:multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);
// 可以定义一个vector<int>::iterator到int的map吗?
// 答:可以。因为vector的迭代器支持比较操作。
// 那list<int>::iterator到int的map呢?
// 答:不可以。因为list的元素不是连续存储,其迭代器不支持比较操作。
3. pair类型
- 在 头文件utility 中定义。
- 一个pair保存两个数据成员,两个类型不要求一样。
- pair操作:
| 操作 | 解释 | 
|---|---|
| pair<T1, T2> p; | p是一个pair,两个类型分别是T1和T2的成员都进行了值初始化。 | 
| pair<T1, T2> p(v1, v2); | first和second分别用v1和v2进行初始化。 | 
| pair<T1, T2>p = {v1, v2}; | 同上。 | 
| make_pair(v1, v2); | pair的类型从v1和v2的类型推断出来。 | 
| p.first | 返回p的名为first的数据成员。 | 
| p.second | 返回p的名为second的数据成员。 | 
| p1 relop p2 | 运算关系符按字典序定义。 | 
| p1 == p2 | 必须两对元素两两相等。 | 
| p1 != p2 | 同上。 | 
三、关联容器操作
- 关联容器额外的类型别名:
| 类型别名 | 解释 | 
|---|---|
| key_type | 此容器类型的关键字类型。 | 
| mapped_type | 每个关键字关联的类型,只适用于map。 | 
| value_type | 对于map,是pair<const key_type, mapped_type>; 对于set,和key_type相同。 | 
1. 关联容器迭代器
- 解引用一个关联容器迭代器时,会得到一个类型为容器的value_type的值的引用。
- 注意:一个map的value_type是一个pair,可以改变pair的值,但是不能改变关键字成员的值。
- set的迭代器是const的。可以用一个set迭代器来读取元素的值,但不能修改。
- 遍历关联容器:使用begin和end,遍历map、multimap、set、multiset时,迭代器按关键字升序遍历元素。
- 通常不对关联容器使用泛型算法。关键字是const这一特性意味着不能将关联容器传递给修改或重排容器元素的算法,因为这类算法需要向元素写入值,而set类型中的元素是const的,map中的元素是pair,其第一个成员是const的。
- 实际开发中,真要用一个关联容器使用算法,要么是将它当作一个源序列,要么当作一个目的位置。
2. 添加元素
- 关联容器 insert 操作:
| 操作 | 解释 | 
|---|---|
| c.insert(v) c.emplace(args) | v是value_type类型的对象;args用来构造一个元素。 对于map和set,只有元素的关键字不存在c中才插入或构造元素。函数返回一个pair,包含一个迭代器,指向具有指定关键字的元素,以及一个指示插入是否成功的bool值。对于multimap和multiset则会插入范围中的每个元素。 | 
| c.insert(b, e) c.insert(il) | b和e是迭代器,表示一个 c::value_type 类型值的范围;il是这种值的花括号列表。函数返回void。对于 map和set,只插入关键字不在c中的元素。 | 
| c.insert(p, v) c.emplace(p, args) | 类似insert(v),但将迭代器p作为一个提示,指出从哪里开始搜索新元素应该存储的位置。返回一个迭代器,指向具有给定关键字的元素。 | 
- 向 set 添加元素:
vector<int> vec{1,244,642,72,3,6,3,6,1};
set<int>set2;
set2.insert(vec.cbegin(), vec.cend());   // set2现有6个元素
set2.insert({5, 8, 5, 8});  // set2现有8个元素
- 向 map 添加元素:
// 四种插入方法
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));
3. 删除元素
- 从关联容器中删除元素:
| 操作 | 解释 | 
|---|---|
| c.erase(k) | 从c中删除每个关键字为k的元素。返回一个size_type值,指出删除的元素的数量。 | 
| c.erase(p) | 从c中删除迭代器p指定的元素。p必须指向c中一个真实元素,不能等于c.end()。返回一个指向p之后元素的迭代器,若p指向c中的尾元素,则返回c.end()。 | 
| c.erase(b, e) | 删除迭代器对b和e所表示范围中的元素。返回e。 | 
4. map的下标操作
- 只支持map和unordered_map的下标操作。
- 由于下标运算符可能插入一个新元素,只可以对非const的map使用下标操作。
- 对一个map使用下标操作,其行为与数组或vector上的下标操作有很大不同,使用一个不在容器中的关键字作为下标,会添加一个具有此关键字的元素到map中。
- map和unordered_map的下标操作:
| 操作 | 解释 | 
|---|---|
| c[k] | 返回关键字为k的元素;如果k不在c中,添加一个关键字为k的元素,对其值初始化。 | 
| c.at(k) | 访问关键字为k的元素,带参数检查;若k不存在在c中,抛出一个out_of_range异常。 | 
5. 访问/查找元素
- lower_bound和upper_bound不适用于无序容器。
- 下标 和 at 操作只适用于非const的map和unordered_map。
- 如果只关心一个特定元素是否在容器中,可能find是最佳选择。对于不允许重复的容器,可能使用find和count没有什么区别。对于允许重复的容器,count还会做更多事,如果元素在容器中,它还会统计次数。如果不需要计数,最好使用find。
- 在一个关联容器中查找元素:
| 操作 | 解释 | 
|---|---|
| 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()。 | 
- 如果只想知道一个关键字是否在map中时,推荐使用find代替下标操作,因为下标操作可能会引发意想不到的问题。
6. 一个综合的例子
给定一个string,将它转换为另一个string。程序的输入是两个文件。第一个文件保存一些规则,用来转换第二个文件中的文本。每条规则由两部分组成:一个可能出现在输入文件中的单词和一个替换它的短语。
// 给定一个string,将它转换为另一个string。程序的输入是两个文件。第一个文件保存一些规则,用来转换第二个文件中的文本。
// 每条规则由两部分组成:一个可能出现在输入文件中的单词和一个替换它的短语。
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <map>
//#include <algorithm>
using namespace std;
// 转换映射
map<string, string> buildMap(ifstream &map_file) {
    map<string, string> trans_map;
    string key;
    string value;
    while(map_file >> key && getline(map_file, value)) {
        if(value.size() > 1)
            trans_map[key] = value.substr(1);
        else
            throw runtime_error("no rule for " + key);
    }
    return trans_map;
}
// 生成转换文本
const string & transform(const string &s, const map<string, string> &m) {
    auto map_it = m.find(s);
    if(map_it != m.cend())
        return map_it->second;
    else
        return s;
}
// 单词转换
void word_transform(ifstream &map_file, ifstream &input) {
    auto trans_map = buildMap(map_file);
    string text;
    while(getline(input, text)) {
        istringstream stream(text);
        string word;
        bool firstWord = true;
        while(stream >> word) {
            if(firstWord)
                firstWord = false;
            else
                cout << " ";
            cout << transform(word, trans_map);
        }
        cout << endl;
    }
}
int main() {
    ifstream map_file_in("../ch11/map_file.txt");
    ifstream input_in("../ch11/input.txt");
    word_transform(map_file_in, input_in);
    return 0;
}
四、无序容器
- C++11定义了4个无序关联容器,它们不是使用比较运算符来组织元素,而是使用一个 哈希函数 和 关键字类型的==运算符 。
- 如果关键字类型固有就是无序的,或者性能测试发现问题可以用哈希计数解决,就可以使用无序容器。
- 无序容器在存储上组织为一组桶,每个桶保存零个或多个元素。无序容器使用一个哈希函数将元素映射到桶。所以,无序容器的性能依赖于哈希函数的质量和桶的数量和大小。
- 无序容器管理操作:允许查询容器的状态以及必要时强制容器进行重组。
| 操作 | 解释 | 
|---|---|
| 桶接口 | |
| c.bucket_count() | 正在使用的桶的数目 | 
| c.max_bucket_count() | 容器能容纳的最多的桶的数目 | 
| c.bucket_size(n) | 第n个桶中有多少个元素 | 
| c.bucket(k) | 关键字为k的元素在哪个桶中 | 
| 桶迭代 | |
| local_iterator | 可以用来访问桶中元素的迭代器类型 | 
| const_local_iterator | 桶迭代器的const版本 | 
| c.begin(n),c.end(n) | 桶n的首元素迭代器 | 
| c.cbegin(n),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_factor | 
| c.reverse(n) | 重组存储,使得c可以保存n个元素且不必rehash | 
【C++】《C++ Primer 》第十一章的更多相关文章
- C++Primer 第十一章
		//1.关键容器支持高效的关键字查找和访问. map 关联数组:保存关键字-值对.通过关键字来查找值. set 关键字即值,即只保存关键字的容器. multimap 关键字可重复出现的map mult ... 
- C++ Primer : 第十一章 : 关联容器示例: 一个单词转换的map
		单词转换就是:将一些缩写的单词转换为实际的文本.第一个文件保存的是转换的规则,而第二个文件保存的是要转换的文本. 假设单词转换的规则的文件如下: brb be right back k okay? y ... 
- C++ Primer : 第十一章 : 关联容器之关联容器的迭代器和操作
		关联容器的操作 除了和顺序容器定义的类型之外,关联容器还定义了一下几种类型: 关联容器额外的类型别名 key_type 此容器类型的关键字类型 mapped_type 每个关键字关联的类型, ... 
- C++ Primer : 第十一章 : 关联容器之概述、有序关联容器关键字要求和pair类型
		标准库定义了两种主要的关联容器:map和set map中的元素时一些关键字-值(key-value)对,关键字起到索引的作用,值则表示与索引相关的数据.set中每个元素只包含一个关键字,可以完成高效的 ... 
- CPrimerPlus第十一章中的“选择排序算法”学习
		C Primer Plus第十一章字符串排序程序11.25中,涉及到“选择排序算法”,这也是找工作笔试或面试可能会遇到的题目,下面谈谈自己的理解. 举个例子:对数组num[5]={3,5,2,1,4} ... 
- <构建之法>第十一章、十二章有感
		十一章:软件设计与实现 工作时要懂得平衡进度和质量.我一直有一个困扰:像我们团队这次做 男神女神配 社区交友网,我负责主页的设计及内容模块,有个队友负责网站的注册和登录模块,有个队友负责搜索模块,有个 ... 
- sql 入门经典(第五版) Ryan Stephens 学习笔记 (第六,七,八,九,十章,十一章,十二章)
		第六章: 管理数据库事务 事务 是 由第五章 数据操作语言完成的 DML ,是对数据库锁做的一个操作或者修改. 所有事务都有开始和结束 事务可以被保存和撤销 如果事务在中途失败,事务中的任何部分都不 ... 
- 第十一章 TClientDataSet
		第十一章 TClientDataSet 与TTable.TQuery一样,TClientDataSet也是从TDataSet继承下来的,它通常用于多层体系结构的客户端.TClientDataSet最大 ... 
- 第十一章、认识与学习BASH
		第十一章.认识与学习 BASH 最近升级日期:2009/08/25 1. 认识 BASH 这个 Shell 1.1 硬件.核心与 Shell 1.2 为何要学文字接口的 shell 1.3 系统的合法 ... 
随机推荐
- linux c++ 内存泄漏检测工具:AddressSanitizer(ASan)
			1.介绍 AddressSanitizer(ASan),该工具为gcc自带,4.8以上版本均可以使用. 2.使用 编译的方式很简单,只需要添加 -fsanitize=address -g 即可,如 g ... 
- Jmeter(三十三) - 从入门到精通 - Jmeter Http协议录制脚本工具-Badboy6(详解教程)
			1.简介 今天分享的就是在上一篇文章的基础上来进行讲解和分享:Badboy使用数据源Excel进行脚本参数化.然后在使用读取的参数进行对比断言. 2.具体场景 Badboy录制一个搜索的脚本,并对搜索 ... 
- geoserver的demo使用过程
			先贴一个效果图,使用的geoserver版本2.18.0,需要对应版本插件netcdf插件[Extensions>Coverage Formats>NetCDF],使用tomcat8进行发 ... 
- Angular:组件之间的通信@Input、@Output和ViewChild
			①父组件给子组件传值 1.父组件: ts: export class HomeComponent implements OnInit { public hxTitle = '我是首页的头部'; con ... 
- PDF格式分析
			系列文章是csdn作者'秋风之刀'写的,我只是把目录列出来而已,感谢作者辛苦付出. PDF格式分析(一)简介 PDF格式分析(二)语法之对象 PDF格式分析(三)语法之Filter PDF格式分析(四 ... 
- SpringBoot + SpringSecurity + Mybatis-Plus + JWT + Redis 实现分布式系统认证和授权(刷新Token和Token黑名单)
			1. 前提 本文在基于SpringBoot整合SpringSecurity实现JWT的前提中添加刷新Token以及添加Token黑名单.在浏览之前,请查看博客: SpringBoot + Sp ... 
- 如何自定义Kubernetes资源
			目前最流行的微服务架构非Springboot+Kubernetes+Istio莫属, 然而随着越来越多的微服务被拆分出来, 不但Deploy过程boilerplate的配置越来越多, 且繁琐易错, 维 ... 
- RocketMQ集群搭建(3m-3s-async)
			RocketMQ集群搭建(3m-3s-async) 各角色介绍 角色 作用 Producer 消息发送者,将消息发送到 Broker.无状态,其与NameServer集群中的一个节点建立长连接,定期从 ... 
- 【剑指offer】01 二维数组中的查找
			题目地址:二维数组中的查找 题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照 ... 
- burpsuite暴力破解之四种方式
			给出字典排列.详情: 1. 2. 第一项:snipper(中译:狙击手) 1.为两个参数添加payload并且选中snipper,同时指定一个字典. 2.开始attack,并且给出响应结果. 可见有两 ... 
