《C++ Primer》第11章 关联容器

参考资料:

  • 《C++ Primer》第5版
  • 《C++ Primer 习题集》第5版

关联容器支持高效关键字查找和访问,两个主要的关联容器是 mapsetmap 中的元素是键-值( key value )对set 中的元素只包含一个关键字。

标准库提供 8 个关联容器:

c1bb87d4899db8488ee0fe08d9b3f9e

容易看出,上面的 8 个关联容器的不同点主要体现在 3 个维度上:

  1. 或者是 set ;或者是 map
  2. 或者要求不重复关键字;或者允许重复关键字,以 multi 开头
  3. 或者按顺序保存元素;或者无序保存,以 unordered_ 开头

mapmultimap 定义在头文件 map 中;setmultiset 定义在头文件 set 中;无序容器则分别定义在头文件 unordered_mapunordered_set 中。

11.1 使用关联容器(P374)

map 通常被称为关联数组( associative array ),可以通过关键字来查找值;set 可以用来判断某个值是否存在。

使用map

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

map<string, size_t> word_count;
string word;
while (cin >> word) {
	++word_count[word];
}
for (const auto &w : word_count) {
	cout << w.first << ' ' << w.second << endl;
}

map 也是模板,在定义时必须指定关键字和值的类型。在 while 循环中,如果关键字 wordword_count 中还不存在,下标运算符会创建一个新元素,其关键字为 word ,值为 0 。当从 map 中提取一个元素时,会得到一个 pair 对象,其 first 成员保存关键字,second 成员保存值。

使用set

如果我们在上述程序的基础上,想要忽略一些常见单词,可以使用 set

// 将需要忽略的单词保存在set中
set<string> exclude = { "the","and","a","an" };
// 判断当前单词word是否需要忽略
if(exclude.find(word) == exclude.end())
    ...

11.2 关联容器概述(P376)

关联容器(有序和无序)支持大多数普通容器的操作,除了:

  • 和位置相关的操作,如 push_backpush_front
  • 接受一个元素值和一个数量值的构造函数和插入操作。

关联容器的迭代器都是双向的。

11.2.1 定义关联容器(P376)

每个关联容器都定义了一个默认构造函数,它创建一个空容器;我们也可以将关联容器初始化为另一个同类型容器的拷贝;也可以对关联容器进行值初始化:

map<string, string> authors = {
	{"Joyce", "James"},
	{"Austen", "Jane"}
};

初始化multimapmultiset

vector<int> vi;
// 定义一个含有20个元素的vector
for (int i = 0; i < 10; ++i) {
	vi.push_back(i);
	vi.push_back(i);    // 重复一次
}
set<int> si(vi.cbegin(), vi.cend());    // si的size为10
multiset<int> msi(vi.cbegin(), vi.cend());    // msi的size为20

11.2.2 关键字类型的要求(P378)

对于有序容器,关键字类型必须定义元素比较的方法,默认使用 < 运算符。

有序容器的关键字类型

我们可以提供自己定义的操作来替换关键字类型的 < 运算符,所提供的操作必须在关键字类型上定义一个严格弱序(strict weak ordering)。严格弱序可以看作“小于”,具有如下基本性质:

  • 两个关键字不能同时“小于”对方;
  • 如果 a “小于” b 且 b “小于” c ,那么 a “小于” c ;
  • 如果两个关键字都不“小于”对方,则这两个关键字等价

如果两个关键字是等价的,那么容器将它们视作相等

使用关键字类型的比较函数

为了指定使用自定义操作,我们必须在定义关联容器类型时提供操作的类型,操作类型在尖括号中紧跟着元素类型给出:

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

multiset<Sales_data, decltype(compareIsbn)*>
    bookstore(compareIsbn));

此处,我们使用 decltype 来指出自定义操作的类型,用 compareIsbn 来初始化 bookstore 对象,表明当我们向 bookstore 中添加元素时,调用 compareIsbn 来为这些元素排序。

11.2.3 pair类型(P379)

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

pair<string, string> anon;
pair<string, size_t> word_count;

pair 的默认构造函数对数据成员进行值初始化pair 的数据成员是 public 的,两个成员分别命名为 firstsecondpair 支持的操作如下:

2d633684fee8d2c54767091fb3cd7d4

创建pair对象的函数

pair<string, size_t> process(vector<string> &v) {
	if (!v.empty()) {
		return { v.back(), v.back().size() };
	}
	else {
		return {};
	}
}

上面的代码中,我们使用了初始值列表来返回 pair 。此外,我们还可以显式构造返回值或者使用 make_pair 函数:

