关联式容器(Associative Container)
关联式容器依据特定的排序准则,自动为其元素排序。元素可以是任何类型的 value,也可以是 key/value pair,其中 key 可以是任何类型,映射至一个相关 value,而 value 也可以是任意类型。排序准则以函数形式呈现,用来比较 value,或比较 key/value 中的 key 。默认情况下所以容器都以操作符 < 进行比较,但是你可以提供自己的比较函数,定义不同的排序准则。
通常关联式容器由二叉树实现出来。在二叉树中,每个元素(节点)都有一个父节点和两个子节点,左子树的所有元素都比自己小,右子树的所有元素都比自己大。关联容器的差别主要在于元素的种类以及处理重复元素时的方式。
关联式容器的主要优点是,它能很快找出一个具有某特定 value 的元素,因为它具备对数复杂度,而任何循序式容器的复杂度是线性。因此,使用关联式容器,面对1000个元素,平均而言你将有10次而不是500次比较动作。然而它有一个缺点是,你不能直接改动元素的 value ,因为那会破坏元素的自动排序。
下面是 STL 定义的关联式容器:
set 元素依据其 value 自动排序。每个元素只能出现一次,不允许重复。
multiset 和 set 的唯一差别是:元素可以重复。也就是 multiset 可包括多个 "value相同"的元素。
map 每个元素都是 key/value pair,其中 key 是排序准则的基准。每个 key 只能出现一次,不允许重复。
multimap 和 map 的唯一差别是:元素可以重复,也就是 multimap 允许其元素拥有相同的 key。
map
template <typename Key, typename Tp, typename Compare = std::less<Key>,
typename Alloc = std::allocator<std::pair<const Key, Tp> > >
class map
{
public:
typedef Key key_type; // 第一个参数模板 Key
typedef Tp mapped_type; // 第二个参数模板 Tp
typedef std::pair<const Key, Tp> value_type;
typedef Compare key_compare;
typedef Alloc allocator_type;
typedef typename Alloc_traits::pointer pointer;
typedef typename Alloc_traits::const_pointer const_pointer;
typedef typename Alloc_traits::reference reference;
typedef typename Alloc_traits::const_reference const_reference;
typedef typename Rep_type::iterator iterator; // value_type的双向迭代器
typedef typename Rep_type::const_iterator const_iterator; // const value_type的双向迭代器
typedef typename Rep_type::size_type size_type; // 无符号整数类型
typedef typename Rep_type::difference_type difference_type; // 一个有符号整数类型
typedef typename Rep_type::reverse_iterator reverse_iterator;
typedef typename Rep_type::const_reverse_iterator const_reverse_iterator;
};
默认构造
//定义一个默认状态的空map容器
explicit map (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
#include<iostream>
#include<map>
using namespace std;
int main()
{
map<string,int> arr1;//定义一个空的map容器
map<string,int> arr2{
{"小明",10086},
{"小红",1008611},
{"小兰",123456}
};//使用初值列定义一个包含三个string/int的map容器
for(auto i:arr2) //输出所有元素
cout<<i.first<<" "<<i.second<<endl;
cout<<endl;
map<string,int> arr3(arr2);//使用一个map容器作为初始值
for(auto i:arr3) //输出所有元素
cout<<i.first<<" "<<i.second<<endl;
cout<<endl;
map<string,int> arr4(++arr2.begin(),arr2.end());//使用迭代器赋值
for(auto i:arr4) //输出所有元素
cout<<i.first<<" "<<i.second<<endl;
}
自定义排序准则
//自定义比较方法
explicit map(const key_compare& comp, const allocator_type& a = allocator_type());
#include<iostream>
#include<map>
using namespace std;
bool upper(int a,int b){return a>b;} //自定义大到小比较函数
bool lower(int a,int b){return a<b;} //自定义小到大比较函数
int main()
{
map<int,string,bool(*)(int,int)> arr({{1,"小红"},{3,"小明"},{2,"小兰"}},lower);//按照key从小到大排列
for(auto i:arr)
cout<<i.first<<" "<<i.second<<endl;
cout<<endl;
map<int,string,bool(*)(int,int)> buf({{1,"小红"},{3,"小明"},{2,"小兰"}},upper);//按照key从大到小排列
for(auto i:buf)
cout<<i.first<<" "<<i.second<<endl;
}
用自定义的函数对象来比较元素
如果 map 或 multimap 中的键是指针的话,那么需要定义一个函数来比较它们所指向的对象,否则会比较指针所表示的地址,这并不是我们想要的。如果键是不支持直接进行 < 或 > 比较的类型,为了可以在 map 或 multimap 中使用它们,必须为它们定义一个适当的函数对象。处理这两种情况的方式在本质上相同。
#include<iostream>
#include<map>
#include<string>
#include<memory>
using namespace std;
class Key_compare
{
public:
bool operator () (const unique_ptr<string>& p1, const unique_ptr <string>& p2) const
{
return *p1 < *p2;
}
};
int main()
{
map<unique_ptr<string>,string,Key_compare> phonebook;
phonebook.emplace(make_unique<string>("Fred"), "914-626-7897");
phonebook.insert(make_pair(make_unique<string>("Lily"), "212-896-4337"));
phonebook.emplace(make_unique<string>("Eloik"), "871-343-4714");
for (const auto& p: phonebook)
cout << *p.first << " " << p.second << endl;
}
赋值
操作符= 功能是将值赋给map对象,值可以为一个map容器,也可以是一个初值列。
map&operator =(const map&x);
map&operator =(map && x);
map&operator =(initializer_list <value_type> il);
swap 功能为交换两个map容器。
void swap (map& x);
无论是= 还是 swap,操作两个不同的容器时,其key/value 必须一致。
#include<iostream>
#include<map>
using namespace std;
bool upper(string a,string b){return a>b;}
int main()
{
map<string,int> arr1;
map<string,int> arr2;
map<string,int> arr3;
map<string,int,bool(*)(string,string)> arr4(upper);
map<string,char> arr5;
arr1 = { {"小明",10086},
{"小红",1008611},
{"小兰",123456}}; //使用初值列赋值
arr2 = arr1; //使用容器赋值
arr3["小黑"] = 666666; //使用数组的方法赋值
arr1.swap(arr3); //交换两个容器
//arr2 = arr4;//错误 参数模板 不一致
//arr2 = arr5;//错误 key/value 不一致
//arr2.swap(arr4); //错误 参数模板 不一致
//arr2.swap(arr5); //错误 key/value 不一致
}
multimap
multimap 容器保存的是有序的键/值对,但它可以保存重复的元素(重复键的排序方式为:不改变其相对顺序)。multimap 中会出现具有相同键的元素序列,它们会被添加到容器中。multimap 和 map 有相同范围的构造函数,默认的比较键的函数是 less()。
multimap 大部分成员函数的使用方式和 map 相同。因为重复键的原因,multimap 有一些函数的使用方式和 map 有一些区别。
#include<iostream>
#include<map>
#include<string>
#include<algorithm>
#include<utility>
using namespace std;
int main()
{
multimap<string, size_t> people {{"Ann",44},{"Ann",25},{"Bill", 46}, {"Jack", 77},
{"Jack", 32},{"Jill", 32}, {"Ann", 35}};
auto it = people.find("Ann");//输出指向第一个Ann的迭代器
if(it != people.end())
cout<<it->first<<" "<<it->second<<endl;
cout<<endl;
auto pr = people.equal_range("Ann");//遍历所有键为 Ann 的元素
if(pr.first != end(people))
{
for (auto iter = pr.first ; iter != pr.second; ++iter)
cout << iter->first << " is " << iter->second << endl;
}
auto iter1 = people.lower_bound("Ann");
auto iter2 = people.upper_bound("Ann");
if(iter1 != end(people))
{
for(auto iter = iter1 ; iter != iter2; ++iter)
cout << iter->first << " is " << iter->second << endl;
}
}
equal_range() 的参数可以是和键同类型的对象,或是不同类型的但可以和键比较的对象。返回的 pair 对象的成员变量 first 是一个迭代器,它指向第一个大于等于参数的元素;如果键和参数相等的元素存在的话,它是第一个键和参数相同的元素。如果键不存在,pair 的成员变量 first 就是容器的结束迭代器,所以应该总是对它们进行捡查。
pair 的成员变量 second 也是一个迭代器,它指向键值大于参数的第一个参数;如果没有这样的元素,它会是一个结束迭代器。
multimap 的成员函数 lower_bound() 会返回一个迭代器,它指向键值和参数相等或大于参数的第一个元素,或者指向结束迭代器。upper_bound() 也返回一个迭代器,它指向键值大于函数参数的第一个元素,如果这样的元素不出现的话,它就是一个结束迭代器。所以,当存在一个或多个相等键时,这些函数会返回一个开始迭代器和一个结束迭代器,它们指定了和参数匹配的元素的范围,这和 equal_range() 返回的迭代器是相同的。