关联容器和顺序容器有着根本的不同:关联容器中的元素是按关键字来保存和访问的。与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。
虽然关联容器的很多行为与顺序容器相同,但其不同之处反映了关键字的作用。
关联容器支持高效的关键字查找和访问。两个主要的关联容器类型是 map
和 set
。
map
中的元素是一些关键字-值(key-value)对:关键字起到索引的作用,值则表示与索引相关联的数据。
set
支持高效的关键字查询操作——检查一个给定关键字是否在 set
中。
类型 map
和 multimap
定义在头文件 map
中;set
和 multiset
定义在头文件 set
中;无序容器则定义在头文件 unordered_map
和 unordered_set
中。
使用关联容器
关联数组的下标不必是整数。
使用 map
//统计每个单词在输入中出现的次数
map<string,size_t> word_count; // string 到size_t的空map
string word;
while (cin >>word)
++word_count[word];//提取 word 的计数器并将其加1
for (const auto &w : word_count)//对map中的每个元素
//打印结果
cout << w.first << " occurs " << w.second
<< ((w.second > 1) ? " times":" time")<< endl;
使用 set
//统计输入中每个单词出现的次数
map<string,size_t> word_count;// string到size_t的空 map
set<string> exclude ={"The","But", "And","or","An","A",
"the","but","and", "or", "an", "a"};
string word;
while (cin >> word)
//只统计不在exclude中的单词
if (exclude.find(word)==exclude.end())
++word_count[word];//获取并递增word的计数器
关联容器概述
1.关联容器不支持顺序容器的位置相关的操作,例如 push_front 或 push_back。因为,关联容器中元素是根据关键字存储的,这些操作对关键容器没有意义。
2.关联容器也不支持构造函数或插入操作。
3.关联容器的迭代器都是双向的
定义关联容器
定义 map
时,必须既指明关键字类型又指明值类型:{key,value}
定义 set
时,只需要指明关键字类型。因为 set
中没有值,元素类型就是关键字的类型。
每个关联容器都定义了一个默认构造函数,它创建一个指定类型的空容器。也可以将关联容器初始化为另一个同类型容器的拷贝,或者是从一个值范围来初始化关联容器,只需要这些值可以转换为容器所需类型就可以。
map<string,size_t> word count;//空容器//列表初始化
set<string> exclude = {"the","but","and","or","an","a","The","But","And" , "or", "An","A"};
//三个元素;authors 将姓映射为名
map<string,string> authors = { {"Joyce", "James"},
{"Austen", "Jane"},
{ "Dickens", "Charles"} };
一个map
或set
中的关键字必须是唯一的,即,对于一个给定的关键字,只能有个元素的关键字等于它。容器multimap
和multiset
没有此限制,它们都允许多个元素具有相同的关键字。
关键字类型的要求
默认情况下,标准库使用关键字类型的 <
运算符来比较两个关键字。在集合类型中,关键字类型就是元素类型;在映射类型中,关键字类型是元素的第一部分的类型。
传递给排序算法的可调用对象必须满足与关联容器中关键字一样的类型要求。
有序容器的关键字类型
可以向算法提供一个自己定义的比较操作,操作必须在关键字类型上定义一个严格弱序,类似小于等于但不一样:
1.两个关键字不能同时”小于等于“对方。
2.该操作有传递性。
3.如果两个关键字互不”小于等于“对方,那么两个就是等价的。容器将它们看做相等。
如果两个关键字是等价的(即,任何一个都不“小于等于”另一个),那么容器将它们视作相等来处理。
当用作map 的关键字时,只能有一个元素与这两个关键字关联,可以用两者中任意一个来访问对应的值。
使用关键字类型的比较函数
为了使用指定自定义的操作,必须在定义关联容器类型时提供此操作的类型。自定义的操作类型(函数指针类型)必须在尖括号中紧跟元素类型给出。
比较函数应该返回 bool
值,两个参数的类型应该都是容器的关键字类型。
当用 decltype
来获得一个函数指针类型时,必须加上一个 *
来指出用使用一个给定函数类型的指针。
pair 类型
pair
标准库类型,定义在头文件 utility
中。一个 pair
保存两个数据成员,是一个用来生成特定类型的模板。
创建一个 pair 时,必须提供两个类型名,pair 的数据成员将具有对应的类型。两个类型可以不一样:
pair<string,string>anon;//保存两个string
pair<string,size_t> word_count;//保存一个string和一个size_t
pair<string,vector<int>> line;//保存string和vector<int>
pair 上的操作
创建 pair 类型的函数
pair<string,int>
process(vector<string> &v){
//处理 v
if(!v.empty())
return (v.back(),v.back().size());//列表初始化
else
return pair<string,int>();//隐式构造返回值
}
可以使用 make_pair
来生成pair 对象,pair 的两个类型来自于 make_pair
的参数:
if (!v.empty())
return make_pair(v.back(), v.back().size());
关联容器操作
关联容器额外的类型别名
不能改变一个元素的关键字,因此pair
的关键字部分是 const
的:
set<string>:: value_type v1;//v1 是一个 string
set<string>:: key_type v2;//v2 是一个 string
map<string,int>:: value_tupe v3;//v3 是一个 pair<const string,int>
map<string,int>:: key_type v4;//v4 是一个 string
map<string,int>:: mapped_type v5;//v5 是一个 int
关联容器迭代器
当解引用一个关联容器迭代器时,会得到一个类型为容器的 value_type
的值的引用。对 map
而言,value_type
是一个 pair 类型,其 first
成员保存 const
的关键字,second
成员保存值。可以改变 pair
的值,但不能改变关键字成员的值。
//获得指向word count中一个元素的迭代器
auto map it=word count.begin();
// *map it是指向一个pair<const string,size_t>对象的引用
cout << map it->first;//打印此元素的关键字
cout << " "<<map it->second;//打印此元素的值
map_it->first = "new key";//错误:关键字是const的
++map_it->second;//正确:我们可以通过迭代器改变元素
set
的迭代器是 const
的,可以用 set
的迭代器读取元素值,但不能修改。
关联容器中的元素不能通过它们的关键字(快速)查找。
在实际编程中,如果我们真要对一个关联容器使用泛型算法,要么是将它当作一个源序列,要么当作一个目的位置。
例如,可以用泛型 copy
算法将元素从一个关联容器拷贝到另一个序列。也可以用 inserter
将一个插入器绑定到另一个关联容器。
添加元素
关联容器 insert
操作
map
和 set
(以及对应的无序类型)包含不重复的关键字,因此插入一个已知存在的元素对容器没有任何影响。
vector<int> ivec = {2,4,6,8,2,4,6,8;// ivec有8个元素
set<int> set2;//空集合
set2.insert(ivec.cbegin(), ivec.cend());//set2有4个元素
set2.insert({1,3,5,7,1,3,5,7});//set2现在有8个元素
向 map 添加元素
对一个 map
进行 insert
操作时,元素类型必须是 pair
。
//向word_count插入word的4种方法
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));
检测 insert 的返回值
insert
(或 emplace
)返回的值依赖于容器类型和参数。
对于不包含重复关键字的容器,添加单一元素的 insert
和 emplace
版本返回一个 pair
,这个 pair
的 first
成员是一个迭代器,指向具有给定关键字的元素;second
成员是一个 bool
值,指出元素是插入成功还是已经存在于容器中:
已经存在,则insert
什么也不做,bool
部分为 false
;不存在,元素被插入容器中,bool
部分为 true
。
//统计每个单词在输入中出现次数的一种更烦琐的方法
map<string,size_t> word_count;1/从 string到size_t的空mapstring word;
while (cin>>word) {
//插入一个元素,关键字等于word,值为1;
//若word已在word count中, insert什么也不做
auto ret = word_count.insert({word,1});
if(!ret.second)//word已在word_count中
++ret.first->second;//递增计数器
}
//ret 保存 insert 返回的值,是一个 pair。
//ret.first 是 pair 的第一个成员,是一个 map 迭代器,指向具有给定关键字的元素。
//ret.first->解引用此迭代器,提取 map 中的元素,元素也是一个 pair。
//ret.first->second map 中元素的值部分。
//++ret. first->second 递增此值。
在 multiset
或 multimap
上调用 insert
总是可以插入元素,因为它们允许关键字出现多次。
删除元素
定义了三种 erase()
函数来从关联容器删除元素:
常用操作:
//删除一个关键字,返回删除的元素数量
if(word_count.erase(removal_word))
cout<<"ok : "<<removal_word<< "removed\n";
else
cout<<"oops: "<<removal_word<<" not found!\n";
对于保存不重复关键字的容器,erase
的返回值总是0或1。若返回值为0,则表明想要删除的元素并不在容器中。
map 的下标操作
map 和 unordered_map 的下标操作
不能对一个multimap
或一个unordered_multimap
进行下标操作,因为这些容器中可能有多个值与一个关键字相关联。
类似其他下标运算符,map
下标运算符接受一个索引(即,一个关键字),获取与此关键字相关联的值。但是,与其他下标运算符不同的是,如果关键字并不在map
中,会为它创建一个元素并插入到 map
中,关联值将进行值初始化。
当对一个map
进行下标操作时,会获得一个mapped_type
对象;但当解引用一个map迭代器时,会得到一个value_type
对象。
访问元素
对于不允许重复关键字的容器,使用 count 和 find 没什么区别,因为只有一个。
lower_bound
和 upper_bound
不适用于无序容器。
下标和 at 操作只适用于非 const
的 map
和 unordered_map
。
可以对 map 使用 find 代替下标操作:
// 检查一个元素是否存在
if (word_count.find("foobar")== word_count.end())
cout << "foobar is not in the map" <<endl;
如果一个 multimap
或 multiset
中有多个元素具有给定关键字,则这些关键字在容器中会相邻存储。
三种不同的关键字查找方法:
1.使用 find
和 count
。使用 count
确定有多个元素,然后调用 find
获得一个迭代器,指向第一个关键字为此关键字的元素,for
循环的迭代次数依赖于 count
的返回值。
2.使用 lower_bound
和 upper_bound
得到一个迭代器范围,表示所有具有该关键字的元素的范围。
3.使用 equal_range
函数。此函数接受一个关键字,返回一个迭代器 pair
。若关键字存在,则第一个迭代器指向第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素之后的位置。若未找到匹配元素,则两个迭代器都指向关键字可以插入的位置。
无序容器
4 个无序关联容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的 == 运算符。
如果关键字类型固有就是无序的,或者性能测试发现问题可以用哈希技术解决,就可以使用无序容器。
除了哈希管理操作之外,无序容器还提供了与有序容器相同的操作(find,insert等)。
无序容器也有允许重复关键字的版本。
通常可以用无序容器替换对应的有序容器,反之亦然。但是,无序容器中元素未按顺序存储。
管理桶
无序容器在存储上组织为一组桶,每个桶保存零个或多个元素。无序容器使用一个哈希函数将元素映射到桶。
为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。容器将具有一个特定哈希值的所有元素都保存在相同的桶中。
如果容器允许重复关键字,所有具有相同关键字的元素也都会在同一个桶中。
对于相同的参数,哈希函数必须总是产生相同的结果。理想情况下,哈希函数还能将每个特定的值映射到唯一的桶。但是,将不同关键字的元素映射到相同的桶也是允许的。
当一个桶保存多个元素时,需要顺序搜索这些元素来查找我们想要的那个。计算一个元素的哈希值和在桶中搜索通常都是很快的操作。但是,如果一个桶中保存了很多元素,那么查找一个特定元素就需要大量比较操作。
无序容器管理操作
无序容器对关键字类型的要求
默认情况下,无序容器使用关键字类型的==
运算符来比较元素,它们还使用一个hash<key_type>
类型的对象来生成每个元素的哈希值。标准库为内置类型(包括指针)提供了hash模板,还包括 string 和智能指针也提供了 hash
模板。
不能使用默认的 hash
,需要自定义 hash
模板来定义关键字类型为自定义类类型的无序容器。
//hasher函数使用一个标准库hash类型对象来计算ISBN成员的哈希值,该hash类型建立在string类型之上。e
size_t hasher(const Sales_data &sd){
return hash<string>()(sd.isbn());
}
//qOp函数通过比较ISBN号来比较两个Sales_data。
bool eqOp(const Sales_data &lhs,const Sales_data &fhs){
return lhs.isbn() == rhs.isbn();
}
重要术语
哈希函数(hash function) 将给定类型的值映射到整形(size_t)值的函数。相等的值必须映射到相同的整数:不相等的值应尽可能映射到不同整数。
严格弱序(strict weak ordering) 关联容器所使用的关键字间的关系。在一个严格弱序中,可以比较任意两个值并确定哪个更小。若任何一个都不小于另一个,则认为两个值相等。