return pair<string, size_t>(v.back(), v.back().size());

return make_pair(v.back(), v.back().size())

11.3 关联容器操作(P381)

关联容器还定义了一些类型别名

7db39818b8341c2e441ab334da43ad2
set<string>::value_type v1;    // string
set<string>::key_type v2;    // string
map<string, int>::value_type v3;    // pair<const stirng, int>
map<string, int>::key_type v4;    // string
map<string, int>::mapped_type v5;    // int

11.3.1 关联容器迭代器(P382)

解引用关联容器迭代器时,会得到一个 value_type 的引用:

auto map_it = word_count.begin();
map_it->first = "new key";    // 错误,关键字是const
map_it->second = 0;    // 正确

set的迭代器是const

set 类型同时定义了 iteratorconst_iterator ,但两种类型都只允许只读访问 set 中的元素:

set<int> iset = { 0,1,2 };
auto set_it = iset.begin();
*set_it = 1;    // 错误

遍历关联容器

auto map_it = word_count.cbegin();
while (map_it != word_count.end()) {
	cout << map_it->first << ' ' << map_it->second;
	++map_it;
}

本程序的输出是按照字典序排列的。

关联容器和算法

我们通常不对关联容器使用泛型算法。在实际编程中,如果我们真要对一个关联容器使用算法,要么将它当成一个源序列,要么当作一个目的位置。例如,使用 copy 算法将关联容器中的内容拷贝到另一个序列。

练习

c95a5aa0961e88ce71564d030a723fd

只有第二个调用是错误的,因为 back_inserter 是通过容器的 push_back 成员实现元素插入的,而关联容器没有 push_back 成员。

11.3.2 添加元素(P383)

向不允许重复关键字的容器(如 mapset )插入一个已经存在的关键字,对容器没有任何影响

vector<int> vi = { 0,1,2,3 };
set<int> s;
s.insert(vi.cbegin(), vi.cend());
s.insert({ 0,1,2 });
s.insert(4);    // 只添加一个元素也是可以的

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));
55c0d8b739520b29b72fae0799bda88

检测insert的返回值

对于不允许重复关键字的关联容器,添加单一元素的 insertemplace 返回一个 pairpairfirst 成员是一个迭代器,指向具有给定关键字的元素second 成员是一个 bool 值,指出元素是否插入成功(插入成功为 true):

map<string, size_t> word_count;
string word;
while (cin >> word) {
	auto it = word_count.insert({ word,1 });
	if (!it.second) {
		++it.first->second;
	}
}

multisetmultimap添加元素

对于允许关键字重复的关联容器,接受单个元素的 insert 返回一个指向新元素的迭代器

11.3.3 删除元素(P386)

0c6e296d8421cbf36821b044c0ccbae

关联容器提供一个特殊版本的 erase :它接受一个 key_type 参数,删除所有关键词匹配的元素,返回实际删除的元素数量。

11.3.4 map的下标操作(P387)

mapunordered_map 都支持下标运算符和 at 函数,但 multimapunordered_multimap 不支持。

map 的下标运算符接受一个关键字。如果该关键字不在 map 中,下标运算符会为它创建一个元素并插入到 map 中,关联值进行值初始化。

5a1c69eed6b23bf8cd366d4f3a3d39c

使用下标操作的返回值

对一个 map 进行下标操作时,会获得一个 mapped_type 对象;解引用一个 map 迭代器时,会得到一个 value_type 对象。

11.3.5 访问元素(P388)

df3cb934f7a853a075fd797234cdea1 beefc3a822da4c064cf3e2b59b24b7d

需要纠正的是,constmapunordered_map 是可以使用 at 操作的。

当我们需要判断一个特定元素是否在容器中时,find 是最佳选择。

map使用find代替下标操作

前面提到,下标运算符会在关键字不存在时插入对应元素,如果我们不希望插入新元素的话,可以使用 find

multimapmultiset中查找元素

如果一个 multimapmultiset 中有多个元素具有相同的关键字,则这些元素在容器中会相邻存储

