【STL】关联式容器 - Map和Multimap
Map和Multimap
STL提供的Map和Multimap是一种关联式容器,将key/value pair(键值/实值 对组)当做元素,进行管理。它们可以根据key的排序准则自动将元素排序。Multimap允许重复元素,map不允许。
Map和Multimap结构:
在使用map和multimap之前,必须先含入头文件
#include <map>
在其中,map和multimap被定义为命令空间std内的class template:
namespace std
{
template <class Key,class T,
class Compare = less<Key>,
class Allocator = allocator<pair<const Key,T> > >
class map;
template <class Key,class T,
class Compare = less<Key>,
class Allocator = allocator<pair<const Key,T> > >
class multimap;
}
第一个template参数被当做元素的key,第二个template被当做元素的value。Map和multimap的元素型别Key和T,必须满足两个条件:
1.key/value必须具备assignable(可赋值的)和copyable(可赋值的)性质。
2.对排序准则而言,key必须是comparable(可比较的)
第三个template参数可有可无,用来定义排序的顺序,这个排序的准则必须定义为struct weak ordering。元素的次序由它们的key决定,和value无关。排序准则也可以用来检查相等性:如果两个元素的key彼此都不小于对方,则两个元素被视为相等。如果使用者未传入特定排序准则,就使用缺省的less排序准则,以 operator<
来进行比较。
第四个template参数也是可有可无,用来定义内存模型,缺省的内存模型是allocator,由C++标准库提供
Maps和Mulimaps的能力
和标准的关联式容器一样,map/mulitimap以平衡二叉树完成。
Map和Multimap内部结构:
Map和Multimap根据元素的key自动对元素进行排序。在根据已知的key搜寻某个元素时,能够有很好的性能。自动排序这一性质导致使用时有一条重要的限制:不可以直接改变元素的key,因为会破坏正确的次序。要修改元素的key,所以必须先移除拥有该Key的元素,然后插入拥有新的key/value的元素。
Maps和Mulimaps的操作函数
生成(Create)、复制(Copy)和销毁(Destroy):
操作 | 效果 |
---|---|
map c | 产生一个空的map/multimap |
map c(op) | 以op为排序准则,产生一个空的map/multimap |
map c1(c2) | 产生某个map/multimap的副本,所有元素都被复制 |
map c(beg,end,op) | 以op为排序准则,使用[beg;end]内的元素生成一个map/multimap |
c.~map() | 销毁所有元素,释放内存 |
非变动性操作
maps和multimaps提供常见的非变动性操作,用来查询大小、相互比较
操作 | 效果 |
---|---|
c.size() | 返回容器的大小 |
c.empty() | 返回判断容器大小是否为空。等同于size() == 0 |
c.max_size() | 返回可容纳的最大元素数量 |
c1 == c2 | 判断是否c1等于c2 |
c1 != c2 | 判断是否c1不等于c2 |
c1 < c2 | 判断是否c1小于c2 |
c1 > c2 | 判断是否c1大于c2 |
c1 <= c2 | 判断是否c1小于等于c2 |
c1 >= c2 | 判断是否c1大于等于c2 |
元素比较动作只能用作于型别相同的容器。容器的key、value、排序准则都必须有相同的型别,否则编译器会产生型别方面的错误。例如:
std::map<float ,std::string> c1;
std::map<float,std::string,std::greater<float>> c2;
if(c1 == c2)
{
...
}
如果比较不同型别的容器,就需要采用“比较算法”。
特殊的搜寻方法(Special Serach Operations)
map和multimaps提供了特殊的搜寻函数,便于利用内部树状结构获取较好的性能。
成员函数find()用来搜寻拥有某个key的第一个元素,并返回一个迭代器,指向该位置,如果没有找到这样的元素,就返回容器的end()。但是find方法不能用于搜寻某个特定value的元素,必须改用通用算法如find_if(),或者写一个显式循环,来对特定的所有元素进行某项操作:
std::multimap<std::string,float> coll;
std::multimap<std::string,float>::iterator pos;
for(pos = coll.begin();pos != coll.end();++pos)
{
if(pos->second== value)
{
do_something();
}
}
map和Multimaps的特殊搜寻操作函数:
操作 | 效果 |
---|---|
count(key) | 返回"键值等于key"的元素个数 |
find(key) | 返回"键值等于key"的第一个元素,找不到就返回end() |
lower_bound(key) | 返回"键值为key"元素的第一个可安插位置 |
upper_bound(key) | 返回"键值为key"元素的最后一个可安插位置 |
equal_range(key) | 返回"键值为key"元素的第一个可安插位置和最后一个可安插位置 |
赋值(Assignments)
maps和multimaps只支持所有容器都提供基本赋值操作
操作 | 效果 |
---|---|
c1 = c2 | 将c2中所有元素都赋值给c1 |
c1.swap(c2) | 将c1和c2的元素互换 |
swap(c1,c2) | 将c1和c2的元素互换(全局函数) |
以上操作函数,也需要满足型别相同。
迭代器函数(Interator Function)和元素存取(Element Access)
Map和multimap不支持元素直接存取,所以元素的存取通常是通过迭代器进行操作的。有个例外:map提供subscript(下标)操作符,可以直接存取元素。
迭代器相关操作函数表:
操作 | 效果 |
---|---|
c.begin() | 返回一个双向迭代器,指向第一元素 |
c.end() | 返回一个双向迭代器,指向最后元素的下一个位置 |
c.rbegin() | 返回一个逆向迭代器,指向逆向遍历的第一个元素 |
c.rend() | 返回一个逆向迭代器,指向逆向遍历时的最后一个元素的下一个位置 |
map迭代器使用实例:
std::map<std::string,float> coll;
...
std::map<std::string,float>::iterator pos;
for(pos = coll.begin();pos != coll.end();++pos)
{
std::cout<<"key: "<<pos->first<<"\t"
<<"value:" << pos->second << std::endl;
}
迭代器pos遍历了string/float pair所组成的序列,如果尝试改变元素的Key,会引发错误:
pos->first = "hello "; // ERROR at compile time
如果一定要改变元素的key,可以使用一个"value相同"的新元素替换掉旧元素,以一个泛化函数为例:
namespace MyLib
{
template <class Cont>
inline bool replace_key(Cont& c,
const typename Cont::key_type& old_key,
const typename Cont::key_type& new_key)
{
typename Cont::iterator pos;
pos = c.find(old_key);
if(pos != c.end())
{
// 插入一组新的<key,value>,不过key使用new_key,value仍是old_value
c.insert(typename Cont::value_type(new_key,pos->second));
// 移除旧的<old_key,old_value>
c.erase(pos);
return true;
}
else
{
// 没有找到key
retrun false;
}
}
}
使用方法:
std::map<std::string,float> coll;
MyLib::replace_key(coll,"old key","new key");
map还提供了下标操作符方法可以改变元素的key(详细下文Map视为关联式数组):
// insert new element with value of old element
coll["new_key"] = coll["old_key"];
// remove old element
coll.erase("old_key");
元素的插入(Inserting)和移除(Removing)
Maps个Multimaps的元素安插和移除:
操作 | 效果 |
---|---|
c.insert(elem) | 插入一份elem副本,返回新元素位置 |
c.insert(pos,elem) | 插入一份elem副本,返回新元素位置(指明pos会提高速度) |
c.insert(beg,end) | 将区间[beg;end]内所有元素的副本安插到c(无返回值) |
c.erase(elem) | 移除"value以elem相等"的所有元素,返回被移除的元素个数 |
c.erase(pos) | 移除迭代器pos上所指位置上的元素,无返回值 |
c.erase(beg,end) | 移除区间[beg;end]内的所有元素,无返回值 |
c.clear() | 移除所有元素,清空整个容器 |
插入一个key/value pair的时候,需要注意,key被视为常数,要不就提供正确型别,要不就提供隐式或者显式类型转换。有三个方法可以将value传入map;
1.使用value_type
可以利用value_type传递正确型别。value_type是容器本身提供的型别定义。比如:
std::map<std::string,float> coll;
coll.insert(std::map<std::string,float>::value_type("otto",44.6));
2.使用pair<>
std::map<std::string,float> coll;
// 使用非显式转换
coll.insert(std::pair<std::string,float>("otto",22.3));
// 不使用非显式转换
coll.insert(std::pair<const std::string,float>("otto",22.3));
3.运用make_pair()
最方便的方法是使用make_pair()根据传入的两个参数构造出一个pair对象
std::map<std::string,float> coll;
coll.insert(std::make_pair("otto",33.2));
在map中安插一个元素,并检查是否成功:
std::map<std::string,float> coll;
if(coll.insert(std::make_pair("otto",22.3)).second)
{
std::cout<<"Ok"<<std::endl;
}
else
{
std::cout<<"No"<<std::endl;
}
如果需要移除"带有某个value"的元素,则调用erase():
std::map<std::string,float> coll;
coll.erase(key);
如果删除multimap内含有的重复元素:
typedef std::multimap<std::string,float> StringFloatMMap;
StringFloatMMap coll;
StringFloatMMap::iterator pos;
pos = coll.find(key);
if(pos != coll.end())
{
coll.erase(pos);
}
移除“迭代器所指元素”的正确做法:
typedef std::multimap<std::string,float> StringFloatMMap;
StringFloatMMap coll;
StringFloatMMap::iterator pos;
for(pos = coll.begin();pos != coll.end(); )
{
if(pos->second == value){
coll.erase(pos++);
}
else
{
++pos;
}
}
将Map视为关联式数组(Associated Arrays)
通常关联式容器并不提供元素的直接存取,必须依靠迭代器,但是map是个例外,Non-const map提供下标操作符,支持元素的直接存取。只不过下标操作符的索引值不是元素整数位置,而是元素的key。
map的直接元素存取:
操作 | 效果 |
---|---|
m[key] | 返回一个reference,指向键值为key的元素。如果元素尚未存在,就安插此元素 |
关联式数组的行为方式各有优缺点:
- 优点:可以透过更方便不的接口对着map安插新元素
std::map<std::string,float> coll;
coll["otto"] = 8.8;
map对coll["otto"] = 8.8的处理如下:
如果存在键值为"otto"的元素,则返回该元素的reference。
如果没有任何元素的键值是"otto",map将会自动安插一个新元素,key为"otto",value由defalut构造函数完成,并返回一个reference指向新元素。紧接着将8.8赋值给上述新元素。
这样map之内就包含了一个键值为“otto”,实值为8.8的元素。
- 缺点:在使用过程中存在不小心误置新元素。
比如:使用std::cout << coll["otto"];
会先插入一个键值为"otto"的新元素,然后打印实值,缺省情况下是0。
同时这种安插方式比一般的map安插方式执行慢。原因是新元素必须先使用default构造函数将value构造,然后再使用真正的value覆盖。
异常处理(Exception Handling)
map和multimap是以节点为基础的容器,如果节点构造失败,容器仍保持原样。此外由于析构函数并不抛出异常,所以节点的移除不可能失败。
参考文章:C++标准程序库