本文为对《C++ primer (中文版第五版)》中有关关联容器的讲解和网上大神们的博客整理而成。
关联容器和顺序容器的根本不同在于:关联容器中的元素是按关键字来保存和访问的,而顺序容器中的元素则是按它们在容器中的位置来顺序保存和访问的。顺序容器有vector、deque、list、forward_list、array、string等,更多详情,可见melonstreet的博客。
关联容器支持高效的关键字查找和访问。主要的关联容器类型是map和set,其中map中的元素是一些关键字-值(key-value)对:关键字起到索引的作用,值则表示索引相关联的数据;set中每个元素只包括一个关键字,set支持高效的关键字查询操作-检查一个给定关键字是否在set中。关联容器类型如下:
一)关联容器的使用
类似顺序容器,关联容器也是模板,所以为了定义一个map必须要指定关键字和值的类型,定义一个set同样要指定关键字的类型。
1)map的使用--单词计数
1 //统计每个单词在输入中出现的次数 2 map<string,size_t> word_count; 3 string word; 4 5 while(cin>>word) 6 ++word_count[word];
关键字的类型是string,值的类型是int。进行下标操作是,我们使用string作为下标,得到与此string相关联的size_t类型的计数器。值得注意的是:如果word还未在map中,下标运算会创建一个新元素,其关键字是word,值为0,然后再将其值加1;从map中提取一个元素时,得到的是一个pair类的对象,pair为模板类型,保存两个名为first和second的公有数据成员,这里first成员保存关键字,second成员保存对应的值。
2)set的使用--忽略某些单词
1 //忽略输入中的指定单词,给其他单词计数 2 set<string> exclude={"The","But","and","a"}; 3 string word; 4 5 while(cin>>word) 6 { 7 if(exclude.find(word)==exclude.end()) 8 ++word_count[word]; 9 }
对关联容器的初始化可以进行列表初始化,如,第一行。find调用返回一个迭代器,若,给定关键字在set中,迭代器指向该关键字,否则,find返回尾后迭代器。本程序中第7行,仅当单词不在exclude中时,才更新计数器。
二)关联容器概述
关联容器不支持顺序容器的位置相关的操作,如push_back,因为,关联容器是根据关键词存储的。而且,关联容器也不支持构造函数或插入操作这些接受一个元素值和一个数量值的操作。
1)定义关联容器
每个关联容器都定义了一个默认构造函数,它创建一个指定类型的空容器。i)可以将关联容器初始化为另一个同类型容器的拷贝;ii)从一个值范围来初始化关联容器;iii)对关联 容器进行值初始化。
特别值得注意的是:初始化map时,必须提供关键字类型和值类型,我们将每个关键字-值对包围在花括号中:{key,value},来指出由它们一起构成map中的一个元素。如:
1 map<string,string> authors={ 2 {"Joyce","James"}, 3 {"Austen","Jane"} };
authors初始化后,仅有两个元素。另一种初始化方法如下:
1 vector<int> ivec(10,1); 2 set<int> iset(ivec.begin(),ivec.end());
2)关键字的要求
对于有序的容器-map、multimap、set及multiset,关键字的类型必需定义元素比较的方法。i)可以向算法提供我们自己定义的比较操作,但是该操作必须在关键字类型上定义一个严格弱序 (看做“小于等于”);ii)使用关键字类型的比较函数。
三)关联容器操作
针对map元素的类型:
1 map<string,int>::value_type v3; //v3是一个pair<const string,int>
1)当解引用一个关联容器迭代器时,会得到一个类型为容器的value_type的值的引用。值得注意的是:一个map的value_type是一个pair,可以改变pair的值,但是不可以改变关键字的关键字成员的值,同理set关键也是const的,可以使用一个set迭代器来读取元素,但不能修改。
1 auto map_it=word_count.begin(); 2 map_it->first="new_key"; //错误:关键字不可修改
遍历关联容器操作如下:
1 auto map_it=word_count.cbegin(); 2 while(map_it !=word_count.cend()) 3 { 4 ...... 5 } //也可以使用for循环
2)添加元素---map和set(以及对应的无序类型)包含不重复的关键字
i)对set, insert有两个版本:接受一对迭代器;一个初始化列表。
1 vector<int> ivec={2,4,6,8,2,4,6,8}; 2 set<int> set1; //注意无重复 3 set1.insert(ivec.begin(),ivec.end()); //set1中4个元素 4 set1.insert({1,3,5,7,1,3,5,7}); //set1中8个元素
ii)对map有四种方式,最简单的是:在参数列表中使用花括号初始化。
word_count.insert({word,1});
iii)检测insert的返回值
对于不包含重复关键字的关联容器,insert返回的是一个pair,pair的first成员是一个指向给定关键字的元素的迭代器;second成员是一个bool型,若该关键字已在容器中,bool部分返回false,否则为true。
iv)删除元素
v)我们不能对一个multi-的关联容器进行下标操作,因为这些容器中可以有多个值和一个关键字向关联。注意区分,对map进行下标操作和解引用一个map迭代器的区别,前者返回一个mapped_type对应,后者返回一个value_type(即pair)。
vi)访问元素
若,我们所关心的只是一个特定的元素是否已在容器中,最好的选择是find,至于count在multi-容器中有更多的工作。
值得注意的是:使用下标计数依赖这样一个特性:使用一个不存在的关键字作为下标,会插入一个新元素,其关键字为给定关键字,其值为0。有时候,若仅想知道给定关键字是否在map中,不想改变map,需使用find。
针对multi-关联容器,可以使用:
lower_bound和upper_bound来解决问题,这两个操作都是接受一个关键字,返回一个迭代器。若关键字在容器中,lower_bound返回的迭代器将指向第一个叫 具有该关键字的元素,而upper_bound返回的迭代器则指向最后一个匹配该关键字的元素之后的位置。若不在容器中,则两者返回相等的迭代器--指向一个不影响排序的关键字插入位置。
1 string dic="and"; 2 for(auto beg=word_count.lower_bound(dic),end=word_count.upper_bound(dic);beg !=end;++beg) 3 { 4 .... 5 }
使用equal_range函数。直接调用equal_range即可,该函数接受一个关键字,返回一个迭代器pair,若关键字存在,第一个迭代器指向第一个关键字匹配的元素,第二迭代器指向最后一个匹配元素之后的位置。
string dic="and"; for(auto pos=word_count.equal_range(dic);pos.first !=pos.second;++pos.first) { .... }
四)无序容器
这些容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的==运算符。无序容器在存储上组织为一组桶,每个桶保存零个或多个元素。