【C++标准库】7-STL容器-Map与Multimap-导读-能力-操作函数

目录

0.导读

1.Map和Multimap的能力

2.Map和Multimap的操作函数

2.1  创建,赋值,销毁

2.1.1 创建-赋值-销毁的操作函数

2.1.2 map的可能形式

2.1.3 定义排序准则的两种方式

2.1.4 建议

2.2  非更易型操作

2.3  特殊查找动作(Special Search Operation)

2.4  赋值(Assignment)

2.5 迭代器函数和元素访问(Iterator Function and Element Access)

2.6 元素安插和移除( Inserting and Removing)

2.6.1 插入元素

2.6.2 移除元素


0.导读

Map和 multimap将key/value pair当作元素进行管理。它们可根据key的排序准则自动为元素排序。Multimap允许重复元素,map 不允许。

【C++标准库】7-STL容器-Map与Multimap-导读-能力-操作函数_第1张图片

使用map和 multimap之前,你必须先包含头文件:

#include
There, the types are defined as class templates inside namespace std:
namespace std {
template typename Compare = less,
typename Allocator = allocator > >
class map;
template typename Compare = less,
typename Allocator = allocator > >
class multimap;
}

第一个template实参将成为元素的key类型,第二个template实参将成为元素的value类型。Map和 multimap的元素类型Key和T必须满足以下两个条件:

1. Key和 value都必须是copyable (可复制的)或movable(可搬移的)。

2. 对指定的排序准则而言,key必须是comparable(可比较的)。


注意,元素类型(value_type)是个pair 。第三个template实参可有可无,用来定义排序准则。和set一样,这个排序准则必须定义为strict weak ordering(见7.7节第314页)。元素的次序由它们的key 决定,和value无关。排序准则也可以用来检查相等性:如果两个元素的key彼此都不小于对方,两个元素被视为相等。

1.Map和Multimap的能力

和其他所有关联式容器一样, map/multimap通常以平衡二叉树完成,如图7.15所示。C++standard并未明定这一点,但是从map和multimap各项操作的复杂度自然可以得出这一结论。通常set、multiset、 map和multimap使用相同的内部结构,因此,你可以把set和multiset视为特殊的 map和 multimap,只不过set元素的 value和 key是同一对象。因此,map和multimap拥有set和 multiset 的所有能力和所有操作。当然,某些细微差异还是有的: 首先,它们的元素是key/value pair,其次, map可作为关联式数组(associative array)来运用。

【C++标准库】7-STL容器-Map与Multimap-导读-能力-操作函数_第2张图片

Map和multimap 会根据元素的key自动对元素排序。这么一来,根据已知的key查找某个元素时就能够有很好的效率,而根据已知value查找元素时,效率就很糟糕。“自动排序”这一性质使得 map和 multimap身上有了一条重要限制: 你不可以直接改变元素的key,因为这会破坏正确次序。要修改元素的key,必须先移除拥有该key的元素,然后插入拥有新key/value的元素(详见7.8.2节)。从迭代器的观点看元素的key是常量。至于元素的value倒是可以直接修改,当然前提是value并非常量。
 

2.Map和Multimap的操作函数

2.1  创建,赋值,销毁

2.1.1 创建-赋值-销毁的操作函数

Operation Effect 效果
map c Default constructor; creates an empty map/multimap
without any elements
默认构造函数,建立一个空map/multimap,不含任何元素
 
map c(op) Creates an empty map/multimap that uses op as the
sorting criterion
建立一个空map/multimap,以op为排序准则
map c(c2) Copy constructor; creates a copy of another
map/multimap of the same type (all elements are copied)
拷贝构造函数,为相同类型的另一个map/multimap建立一份拷贝,所有元素均被复制
map c = c2 Copy constructor; creates a copy of another
map/multimap of the same type (all elements are copied)
拷贝构造函数,为相同类型的另一个map/multimap建立一份拷贝,所有元素均被复制
map c(rv) Move constructor; creates a new map/multimap of the
same type, taking the contents of the rvalue rv (since
C++11)
移动构造函数,建立一个新的map/multimap,有相同类型,取rvalue rv的内容(始自C++11)
 
