第十一章 关联容器
关联容器和顺序容器有着根本的不同:关联容器中的元素是按关键字来保存和访问的。 与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。 两个主要的关联容器map 和set 。 标准库提供8个关联容器。
关联容器类型 解释 按关键字有序保存元素 map 关联数组;保存关键字-值对 set 关键字即值,即只保存关键字的容器 multimap 关键字可重复出现的map multiset 关键字可重复出现的set 无序集合 unordered_map 用哈希函数组织的map unordered_set 用哈希函数组织的set unordered_multimap 哈希组织的map:关键字可以重复出现 unordered_multiset 哈希组织的set:关键字可以重复出现
11.1 使用关联容器
map类型通常被称为关联数组 ,与正常数组类似,不同之处在于其下标不必是整数,通过一个关键字而不是位置来查找值。 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;
}
map< string, size_t> word_cout;
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时,只需指明关键字类型。
map< string, size_t> word_cout;
set< string> exclude = { "The" , "But" , "And" , "Or" , "An" , "A" , "the" , "but" , "and" , "or" , "an" , "a" } ;
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,关键字类型必须定义元素比较的方法。 有序容器的关键字类型可以向一个算法提供自己定义的比较操作,所提供的操作必须在关键字类上定义一个严格弱序 。
两个关键字不能同时小于等于对方;如果kl小于等于k2,那么k2绝不小于等于kl。 如果kl小于等于k2,且k2小于等于k3,那么kl必须小于等于k3。 如果存在两个关键字,任何一个都不小于等于另一个,那么我们称这两个关键字是”等价”的。如果kl等价于k2,且k2等价于k3,那么kl必须等价于k3。 不能直接定义一个自定义类型multiset,使用关键字类型的比较函数定义了一个严格弱序 。
bool comparelsbn ( const Sales_data & lhs, const Sales_data & lhs)
{
return Ihs. isbn ( ) < rhs. isbn ( ) ;
}
multiset< Sales_data, decltype ( compareisbn) * > bookstore ( compareisbn) ;
11.2.3 pair类型
关于pair 标准库类型,定义在utility中。 一个pair保存两个数据成员。
pair< string, string> anon;
pair< string, size_t> word_count;
pair< string, vector< int >> line;
pair< string, string> author{ "James" , "Joyce" } ;
cout << w. first << " occurs " << w. second << ( ( w. second > 1 ) ? " times" : " time" ) << endl;
pair上的操作 解释 pair<T1, T2> p; p是一个pair,两个类型分别为T1和T2的成员都进行了值初始化 pair<T1, T2> p(v1, v2) p是一个pair,分别用v1和v2进行初始化 pair<T1, T2>p = {vl, v2} ; 同上 make_pair(v1, v2) 返回一个用v1和v2初始化的pair,pair的类型从v1和v2的类型推断出来 p.first 返回p的名为first的(公有)数据成员 p.second 返回p的名为second的(公有)数据成员 pl relop p2 关系运算符(<、>、<=、>=)按字典序定义 pl == p2 当first和second成员分别相等时,两个pair相等 pl != p2 判断利用元素的==运算符实现
pair< string, int > process ( vector< string> & v)
{
if ( ! v. empty ( ) )
{
return { v. back ( ) , v. back ( ) . size ( ) } ;
}
else
{
return pair < string, int > ( )
}
}
在较早的C++版本中,不允许用花括号包围的初始化器来返同pair这种类型的对象。
if ( ! v. empty ( ) )
{
return pair< string, int > { v. back ( ) , v. back ( ) . size ( ) } ;
}
if ( ! v. empty ( ) )
{
return make_pair < string, int > ( v. back ( ) , v. back ( ) . size ( ) ) ;
}
11.3 关联容器操作
关联容器额外的类型别名 解释 key_type 此容器类型的关键字类型 mapped_type 每个关键字关联的类型:只适用于map value_type 对于set,与key_type相同,对于map,为pair<const key_type, 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的值的引用。
auto map_it = word_count. begin ( ) ;
cout << map_it-> first;
cout << " " << map_it-> second;
map_it-> first = "new key" ;
++ map_it-> second;
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操作。
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成员向容器中添加一个元素或元素范围。 插入一个已经处在的元素对容器没有任何影响。
vector< int > ivec = { 2 , 4 , 6 , 8 , 2 , 4 , 6 , 8 } ;
set< int > set2;
set2. insert ( ivec. cbegin ( ) , ivec. cend ( ) ) ;
set2. insert ( { 1 , 3 , 5 , 7 , 1 , 3 , 5 , 7 } ) ;
对一个map进行insert时,必须元素类型是pair。
word_count. insert ( { word, 1 } ) ;
word_count. insert ( make_pair ( word, 1 ) ) ;
word_count. insert ( pair < string, size_t> ( word, l) ) ;
word—count. insert ( map < string, size_t> :: value_type ( word, 1 ) ) ;
关联容器insert操作 解释 c.insert(v) v是value_type类型的对象 c.emplace(args) args用来构造一个元素 c.insert(b, e) b和e是迭代器,表示一个c::value_type类型值的范围 c.insert(il) il是这种值的花括号列表 c.insert(p, v) 类似insert(v)或emplace(args),但将迭代器p作为一个提示,指出从哪里开始搜索新元素应该存储的位置。 c.emplace(p,args) 类似insert(v)或emplace(args),但将迭代器p作为一个提示,指出从哪里开始搜索新元素应该存储的位置。
insert(或emplace)返回的值依赖于容器类型和参数。返回一个pair,first成员是一个迭代器,指向具有给定关键字的元素,second成员是一个cool值,指出元素是插入成功还是已经存在于容器中。 使用insert重写单词计数程序。
map< string, size_t> word_count;
string word;
while ( cin >> word)
{
auto ret = word_count. insert ( { word, 1 } ) ;
if ( ! ret. second)
{
++ ret. first-> second;
}
}
展开递增语句。
ret 保存insert返回的值,是一个pair。ret.first 是pair的第一个成员,是一个map迭代器,指向具有给定关键字的元素。**ret.first->**解引用此迭代器,提取map中的元素,元素也是一个pair。 ret.first->second map中元素的值部分。++ret.first->second 递增此值。 新标准推出之前,不使用auto。
pair< map< string, size_t> :: iterator, bool > ret = word_count. insert ( make_pair ( word, 1 ) ) ;
向multiset或mutimap添加元素,接受单个元素的insert操作返回一个指向新元素的迭代器。
multimap< string, string> authors;
authors. insert ( { "Barth,John" , "Sot-Weed Factor" } ) ;
authors. insert ( { "Barth, John" , "Lost in the Funhouse" } ) ;
11.3.3 删除元素
从关联容器删除元素 解释 c.erase(k) 从c中删除每个关键字为k的元素,返回一个size_type值,指出删除的元素的数量 c.erase§ 从c中删除迭代器p指定的元素,p必须指向c中一个真实元素,不能等于c.end(),返回一个指向p之后元素的迭代器,若p指向c中的尾元素,则返回c.end() c.erase(b, e) 删除迭代器对b和e所表示的范围中的元素,返回e
11.3.4 map的下标操作
map和unordered_map容器提供了下标运算符和一个对应的at函数。
map < string, size_t> word_count;
word_count[ "Anna" ] = 1 ;
将执行如下操作:
在word_count中搜索关键字为Anna的元素,未找到。 将一个新的关键字-值对插入到word_count中,关键字是一个const string,保存Anna,值进行值初始化,在本例中意味着值为0。 提取出新插入的元素,并将值1赋予它。 由于下标运算符可能插入一个新元素,只可以对非const的map使用下标操作。
map和unordered_map的下标操作 解释 c[k] 返回关键字为k的元素,如果k不在c中,添加一个关键字为k的元素,对其进行值初始化 c.at(k) 访问关键字为k的元素,带参数检查,若k不在c中,抛出一个out_of_range异常
cout << word_count[ "Anna" ] ;
++ word_count[ "Anna" ] ;
cout << word_count[ "Anna" ] ;
11.3.5 访问元素
set< int > iset = { 0 , 1 , 2 1 3 , 4 , 5 , 6 , 7 , 8 , 9 } ;
iset. find ( 1 ) ;
iset. find ( 11 ) ;
iset. count ( 1 ) ;
iset. count ( 11 ) ;
在一个关联容器中查找元素的操作 解释 lower_bound和upper_bound不适用于无序容器 下标和at操作只适用于非const的map和unordered_map c.find(k) 返回一个迭代器,指向第一个关键字为k的元素,若k不在容器中,则返回尾后迭代器 c.count(k) 返回关键字等于k的元素的数量,对于不允许重复关键字的容器,返回值永远是0或1 c.lower_bound(k) 返回一个迭代器,指向第一个关键字不小于k的元素(指向第一个具有给定关键词的元素) c.upper_bound(k) 返回一个迭代器,指向第一个关键字大于k的元素(指向最后一个匹配给定关键词的元素之后的位置) c.equal_range(k) 返回一个迭代器pair, 表示关键字等于k的元素的范围,若k不存在,pair的两个成员均等于c.end()
如果关键字还未在map中,下标操作会插入一个具有给定关键字的元素,这种时候使用find。
if ( word_count. find ( "foobar" ) == word_count. end ( ) )
{
cout << "foobar is not in the map" << endl;
}
如果一个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;
}
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 << pos. first-> second << endl;
}
11.3.6 一个单词转换的map
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 firstword = true ;
while ( stream >> word)
{
if ( firstword)
{
firstword = false ;
}
else
{
cout << " " ;
}
cout << transform ( word, trans_map) ;
}
cout << endl;
}
}
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
{
throw runtime_error ( "no rule for" + key) ;
}
}
return trans_map;
}
const string & transform ( const string & s, canst map< string, string> & m)
{
auto map_it = m. find ( s) ;
if ( map_it != m. cend ( ) )
{
return map_it-> second;
}
else
{
return s;
}
}
11.4 无序容器
新标准定义了4个无序关联容器 。 这些容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的==运算符。 通常可以用一个无序容器替换对应的有序容器,但是无序的输出与有序版本不同。 无序容器在存储上组织为一组桶,每个桶保存零个或多个元素,使用一个哈希函数将元素映射到桶。
无序容器管理操作 解释 桶接口 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) 与前两个函数类似,但返同const_local_iterator 哈希策略 c.load_factor() 每个桶的平均元素数量,返回float值 c.max_load_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>类型的对象来生成每个元素的哈希值。