关联容器和顺序容器有着根本的不同:关联容器的元素是按关键字来保存和访问的。与之相对,顺序容器中的元素是按他们在容器中的位置来顺序保存和访问的
按关键字有序保存元素 | |
map | 关联数组:保存关键字-值对 |
set | 只保存关键字的容器 |
multimap | 关键字可重复出现的map |
multiset | 关键字可重复出现的set |
无序集合 | |
unordered_map | 用哈希函数组织的map |
unordered_set | 用哈希函数组织的set |
unordered_multimap | 用哈希函数组织的map:关键字可重复出现 |
unordered_multiset | 用哈希函数组织的set:关键字可重复出现 |
使用map
map是关键字-值对的集合,例如:姓名-电话号码。map类型通常被称为关联数组,与数组不同之处在于其下标不必是整数,而是通过一个关键字而不是位置来查找值。
map word_count; //string到size_t的空map
string word;
while(cin>>word)
++word_count[word]; //提取word的计数器并将其加1
for(const auto &w:word_count)
cout<1)?"times":"time")<
while循环中,如果word还未在map中,下标运算符会创建一个新元素,其关键字为word,值为0.不管元素是否是新创建的,我们都将其值加1.
从map中提取一个元素时,会得到一个pair类型的对象。简单来说,pair是一个模板类型,保存两个名为first和second的(公有)数据成员。
使用set
//统计输入中每个单词出现的次数
map word_count;
set exclude={"The","But","And","Or","An","A"};
string word;
while(cin>>word)
//只统计不再exclude中的单词
if(exclude.find(word)==exclude.end())
++word_count[word];
关联容器都支持普通容器操作。关联容器不支持顺序容器的位置相关的操作,例如push_back或push_front。而且,关联容器也不支持构造函数或插入操作这些接受一个元素值和一个数量值的操作。
关联容器的迭代器都是双向的。
空容器+拷贝初始化+范围初始化+值初始化:
map word_count; //空容器
//列表初始化
set exclude={"the","but","and","or","an","a"};
//三个元素;authors将姓映射为名
map authors={{"Joyce","James"},
{"Austen","Jane"},{"Dickens","Charles"}};
容器multimap和multiset允许多个元素具有相同的关键字
对于有序容器——map、multimap、set、multiset,关键字类型必须定义元素比较的方式。默认情况下,标准库使用关键字类型的<运算来比较两个关键字。在集合类型中,关键字类型就是元素类型;在映射类型中,关键字类型是元素的第一部分的类型。
传递给排序算法的可调用对象必须满足与关联容器中关键字一样的类型要求。
我们可以自定义操作来代替关键字上的<操作,但所提供的操作必须在关键字类型上定义一个严格弱序——可以看做“小于等于”
在实际编程中,重要的是,如果一个类型定义了“行为正常”的<运算符,则它可以用作关键字类型
使用关键字类型的比较函数
当关键字类型使用自己定义的操作时,必须在定义关联容器类型是提供此操作的类型。即,用尖括号指出要定义哪种类型的容器,自定义的操作必须在尖括号中紧跟着元素类型给出。
用compareIsbn来初始化bookstore对象,这表示当我们向bookstore中添加元素时,将通过调用compareIsbn来为这些元素排序。
bool compareIsbn(const Sales_data &lhs,const Sales_data &rhs)
{
return lhs.isbn()
bookstore(compareIsbn);
pair类型定义在头文件utility中
pair |
p是一个pair,并进行值初始化 |
pair |
用v1和v2对p进行初始化 |
pair |
等价于p(v1,v2); |
make_pair(v1,v2) | 返回一个用v1和v2初始化的pair。pair的类型从v1和v2的类型推断出来 |
p.first | 返回p的first数据成员(公有) |
p.second | 返回p的second数据成员(公有) |
p1 relop p2 | 关系运算符(< > <= >=)按字典序定义 |
p1==p2 | 当first和second成员分别相等时,两个pair相等 |
p1!=p2 |
创建pair对象的函数
pair
process(vector &v)
{
if(!v.empty())
//以下三种创建pair的方法等价
return {v.back(),v.back().size()}; //列表初始化
return pair(v.back(),v.back().size());
return make_pair(v.back(),v.back().size());
else
return pair(); //隐式构造函数返回值
}
key_type | 此容器类型的关键字类型 |
mapped_type | 每个关键字关联的类型;只适用于map |
value_type | 对于set,与key_type相同 对于map,为pair |
当解引用一个关联容器迭代器时,我们会得到一个类型为容器的value_type的值的引用。
必须记住,一个map的value_type是一个pair,我们可以改变pair的值,但是不能改变关键字成员的值
auto map_it=word_count.begin();
//*map_it是指向一个pair对象的引用
cout<first;
cout<<" "<second;
map_it->first="new_key"; //错误:关键字是const的
++map_it->second; //正确:可以通过迭代器改变元素
set的迭代器是const的
一个set中的关键字也是const的,可以用一个set迭代器来读取元素的值,但不能修改。
遍历关联容器
当使用一个迭代器遍历一个map、multimap、set或multiset时,迭代器按关键字升序遍历元素
关联容器和算法
我们通常不对关联容器使用泛型算法。因为关键字是const这一特性使得不能对容器使用修改或重排的算法,另外,关联容器的元素不能通过他们的关键字进行(快速)查找,因此对其使用泛型搜索算法是个坏主意
在实际编程中,如果我们真的要对一个关联容器使用算法,要么将它当做一个源序列,要么当做一个目的位置
c.insert(v) | 对于map和set,只有当元素的关键字不在c中才插入(构造)元素。函数返回一个pair,包含一个迭代器,指向具有指定关键字的元素,以及一个指示插入是否成功的bool值。 对于multimap和multiset,总会插入(构造)给定元素,并返回一个指向新元素的迭代器 |
c.emplace(args) | |
c.insert(b,e) | 函数返回void 对于map和set,只有当元素的关键字不在c中才插入(构造)元素。对于multimap和multiset,总会插入(构造)范围中的每个元素 |
c.insert(il) | |
c.insert(p,v) | 将元素插入指定元素,返回一个迭代器,指向具有给定关键字的元素 |
c.emplace(p,args) |
向map添加元素
//向word_count插入word的4种方法
word_count.insert({word,1});
word_count.insert(pair(word,1));
word_count.insert(make_pair(word,1));
word_count.insert(map::value_type(word,1));
检测insert的返回值
如果关键字已在容器中,则insert什么事情也不用做,且返回值中的bool部分为false;如果关键字不存在,则元素被插入容器中,且bool值为true。
//统计每个单词在输入中出现次数的一种更繁琐的方法
map word_count;
string word;
while(cin>>word)
{
//ret->first返回迭代器指向的元素,为一pair, ++ret->first.second递增其计数器
auto ret=word_count.insert({word,1});
if(!ret->second)
++ret->first.second;
}
ret——是一个pair类型,
ret.first——是一个map迭代器,指向具有指定关键字的元素
ret.first->——是个pair,
ret.first->second——map中元素的值部分
++ret.first->second——递增此值
向multiset或multimap添加元素
由于一个multi容器中的关键字不必唯一,在这些类型上调用insert总会插入一个元素,所以接受单个元素的insert操作返回一个指向新元素的迭代器,无需返回一个bool值:
multimap authors;
authors.insert({ "Barth,Jhon","Sot-Weet Factor" });
authors.insert({ "Barth,Jhon","Lost in the Funhouse" });
c.erase(k) | 删除每个关键字为k的元素。返回一个size_type值,指出删除的元素的数量 |
c.erase(p) | 删除迭代器p指定的元素,返回一个指向p之后元素的迭代器 |
c.erase(b,e) | 删除迭代器b和e表示的范围中的元素。返回e |
c[k] | 返回关键字为k的元素;如果k不在c中,添加一个关键字为k的元素,对其进行值初始化 |
c.at(k) | 访问关键字为k的元素,带参数检查;若k不在c中,抛出一个out_of_range异常 |
set类型不支持下标,因为set中没有与关键字相关联的“值”。
multimap和undered_maltimap不支持下标操作,因为这些容器中可能有多个值与一个关键字相关联。
由于下标运算符可能插入一个新元素,我们只可以对非const的map使用下标操作
使用下标操作的返回值
与vector和string不同,map的下标运算符返回的类型与解引用map迭代器得到的类型不同:
对一个map进行下标操作时,会获得一个mapped_type(p.second)对象,但当解引用一个map迭代器时,会得到一个value_type对象(pair)。
lower_bound和upper_bound不适用于无序容器 下标和at操作只适用于非const的map和unordered_map(下标操作无对应关键字,则会新增元素) |
|
c.find(k) | 返回一个迭代器,指向第一个关键字为k的元素 |
c.count(k) | 返回关键字为k的元素的数量 |
c.lower_bound(k) | 返回一个迭代器,指向第一个关键字不小于k的元素 |
c.upper_bound(k) | 返回一个迭代器,指向第一个关键字大于k的元素 |
c.equal_range(k) | 返回一个pair,表示关键字等于k的元素的范围。若k不存在,pair的两个成员均等于c.end() |
set iset={0,1,2,3,4,5,6,7,8,9};\
iset.find(1); //返回指向key==1的迭代器
iset.find(11); //返回iset.end()
iset.count(1); //返回1
iset.count(11); //返回0
对map使用find代替下标操作
有时,我们只想知道一个给定关键字是否在map中,而不想改变map。此时应该使用find:
if(word_count.find("foobar")==word_count.end())
cout<<"foobar is not in the map"<
在multimap或multiset中查找元素
在容器中可能有很多元素具有给定的关键字。如果一个multimap或multiset中有多个元素具有给定关键字,则这些元素在容器中会相邻存储。
下面从一个作者到著作题目的映射中,打印一个特定作者的所有著作:
//下面三种方法等价
//第一种方法
string search_item("Alain de Botton"); //要查找的作者
auto entries=authors.count(search_item); //著作的数量
auto iter=authors.find(search_item); //第一本著作
while(entries){
cout<second<second<second<
#include
#include
#include
#include