map c = rv Move constructor; creates a new map/multimap of the
same type, taking the contents of the rvalue rv (since
C++11)
移动构造函数,建立一个新的map/multimap,有相同类型,取rvalue rv的内容(始自C++11)
map c(beg,end) Creates a map/multimap initialized by the elements of the
range [beg,end)
以区间[beg,end)内的元素为初值,建立一个map/multimap
map c(beg,end,op) Creates a map/multimap with the sorting criterion op
initialized by the elements of the range [beg,end)
以区间[beg,end)内的元素为初值,并以op为排序准则,建立一个map/multimap
map c(initlist) Creates a map/multimap initialized with the elements of
initializer list initlist (since C++11)
建立一个map/multimap,以初值列initlist的元素为初值(始自C++11)
 
map c = initlist Creates a map/multimap initialized with the elements of
initializer list initlist (since C++11)
建立一个map/multimap,以初值列initlist的元素为初值(始自C++11)
 
c.~map() Destroys all elements and frees the memory 销毁所有元素,释放内存

2.1.2 map的可能形式

map effect 效果
map A map that by default sorts keys with less<> (operator <) 一个map,以less<>( operator <)为排序准则
map A map that by default sorts keys with Op 建立一个空map/multimap,以op为排序准则
 
multimap A multimap that by default sorts keys with less<>
(operator <)
个multimap,以less<> (operator <)为排序准则
multimap A multimap that by default sorts keys with Op 一个multimap,以0p为排序准则

                                                                             表7.40 Map和 Multimap的构造函数和析构函数

2.1.3 定义排序准则的两种方式

(1) 以template实参定义

例如:

std::map> coll;

这种情况下,排序准则就是类型的一部分。因此类型系统确保“只有排序准则相同的容器才能被合并”。这是比较常见的一种排序准则指定法。更精确地说,第三参数是排序准则的类型。实际的排序准则是容器所产生的函数对象(function object)。为了产生它,构造函数会调用“排序准则类型”的default构造函数。10.1.1节有一个“用户自定义之排序准则”的运用实例。
(2) 以构造函数参数定义

这种情况下,你可以有一个“排序准则类型”并为它指定不同的排序准则实例(也就是说,让该类型所产生的对象〔代表一个排序准则〕的初值或状态不同)。如果运行期才能获得排序准则,而且程序需要用到不同的排序准则(但其类型必须相同),这一方式可派上用场。典型例子是在运行期指定“以string 为 key”的排序准则。完整例子见7.8.6节。
如果用户没有指定任何排序准则,就采用默认准则——函数对象less<>。less<>通过opera-tor <对元素排序。再强调一次,排序准则也被用来检验同一容器内的两个元素的等价性(例如用来找出重复元素)。只有当比较两个容器时,才需要操作符==.

2.1.4 建议

(1) 使用typedef

我想你应该宁愿另外定义一个类型,避免无聊而重复地为了一个类型写那么一长串:

typedef std: :map> StringFloatMap;
...

StringFloatMap coll;

(2) 注意元素类型

某些构造函数使用区间的起点和终点作为实参,它们的初值可以来自不同类型的容器、array或标准输人设备(standard input),详见7.1.2节。然而,由于元素是key/value pair,因此你必须确定源区间的元素类型也是pair,或至少可转化成pair.

 

2.2  非更易型操作

Map和 multimap提供了若干常见的非更易型操作,用来查询大小、相互比较,如表7.41所列。

“元素比较”函数只能用于类型相同的容器身上,换言之,两个容器的 key、value、排序准则都必须有相同的类型,否则编译期会产生类型方面的错误。例如:

std: :map c1;                                 //  sorting criterion: less<>

std: :map > c2;

...

if (c1 == c2) { // ERROR: differen types

       ...
}

operation(操作)