假设我们有一个作者到著作的 multimap 映射,我们想要打印一个特定作者的所有著作,可以通过三种不同的方法来解决这个问题:

  1. 使用 findcount

    auto it = authors.find(name);
    auto cnt = authors.count(name);
    while(cnt--){
        cout << it->second << endl;
        ++it;
    }
    
  2. 使用 lower_boundupper_bound 。如果关键字在容器中,lower_bound 指向第一个具有给定关键字的元素,upper_bound 指向最后一个匹配元素之后的位置;如果关键字不在容器中,lower_boundupper_bound 会返回相等的迭代器,指向一个不影响排序的关键字插入位置

    for (auto beg = authors.lower_bound(name),
    	end = authors.upper_bound(name); beg != end; ++beg) {
    	cout << beg->second << endl;
    }
    
  3. 使用 equal_range 函数。此函数接受一个关键字,返回一个迭代器 pairfirst 成员和 lower_bound 返回值相同,second 成员和 upper_bound 返回值相同:

    for (auto pos = authors.equal_range(search_item);
    	pos.first != pos.second;++pos.first) {
    	cout << pos.first->second << endl;
    }
    

11.4 无序容器(P394)

**无序关联容器(unordered associative container)**使用哈希函数和关键字类型的 == 来组织元素。

虽然理论上哈希技术具有较好的平均性能,但实际中想要达到很好的效果需要进行一些测试和调优工作。通常来讲,使用有序容器更为简单,也更为高效(这里中文版翻译错了)。

使用无序容器

通常可以用无序容器替换对应的有序容器。

管理桶

无序容器在存储上组织为一组,每个通保存零个或多个元素。为了访问一个元素,容器首先计算元素的哈希值,根据哈希值顺序搜索对应的桶。无序容器的性能依赖于哈希函数的质量和桶的数量。

无序容器提供了一组管理桶的函数:

cdaafd9d016087a8d912923ed1361eb

无序容器对关键字类型的要求

默认情况下,无序容器使用关键字类型的 == 来比较元素,使用 hash<key_type> 类型的对象来生成元素的哈希值。标准库为内置类型、string 和智能指针提供了 hash 模板。

如果我们想要定义关键字类型自定义类型的无序容器,必须提供 hash 模板版本。为了将 Sale_data 用作关键字,我们可以提供函数来替代 == 和 哈希函数:

size_t hasher(const Sales_data &sd){
    // 可以理解为:先利用默认构造函数构造一个匿名对象,然后调用对象重载的圆括号运算符
    return hash<string>()(sd.isbn());
}
bool equal(const Sales_data &lhs, const Sales_data &rhs){
    return lhs.isbn() == rhs.isbn();
}

然后,我们可以利用上面的函数定义无序容器:

using SD_multiset = unordered_multiset<Sales_data,
	decltype(hasher) *, decltype(equal) *>;
// 参数是桶数量的下限、哈希函数指针、相等判断函数指针
SD_multiset bookstore(42, hasher, equal);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/219796.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言学习笔记之数组篇

数组是一组相同类型元素的集合。 目录 一维数组 数组的创建 数组的初始化 数组的使用 数组在内存中的存储 二维数组 数组的创建 数组的初始化 数组的使用 数组在内存中的存储 数组名 数组名作函数参数 一维数组 数组的创建 type_t arr_name [const_n]; //type_…

Python采集豆丁网站文档数据内容, 保存word文档

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 开发环境: 版 本&#xff1a; python 3.8 编辑器&#xff1a;pycharm 2022.3.2 模块使用: requests --> pip install requests re base64 docx --> pip …

vue中的动画组件使用及如何在vue中使用animate.css

“< Transition >” 是一个内置组件&#xff0c;这意味着它在任意别的组件中都可以被使用&#xff0c;无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发&#xff1a; 由 v-if 所触发的切换由 v-show 所触…

K8S部署nginx并且使用NFS存储数据

安装NFS 在master安装NFS systemctl start nfs-server修改配置 /etc/exports /data *(rw,no_root_squash,no_all_squash,sync)目录为 /data 允许所有地址访问 验证下 [rootmaster nginx]# showmount -e 192.168.57.61 Export list for 192.168.57.61: /data *共享可以正常…

我不是DBA之慢SQL诊断方式

最近经常遇到技术开发跑来问我慢SQL优化相关工作&#xff0c;所以干脆出几篇SQL相关优化技术月报&#xff0c;我这里就以公司mysql一致的5.7版本来说明下。 在企业中慢SQL问题进场会遇到&#xff0c;尤其像我们这种ERP行业。 成熟的公司企业都会有晚上的慢SQL监控和预警机制。…

手动创建spring bean并注入

文章目录 前言一、jar包中,相同class不同类加载器加载的时候是同一个class嘛&#xff1f;二、利用ConfigurableListableBeanFactory手动注册bean注册bean,并自动注入依赖bean根据类型获取注入的bean,两个bean是一个吗? 三、同一份字节码,class隔离,bean隔离总结 前言 注入一个…

