与顺序容器的根本不同:关联容器中的元素是按关键字来保存和访问的,而顺序容器是按位置保存的
关联容器支持高效的查找和访问
主要的有map和set
一共有8种,主要分类依据:
1、set或map
2、是否允许重复关键字,multi
3、按顺序保存或无序,unordered_
使用关联容器
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中提取一个元素时,得到的是pair
使用set
例:对上一个程序中的常见单词进行忽略,可以用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];
关联容器概述
关联容器不支持位置相关的操作,都支持一些其他操作。
还提供用来调整哈希性能的操作。
迭代器都是双向的
定义关联容器
map<string, size_t> word_count; //空容器
//列表初始化
set<string> exclude = { "The", "But", "And", "Or", "An", "A",
"the", "but", "and", "or", "an", "a"};
//map初始化
mat<string, string> authors = { {"joyce", "james"},{"Austem", "Jane"}};
把关键字-值包围在花括号中
multimap 或multiset
允许拥有重复的关键字
关键字类型的要求
关键字类型必须定义元素比较的方法,默认使用<
可以定义自己的操作来带起关键字上的<运算符,必须定义一个严格弱序("小于等于")
例:为了使用自己定义的操作,必须提供两个类型:Sakes_data以及比较操作类型
multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);
pair类型
定义在utility中,一个pair保存两个数据成员
pair<string, string> anon; //保存两个string
pair的默认构造函数对数据成员进行值初始化,也可以提供初始化器
pair<string, string> author{"James", "Joyce"};
pair的数据成员是public的,分别为first和second
其它操作见表11.2
创建pair对象的函数
可以用花括号
pair<string, int> process(vector<string> &v)
{
if(!v.empty())
return {v.back(), v.back().size());
else
return pair<string, int>();
}
老版本不能用花括号,必须显式构造
return pair<string, int>(v.back(), v.back().size());
或者用make_pair
return make_pair(v.back(), v.back().size());
关联容器操作
关联容器额外的类型别名
key_type 此容器类型的关键字类型
mapped_type 每个关键字关联的类型;只适用于map
value_type 对于set,于key_type相同
对于map,为pair<const key_type, mapped_type>
这些pair关键字部分是const的
set<string>::value_type v1; //v1是string
set<string>::key_type v2; //v2是string
map<string, int>::value_type v3; //V3是pair<const string, int>
map<string, int>::key_type v4; //v4是string
map<string, int>::mapped_type v5; //v5是int
关联容器迭代器
map的value_type是一个pair。只能改变值不能改变关键字
map_it->first = "new"; //错误,关键字是const
++map_it->second; //正确,元素可以改
set的迭代器是const的
iterator和const_iterator都是只读的
遍历关联容器
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;
}
map、multimap、set、multiset的迭代器按关键字升序遍历元素
关联容器和算法
关联容器可用于只读取的算法,修改或重排的不行。set是const的,map中的pair第一个成员是const的。
使用时要么把它当作一个源序列,要么当作一个目的位置。
添加元素
使用insert添加元素,如果插入一个已存在元素对容器也没有任何影响
向map添加元素
有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)); //自动构造合适的pair
其它操作见表11.4
检测insert的返回值
insert(或emplace)的返回值依赖于容器类型和参数。
对于不重复的容器,添加单一元素的insert返回一个pair,first是迭代器,指向给定关键字的元素;second是bool值,指出是插入成功还是以及存在于容器中。
例:计数单词
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.first) -> second);
向multiset或multimap添加元素
允许重复关键字的容器,接受单个元素的insert操作返回一个指向新元素的迭代器。不需要返回bool
删除元素
通过erase删除,有3个版本。可以传递迭代器或迭代器对表示的范围来删除元素
关联容器可以接受一个key_type参数,删除所有匹配给定关键字的元素,返回实际删除的元素的数量。
map的下标元素
map和unordered_map都有下标运算符和at函数,set不支持下标。
下标接受索引,但如果下标不存在,会创建一个元素并插入到map中,关联值将进行值初始化。
word_count["Anna"] = 1; //插入一个关键字值对并用1初始化
at则是访问关键字为k的元素,如果不存在抛出异常
访问元素
对于不允许重复的容器find和count没区别
允许重复的容器count会统计次数,如果不需要计数用find
set<int> iset = {0,1,2,3,4,5,6,7,8,9};
iset.find(1); //返回key==1的元素的迭代器
iset.find(11); //返回iset.end(0)
iset.count(1); //返回1
iset.count(11); //返回0
map还有其它操作
c.lower_bound(k); //指向第一个关键字不小于k的元素
c.upper_bound(k); //指向第一个关键字大于k的元素
c.equal_range(k); //返回一个迭代器pair,表示关键字等于k的元素的范围。若k不存在则两个成员都是c.end();
对map使用find代替下标操作
下标一个副作用:如果关键字不存在会创建一个
如果只想知道一个给定的关键字是否在map中,而不想改变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;
}
先用count获取数量,再用find获得第一个迭代器,进行循环
一种面向迭代器的解决方法
用lower_bound和upper_bound来解决
如果关键字在容器,返回第一个元素迭代器和最后一个迭代器之后的位置
如果关键字不在,都返回一个不影响排序的关键字插入位置
如果查找的元素具有最多的关键字,upper_bound返回尾后迭代器。如果关键字不存在且大于容器中的任何关键字,则lower_bound也是尾后迭代器
//重写上面
for (auto beg = authors.lower_bound(search_item),
end = authors.upper_bound(search_item);
beg != end; ++beg)
cout << beg->second << endl
equal_range
此函数接受一个关键字,返回一个迭代器pair。
若关键字存在,则第一个迭代器指向关键字匹配元素,第二个迭代器指向最后一个匹配元素之后的位置。
若不存在则都返回指向可以插入的位置
//重写
for (auto pos = authors.equal_range(search_item);
pos.first != pos.second; ++pos.first)
cout << pos.first->second << endl;
一个单词转换的map程序
给定一个string,根据对应表,转换成另一个string。
程序使用三个函数,word_transform管理整个过程,接受两个ifstream:一个绑定到单词转换文件,一个绑定到我们要转换内容的映射。
buildMap读取转换规则文件,并创建一个map,保存每个单词到其转换内容的映射。
transform接受一个string,如果存在转换规则,返回转换后的内容
void word_transform(ifsteam &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(ifsteam &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, const map<sting, string> &m)
{
auto map_it = m.find(s);
if (map_it != m.cend())
return map_it->second;
else
return s;
}
11.4 无序容器
unordered_map、unordered_set
新标准定义了4个无序关联容器,使用哈希函数和关键字类型得==运算符
无序容器也能用有序容器相同得操作如(find、insert)。
管理桶
无序容器在存储上组织为一组桶,每个桶保存零或多个元素。使用哈希函数将元素映射到桶。
访问时先计算元素得哈希值,将一个具有特定哈希值得所有元素都保存在同一个桶中。如果允许重复关键字,所有具有相同关键字得元素也都会在同一个桶中。
无序容器得性能依赖于哈希函数得质量和桶得数量和大小。
理性情况下,每个特定的值都映射到唯一的桶,但是将不同的关键字映射到相同的桶也是允许的。
桶的管理操作见表11.8
无序容器对关键字的要求
无序容器用==来比较,使用hash<key_tpye>类型的对象来生成每个元素的哈希值。
标准库为内置类型都定义了hash,可以直接定义关键字是内置类型或string或智能指针的无序容器。
不能直接定义自定义类型的无序容器,必须提供自己的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();
}
//定义一个unordered_multiset
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);