关联容器中的元素是按关键字来保存和访问的,支持高效的关键字查找和访问。两个主要的关联容器是map和set。
- 类型map和multimap定义在头文件map中。
- 类型set和multiset定义在头文件set中。
- 无序容器则定义在头文件unordered_map和unordered_set中
按关键字有序保存元素 | |
map | 关联数组:保存关键字-值对(例子:字典,单词-释义) |
set | 关键字即值,即只保存关键字的容器 |
multimap | 关键字可重复出现的map |
multiset | 关键字可重复出现的set |
无序集合 | |
unordered_map | 用哈希函数组织的map |
unordered_set | 用哈希函数组织的set |
unordered_multimap | 哈希组织的map;关键字可重复出现 |
unordered_multiset | 哈希组织的set;关键字可重复出现 |
使用关联容器
使用map
单词计数:
map<string, size_t> word_count;
string word;
while(cin >> word)
++word_count[word];
for( const auto &w:word_count)
cout << w.first << "occurs" << w.second
<< (w.second > 1)? "times" : "time" << endl;
关键词是string,值是size_t。从map提取一个元素时,会得到一个pair类型的对象,该模板类型保存两个名字未first和second的公有数据成员。map所使用的pair用first保存关键字,用second成员保存对应的值。
使用set
统计不在exclude中的单词的数量。
map<string, size_t> word_count;
set<string> exclude = {"The", "But", "And", "Or", "An", "A",
"the", "but", "and", "or", "an", "a"};
string word;
while(cin >> word)
if( exclude.find(word) == exclude.end())
++word_count[word];
关联容器概述
map<string, string> authors = {{"Joyce", "James"},
{"Austen", "Jane"},
{"Dickens", "Charles"}};
关键字类型的要求
关联容器对于其关键字类型有一些限制。对于有序容器(map、set、multimap、multiset)关键字类型必须定义元素比较的方法。默认情况下,标准库使用关键字类型的<运算符来比较两个关键字。可以提供自己定义的操作来代替关键字上的<运算符。所提供的操作必须在关键字类型上定义一个严格弱序(strict weak ordering,可以看做“小于等于”):
- 两个关键字不能同时“小于等于”对方;
- 如果k1“小于等于”k2,且k2“小于等于”k3,那么k1必须“小于等于”k3
- 如果存在两个关键字,任何一个都不“小于等于”另一个,则称这两个关键字是“等价”的.
使用关键字类型的比较函数
用来组织一个容器中元素的操作的类型也是该容器类型的一部分。为了指定使用自定义的操作,必须在定义关联容器时提供此操作的类型。即用尖括号指出要定义哪种类型的容器,自定义的操作类型必须在尖括号中紧跟着元素类型给出。
bool compareIsbn( const Sales_data &lhs, const Sales_data &rhs)
{
return lhs.isbn() < rhs.isbn();
}
multiset< Sales_data, decltype(compareIsbn)*>
bookstore(compareIsbn);
使用decltype来指定自定义的操作的类型。当用decltype来或得一个函数指针类型时,必须加上一个*来指出要使用一个给定函数类型的指针。用compareIsbn来初始化bookstore对象,这表示当我们向bookstore添加元素时,通过调用compareIsbn来为这些元素排序。即,bookstore中的元素将按它们的ISBN成员的值排序
pair类型
定义在头文件utility中。创建一个pair时,必须提供两个类型名。
#include <utility>
pair<string, string> anon;
pair<string, size_t> word_count;
pair<string, vector<int>> line;
pair< T1, T2> p; | p是一个pair,两个类型分别为T1和T2的成员都进行了值初始化 |
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的名叫first的(公有)数据成员 |
p.second | 返回p的名叫second的(公有)数据成员 |
p1 relop p2 | 关系运算符(<, >, <=, >=)按字典序定义:例如,当p1.first < p2.first或 !(p1.first < p2.first) && p1.second < p2.second成立时,p1<p2为true |
p1 == p2 p1 != p2 | 当first和second成员分别相等时,两个pair相等。 |
pair<string, int>
process( vector<string> &v)
{
if( !v.empty())
return {v.push_back(), v.push_back().size()};
else
return pair<string, int>;
if( !v.empty())
return pair<string, int>(v.push_back(), v.push_back().size());
else
return pair<string, int>;
if( !v.empty())
return make_pair(v.push_back(), v.push_back().size());
else
return pair<string, int>;
}
关联容器操作
key_type | 此容器类型的关键字类型 |
mapped_type | 每个关键字关联的类型:只适用map |
value_type | 对于set,与key_type相同 对于map,为pair<const key_type, mapped_type> |
关联容器迭代器
set类型虽然同时定义了iterator和const_iterator类型,但都只运行只读访问set中的元素。与不能改变一个map元素的关键字一样,一个set中的关键字也是const的。
vector<int> ivec = {2, 4, 6, 8, 2, 4, 6, 8};
set<int> set2;
set2.insert(ivec.cbegin(), ivec.cend()); //set2中有4个元素
set2.insert({1, 3, 5, 7, 1, 3, 5, 7}); //set2中有8个元素
word_count.insert({word, 1});
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, int>(word, 1));
word_count.insert(map<string, int>::value_type(word, 1));
c.insert( v) c.emplace( args ) | v是value_type类型的对象;args是用来构造一个元素 对于map和set,只有当元素的关键字不在c中时才插入(或构造)元素。函数返回一个pair,包含一个迭代器,指向具有指定关键字的元素,以及一个指示插入是否成功的bool值 |
c.insert( b, e) c.insert( i1 ) | b和e是迭代器,表示一个c::value_type类型值的范围; i1是这种值的花括号列表 函数返回void |
c.insert(p, v) c.emplace(p, args ) | 类似c.insert( v)和c.emplace( args ),但将迭代器p作为一个提示,指出从哪里开始搜索新元素应该存储的位置。返回一个迭代器,指向具有给定关键字的元素 |
map<string, size_t> word_count;
string word;
while( cin >> word)
{
auto ret = word_count.insert((word, 1));
pair< map< string, size_t>::iterator, bool> ret =
word_count.insert(make_pair(word, 1));
if( !ret.second )
++ret.first->second;
}
c.erase( k ) | 从c中删除每个关键字为k的元素。返回一个size_type值,指出删除元素的数量 |
c.erase( p ) | 从c中删除迭代器p指定的元素。p必须指向c中一个真实元素,不能等于c.end()。返回一个指向p之后元素的迭代器 |
c.erase( b, e) | 删除迭代器对b和e所表示的范围中的元素。返回e |
c[k] | 返回关键字为k的元素。如果k不在c中,添加一个关键字为k的元素,对其进行值初始化 |
c.at(k) | 访问关键字为k的元素,带参数检查:若k不在c中,抛出out_of_range异常 |
map的下标运算符和其他下标运算符有如下不同:
- 如果关键字不在map内,会为它创建一个元素并插入到map中,关联值将进行值初始化
- 通常解引用一个迭代器所返回的类型与下标运算符返回的类型是一样的。但对map进行下标操作,返回mapped_type对象,但解引用为value_type对象
lower_bound和upper_bound不适用无序容器 | |
下标和at操作只适用于非const的map和unordered_map | |
c.find( k ) | 返回一个迭代器,指向第一个关键字为k的元素,若k不在容器中,返回尾后迭代器 |
c.count( k) | 返回关键字等于k的元素的数量。对于不允许重复关键字的容器,返回值为0或1 |
c.lower_bound(key) | 返回一个迭代器,指向键值>= key的第一个元素。 |
c.upper_bound(key) | 返回一个迭代器,指向键值> key的第一个元素。 |
c.equal_range(key) | 返回一个迭代器pair,表示关键字等于key的元素的范围。若k不存在,pair的两个成员都等于c.end() |
在multimap或multiset中查找元素
给定一个从作者到著作题目的映射,打印一个特定作者的所有著作。有3中不同的方法:
string search_item("Alain de Botton");
auto entries = authors.count( search_item );
auto iter = authors.find( search_item );
while( entries )
{
cout << iter->second << endl;
++iter;
--entries;
}
lower_bound和upper_bound并不报告关键字是否存在,重要的是它们的返回值可作为一个迭代器范围。
string search_item("Alain de Botton");
for( auto beg = authors.lower_bound(search_item),
end = authors.upper_bound(search_item);
beg != end; ++beg)
cout << beg->second << endl;
for( auto pos = authors.equal_range( search_item);
pos.first != pos.second; ++pos.first)
cout << beg->second << endl;
一个单词转换的map
map<string, string> buildMap( ifstream &map_file)
{
map<string, string> trans_map;
string key;
string value;
while( map_file >> key && getline( map_file, value)
if(value.size() > 1)
trans_map[key] = value.substr(1);
else
trow runtime_error("no rule for" + key );
return trans_map;
}
const string &
transform( const string &s, const map<string, string> &m)
{
auto map_it = m.find(s);
if( map_it != map_it.cend())
return map_it->second;
else
return s;
}
void word_transform( ifstream &map_file, ifstream &input)
{
auto trans_map = buildMap( map_file );
string text;
while( getline( input, text )
{
istringstream stream(text);
string word;
bool first_word = true;
while( stream >> word )
{
if( first_word)
first_word = false;
else
cout << " ";
cout << transform(word, trans_map);
}
}
}
无序容器
通常无序容器和有序容器可以相互替换。无序容器在存储上组织为一组桶,每个桶保存0个或多个元素。使用一个哈希函数将元素映射到桶,因此,无序容器的性能依赖与哈希函数的质量和痛的数量和大小。
桶接口 | |
c.bucket_count() | 正在使用的桶的数目 |
c.max_bucket_count() | 容器能最多容纳的桶的数量 |
c.bucket_size(n) | 第n个桶中有多少个元素 |
c.bucket(k) | 关键字为k的元素在哪个桶中 |
桶迭代 | |
local_iterator | 可以用来访问桶中元素的迭代器类型 |
const_local_iterator | |
c.begin(n), c.end(n) | 桶n的首元素迭代器和尾后迭代器 |
c.cbegin(n), c.cend(n) | |
哈希策略 | |
c.local_factor() | 每个桶的平均元素数量,返回float值 |
c.max_local_factor() | c试图维护的平均桶大小,返回float。c会在需要时添加新的桶。以使得load_factor <= max_load_factor |
c.rehash(n) | 重组存储,使得bucket_count >= n 且bucket_count > size/max_load_factor |
c.reserve( n) | 重组存储,使得c可以保存n个元素而不必rehash |
无序容器对关键字类型的要求
默认情况下,无序容器使用关键字类型的==运算符来比较元素,它们还使用一个hash<key_type>类型的对象来生成每个元素的哈希值。标准库为内置类型(包括指针)和包括string智能指针类型在内的标准库类型定义了hash模板。但我们不能直接定义关键字类型为自定义类类型的无序容器。与容器不同,不能直接使用hash模板,必须提供自己的hash模板版本。
size_t hasher(const Sales_data &sd)
{
return hash<string>()(sd.isbn());
}
bool eqOp(const Sales_data &lns, const Sales_data &rhs)
{
return lns.isbn() == rhs.isbn();
}
using SD_multiset = unordered_multiset<Sales_data,
decltype(hasher)*, decltype(eqOp)*>;
//参数是桶大小、哈希函数指针和相等性判断运算符指针
SD_multiset bookstore(42, hasher, eqOp);
如果我们的类定义了==运算符,则可以只重载哈希函数:
unordered_set<Foo, decltype( FooHash)*> fooSet( 10, FooHash);