C++ Primer:第11章 关联容器


第11章 关联容器

关联容器类型 描述
map 保存关键字-值对
multimap 关键字可重复的map
set 保存关键字
multiset 关键字可重复的set
unordered_map 哈希函数组织的map
unordered_multimap 哈希函数组织的,关键字可重复的map
unordered_set 哈希函数组织的set
unordered_multiset 哈希函数组织的,关键字可重复的set
  1. 关联容器与顺序容器的根本不同:关联容器按关键字来保存和访问元素;顺序容器按位置保存和访问元素。
  2. map是关键字-值对集合,关键字用于索引,值是与索引相关联的数据。
  3. set是关键字集合,支持高效的关键字查询操作。
  4. map和multimap定义在头文件map;set和multiset定义在头文件set;unordered_map、unordered_multimap定义在头文件unordered_map;unordered_set、unordered_multiset定义在头文件unordered_set
  5. multi前缀代表元素的关键字可以重复,unordered前缀代表无序关联容器。

11.1 使用关联容器

  1. map中的元素是pair,first保存关键字,second保存对应的值。
  2. 定义map需指定关键字和值的类型,定义set需指定关键字类型。

11.2 关联容器概述

  1. 关联容器按关键字存储元素,故不支持顺序容器的位置相关的操作,如push_front、push_back等。
  2. 关联容器不支持接受一个元素值和一个数量值的构造函数或插入操作。
  3. 无序关联容器不支持>、>=、<、<=。
  4. assign仅支持顺序容器,不支持关联容器。
  5. 关联容器的迭代器都是双向迭代器。

11.2.1 定义关联容器

  1. 定义map需指定关键字和值的类型,定义set只需指定关键字类型。
  2. 关联容器可用同类型容器来初始化,或一个可转换的值范围初始化。

11.2.2 关键字类型的要求

  1. 对于有序关联容器(map、multimap、set、multiset),关键字类型必须定义元素比较的方法,默认使用<运算符
  2. 有序关联容器的比较函数必须是严格弱序。<是严格弱序,<=不是严格弱序。
  3. 两个关键字等价,即任何一个都不“小于”对方。
  4. 严格弱序的比较函数F具备的基本性质:
    -- 若F(k1,k2)=true,则F(k2,k1)=false;
    -- 若F(k1,k2)=true, F(k2,k3)=true,则F(k1,k3)=ture;
    -- 若F(k1,k2)=false, F(k2,k1)=false,则k1=k2。若k1=k2,k2=k3,则k1=k3。
  5. 可以使用严格弱序的自定义比较函数替代<运算符。
bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)
{
    return lhs.isbn() < rhs.isbn();
}
multiset<Sales_data, decltype(compareIsbn) *> bookstore(compareIsbn);
multiset<Sales_data, bool (*)(const Sales_data &lhs, const Sales_data &rhs)> bookstore(compareIsbn);

11.2.3 pair类型

pair操作 描述
pair<T1,T2> p; p是一个数据成员类型为T1和T2的pair
对first和second值初始化
pair<T1,T2> p(v1, v2);
pair<T1,T2> p = {v1, v2};
p是一个数据成员类型为T1和T2的pair
对first,second用v1, v2初始化
make_pair(v1, v2) 返回一个用v1和v2初始化的pair
pair的类型由v1和v2的类型推断出来
p.first
p.second
返回p的名为first的public数据成员
返回p的名为second的public数据成员
p1 relop p2 关系运算符(<、<=、>、>=)按字典序定义
p1 == p2
p1 != p2
相等性判断利用元素的==运算符实现。
  1. pair定义在头文件utility
  2. pair类似于容器,是一个用来生成特定类型的模板。一个pair保存两个public数据成员,分别是first和second。
  3. map的元素是pair。
  4. 比较pair:若 p1.first<p2.first,或者 !(p2.first<p1.first) && p1.second<p2.second,则p1<p2。若p1.first=p2.first && p1.second=p2.second,则p1=p2。
  5. 在早期C++版本的函数中,不允许用花括号包围的初始化器来返回pair,必须显示构造返回值。

11.3 关联容器操作

关联容器额外的类型别名 描述
key_value 关键字类型
mapped_type 值类型,只适用于map
value_type 对于set,与const key_type相同
对于map,为pair<const key_type, mapped_type>

11.3.1 关联容器迭代器

  1. 关联容器迭代器指向类型为value_type的元素。
  2. 对于map,value_type是pair<const key_type, mapped_type>,其first成员保存const的关键字,second成员保存值。
  3. 对于set,value_type是const key_type。iteraroe与const_iterator都只能读取而不可修改set中的元素。
  4. 使用迭代器遍历map、multimap、set、multiset时,是按关键字升序遍历。
  5. 关联容器可用于只读算法;不宜对关联容器使用泛型搜索算法(如find等),因为关联容器中的元素可以直接通过关键字快速查找;不能将关联容器传递给修改或重排容器元素的算法,因为set的value_type是const key_type,map的value_type是pair<const key_type, mapped_type>,const不可修改。
  6. 通常不对关联容器使用泛型算法。在实际编程中,若真要对关联容器使用泛型算法,则关联容器要么是一个源序列,要么是一个目的位置。

11.3.2 添加元素

关联容器insert操作 描述
c.insert(v)
c.emplace(args)
1. v是value_type类型的对象;
 args用来构造一个元素。
2. 对于map和set,只有当关键字不存在时才会插入或构造元素。
 返回pair,first是一个迭代器,指向具有给定关键字的元素,
 second是bool值,表示是否插入成功。
3. 对于multimap和multiset,总会插入或构造给定元素。
 返回一个指向新元素的迭代器。