2952. 需要添加的硬币的最小数量(结论题)

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 分析知&#xff1a;设指针值从1开始依次递增&#xff0c;每次将coins里的值累加起来看能否得到或者大于当前指针值 &#xff0c;否则就将该指针值累加起来&#xff0c;即需要添加的数 class Solution …

JOSEF 单相电压继电器 WY-31A1 DC220V 过压动作,导轨安装

系列型号 单相 JY-45A1电压继电器&#xff1b;JY-45B1电压继电器&#xff1b; JY-45C1电压继电器&#xff1b;JY-45D1电压继电器&#xff1b; JY-41A1电压继电器&#xff1b;JY-41B1电压继电器&#xff1b; JY-41C1电压继电器&#xff1b;JY-41D1电压继电器&#xff1b; …

冬天来了,波司登的高端化“春天”不远了?

最近&#xff0c;羽绒服频繁“贵”上热搜。 在众多热搜词条中&#xff0c;一条“国产羽绒服卖到7000元”的话题一度将波司登推上了舆论的风口浪尖。 对此&#xff0c;波司登在最新的业绩说明会上进行了回应&#xff0c;公司表示&#xff1a;“波司登旗下主品牌及子品牌将形成差…

学习数分--简单案例1

业务背景&#xff1a;某服务类app&#xff0c;近期发现日新增用户数下滑明显。 具体描述&#xff1a;假设公司产品&#xff08;一款本地服务类app&#xff09;&#xff0c;近期发现日新增用户数下滑明显。老板要求你分析&#xff1a;数据异动的原因是什么&#xff1f; #最开始…

揭秘DeepMind、OpenAI成立内幕,马斯克、奥特曼、佩奇、哈萨比斯的爱恨情仇......

前些天OpenAI内斗的政权之争&#xff0c;相信各位看官在吃瓜的同时会感到大为震撼。OpenAI这次“政变”事件&#xff0c;让世人第一次看到那些将决定人工智能发展未来的科技大佬之间的激烈争斗。 但权利的斗争在硅谷AI激荡发展十余年中绝不是第一次。《纽约时报》为此采访了80…

VBA技术资料MF92:将多个Excel表插入Word文档的不同位置

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

Hadoop学习笔记(HDP)-Part.12 安装HDFS

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

MATLAB|学习小提示

Part1一些小小小提示 1遇到问题怎么办 不要怕提问&#xff0c;谁都是新手过来的&#xff0c;matlab程序我是自学的从来也没人教过我&#xff0c;我不懂就百度解决的&#xff0c;作为初学者&#xff0c;你遇到的问题&#xff0c;其他人也大多遇到过&#xff0c;绝大多数百度可以…

avamar DD组合的备份故障

证书过期导致的失败 先是显示DD页面崩了 Avamar DD 集成 — DD 在 Avamar AUI/GUI 中显示红色解决方案路径 | Dell 中国 排查了一番 尝试了重启DD 然而并没用 然后尝试更新证书 页面确实起来了 但是证书还是更新失败 确定原因还是因为版本太低而宣告失败 证书更新失败 …

Flannel源码解析

Flannel源码解析 项目地址: https://github.com/flannel-io/flannel 更多文章访问 https://www.cyisme.top flannel中有三种工作模式: udp。 性能最低&#xff0c;利用tun/tap设备&#xff0c;通过udp封装ip包。中间需要经过多次内核态和用户态的切换。vxlan。 性能中等&…

判断一个链表是否为回文结构

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 &#x1f324;️题目结构 给定一个…

Python技术操作1-高效办公:将文本、图片和表格信息批量写入Word文档

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下Python技术操作1-高效办公:将文本、图片和表格信息批量写入Word文档。在现代办公、教育、科研等多个领域都有广泛的应用场景。本文列举一些具体的应用场景&#xff0c;并简要说明其中的原理&#xff0c;并介绍实现的…

C++入门第十一篇----多态

前言&#xff1a; 和前面的继承一样&#xff0c;多态也是对类和对象的功能进行扩展&#xff0c;以让其更加好用的一个知识点&#xff0c;接下来&#xff0c;就让我们总结一下多态&#xff0c;这个依托了继承的一个重要知识点。 对多态的理解和多态的概念&#xff1a; 何为多…

基于go文件同步工具的升级迭代

介绍 同样&#xff0c;该工具适用于多个项目不同版本的维护&#xff0c;文件更新和新增的同步(自动创建目录)&#xff0c;支持自动提交svn。 升级迭代 之前的文件同步工具&#xff0c;依赖chrome和http包&#xff0c;有时候js加载页面不太稳定&#xff0c;所以有空闲就升级迭…