关联容器

关联容器与顺序容器的本质差别在于:关联容器通过键值(key)存储和读取元素,而顺序容器则通过袁术在容器中的位置存储和访问元素。

1 关联容器类型概述

关联容器(Associative containers)支持通过键来高效地查找和读取元素。两个基本的关联容器类型是mapset。标准库提供了8个关联容器:

  • map类型通常被称为"关联数组"map 的元素以键-值(key-value)对的形式组织:键用作元素在 map 中的索引,而值则表示所存储和读取的数据。关联数组与普通数组类似,不同之处在于其下标不必是整数,需要通过关键值而非位置来查找对应值。

  • set类型是简单关键词的集合,高效存储和访问不同的关键词。set 仅包含一个键,并有效地支持关于某个键是否存在的查询。

  • 另外6个类型与map以及set类型相关,multi前缀类型支持重复的关键词,unorder前缀类型是无序集合,用哈希函数组织。

2 关键字要求

关联容器对其关键字有一些限制:

2.1 有序容器关键字

默认情况下,标准库使用关键字类型的<运算符来比较两个关键字。用户可以提供一个自己定义的操作来替代关键字上的<,所提供的操作必须是严格弱序的。所谓严格弱序,必须遵序以下几点:

  • 不能同时出现a<= b, b<=a的情况
  • 如果a<=b且b<=c,那么a<=c
  • 如果存在a不小于c,c不小于等于a的情况,则a,c等价。如果a,c等价,b,c等价,则a,c等价。
2.2 无序容器关键字

默认情况下,使用关键字类型==运算符来比较元素,它们还使用一个hash<key_type>类型的对象为每个元素生成哈希值。标准库为内置类型,包括指针提供了哈希模板,还为string等标准库类型定义了哈希,因此上述类型可直接定义无需容器。

当使用自定义类型定义无序容器时,我们还需要通过模板特例化,提供自己的hash模板版本。

3 pair类型

在介绍关联容器之前,我们需要了解名为pair的标准库类型,它定义在头文件utility中。

一个pair保存两个数据成员。类似容器,pair是一个用来生成特定类型的模板。创建一个pair时,必须提供两个类型名,pair的数据成员将具有对应的类型。pair上定义的操作如下:

除了构造函数,标准库还定义了一个make_pair函数,由传递给它的两个实参生成一个新的pair对象,实例如下:

pair<string, string> example;
string first, second;
example = make_pair(first, second);

提示:pair类型定义较为繁琐,适当使用typedef可以提高编程的效率。

4 关联容器的操作

4.1 类型操作

关联容器支持以下类型:

set<string>::value_type v1;          //v1是string类型
set<string>::key_type v2;            //v2是string类型
map<string, int>::value_type v3;     //v3是pair<string,int>类型
map<string, int>::key_type v4;       //v4是string类型
map<string, int>::mapped_type v5;    //v5是int类型
4.2 迭代器

当解引用一个关联容器迭代器时,我们会得到一个类型为value_type的值的引用。对map而言,这是一个pair,其firstconst的关键字,second成员保存值。对set而言,其迭代器是const的,其关键字不能修改,示例如下:

map<string, int> mWordCnt;
auto m_iter = mWordCnt.begin();
//*m_iter是指向pair<tring, int>对象的引用
//iter->first是const,不能修改;只能修改其关联的值
m_iter->first = "new key";  //错误
++m_iter->second;            //正确
cout << m_iter->first << " " << m_iter->second;

set<string> sNames;
auto s_iter = sNames.begin();
*s_iter = "new key";          //错误,关键字不能修改

mapset都支持用beginend函数问出首尾迭代器,并以此进行关联容器的遍历。由于关键字是const这一特性,我们不能将关联容器传递给修改或者重排容器元素的算法。在实际编程中,我们不通常不对关联容器使用泛型算法,即使使用,也只是将其作为一个源序列,或者当做一个目标位置来存放计算结果。

4.3 添加元素

关联容器的inset成员可以向容器中添加一个元素或者一个元素的范围。由于mapset不包含重复的关键字,因此插入一个已经存在的元素没有任何影响。

向map添加元素

在C++11中,使用insert函数添加单个元素通常有以下四种操作方式:

map<string, int> mWordCnt;
mWordCnt.insert({ "fiset", 1 });
mWordCnt.insert(make_pair("fiset", 1));
mWordCnt.insert(pair<string, int>("fiset", 1));
mWordCnt.insert(map<string, int>::value_type("fiset", 1));  

