【STL】关联式容器 - Map和Multimap

Map和Multimap

STL提供的Map和Multimap是一种关联式容器,将key/value pair(键值/实值 对组)当做元素,进行管理。它们可以根据key的排序准则自动将元素排序。Multimap允许重复元素,map不允许。

Map和Multimap结构:

image

在使用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内部结构:

image

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++标准程序库

posted @ 2022-11-06 20:25  Emma1111  阅读(132)  评论(0编辑  收藏  举报