11.1 使用关联容器
关联容器支持高效的关键字查找和访问操作。2个主要的关联容器(associative-container)类型是 map 和 set。 允许重复保存关键字的容器名字都包含单词 multi;无序保存元素的容器名字都以单词 unordered 开头。 map 类型通常被称为 关联数组(associative array)。从 map 中提取一个元素时,会得到一个pair类型的对象。pair 是一个模板类型,保存两个名为 first 和 second 的公有数据成员。map 所使用的 pair 用 first 成员保存关键字,用 second 成员保存对应的值。
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;
set 类型的 find 成员返回一个迭代器。如果给定关键字在 set 中,则迭代器指向该关键字,否则返回的是尾后迭代器。
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] ;
11.2 关联容器概述
11.2.1 定义关联容器
map 和 set 中的关键字必须唯一,multimap 和 multiset 没有此限制。 初始化 map 时,必须提供关键字类型和值类型,提供的每个键值对用花括号 {} 包围,{key, value}。
map< string, size_t> word_count;
set< string> exclude = { "the" , "but" , "and" } ;
map< string, string> authors =
{
{ "Joyce" , "James" } ,
{ "Austen" , "Jane" } ,
{ "Dickens" , "Charles" }
} ;
vector< int > ivec;
for ( vector< int > :: size_type i = 0 ; i != 10 ; ++ i) {
ivec. push_back ( i) ;
ivec. push_back ( i) ;
}
set< int > iset ( ivec. cbegin ( ) , ivec. cend ( ) ) ;
multiset< int > miset ( ivec. cbegin ( ) , ivec. cend ( ) ) ;
cout << ivec. size ( ) << endl;
cout << iset. size ( ) << endl;
cout << miset. size ( ) << endl;
11.2.2 关键字类型要求
对于有序容器——map、multimap、set 和 multiset,关键字类型必须定义元素比较的方法。默认情况下,标准库使用关键字类型的 < 运算符来进行比较操作。 传递给排序算法的可调用对象必须满足与关联容器中关键字一样的类型要求。 在实际编程中,重要的是,如果一个类型定义了“行为正常”的 < 运算符,则它可以用作关键字类型。 用来组织容器元素的操作的类型也是该容器类型的一部分。如果需要使用自定义的比较操作,则必须在定义关联容器类型时提供此操作的类型。操作类型在尖括号中紧跟着元素类型给出。
bool compareIsbn ( const Sales_data & lhs, const Sales_data & rhs)
{
return lhs. isbn ( ) < rhs. isbn ( ) ;
}
multiset< Sales_data, decltype ( compareIsbn) * > bookstore ( compareIsbn) ;
11.2.3 pair类型
pair 定义在头文件 utility 中。一个 pair 可以保存两个数据成员,分别命名为 first 和 second。
pair< string, string> anon;
pair< string, size_t> word_count;
pair< string, vector< int >> line;
pair 的默认构造函数对数据成员进行值初始化。 在C++11中,如果函数需要返回 pair,可以对返回值进行列表初始化。早期C++版本中必须显式构造返回值。
pair< string, int > process ( vector< string> & v)
{
if ( ! v. empty ( ) )
return { v. back ( ) , v. back ( ) . size ( ) } ;
else
return pair< string, int > ( ) ;
}
11.3 关联容器操作
关联容器定义了类型别名来表示容器关键字和值的类型: 对于 set 类型,key_type 和 value_type 是一样的。set 中保存的值就是关键字。对于 map 类型,元素是关键字-值对。即每个元素是一个 pair 对象,包含一个关键字和一个关联的值。由于元素关键字不能改变,因此 pair 的关键字部分是const的。 另外,只有 map 类型(unordered_map、unordered_multimap、multimap、map)才定义了 mapped_type。
set< string> :: value_type v1;
set< string> :: key_type v2;
map< string, int > :: value_type v3;
map< string, int > :: key_type v4;
map< string, int > :: mapped_type v5;
11.3.1 关联容器迭代器
解引用关联容器迭代器时,会得到一个类型为容器的 value_type 的引用。对 map 而言,value_type 是 pair 类型,其 first 成员保存 const 的关键字,second 成员保存值。可以改变pair的值,但是不能改变pair的关键字。
auto map_it = word_count. begin ( ) ;
cout << map_it-> first;
cout << " " << map_it-> second;
map_it-> first = "new key" ;
++ map_it-> second;
set的迭代器是const的
set< int > iset = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } ;
set< int > :: iterator set_it = iset. begin ( ) ;
if ( set_it != iset. end ( ) )
{
* set_it = 42 ;
cout << * set_it << endl;
}
map 和 set 都支持 begin 和 end 操作。使用迭代器遍历 map、multimap、set 或 multiset 时,迭代器按关键字升序遍历元素。
auto map_it = word_count. cbegin ( ) ;
while ( map_it != word_count. cend ( ) ) {
cout << map_it-> first << " occurs "
<< map_it-> second << " times" << endl;
++ map_it;
}
通常不对关联容器使用泛型算法。
11.3.2 添加元素
使用 insert 成员可以向关联容器中添加元素。向 map 和 set 中添加已存在的元素对容器没有影响。 通常情况下,对于想要添加到 map 中的数据,并没有现成的 pair 对象。可以直接在 insert 的参数列表中创建 pair。
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;
string map;
while ( cin >> map) {
auto ret = word_count. insert ( { word, 1 } ) ;
if ( ! ret. second) {
++ ret. first-> second;
}
}
对于允许包含重复关键字的容器,添加单一元素的 insert 和 emplace 版本返回指向新元素的迭代器。
11.3.3 删除元素
关联容器的删除操作: 与顺序容器不同,关联容器提供了一个额外的 erase 操作。它接受一个 key_type 参数,删除所有匹配给定关键字的元素(如果存在),返回实际删除的元素数量。 对于不包含重复关键字的容器,erase 的返回值总是1或0。若返回值为0,则表示想要删除的元素并不在容器中。
11.3.4 map的下标操作
map 下标运算符接受一个关键字,获取与此关键字相关联的值。如果关键字不在容器中,下标运算符会向容器中添加该关键字,并值初始化关联值。 由于下标运算符可能向容器中添加元素,所以只能对非 const 的 map 使用下标操作。 对 map 进行下标操作时,返回的是 mapped_type 类型的对象;解引用 map 迭代器时,返回的是 value_type 类型的对象。 map 和 unordered_map 的下标操作: 与 vector 与 string 不同,map 的下标运算符返回的类型与解引用 map 选代器得到的类型不同。
11.3.5 访问元素
关联容器的查找操作: 如果 multimap 或 multiset 中有多个元素具有相同关键字,则这些元素在容器中会相邻存储。
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 操作都接受一个关键字,返回一个迭代器。如果关键字在容器中,lower_bound 返回的迭代器会指向第一个匹配给定关键字的元素,而 upper_bound 返回的迭代器则指向最后一个匹配元素之后的位置。如果关键字不在 multimap 中,则 lower_bound 和 upper_bound 会返回相等的迭代器,指向一个不影响排序的关键字插入位置。因此用相同的关键字调用 lower_bound 和 upper_bound 会得到一个迭代器范围,表示所有具有该关键字的元素范围。 lower_bound 返回的迭代器可能指向一个具有给定关键字的元素,但也可能不指向。如果关键字不在容器中,则 lower_bound 会返回关键字的第一个安全插入点一—不影响容器中元素顺序的插入位置。
for ( auto beg = authors. lower_bound ( search_item) ,
end = authors. upper_bound ( search_item) ;
beg != end; ++ beg)
cout << beg-> second << endl;
lower_bound 和 upper_bound 有可能返回尾后迭代器。如果查找的元素具有容器中最大的关键字,则 upper_bound 返回尾后迭代器。如果关键字不存在,且大于容器中任何关键字,则 lower_bound 也返回尾后迭代器。 equal_range 操作接受一个关键字,返回一个迭代器 pair。若关键字存在,则第一个迭代器指向第一个匹配关键字的元素,第二个迭代器指向最后一个匹配元素之后的位置。若关键字不存在,则两个迭代器都指向一个不影响排序的关键字插入位置。
for ( auto pos = authors. equal_range ( search_item) ;
pos. first != pos. second; ++ pos. first)
cout << pos. first-> second << endl;
11.3.6 一个单词转换的map
11.4 无序容器
新标准库定义了4个 无序关联容器(unordered associative container),这些容器使用 哈希函数(hash function) 和关键字类型的 == 运算符组织元素。 无序容器在存储上组织为一组桶,每个桶保存零或多个元素。无序容器使用一个哈希函数将元素映射到桶。为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。容器将具有一个特定哈希值的所有元素都保存在相同的桶中。因此无序容器的性能依赖于哈希函数的质量和桶的数量及大小。 无序容器管理操作 默认情况下,无序容器使用关键字类型的==运算符比较元素,还使用一个 hash<key_type> 类型的对象来生成每个元素的哈希值。标准库为内置类型和一些标准库类型提供了hash模板。因此可以直接定义关键字是这些类型的无序容器,而不能直接定义关键字类型为自定义类类型的无序容器,必须先提供对应的hash模板版本。
size_t hasher ( const Sales_data & sd) {
return hash< string> ( ) ( sd. isbn ( ) ) ;
}
bool eqOp ( const Sales_data & lhs, const Sales_data & rhs) {
return lhs. isbn ( ) == rhs. isbn ( ) ;
}
using SD_multiset = unordered_multiset< Sales_data, decltype ( hasher) * , decltype ( eqOp) * > ;
SD_multiset bookstore ( 42 , hasher, eqOp) ;
unordered_multiset< Sales_data, decltype ( hasher) * > fooset = fooset ( 10 , FooHash) ;