S11关联容器
一、使用关联容器
1、关联容器:关联容器与顺序容器有着根本的不同,关联容器中的元素是按关键字来保存和访问的,顺序容器中的元素是在容器中的位置来顺序保存和访问的,两个最主要的关联容器是map和set
2、标准库关联容器表
//关键字有序保存,定义在map和set头文件中
map //关联数组,保存key-value对
set //key即value,只保存key
multimap //key可重复出现的map
multiset //key可重复出现的set
//无序集合,定义在unordered_map和unordered_set中
unordered_map //用hash函数组织的map
unordered_set //用hash函数组织的set
unordered_multimap //hash组织的map,key可重复出现
unordered_multiset //hash组织的set,key可重复出现
二、关联容器概述
注意:关联容器支持S09顺序容器中的常见操作,而不支持顺序容器与位置相关的操作,由于关联容器是按关键字存储的,故位置操作对关联容器而言没有意义
注意:关联容器的迭代器都是双向(bidirectional iterator)的
1、定义关联容器
(1)关联容器的定义必须指明key类型和value类型,每个关联容器的默认构造函数创建一个指定类型的空容器
map<T1, T2> m = {{key1, val1}, {key2, val2} , ...};
set<T> s = {key1, key2, key3, ...};
(2)由于map和set不支持关键词重复,因此当初始化时,重复的关键字只保留第一个,相对的,multimap和multiset支持重复的关键词
2、关键字类型的要求
(1)对于有序容器,key的类型必须定义了元素比较的方法,默认情况下标准库使用<
来比较key
map<vector<int>::iterator, int> a{..}; //正确,vector的迭代器之间定义了比较操作
map<list<int>::iterator, int> b{..}; //错误,list的迭代器之间没有比较操作,但编译可以通过
(2)自定义key的比较操作:由于组织关联容器中元素的操作类型也是容器类型的一部分,因此在定义容器类型时就应该指定操作类型,在创建一个容器对象时才会以构造函数的参数形式真正提供比较操作
bool compareIsbn(..., ...) {...}
multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn); //注意*代表指向函数的指针
3、pair类型
(1)pair类型定义在utility头文件中,一个pair保存两个数据成员,成员的类型可以不一致,创建pair时必须提供两个类型,默认构造函数将对成员进行值初始化
注意:与其他标准库类型不同,pair的成员是public的,分别命名为first和second
(2)pair的基本操作
pair<T1, T2> p; //p是pair,p的两个类型为T1/T2的成员进行了值初始化
pair<T1, T2> p(v1, v2); //p.first = v1, p.second = v2初始化,等价于用{v1, v2}列表初始化
make_pair(v1, v2); //返回一个用v1/v2初始化的pair,类型从v1/v2自动推断出来
p.first/p.second //p的成员是public的,直接访问
p1 </<=/>/>= p2 //比较p1和p2,按字典序定义,先比较first,若first相等则再比较second
p1 !=/== p2 //当p1/p2的成员分别相等时,p1与p2相等
(3)创建pair对象的函数:若一个函数返回pair,则可以用多种方式对返回值进行初始化
pair<int, int> fun(...)
{
...
return {1, 2}; //列表初始化返回的pair,比较简洁易懂
//return pair<int, int>(); //隐式构造返回值
//return pair<int, int>(1, 2); //显式构造返回值
//return make_pair(1, 2); //调用make_pair生成返回的pair
}
三、关联容器操作
1、关联容器额外的类型别名
//除了S09中容器库常见表中的别名,关联容器还有如下别名
key_type //此容器类型的关键字类型,即key的类型
mapped_type //每个关键字关联的类型,只有map有,即value的类型
value_type //对于set,与key_type相同,对于map,为pair<const key_type, mapped_type>
注意:map的元素为pair对象,由于不能改变关键字,故是const key_type,因此在map中迭代提取出来的是pair类型
2、关联容器迭代器
(1)解引用关联容器的迭代器获得一个类型为容器的value_type
的对象的引用
(2)set的迭代器是const
的,不能通过迭代器改变set的元素
(3)遍历关联容器,关联容器定义了begin()
和end()
成员函数
(4)一般不对关联容器使用泛型算法,因为map的元素中有const
的部分,而set的元素都是const
的,且关联容器自身定义了一些只读的高效算法,因此如果真要使用泛型算法,一般关联容器只作为源序列或目的序列。
3、添加元素
成员函数insert
添加元素,对map/set而言,关键字不能重复,插入已有关键字的元素对容器没有任何影响
(1)向map/set添加元素
map的元素类型是pair,因此调用insert
时必须添加pair类型的元素
a.insert({word, 1});
a.insert(make_pair(word, 1));
a.insert(pair<string, int>(word, 1));
a.insert(map<string, int>::value_type(word,1));
c.emplace(args) //args用来构造元素,对map/set只有当关键字不在c中时才发生插入
//返回一个pair对象,包括一个指向具有指定关键字的元素以及一个指示插入是否成功的bool
//例如pair<map<string, size_t>::iterator, bool>
c.insert(v) //v是value_type类型的对象
c.insert(b, e) //b/e是表示c::value_type类型值的范围
c.insert(il) //il是上述类型值花括号列表,对map/set只有b/e范围内不在c中的关键字才发生插入,函数返回void
//对multimap/multiset则会插入b/e范围内的每个元素
(2)向multimap/multiset添加元素
对允许重复的multimap/multiset添加元素,接受单个元素的insert
返回一个指向新元素的迭代器,不再返回bool
4、删除元素
成员函数erase
删除元素
c.erase(k) //从c中删除每个key为k的元素,函数返回一个size_type类型的值表示成功删除元素的个数
c.erase(p) //从c中删除迭代器p指定的元素,p必须指向c中真实的元素,返回一个指向被删除元素之后的迭代器
c.erase(b, e) //删除迭代器b/e范围中的元素,返回e
5、map/unordered_map的下标操作
multi的容器一个关键字可能有多个值,因此不能使用下标操作,下列操作仅适用非const
的map/unordered_map
c[k] 返回关键字是k的元素,如果不存在,则添加一个关键字为k的元素,并对其值初始化
c.at(k) 返回关键字是k的元素,带参数检查,如果不存在则抛出一个out_of_range异常
注意:一般情况下,对容器下标操作和对迭代器解引用操作会返回相同的类型,然而map不同,对map下标操作时,返回mapped_type对象,对map迭代器解引用时,返回value_type对象(pair对象)
6、访问元素
c.find(k) //回一个迭代器,指向第一个关键字为k的元素,若不存在返回尾后迭代器c.end()
c.count(k) //返回key==k的元素数量
c.lower_bound(k) //返回一个迭代器,指向第一个key>=k的元素,不适用无序容器
c.upper_bound(k) //返回一个迭代器,指向第一个key>k的元素,不适用无序容器
c.equal_range(k) //返回一个迭代器pair,表示key==k的元素的范围
//若k不存在,则pair的两个成员均等于合适的插入点,类似组合了lower_bound和upper_bound的函数,若是无序容器,则返回两个尾后迭代器组成的pair
注意:对map要注意使用find()来访问元素,因为下表操作可能带来改变容器的副作用
注意:对multi的有序容器访问元素更复杂,具有相同关键字的元素会在容器中相邻存储
string search_item("Alen Green"); //要查找的关键字,即作者名
//方法一:采用find和count在获取初始位置和数量控制下操作
auto entries = authors.count(search_item); //与该key对应的元素的数量,authors是multimap
auto iter = authors.find(search_item); //返回此key对应的第一个元素,即此作者的第一本书
while(entries)
{
do something with iter->second;
++iter; //获得同一作者的下一本书,即同key的下一个元素的迭代器
--entries; //entries控制不会迭代超过这个key的元素
}
//方法二:采用lower_bound和upper_bound在范围内操作
auto beg = authors.lower_bound(search_item);
auto end = authors.upper_bound(search_item);
for(; beg != end; ++beg)
{
//若元素存在,则lower_bound指向第一个与key相等元素,upper_bound指向最后一个与key相等元素之后
//若元素不存在,则两者都指向这个关键字应该插入位置之后的元素,一个安全插入点
do something with beg->second;
}
//方法三:采用equal_range直接在范围内操作
for(auto pos = authors.equal_range(search_item); pos.first != pos.second; ++pos.first)
{
//在有序map中,equal_range的行为就是整合之后的lower_bound和upper_bound
do something with pos.first->second;
}
四、无序容器
1、无序容器在存储上组织为一组桶(buckets),每个桶保存零个或多个元素,通过hash函数将元素映射到桶
//桶接口
c.bucket_count() //正在使用的桶的数目
c.max_bucket_count() //容器能容纳最多的桶的数量
c.bucket_size(n) //第n个桶中有多少元素
c.bucket(k) //key==k的元素在哪个桶中
//桶迭代
local_iterator //可以用来访问桶中元素的迭代器
const_local_iterator
c.begin(n)/c.end(n) //桶n的首元素迭代器和尾后迭代器
c.cbegin(n)/c.cend(n)
//哈希策略
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
2、无序容器对关键字类型的要求
默认情况下无序容器使用关键字类型定义的==
运算符来比较元素,并使用一个hash<key_type>
类型的对象来生成每个元素的哈hash值,因此不能直接定义关键字类型为自定义类类型的无序容器,除非提供自定义类类型的==
操作和hash
操作给无序容器
unordered_multiset<Sales_data, decltype(hasher)*, decltype(eqOp)*> bookstore(42, hasher, eqOp)
五、其他
1、一些课本上的习题或参考代码
练习11.4:
int main(int argc, char *argv[])
{
ifstream file(argv[1]);
map<string, size_t> word_count;
string exclude{ "abcdefghijklmnopqrstuvwxyz" };
string word;
while (file >> word)
{
for (string::size_type p = 0; p < word.size(); p++)
{
if (word[p] >= 'A' && word[p] <= 'Z')
word[p] -= ('A' - 'a');
else if (exclude.find_first_of(word[p]) == string::npos)
word.erase(p, 1);
}
++word_count[word];
}
vector<pair<string, size_t>> word_count_sort;
for (auto w : word_count)
{
word_count_sort.push_back(w);
}
sort(word_count_sort.begin(), word_count_sort.end(),
[](pair<string, size_t> &a, pair<string, size_t> &b) { return a.second > b.second; });
for (auto w : word_count_sort)
{
cout << w.first << " : " << w.second << endl;
}
return 0;
}
练习11.13:
int main()
{
vector<pair<string, int>> test;
string ss;
int i;
while (cin >> ss >> i)
{
test.push_back(make_pair(ss, i));
//test.push_back({ss, i});
//test.push_back(pair<string, int>(ss, i));
}
return 0;
}
关联容器练习:
int main()
{
map<int, int> a{ {1,1},{2,2} };
auto p = a.begin();
auto inserter_iter = inserter(a, p);
vector<pair<int, int>> src{ {3,3},{4,4} };
copy(src.begin(), src.end(), inserter_iter);
auto ret1 = a.insert({ 5,5 }); //ret1: ((5, 5), true)
//注意ret1是返回的pair,(5,5)是map迭代器解引用后的对象,也是pair
auto ret2 = a.insert(a.begin(), { 6,6 }); //ret2: (6,6)
auto ret3 = a.insert({ 5,5 }); //ret1: ((5, 5), false)
auto ret4 = a.insert(a.begin(), { 6,6 }); //ret2: (6,6)
return 0;
}
练习11.29:
int main()
{
map<int, int> a{ {1,1},{2,2},{3,3},{5,5} };
auto up = a.upper_bound(4); //up: (5,5)
auto lo = a.lower_bound(4); //lo: (5,5)
auto fi = a.find(4); //fi: end
auto eq = a.equal_range(4); //eq: ((5,5),(5,5))
return 0;
}