关联容器支持高效的查找和访问:map和set
map-键值对(key-val):字典
set中每个元素只包含一个关键字。set支持高效的关键字查询操作
11.1使用关联容器:
使用map:
map<string, size_t> word_count;
string word;
while(cin>> word){
++word_count[word];
}
for(const auto &w :word_count){
//.操作符,first和second是共有数据成员
//从map中提取一个元素时,会得到一个pair类型对象
cout<<w.first<<"occurs" << w.second <<((w.second>1) ? " times":" time")<<endl;
}
使用set:
忽略上述程序中的常见单词
map<string,size_t> word_count;
set<string> exclude = {"The","the","But","but"};
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_count;
set<string> exclude = {"the","but"};
map<string,string> authors = {
{"jon","avb"}
{"ss","avc"}
};
11.2.2 关键字类型的要求
默认情况下,标准库使用关键字类型的<运算符来比较两个关键字,集合中关键字的类型就是元素类型,映射类型中,关键字类型是元素的第一部分的类型。例如:exclude中关键字类型为string,而word_count的关键字类型也是string
有序容器在定义其关键字时,其关键字的类型必须包含元素比较的方法,如果是一个类类型,且没有包含比较方法,则不合法,可以自行定义比较类型。
传递给排序算法的可调用对象必须满足于关联容器中关键字一样的类型要求
使用关键字类型的比较函数:
我们不能直接定义一个Sales_data的multiset,因为自己定义的Sales_data没有<运算符,但是可以运用compareIsbn函数定义一个multiset。这样就在Sales_data对象的ISBN成员上定义了一个严格弱序。
bool compareIsbn(const Sales_data &lhs,const Sales_data &rhs){
return lhs.isbn()<rhs.isbn();
}
//bookstore中 多条记录可以有相同的ISBN
//bookstore中是按照ISBN的顺序排序
multiset<Sales_data,decltype<compareIsbn>*> bookstore(compareIsbn);
11.2.3 pair类型
pair<string,string> author{"Dwyane","wade"};
pair的数据成员都是public的
创建pair的三种方式:
vec.push_back(std::make_pair(str, i));//make_pair返回一个用两个参数构成的pair
vec.push_back({str, i}); //列表初始化
vec.emplace_back(str, i); //最简便
11.3 关联容器操作
set<string>::value_type v1; //string
set<string>::key_type v2; //string
map<string,int>::value_type v3; //pair<string,int>
map<string,int>::key_type v4; //string
map<string,int>::mapped_type v5; //int
11.3.1 关联容器迭代器
当解引用一个关联容器迭代器时,会得到一个类型为容器的value_type的值的引用。
auto map_it = word_count.begin();
//*map_it,即解引用时,返回一个pair<const key_value,mapped_type> 即返回一个value_type
cout<< map_it->first;
cout<<" "<<map_it->second;
map_it->first = "new key"; //错误 map_it->first是const
++map_it->second; //正确
set的迭代器是const的
set中只有key_type,所以set中关键字也是const,可以用一个set迭代器来读取元素的值,但不能修改。
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; //错误:set中的关键字是只读的
cout<<*set_it<<endl; //正确:可以读关键字
}
遍历关联容器
auto map_it = word_count.cbegin();
while(map_it != word_count.cend()){
cout<<map_it->first<< "+" <<map_it->second<<endl;
++map_it;
}
//当使用一个迭代器遍历一个map.multimap.set或multiset时,迭代器按关键字升序遍历元素
关联容器和算法
我们通常不对关联容器使用泛型算法,因为关键字是const这一主要特性。
关联容器可用于只读元素的算法。但不适用于泛型算法,最好使用关联容器定义的专用只读算法。例如:find()泛型算法用于关联容器中 不如,map.find()效果好。
实际应用中,关联容器主要作为源序列,或者目标序列来使用算法。
11.3.2 添加元素
向map添加元素的方法:
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<value_type::iterator,bool>,如果关键字已在容器中insert什么也不做,bool返回false。关键字不存在,则插入,且返回true。
map<string,size_t> word_count;
string word;
while(cin>>word){
auto ret = word_count.insert({word,1}); //注意此时,ret的类型
if(!ret.second){ //ret<pair,bool>
++ret.first->second; //知道返回类型就能理解这条语句
}
}
向multiset或multimap添加元素
multimap<string,string> authors;
authors.insert({"Barth,Jonh","Sot-Weed Factor"});
authors.insert({"Barth,Jonh",“Lost in the Funhouse”});
//对于multi的insert函数,返回值是一个指向新元素的迭代器,无须返回一个bool值,因为插入总成功
11.3.3删除元素
关联容器提供一个额外的erase操作,接受一个key_type参数,删除所有与该关键字匹配的值,并且返回实际删除的元素的数量。
if(word_count.erase(removal_word))
cout<<"ok: "<<removal_word <<" removed\n";
else cout<<"not found";
11.3.4 map的下标操作
只有map可以用下标,set不支持(键就是值),multimap不是一对一。
map<string,size_t> word_count;
word_count["Anna"] = 1;
//先搜索
//若存在,则赋值
//若不存在,则不存在,则创建新pair,其中Anna是const string,值是0,
//最后将值置位1
使用下标操作的返回值
map的下标运算符与其他下标运算符不同之处在于:通常情况下,解引用一个迭代器所返回的类型与下标运算符返回的类型是一样的。但map则不然,map下标运算返回的是 mapped_type,而对map迭代器解引用返回的是一个 value_type对象。
相同之处在于:都返回一个左值:
11.3.5 访问元素
对map使用find代替下标操作
使用下标操作,在元素不存在时,会自行插入,不想插入时用find。
if(word_count.find("foobar") == word_count.end())
cout<< "foobar is not in the map" <<endl;
在multimap和multiset中查找元素
如果multimap或multiset中有多个元素具有给定关键字,则这些元素在容器中会相邻存储。
string search_item("author_name");
auto entries = authors.count(search_item);
auto iter = authors.find(seach_name); //返回作者第一本书
while(entries){
cout<<iter->second <<endl;
++iter=; //连续存储
--entries;
}
一种不同的,面向迭代器的解决方法
用相同的关键字调用lower_bound和upper_bound会得到一个迭代器范围。
lower_bound:返回>=的第一个。upper_bound:返回>的第一个
lower_bound返回的迭代器可能指向一个具有给定关键字的元素,但如果关键字不在容器中,则lower_bound会返回关键字的第一个安全插入点--不影响容器中元素顺序的插入位置。
如果没有元素与给定关键字匹配,lower和upper都会返回相等的迭代器--安全插入点
for(auto beg =authors.lower_bound(search_item),
end =authors.upper_bound(search_item);
beg!=end; ++beg){
cout<< beg->second<<endl;
}
equal_range函数
equal_range函数接收一个关键字,返回一个迭代器pair,第一个迭代器指向第一个与关键字匹配的元素,第二个迭代器指向最后一个。若未找到,则两个迭代器都指向关键字可以插入的位置。
for(auto pos = authors.equal_range(search_item);
pos.first!=pos.second; ++pos.first )
cout<<pos.first->second <<endl;
本节主要介绍multi中访问元素的三种方式。find,count,lower upper, equal——range
11.3.6一个单词转换的map
11.4 无序容器
新标准定义了4个无序关联容器,这些容器不使用比较运算符来组织元素,而是使用哈希函数和关键字类型的==运算符。
使用无序容器
unordered_map<string,size_t> word_count;
string word;
while(cin>>word){
++word_count["word"];
}
for(const auto &w: word_count){
cout<<w.first<<w.second<<endl;
}
管理桶
无序容器在存储组织上为一组桶,每个桶保存0个或多个元素。无序容器使用哈希函数将元素映射到桶。查找时,先找桶,再找元素。容器将具有一个特定哈希值的所有元素都保存在相同的桶中。
无序容器对关键字类型的要求
默认情况下,无序函数使用关键字类型==运算符来比较元素。还使用hash<key_type>类型的对象来生成每个元素的哈希值。
size_t hasher(const Sales_data &sd){
return hash<string>()(sd.isbn()); //使用标准库hash类型对象来计算ISBN成员的哈希值,该hash
//类型建立在string类型智商。
}
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);