effect 效果
c.key_comp() Returns the comparison criterion 返回比较准则
c.value_comp() Returns the comparison criterion for values as a whole (an object that
compares the key in a key/value pair)
返回针对value的“比较准则”(那是个对象,用来在一个key/value pair 中比较key)
c.empty() Returns whether the container is empty (equivalent to size()==0 but
might be faster)
返回是否容器为空(相当于size()==0但也许较快)
c.size() Returns the current number of elements 返回目前的元素个数
c.max_size() Returns the maximum number of elements possible 返回元素个数之最大可能量
c1 == c2 Returns whether c1 is equal to c2 (calls == for the elements) 返回c1是否等于c2(对每个元素调用==)
c1 != c2  Returns whether c1 is not equal to c2 (equivalent to !(c1==c2)) 返回c1是否不等于c2(相当于 !( c1==c2) )
c1 < c2 Returns whether c1 is less than c2 返回c1是否小于c2
c1 > c2 Returns whether c1 is greater than c2 (equivalent to c2 返回c1是否大于c2(相当于c2
 
c1 <= c2 Returns whether c1 is less than or equal to c2 (equivalent to !(c2 返回c1是否小于等于c2(相当于!(c2
c1 >= c2 Returns whether c1 is greater than or equal to c2 (equivalent to
!(c1
返回c1是否大于等于c2(相当于!(c1

                                                                                表7.40 Map和 Multimap的构造函数和析构函数

比较函数以“字典 (lexicographical)顺序”检查某个容器是否小于另一个容器(详见11.5.4节第548页)。如果要比较不同类型(拥有不同排序准则)的容器,你必须采用第542页11.5.4节的STL比较算法(comparing algorithm)。

2.3  特殊查找动作(Special Search Operation)

就像set和 multiset一样,map和 multimap也提供特殊查找函数,以便利用内部树状结构获取较好的效能,如表7.42所列。

Operation Effect 效果
c.count(val)  Returns the number of elements with key val 返回“key为val”的元素个数
c.find(val) Returns the position of the first element with key val (or end() if
none found)
返回“key 为val”的第一个元素,找不到就返回end()
c.lower_bound(val) Returns the first position where an element with key val would get
inserted (the first element with a key >= val)
返回“key为val”之元素的第一个可安插位置,也就是“key >=val”的第一个元素位置
 
c.upper_bound(val) Returns the last position where an element with key val would get
inserted (the first element with a key > val)
返回“key为val”之元素的最后一个可安插位置,也就是“key> val”的第一个元素位置
c.equal_range(val) Returns a range with all elements with a key equal to val (i.e., the first
and last positions, where an element with key val would get inserted)
返回“key 为val”之元素的第一个可安插位置和最后一个可安插位置,也就是“key == val”的元素区间

                                                                        表7.42 Map和 Multimap的特殊查找操作

成员函数find()用来查找第一个“拥有某key”的元素,并返回一个迭代器指向该位置。如果没找到这样的元素,就返回容器的end()。你不能以find()查找拥有某特定value的元素,必须改用STL 算法如find_if(),或干脆自行写一个循环(loop)。下面这个例子便是利用一个简单循环对拥有特定value的所有元素进行某项操作:

std::multimap coll;
for (pos = coll.begin() ; pos != coll.end(); ++pos)
{
      if (pos->second ==value)
      {
          do_something();
      }
}

       如果你想利用这个循环来移除元素,请小心。很可能你会锯断你正坐着的树枝。关于这一点,详见7.8.2节第342页。


       如果使用find_if()算法做类似的查找动作,会比写一个上述循环更复杂,因为你必须提供函数对象(functor,亦即 function object),将元素的value拿来和某个value 比较。7.8.5节第350页有这么一个例子。

       至于lower_bound()、 upper_bound()和equal_range(),其行为和 set(见 7.7.2节第319页)的相应函数十分相似,唯一的不同是:这里的元素是个key/value pair。

2.4  赋值(Assignment)

Map和multimap只支持“所有容器都提供的基本赋值操作”(详见7.1.2节第258页),如表7.43所列。
 

Operation Effect 效果
c=c2 Assigns all elements of c2 to c 将c2的全部元素赋值给c
c = rv Move assigns all elements of the rvalue rv to c (since
C++11)
将rvalue rv的所有元素以move assign方式给予c(始自C++11)
 
c = initlist Assigns all elements of the initializer list initlist to c
(since C++11)
将初值列initlist的所有元素赋值给c(始自C++11)
c1.swap(c2) Swaps the data of c1 and c2 置换c1和c2的数据
 
swap(c1,c2) Swaps the data of c1 and c2 置换c1和c2的数据

这些操作函数中,赋值动作的两端容器必须拥有相同类型。尽管“比较准则”本身可能不同,但其类型必须相同。7.8.6节第351页有一个“排序准则不同,但其类型相同”的例子。如果准则不同,准则本身也会随着容器被赋值(assigned)或交换(swapped)。

2.5 迭代器函数和元素访问(Iterator Function and Element Access)

Map和 multimap不支持元素直接访问,因此元素的访问通常是经由range-based for循环(见3.1.4节第17页)或迭代器进行。不过有个例外: map提供at()以及 subscript (下标)操作符可直接访问元素,详见7.8.3节第343页。表7.44列出了map和multimap支持的迭代器相关函数。

Operation effect 效果
c.begin() Returns a bidirectional iterator for the first element 返回一个bidirectional iterator,它指向第一元素
c.end() Returns a bidirectional iterator for the position after the last element 返回一个bidirectional iterator,它指向了最末元素的下一位置
 
c.cbegin() Returns a constant bidirectional iterator for the first element (since
C++11)
返回一个const bidirectional iterator指向第一元素(始自C++11)
c.cend()  Returns a constant bidirectional iterator for the position after the last
element (since C++11)
返回一个const bidirectional iterator指向最末元素的下一位置(始自C++11)
c.rbegin()  Returns a reverse iterator for the first element of a reverse iteration 返回一个反向的(reverse) iterator指向反向迭代的第一元素
c.rend() Returns a reverse iterator for the position after the last element of a
reverse iteration
返回一个反向的(reverse) iterator指向反向迭代的最末元素的下
一位置
c.crbegin() Returns a constant reverse iterator for the first element of a reverse
iteration (since C++11)
返回一个const reverse iterator指向反向迭代的第一元素(始自C++11)
c.crend() Returns a constant reverse iterator for the position after the last
element of a reverse iteration (since C++11)
返回一个const reverse iterator,指向反向迭代的最末元素的下一位置(始自C++11)

                                                              表7.44 Map和 Multimap的迭代器相关操作

和其他所有关联式容器一样,这里的迭代器是双向迭代器(第437页9.2.4节)。所以,对于只能接受随机访问迭代器的STL 算法(例如排序算法或随机乱序〔 random shuffling 〕算法), map和 multimap就无福消受了。

更重要的是,在map和 multimap中,所有元素的key都被视为常量。因此,元素的实质类型是pair。这个限制是为了确保你不会因为变更元素的 key而破坏已排好的元素次序。所以你不能针对map或multimap 调用任何更易型算法(modifying algorithm)。例如你不能对它们调用remove(),因为remove()算法实际上是将其实参值覆盖掉被移除的元素(详见6.7.2节第221页)。如果要移除map和 multimap的元素,你只能使用它们所提供的成员函数。

下面示范使用range-based for循环访问map元素:

std::map coll;
...
for (auto elem& : coll) 
{
    std::cout << "key: " << elem.first << "\t"
    << "value: " << elem.second << std::endl;
}

其中的elem是个reference,指向“容器coll中目前正被处理的元素”。因此elem的类型是pair。表达式elem.first取得元素的key,而表达式elem.second取得元素的value。

std::map coll;
...
std::map::iterator pos;
for (pos = coll.begin(); pos != coll.end(); ++pos) 
{
    std::cout << "key: " << pos->first << "\t"
    << "value: " << pos->second << std::endl;
}

在这里,迭代器pos被用来迭代穿越整个由“以const string和float组成的pair”所构成的序列,你必须使用operator->访问每次访问的那个元素的kev和value【pos->first是(*pos).first的简写形式】. 如果你尝试改变元素的key,会引发错误:

elem.first = "hello"; // ERROR at compile time
pos->first = "hello"; // ERROR at compile time

不过如果value本身的类型并非const,改变value没有问题:

elem.second = 13.5; // OK
pos->second = 13.5; // OK

如果你使用算法或lambda来操作map元素,你必须很明确地声明元素类型:

std::map coll;
...
// add 10 to the value of each element:
std::for_each (coll.begin(), coll.end(),
[] (std::pair& elem) {
elem.second += 10;
});

那么,可以不写std::pair , 而改为std::map::value_type
或者decltype(coll)::value_type来声明元素类型。7.8.5节第345页有一个完整例子。

如果你一定得改变元素的 key,只有一条路: 以一个“value相同”的新元素替换掉旧元素。下面是个泛化函数:
 

// cont/newkey.hpp
namespace MyLib
{
    template 
    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())
        {
            // insert new element with value of old element
            c.insert(typename Cont::value_type(new_key,
                                               pos->second));
            // remove old element
            c.erase(pos);
            return true;
        }
        else
        {
            // key not found
            return false;
        }
    }
}

关于成员函数insert()和erase(),见下一节的讨论。这个泛型函数的用法很简单,把旧key和新key传递进去就行。例如:

std::map coll;
...
MyLib::replace_key(coll,"old key","new key");

如果你面对的是multimap,情况也一样。
注意, map提供了一种非常方便的手法,让你改变元素的 key。只需如此这般:

// insert new element with value of old element
coll["new_key"] = coll["old_key"];
// remove old element
coll.erase("old_key");

关于map的subscript(下标)操作符使用细节,见7.8.3节第343页。
 

2.6 元素安插和移除( Inserting and Removing)

Operation Effect 效果
c.insert(val) Inserts a copy of val and returns the position of the new
element and, for maps, whether it succeeded
安插一个val 考贝,返回新元素位置,不论是否成功,对map而言
c.insert(pos,val) Inserts a copy of val and returns the position of the new
element (pos is used as a hint pointing to where the insert
should start the search)
安插一个val拷贝,返回新元素位置(pos是个提示,指出安插动作的查找起点。若提示恰当可加快速度)
c.insert(beg,end) Inserts a copy of all elements of the range [beg,end)
(returns nothing)
将区间[beg,end)内所有元素的拷贝安插到c(无返回值)
 
c.insert(initlist) Inserts a copy of all elements in the initializer list initlist
(returns nothing; since C++11)
安插初值列initlist内所有元素的一份拷贝(无返回值;始自C++11)
 
c.emplace(args...) Inserts a copy of an element initialized with args and
returns the position of the new element and, for maps,
whether it succeeded (since C++11)
安插一个以args为初值的元素,并返回新元素的位置,不论是否成功——对map而言(始自C++11)
c.emplace_hint(pos,args...) Inserts a copy of an element initialized with args and
returns the position of the new element (pos is used as a
hint pointing to where the insert should start the search)
安插一个以args为初值的元素,并返回新元素的位置(pos是个提示,指出安插动作的查找起点。若提示恰当可加快速度)
c.erase(val)  Removes all elements equal to val and returns the number
of removed elements
移除“与val 相等”的所有元素,返回被移除的元素个数
 
c.erase(pos) Removes the element at iterator position pos and returns
the following position (returned nothing before C++11)
移除iterator位置pos上的元素,无返回值
c.erase(beg,end) Removes all elements of the range [beg,end) and returns
the following position (returned nothing before C++11)
移除区间[beg,end)内的所有元素,无返回值
 
c.clear() Removes all elements (empties the container) 移除所有元素,将容器清空

                                                                              表7.45 Map和 Multimap的元素安插和移除 

表7.45列出了map和multimap支持的元素安插和删除函数。7.7.2节第322页中关于set和multiset的说明此处依然适用。上述操作函数的返回类型有些差异,这与set和multiset之间的情况完全相同。当然,这里的元素是key/value pair。所以这里的用法更复杂些。

对于multimap,C++11保证insert(),emplace()和erase()都会保留等价元素的相对次序,而新增(被安插)的元素则一定被放在既有的等价元素(群)的末尾。

2.6.1 插入元素

安插一个key/value pair时,你一定要记住,在map和 multimap内部,key被视为常量。你要么得提供正确类型,要么得提供隐式或显式类型转换。

自C++11起,安插元素的最方便做法就是把它们以初值列(initializer list)的形式传进去,其中第一笔数据是key,第二笔数据是value:

std::map coll;
...
coll.insert({"otto",22.3});

有三种不同的方法可以将value传入map或multimap内:

1.运用value_type。为了避免隐式类型转换,你可以利用value_type明白传递正确类型。value_type是容器本身提供的类型定义。例如:

std::map coll;
...
coll.insert(std::map::value_type("otto",22.3));
or
coll.insert(decltype(coll)::value_type("otto",22.3));

2.运用pair<>。另一个做法是直接运用pair<>。例如:

std::map coll;
...
// use implicit conversion:
coll.insert(std::pair("otto",22.3));
// use no implicit conversion:
coll.insert(std::pair("otto",22.3));

3.运用make _pair()。C++11面世之前最方便的办法是运用make_pair()函数,它根据收到的两个实参构建出一个pair对象(见5.1.1节第65页):

std::map coll;
...
coll.insert(std::make_pair("otto",22.3));

和上个做法一样,此处也是利用member template insert()执行必要的类型转换。下面是个简单例子,对map安插一个元素,然后检查是否成功:

std::map coll;
...
if (coll.insert(std::make_pair("otto",22.3)).second) {
std::cout << "OK, could insert otto/22.3" << std::endl;
}
else {
std::cout << "OOPS, could not insert otto/22.3 "
<< "(key otto already exists)" << std::endl;
}

关于insert()返回值的讨论,见7.7.2节第322页,那儿有更多例子,并且也适用于map。注意, map提供operator []和at(),作为便捷的元素安插和设定操作(详见7.8.3节第343页)。

使用emplace()安插新元素,并且传值进去以便构建该新元素时,你必须传递两列实参进去:一列为了key,另一列为了元素。完成这件事的最方便做法如下:

std::map> m;
m.emplace(std::piecewise_construct, // pass tuple elements as arguments
std::make_tuple("hello"), // elements for the key
std::make_tuple(3.4,7.8)); // elements for the value

5.1.1节第63页对于pair的逐块式构造(piecewise construction)有详细讨论。

2.6.2 移除元素


欲移除“携带某个value”的元素,调用erase()即可办到:

std::map coll;
...
// remove all elements with the passed key
coll.erase(key);

这个erase()版本会返回被移除元素的个数。如果你处理的是map,erase()的返回值只可能是0或1。

如果multimap内含重复元素,你无法使用erase()删除重复元素中的第一个。但你可以这么做:

std::multimap coll;
...
// remove first element with passed key
auto pos = coll.find(key);
if (pos != coll.end()) {
coll.erase(pos);
}

这里应该采用成员函数find(),而非STL算法find(),因为前者速度更快(参见7.3.2节第277页的例子)。然而你不能使用成员函数find()来移除“拥有某个value (而非某个key)"的元素。详细讨论见7.8.2节第335页。

移除元素时,当心发生意外状况。移除迭代器所指对象时,有一个很大的危险,比如:
 

std::map coll;
... 
for (auto pos = coll.begin(); pos != coll.end(); ++pos)
{
    if (pos->second == value)
    {
        coll.erase(pos); // RUNTIME ERROR !!!
    }
}

对pos所指元素调用erase(),会使pos不再成为coll的一个有效迭代器。如果此后你未对pos重新设值就径直使用pos,前途未卜! 事实上,只要一个++pos动作就会导致不明确行为。

C++11之后的解决方案很容易,因为erase()总是返回一个迭代器指向其后继元素:

std::map coll;
... 
for (auto pos = coll.begin(); pos != coll.end();)
{
    if (pos->second == value)
    {
        pos = coll.erase(pos); // possible only since C++11
    }
    else
    {
        ++pos;
    }
}

不幸的是,在C++11之前,STL 设计过程中否决了这种想法,因为万一用户并不需要这一特性,就会耗费不必要的执行时间。下面是C++11面世之前移除“迭代器所指元素”的正确做法:

typedef std::map StringFloatMap;
StringFloatMap coll;
StringFloatMap::iterator pos;
...
// remove all elements having a certain value
for (pos = coll.begin(); pos != coll.end();)
{
    if (pos->second == value)
    {
        coll.erase(pos++);
    }
    else
    {
        ++pos;
    }
}

注意,pos++会将pos移向下一元素,但返回其原值(指向原位置)的一份拷贝。因此当erase()被调用,pos已经不再指向那个即将被移除的元素了。
 

也请注意,如果set使用迭代器作为元素,调用erase()有可能造成歧义。基于这个原因,C++11提供两个重载版本:erase(iterator)和erase(const_iterator)。

面对multimap,其所有的insert()、emplace()和erase()操作都保留等价元素的相对次序。C++11保证,insert(val)或emplace(args...)一定会将元素安插在等价元素所形成的区间的末尾。
 

你可能感兴趣的:(C++,multimap,map)