6.6 map and multimap(page194)
map/multimap将键值对作为元素进行管理。multimap允许重复的键,而map不允许。
类似其它容器,map/multimap是定义在namespace std中的class template:
namespace std
{
template ,
class Allocator = alloctor > >
class map;
template ,
class Allocator = alloctor > >
class multimap;
}
对于map/multimap的key/value须满足两条件:
1. key/value须具备assignable、copyable。
2. 对排序准则而言,key须是comparable。
6.6.1 map/multimap 能力
元素是key/value pair,其次map可作为关联式数组运用。
map/multimap根据元素key自动对元素排序,如此根据已知key查找性能很好,但根据value查找性能不行。
“自动排序”的特性使得map/multimap有个限制:
不能直接改变元素key,因为这会破坏正确次序。
要修改key,必须先移除拥有该key的元素,然后插入新的key/value元素。
从迭代器角度看,key是常数,非常数型态的value可直接修改。
6.6.2 map/multimap operation
-
生成、复制、销毁
定义排序准则的两个方式:
1. 以template参数定义。
eg:std::map > col1;
该情况下,排序准则是型别的一部分。
因此系统确保“只有排序准则相同的容器才能合并”。
明确而言,第三参数是排序准则的型别,实际的排序准则是容器产生的对象。
为生成它,容器构造函数会调用“排序准则型别”的default构造函数。eg见p294。
2. 以构造函数参数定义。
该情况下,可有一个“排序准则型别”并为它指定不同的排序准则。
即让该型别产生的对象作为一个排序准则。
若执行期才获得该排序准则,且程序需用到不同的排序准则(但数据型别须同),此方式可用。
如执行期指定“key的型别为string"的例子,详见p213。
-
Nonmodifying operation(非变动性操作)
元素比较操作只适用于型别相同的容器,即容器的key/value/排序准则都须相同,否则编译错误。
比较操作以”字典顺序“比较。
-
special search operation(特殊查找操作)(page198)
-
Assignment(赋值)(page200)
赋值操作的两端容器的型别须相同,尽管排序准则可以不同。若排序准则不同,准则也会被赋值或交换。
-
Iterator Functions And Element Access
map/multimap不支持元素随机存取,元素存取通常通过迭代器。但例外的是map提供subscript(下标)操作符,可直接存取元素(见p205)。
与所有关联式容器类似,迭代器是双向迭代器。
且注意元素的key被视为常数,此限制是为了确保不会应为变更元素key而破坏已序元素。
故不能对map/multimap调用modifying algorithms,若要改变元素key,只有一个方法:以一个”value相同“的新元素替换旧元素,下面是个泛函数(以map为例):
namespace MyLib
{
template inline
bool replace_key (Cont& c,
const typename Cont::key_type& old_key,
const typename Cont::key_type& new_key)
{
typename Cong::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;
}
}
}
note:map提供了很方便的手法改变元素的key,只需:
// insert new element with value of old element
col1["new_key"] = col1["old_key"];
// remove old element
col1.erase("old_key");
- Inserting And Rmoving
上图中作者应该笔误了,erase(elem)指的是根据key值删除元素,返回被删除元素的个数。
注,map/multimap内部key被视为常数,插入元素时,要么提供正确型别,要么提供隐式或显式型别转换,有三种方法将value传入map/multimap:
1. 用value_type.
为避免隐式类型转换,可用value_type传递正确型别。
value_type是容器本身提供的型别定义。eg:
std::map col1;
...
col1.insert(std::map::value_type("otto", 22.3) );
2. 用pair<>
eg:
std::map col1;
...
// use implicit conversion:
col1.insert(std::pair("otto", 22.3) );
// use no implicit conversion
col1.insert(std::pair("otto", 22.3) );
第一个insert内的型别并不正确故会被转换为真正的元素型别。
为了做到这点,insert()成员函数被定义为member template。
3. 用make_pair()
最方便就是用make_pair()函数。eg:
std::map col1;
...
col1.insert(std::make_pair("otto", 22.3) );
和用pair一样利用member template进行必要的类型转换。
6.6.3 Associated Arrays(将map视为关联式数组)(page207)
通常关联式容器不提供元素的直接存取,须依靠迭代器,但map是个例外。
Non-const map提供下表操作符,支持元素的直接存取。
新元素的value有default构造函数构造。所有基本数据类型都提供有default构造函数,以0为初值。
6.6.4 Exception Handling
map/multimap和set/multiset类似。参见page185
6.6.5 map/multimap example
- 将map作为关联数组(page207)
// 将map作为关联式数组使用,用于反映股票行情。
// - 元素key为股票名称,value为股票价格
// cont/map1.cpp
#include
#include
output:
- 将multimap作为字典
// 展示如何将multimap作为一个字典使用
// cont/mmap1.cpp
#include
#include
output:
- 查找具有特定value的元素(page211)
// 展示 如何使用全局find_if()算法查找特定value对应的元素
// cont/mapfind.cpp
#include
#include
#include
output:
6.6.6 综合实例
用map,string于执行期指定排序准则。
/* 本例展现如下技巧:
*- 使用map
* - 编写并使用仿函数functor
* - 执行期间定义排序准则
* - 忽略大小写情况下比较字符串
*/
// cont/mapcmp.cpp
#include
#include
#include
output:
6.7 其它STL容器(page217)
STL是个框架,除了提供标准容器,还允许使用其它数据结构作为容器,也可自定义特殊容器,但须遵循“开放性封闭”原则:允许扩展,谢绝修改。
使自定义容器“STL化”的三种方法:
1. The invasive approach(侵入性作法)
直接提供STL所需接口,特别是begin()和end()之类的常用函数。
2. The noninvasive approach(非侵入性作法)
自定义特殊迭代器,作为算法和特殊容器的接口。
此作法只需要“遍历容器所有元素”的能力。
3. The wrapper approach(包装法)
将上述两种方法组合,写一个外套类别 以包装任何数据结构。
6.7.1 String可被视为一种STL容器
C++标准程序库的string类别,是“以侵入法编写STL容器”的一个例子。(详见chapter11 page495)
string可被视为以字符为元素的容器。
因此,标准的string提供了STL容器接口,如begin()和end() ;
同时为支持迭代器和迭代器配接器,也提供了操作函数如push_back()以支持back inserter。
6.7.2 Array可被视为一种STL容器
array不是类别,故不提供任何成员函数,即只能用非侵入法或包装法。
- 直接运用数组
采用非侵入法,只需一个对象——普通指针,然后透过STL迭代器接口遍历数组元素。
”行为类似迭代器“的任何东西即一种迭代器,而之战就是一个随机存取迭代器。
eg:
// 展示以array作为STL容器
// cont/array1.cpp
#include
#include
#include
#include
using namespace std;
int main()
{
int col1[] = {5, 6, 2, 4, 1, 3};
// square all elements
transform (col1, col1+6, // first source
col1, // second source
col1, // destiantion
multiplies() ); // operation
// sort beginning with the second element
sort (col1 + 1, col1 + 6);
// print all elements
copy (col1, col1 + 6, ostream_iterator(cout, " "));
cout << endl;
}
note:参数是 区间最后一个元素的下一个位置。
output:
- 一个数组外包装
在数组外包装一层常用的容器接口
eg:
// cont/carray.hpp
#include
template
class carray
{
private:
T v[thesize]; // fixed-size array of elements of type T
public:
// type definitions
typedef T value_type;
typedef T* iterator;
typedef const T* const_iterator;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// iterator support
iterator begin()
{
return v;
}
const_iterator begin() const
{
return v;
}
iterator end()
{
return v + thesize;
}
const_iterator end() const
{
return v + thesize;
}
// direct element access
reference operator[] (std::size_t i)
{
return v[i];
}
const_reference operator[] (std::size_t i) const
{
return v[i];
}
// size is constant
size_type size() const
{
return thesize;
}
size_type max_size() const
{
return thesize;
}
// conversion to ordinary array
T* as_array()
{
return v;
}
};
应用:
// cont/carray1.cpp
#include
#include
#include "carray.hpp"
#include "../stl/print.hpp"
using namespace std;
int main()
{
carray a;
for (unsigned i = 0; i < a.size(); ++i)
{
a[i] = i + 1;
}
PRINT_ELEMENTS(a);
reverse(a.begin(), a.end());
PRINT_ELEMENTS(a);
transform (a.begin(), a.end(), // source
a.begin(), // destination
negate() ); // operation
PRINT_ELEMENTS(a);
}
output:
6.7.3 Hash Table
hash table可用于群集上,很重要,但未包含于C++标准程序库中。
C++社群已有数种可用的hash table实现版本。
一般而言,程序库会提供四种hash table:hash_set/hash_multiset/hash_map/hash_multimap。
Bjarne Stroustrup的《The C++ Programming Language》3rd 17.6中有hash_map实现。
hash table的运用及设计原理可参考《STL源码剖析》by 侯捷/碁峰2002,5.7的SGI STL。
6.8 实现Reference语义(page222)
要在STL容器中用“reference语义”(不论是因为元素复制代价大或因为 需要不同群集中共享同一个元素),就要用智能型指针,以避免可能的错误。
有一个解决方法:对指针所指的对象采用reference counting智能型指针。
// cont/countptr.hpp
#ifndef COUNTED_PTR_HPP
#define COUNTED_PTR_HPP
/* class for counted reference semantics
* - deletes the object to which it refers when the last CountedPtr
* that refers to it is destoryed
*/
template
class CountedPtr
{
private:
T* ptr; // pointer to the value
long* count; // shared number of owners
public:
// initialize pointer with existing pointer
// - requires that the pointer p is a return value of new
explicit CountedPtr (T* p = 0) : ptr(p), count(new long(1)){}
// copy pointer (one more owner)
CountedPtr (const CountedPtr& p) throw() :
ptr(p.ptr), count(p.count)
{
++*count;
}
// destructor (deletes value if this was the last owner)
~CountedPtr() throw()
{
dispose();
}
// assignment (unshare old and share new value)
CountedPtr& operator= (const CountedPtr& p) throw()
{
if (this != &p)
{
dispose();
ptr = p.ptr;
count = p.count;
++*count;
}
return *this;
}
// access the value to which the pointer refers
T& operator*() const throw()
{
return *ptr;
}
T* operator->() const throw()
{
return ptr;
}
private:
void dispose()
{
if (--*count == 0)
{
delete count;
delete ptr;
}
}
};
#endif /*COUNTED_PTR_HPP*/
上述class与auto_ptr class有些类似。
但和auto_ptr不同之处是,这种智能型指针被复制后,原指针和新指针都有效;当指向同一对象的最后一个智能型指针被销毁时,所指对象才被删除。
eg:
// cont/refsem1.cpp
#include
#include
#include
#include
#include "countptr.hpp"
using namespace std;
void printCountedPtr (CountedPtr elem)
{
cout << *elem << " ";
}
int main()
{
// array of integers (to share in different containers)
static int values[] = {3, 5, 9, 1, 6, 4};
// two different collections
typedef CountedPtr IntPtr;
deque coll1;
list coll2;
/* insert shared objects into the collections
* - same order in coll1 coll1
* - reverse order in coll2 coll2
*/
for (int i = 0; i < sizeof(values)/sizeof(values[0]); ++i)
{
IntPtr ptr(new int(values[i]));
coll1.push_back(ptr);
coll2.push_front(ptr);
}
// print contents of both collections
for_each(coll1.begin(), coll1.end(), printCountedPtr);
cout << endl;
for_each(coll2.begin(), coll2.end(), printCountedPtr);
cout << endl << endl;
/* modify values at different places
* - square third value in coll1
* - negate first value in coll1
* - set first value in coll2 to 0
*/
*coll1[2] *= *coll1[2];
(**coll1.begin()) *= -1;
(**coll2.begin()) = 0;
// print contents of both collections again
for_each (coll1.begin(), coll1.end(), printCountedPtr);
cout << endl;
for_each (coll2.begin(), coll2.end(), printCountedPtr);
cout << endl;
}
output:
note:
若调用一个辅助函数,而它在某处保存了群集内的某个元素,
那即使群集被销毁或元素全被删除,那个智能指针所指元素依然有效。
6.9 容器的运用时机(page226)
6.10.6 返回的迭代器(page239)
6.10.10 STL容器的异常处理(page248)
chapter6.9及其之后参见 STL源码剖析。