c.insert(p, v)
c.emplace(p, args)
1. p是迭代器,指示从p开始搜索新元素的插入位置。
2. 返回一个迭代器,指向具有给定关键字的元素。
c.insert(b, e)
c.insert(il)
1. b和e是迭代器,表示一个value_type类型值的范围;
 il是该值的花括号列表。
2. 返回void。
3. 对于map和set,只插入关键字不在c中的元素。
4. 对于multimap和multiset,则会插入范围内的所有元素。
  1. 对于非multi的关联容器,插入一个元素前需先看其关键字是否存在。若关键字已存在,则什么也不做;若关键字不存在,则插入该元素。
  2. map的元素类型是pair,添加元素时须构建pair对象。

11.3.3 删除元素

关联容器删除元素 描述
c.erase(k) 从c中删除所有关键字为k的元素
返回一个size_type值,指出删除的元素个数
c.erase(p) 从c中删除迭代器p指向的元素
p必须指向c中一个真实存在的元素,不能等于c.end()
返回一个指向p之后元素的迭代器,若p指向尾元素,则返回c.end()
c.erase(b, e) 删除[b,e)内的元素
返回e

11.3.4 map的下标操作

map的下标操作 描述
c[k] 返回关键字为k的元素
若k不在c中,则添加一个关键字为k的元素,并对元素的值进行值初始化
c.at(k) 访问关键字为k的元素
若k不在c中,则抛出out_of_range异常
  1. 只有map和unordered_map支持下标运算符和at函数,因为set只有关键字,无对应的值;multimap和unodered_multimap一个关键字可能对应多个值。
  2. 只能对非const的map和unordered_map使用下标操作,因为下标运算符可能会插入一个新元素。
  3. 通常情况,迭代器解引用返回的类型与下标运算符返回的类型一样,但map不同:解引用一个map迭代器得到一个value_type对象,对map进行下标操作得到一个mapped_type对象。
  4. 下标运算符返回的结果是左值。

11.3.5 访问元素

关联容器查找元素操作 描述
c.find(k) 返回一个迭代器,指向第一个关键字为k的元素
若k不在c中,则返回c.end()
c.count(k) 返回关键字为k的元素的个数
对于非multi的关联容器,返回值只能是0或1
c.lower_bound(k) 返回一个迭代器,指向第一个关键字大于等于k的元素
c.upper_bound(k) 返回一个迭代器,指向第一个关键字大于k的元素
c.equal_range(k) 返回一个pair,[first, second)表示关键字等于k的元素的范围
  1. lower_bound和upper_bound不适用于unordered的关联容器。
  2. 若k在c中,lower_bound返回的迭代器指向第一个具有给定关键字的元素,upper_bound返回的迭代器指向最后一个匹配关键字的元素之后的位置;若k不在c中,lower_bound和upper_bound返回相等的迭代器——指向一个不影响排序的关键字插入位置
  3. equal_range返回一个pair对象,[first, second)表示关键字等于k的元素的范围,若k在c中,first指向第一个匹配关键字的元素,second指向最后一个匹配关键字的元素之后的位置;若k不在c中,first=second,都指向关键字可以插入的位置。

11.3.6 一个单词转换的map

  1. c[key] = value 与 c.insert({key,value}) 的区别:对于multi的关联容器插入多个关键字相同的元素,前者取最后一次插入的元素,后者取第一次插入的元素

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) 第n个桶中const版本的首元素迭代器和尾后元素迭代器
哈希策略 描述
c.local_factor() 每个桶的平均元素数量,返回float
c.max_load_facor() c试图维护的平均桶大小,返回float
c会在需要时添加新桶使local_factor()<=max_load_facor
c.rehash(n) 重组存储,使得bucket_count>=n,且bucket_count>size/max_load_facor
c.reserver(v) 重组存储,使得c可以保存n个元素且不必rehash
  1. 有序关联容器通过比较运算符组织元素;无序关联容器使用哈希函数和==运算符组织元素。
  2. 若元素没有明显的序关系,或维护元素的序的代价高昂,则建议使用无序容器。
  3. 通常可用一个无序容器替换对应的有序容器,反之亦然。由于存储顺序的不同,两者的输出通常不同。
  4. 无序容器是集合,每个桶可保存零个或多个元素。访问元素时,容器先计算元素的哈希值,再搜索哈希值对应的桶。
  5. 若关键字是内置类型(包括指针类型)、string和智能指针类型,则可以直接定义对应类型的无序容器。
  6. 不能直接定义关键字类型是自定义类型的无序容器。对于自定义类型,hash模板不能直接使用,必须提供自定义的hash模板版本。
  7. 在无序或有序容器中,具有相同关键字的元素都是相邻存储的。
// 使用一个标准库hash类型对象来计算isbn成员的哈希值,该hash类型建立在string类型之上
size_t hasher(const Sales_data &sd)
{
    return hash<string>()(sd.isbn());
}
// 通过比较isbn来比较两个Sales_data
bool eqOp(const Sales_data &lhs, const Sales_data &rhs)
{
    return lhs.isbn() == rhs.isbn();
}
// 别名声明
using SD_multiset = unordered_multiset<Sales_data, decltype(hasher)*, decltype(eqOp)*>
// 定义自定义类型的无序容器
// 参数:桶大小,哈希函数指针,相等性判断运算符指针
SD_multiset bookstores(42, hasher, eqOp)

// 若自定义类型已定义==运算符,则可以只重载哈希函数
unordered_set<Foo, decltype(FooHash)*> fooSet(10, FooHash);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,729评论 6 517
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,226评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 169,461评论 0 362
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,135评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,130评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,736评论 1 312
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,179评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,124评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,657评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,723评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,872评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,533评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,213评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,700评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,819评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,304评论 3 379
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,876评论 2 361

推荐阅读更多精彩内容