关联容器有三个额外的类型别名:key_type、mapped_type、value_type。
set<string>::value_type v1; //v1是一个string
set<string>::key_type v2; //v1是一个string
map<string, int>::value_type v3; //v3是一个pair<const string,int>
map<string, int>::key_value v4; //v4是一个string
map<string, int>::mapped_type v5; //v5是一个int
关联容器迭代器
当解引用一个关联容器迭代器时,我们会得到一个类型为容器value_type的值的引用。对map而言,value_type是一个pair类型,其first成员保存const的关键字,second成员保存值:
//获得指向word_count中一个元素的迭代器
auto map_it = word_count.begin();
//*map_it是指向一个pair<const string,size_t>对象的引用
cout << map_it->first; //打印此元素的关键字
cout << " " << map_it->second; //打印此元素的值
map_it->first = "new key"; //错误:关键字是const的
++map_it->second; //正确:我们可以通过迭代器改变元素
set的迭代器是const的
虽然set类型同时定义了iterator和const_iterator类型,但两种类型都只允许只读访问set中的元素。
遍历关联容器
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; //递增迭代器,移动到下一个元素
}
关联容器和算法
我们通常不对关联容器使用泛型算法,关键字是const这一特性意味着不能将关联容器传递给修改或重排容器元素的算法。在实际编程中,如果我们真要对一个关联容器使用算法,要么是将它作一个源序列,要么当作一个目的位置。
添加元素
关联容器的insert成员向容器中添加一个元素或一个元素范围。由于map和set包含不重复的关键字,因此插入一个已存在的元素对容器没有任何影响:
vector<int> ivec = { 2,4,6,8,2,4,6,8 }; //ivec有8个元素
set<int> set2; //空集合
set2.insert(ivec.cbegin(), ivec.cend()); //set2有4个元素
set2.insert({ 1,3,5,7,1,3,5,7 }); //set2现在有8个元素
insert有两个版本,分别接受一对迭代器,或是一个初始化器列表。
向map添加元素
对一个map进行insert操作时,必须记住元素类型是pair。
//向word_count插入word的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));
检测insert的返回值
insert的返回值依赖于容器类型和参数。对于不包含重复关键字的容器,添加单一元素的insert和emplace版本返回一个pair,pair的first成员是一个迭代器,指向具有给定关键字的元素;second成员是一个bool值,指出元素是插入成功还是已经存在于容器中。
向multiset和multimap添加元素
由于multi容器中的关键字不必唯一,在这些类型上调用insert总会插入一个元素:
multimap<string, string> authors;
//插入第一个元素。关键字为Barth,John
authors.insert({ "Barth,John","Sot-Weed Factor" });
//正确:添加第二个元素,关键字也是Barth,John
authors.insert({ "Barth,John","Lost in the Funhouse" });
删除元素
关联容器定义了三个版本的erase,与顺序容器一样,我们可以通过传递给erase一个迭代器或一对迭代器来删除一个元素或者一个元素范围。关联容器还提供一个额外的erase操作,它接受一个key_type参数。此版本删除所有匹配给定关键字的元素,返回实际删除的元素的数量:
//删除一个关键字,返回删除的元素数量
if (word_count.erase(removal_word))
cout << "ok: " << removal_word << " removed\n";
else cout << "oops: " << removal_word << " not found!\n";
map的下标操作
map和unordered_map容器提供了下标运算符和一个对应的at函数。set类型不支持下标。
map<string, size_t> word_count; //empty map
//插入一个关键字为Anna的元素,关联值进行值初始化,然后将1赋予它
word_count["Anna"] = 1;
使用下标运算符可能插入一个新元素,我们只可以对非const的map使用下标操作。
使用下标操作的返回值
map的下标元素符与我们用过的其他下标运算符的另一个不同之处是其返回类型。当对一个map进行下标操作时,会获得一个mapped_type对象;但当解引用一个map迭代器时,会得到一个value_type对象。如果关键字还未在map中,下标运算符会添加一个新元素。
访问元素
关联容器提供多种查找一个指定元素的方法,例如find和count。
set<int> iset = { 0,1,2,3,4,5,6,7,8,9 };
iset.find(1); //返回一个迭代器。指向key==1的元素
iset.find(11); //返回一个迭代器,其值等于iset.end()
iset.count(1); //返回1
iset.count(11); //返回0
在multimap和multiset中查找元素
在容器中可能有很多元素具有给定的关键字,如果一个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会返回相等的迭代器,指向一个不影响排序的关键字插入位置。
//authors和search_item的定义。与前面的程序一样
//beg和end表示对应此作者的元素的范围
for (auto beg = authors.lower_bound(search_item),
end = authors.upper_bround(search_item);
beg != end; ++beg)
cout << beg->second << endl; //打印每个题目
equa_range函数
解决此问题的最后一种方法是三种方法中最直接的:直接调用equal_range即可。此函数接受一个关键字,返回一个迭代器pair。若关键字存在,则第一个迭代器指向第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素的位置,若未找到元素,则两个迭代器都指向关键字可以插入的位置。
//authors和search_item的定义。与前面的程序一样
//pos保存迭代器对,v表示与关键字匹配的元素范围
for (auto pos = authors.equal_range(search_item);
pos.first != pos.second; ++pos.first)
cout << pos.first->second << endl; //打印每个题目