除上述的四种方式,标准库还支持insert某个范围的元素。需要注意的是:在插入单个元素时,insert函数会返回一个pairfirst是当前成员的迭代器,second是一个bool变量,当插入成功是返回true,如果该元素已经存在则返回false;在插入某个范围的元素时,函数只返回void。详细说明如下表:

向set添加元素

同样,可以使用insertset容器添加元素:

set<string> sStr; // empty set
sStr.insert("the"); // sStr now has one element
sStr.insert("and"); // sStr now has two elements

另一种用法是,调用insert函数时,提供一对迭代器实参,插入其标记范围内所有的元素。该版本的insert函数类似于形参为一对迭代器的构造函数——对于一个键,仅插入一个元素:

vector<string> vStr = { "it`s", "a", "test" };
set<string> sStr2;  // empty set
sStr2.insert(vStr.begin(), vStr.end()); // sStr2 has 3 elements
4.4 删除元素

关联容器定义了三个版本的erase。与顺序容器一样,我们可以通过传递给erase一个迭代器或者迭代器对,来删除一个元素或者一个元素的范围。这与添加元素十分相似,不再赘述。

关联容器提供了一个额外的erase,它接受一个key_type参数。此版本删除所有匹配给定关键词的元素,并返回实际删除的数量。示例如下:

vector<string> vStr = { "a", "a", "a" };
multiset<string> sStr3;  // empty set
sStr3.insert(vStr.begin(), vStr.end()); // sStr2 has 3 elements
auto nCnt = sStr3.erase("a");// nCnt  == 3
4.5 map下标

set类型不支持下标,因为set中没有关键字相关联的“值”,“值”本身就是关键字。我们可以对mapunordermap容器进行下标运算符以及at函数的操作。但是,不能对multi的版本进行相应操作,因为后者可能出现多个值与一个关键字对应的情况。

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

map<string, int> mWordCnt;
mWordCnt["basketball"] = 1;

上述代码将会执行如下操作:

  • mWordCnt中查找basketball关键字,未找到
  • 将一个新的关键字-值对插入mWordCnt中,关键字是一个const string,保存basketball,并对second进行默认初始化(int初始化为0)
  • 取出新插入的元素,并将1值赋予它

另外,需要注意的是,map下标运算法返回的是mapped_type对象,但在解引用一个map迭代器时,会得到一个value_type对象。

4.6 元素访问

find函数可以返回指向元素的迭代器,如果元素不存在,则返回尾后迭代器。count返回元素的个数,如果不需要计数,只需要知道是否存在,建议使用find,`可以减少函数工作了,提供效率。

对于multi前缀的类型,一个关键字可能对应多个值,其访问通常有三种方式:

    1. 先用count问出数量,后用find问出第一个元素的位置,最后依次遍历
auto nCnt = mWordCnt.count("basketball");
auto iter = mWordCnt.find("c");
while (nCnt){
    cout << iter->second << endl;
    ++iter;
    --nCnt;
}
    1. 使用lower_boundupper_bound问出关键字的上下界。注意:无需容器不支持这两个函数
multimap<string, int> mWordCnt;
for (auto beg = mWordCnt.lower_bound("basketball"),
    end = mWordCnt.upper_bound("basketball");
    beg != end; ++beg){
        cout << beg->second << endl;
}
    1. 使用equal_range函数,该函数返回一个迭代器pair类型,first是指向第一个元素的迭代器,second指向该关键字尾后迭代器的值。
multimap<string, int> mWordCnt;
for (auto range = mWordCnt.equal_range("basketball"); 
    range.first != range.second; ++range.first){
        cout << range.first->second << endl;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容

  • #include set,multiset #include map,multimap #include unor...
    龙遁流阅读 337评论 0 2
  • 关联容器支持通过键来高效地查找和读取元素,两个基本的关联容器类型是map和set。map的元素以键-值(key-v...
    Mr希灵阅读 1,143评论 0 1
  • 11关联容器 11.1使用关联容器 Map,关键字-值对的集合。例如,可以将一个人的名字做关键字,其电话号码作为值...
    龟龟51阅读 201评论 0 0
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,511评论 1 51
  • 暗夜 我们沉默地站着 多想 你能给我一个拥抱 让我感知你的温度 多想 你能给我一次肯定 我才能 在你的眼里看见自己...
    一江春水1990阅读 407评论 4 9