9. 标准库关联容器:
关联数组是用户定义的类型中最常见的也是最有用的一类,在关注文字处理和符号处理的语言里,关联数组甚至是一种内部类型。
关联数组也被称为映射,或字典,保存的是值的对偶。给定一个关键码的值,就可以访问其对应的 映射值的值。
关联容器是关联数组的推广,map是传统的关联数组,其中与每个关键码相关联的有唯一的一个值,multimap是允许元素中出现重复关键码
的关联数组,set与multiset也可以看作是退化的关联数组,其中没有与关键码相关联的值。
映射——map
一个map就是一个(关键码,值)对偶的序列,提供基于关键码的快速提取操作。map的关键码具有唯一性。
类型定义:
template <class Key, class T, class Cmp=less<Key>>, class A=allocator<pair<const Key, T>> class std::map{
public:
typedef Key key_type;
typedef T mapped_type;
typedef pair<const Key, T> value_type;
typedef Cmp key_compare;
typedef A allocator_type;
typedef typename A::reference reference;
typedef typename A::const_reference const_reference;
typedef implementation_defined1 iterator;
typedef implementation_defined2 const_iterator;
typedef typename A::size_type size_type;
typedef typename A::difference_type difference_type;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
//...
};
map的value_type是(关键码,值)的pair,映射值的类型用mapped_type表示。
迭代器和对偶:
template <class Key, class T, class Cmp=less<Key>>, class A=allocator<pair<const Key, T>> class std::map{
public:
// 迭代器
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
reverse_iterator rend();
const_reverse_iterator rend() const;
//...
};
在map上的迭代器也就是以pair<const Key, mapped_type>为元素的序列上的迭代器。
一个map的迭代器将按照map关键码的递增顺序给出它的元素。
对于pair,总是用first和second索引给出第一个和第二个元素,其定义如下:
template<class T1, class T2> struct std::pair{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair() : first(T1()), second(T2()){};
pair(cosnt T1& x, const T2 &y): first(x), second(y){};
template<class U, class V>
pair(const pair<U, V>&p):first(p.first), second(p.second){}
};
下标操作:
template <class Key, class T, class Cmp=less<Key>>, class A=allocator<pair<const Key, T>> class std::map{
public:
// ...
// 下标操作
mapped_type& operator[](const key_value& k);
//...
};
下标运算是将关键码作为下标去执行查找,并返回对应的值。
注意:如果不存在这个关键码,它就将一个具有该关键码和mapped_type类型的默认值的元素插入map中。
void f()
{
map<string, int> m;
// map开始是空的
int x = m["Henry"];
// 为Henry建立新项初始值为0,返回0
m["Henry"] = 7;
// 为Henry建立新项初始值为0,并赋值7
int y = m["Henry"]; // 返回Henry对应项的值
m["Herry"] = 9;
// 将Herry对应项的值修改为9
}
构造函数:
map提供一组完整的构造函数
template <class Key, class T, class Cmp=less<Key>>, class A=allocator<pair<const Key, T>> class std::map{
public:
// ...
// 构造/复制/析构函数
explicit map(const Cmp&= Cmp(), const A& = A());
template <class In> map(In first, In last, const Cmp& = Cmp(), const A&=A());
map(const map&);
~map();
map& operator=(const map&);
//...
};
比较:
为了在map中找到对应于给定的关键码的元素,map必须能够做关键码比较。
默认规定,关键码比较采用<(小于),可以通过模板或者构造函数的参数提供运算
映射操作:
map的关键思想是基于关键码提取信息,map提供了一些特别的操作
template <class Key, class T, class Cmp=less<Key>>, class A=allocator<pair<const Key, T>> class std::map{
public:
// ...
// map操作
iterator find( const key_type& k);
// 找到关键码为k的元素
const_iterator find( const key_type& k) const;
size_type count( const key_type& k) const;
// 关键码为k的元素的个数
iterator lower_bound(const key_type& k);
// 找到第一个关键码为k的元素
const_iterator lower_bound(const key_type& k) const;
//
iterator upper_bound(const key_type& k);
// 找第一个关键码大于k的元素,也即指向第一个大于k的元素
const_iterator upper_bound(const key_type& k) const;
//
pair<iterator, iterator> equal_range( const key_type& k);
pair<const_iterator, const_iterator> equal_range( const key_type& k) const;
// ...
};
m.find(k)操作产生指向关键码k的元素的迭代器,如果不存在该元素,返回迭代器为m.end();。对于multimap和multiset返回的是
指向这个唯一的关键码为k的元素
void f(map<string, int>& m)
{
map<string, int>::iterator p = m.find("Gold");
if(p!=m.end())
{
//
}
}
表操作:
template <class Key, class T, class Cmp=less<Key>>, class A=allocator<pair<const Key, T>> class std::map{
public:
// ...
// 表操作
pair<iterator, bool> insert(const value_type& val);
// 插入键值对
iterator insert(iterator pos, const value_type& val);
// pos只是个提示
template<class In> void Insert(In first, In last);
// 从序列中插入
void erase(iterator pos);
// 删除被指元素
size_type erase(const key_type& k);
// 删除关键码为K的元素
void erase(iterator first, iterator last);
// 删除一个区间
void clear();
// 删除所有元素
// ...
};
集合——set:
一个set可以看作是一个map,其中的值无关紧要,所以只保留了关键码,这样值引起用户界面的少许变动
teplate <class Key, class Cmp=less<Key>, class A= allocator<Key>> class std::set{
public:
// 与map类似,除了
typedef Key value_type;
typedef Cmp value_compare;
// 无下标操作符[]
};
将value_type定义为Key类型,是一个不错的选择,这将使得使用map和set的代码大部分完全一样。
set依靠的是比较操作<,而不是相等==,这隐含元素的相等需要由不相等定义。
多重集合——multiset
multiset允许重复关键码的set。
equal_range(),lower_bound()和upper_bound()操作是访问关键码的重复出现的基本手段。
拟容器:
内部数组,string,valarray以及bitset也保存元素,许多情况下也可以看作容器,他们缺乏标准容器界面的一部分界面,因此成为拟容器。
10. 算法和函数对象:
标准库里的算法覆盖了容器上的最普遍性的操作,如遍历,排序,检索,插入或删除。标准算法位于名字空间std中。可以在
<algorithm>里看到。
非修改性的序列操作:
for_each()
// 对序列中每一个元素执行某个操作,即遍历
find()
// 在序列中找出某个值的第一个出现
find_if()
// 在序列中找出符合某谓词的第一个元素
find_first_of()
// 在一序列中找出另一个序列里的值
adjust_find()
// 找出相邻的一对值
count()
// 在序列中统计某个值出现的次数
count_if()
// 在序列中统计与某个谓词匹配的次数
mismatch()
// 找出是两个序列相异的第一个元素
equal()
// 如果两个序列对应元素相同则真
search()
// 找出一序列作为子序列的第一个出现位置
find_end()
// 找出一序列作为子序列出现的最后一个出现位置
search_n()
// 找出一序列作为子序列的第N个出现位置
修改性序列:
transform()
// 将操作用于序列中的每个元素
copy()
// 从序列的第一个元素起进行复制
copy_backward()
// 从序列的最后一个元素起进行复制
swap()
// 交换两个元素
iter_swap()
// 交换由迭代器所指定的两个元素
swap_range();
// 交换两个序列中的元素
replace()
// 用一个给定值替换一些元素
replace_if()
// 替换满足谓词的一些元素
replace_copy()
// 复制序列时用一个给定值替换元素
replace_copy_if()
// 复制序列时替换满足谓词的元素
fill()
// 用一个给定值取代所有元素
fill_n()
// 用一个给定值取代前n个元素
generate()
// 用一个操作的结果取代所有的元素
generate_n()
// 用一个操作的结果取代前n个元素
remove()
// 删除具有给定值的元素
remove_if()
// 删除满足一个谓词的元素
remove_copy()
// 复制序列时删除给定值的元素
remove_copy_if()
// 复制序列时删除满足谓词的元素
unique()
// 删除相邻的重复元素
unique_copy()
// 复制序列时删除相邻的重复元素
reverse()
// 反转元素的次序
reverse_copy()
// 复制序列时反转元素的次序
rotate()
// 循环移动元素
rotate_copy()
// 赋值序列是循环移动元素
random_shuffle()
// 采用均匀分布随机移动元素
序列排序:
sort()
// 以很好的平均效率排序
stable_sort()
// 排序,且维持相同元素原有的顺序
partial_sort()
// 将序列的前一部分排好序
partial_sort_copy()
// 复制的同时将序列的前一部分排好序
nth_element()
// 将第n个元素放到它的正确位置
lower_bound()
// 找到某个值的第一个出现
upper_bound()
// 找到大于某个元素的第一个元素
equal_range()
// 找出具有给定值的一个子序列
binary_search()
// 在排好序的序列中确定给定元素是否存在
merge()
// 归并两个排好序的序列
inplace_merge()
// 归并两个接续的排好序的序列
partition()
// 满足谓词的元素都放到前面
stable_partition()
// 满足谓词的元素放在前面,且维持原顺序
集合算法:
include()
// 如果一个序列是另一个的子序列则 真
set_union()
// 构造一个以排序的并集
set_intersection()
// 构造一个以排序的交集
set_difference()
// 构造一个序列,只包含所有在第一个序列中,但不在第二个序列中的元素
set_symmetric_difference()
// 构造一个排序序列,其中只包括在两个序列之一中的元素
堆操作:
make_heap()
// 想序列调整得能够作为堆使用
push_heap();
// 向堆中加入一个元素
pop_heap()
// 从堆中删除一个元素
sort_heap()
// 对堆进行排序
最大最小:
min()
// 两个值中较小的
max()
// 两个值中较大的
min_element()
// 序列中最小元素
max_element()
// 序列中最大元素
lexicographic_compare()
// 两个序列按照字典序在前面的一个
函数对象:
函数对象的基类,为了写函数对象,标准库提供了几个基类:
template <class Arg, class Res> struct unary_function{
typdef Arg argument_type;
typedef Res result_type;
};
template <class Arg, class Arg2, class Res> struct binary_function{
typdef Arg first_argument_type;
typdef Arg2 second_argument_type;
typedef Res result_type;
};
谓词:
谓词就是返回bool的函数对象,或函数。
例如:
template<class T> struct logical_not: public unary_function<T, bool>{
bool operator()(const T& x) const{ return !x;}
};
<functional>中的常见谓词
equal_to
二元
arg1 == arg2
not_equal_to
二元
arg1 != arg2
greater
二元
arg1 > arg2
less
二元
arg1 < arg2
greater_equal
二元
arg1 >= arg2
less_equal
二元
arg1 <= arg2
logical_and
二元
arg1 && arg2
logical_or
二元
arg1 || arg2
logical_not
一元
!arg
例子:
对于一个Club类,如下
class Person{/*...*/};
struct Club{
string name;
list<Person*> members;
list<Person*> offers;
//....
Club(const string& n);
};
用一个给定的名字在list<Club>里寻找相应的Club,显然很合理,但标准的库算法find_if()并不知道Club。
虽然有默认比较大小的方法,但是不是我们希望的比较方法(按照Club的整个值比较)。我们希望将Club的名字
当作关键码使用。因此我们需要写一个谓词来反映这种情况。
class Club_eq : public unary_function<Club, bool>{
string s;
public:
explicit Club_eq(const string& ss) : s(ss){};
bool operator()(const Club& c) const{ return c.name == s;}
};
定义这个谓词非常简单,一旦用户定义类型定义了一个适当谓词,对他们使用标准算法也就简单有效。
void f(list<Club>& lc)
{
typedef list<Club>::iterator LCI;
LCI p = find_if(lc.begin(), lc.end(), Club_eq("Dining Philosophers"));
}
适配器:
约束器,适配器和否定器。
约束器(binder):通过一个参数约束到某个值,使得我们可以将两个参数的函数对象当作一个参数的函数对象使用
成员函数适配器: 是成员函数可以被用作算法的参数
函数指针适配器:使得函数指针可以被作为算法的参数
否定器(negater): 使得我们能描述某个谓词的否定
11. 迭代器和分配器
迭代器:
一个迭代器是一个纯的抽象,也就是说任何在行为上像迭代器的东西也就是迭代器。迭代器是指向序列元素的指针概念的一种抽象
最关键的属性:
—— "当前被指向的元素"(间接,用运算符*和->表示);
—— "指向下一个元素"(增量,用运算符++标识)
—— 相等(用运算符==表示);
迭代器和函数声明在名字空间std中,在<iterator>中找到
迭代器不是一个通用指针,相反,它只是指向数组的指针概念的一个抽象。检测一个迭代器是否指向一个元素的检测很方便,
只要将它与序列的结束进行比较。(而不是将它与某个空元素比较)。
迭代器类别
Input <--
| <-- Forward <-- Bidirectional <-- Random access
Output <--
插入器:
将所产生的输出通过迭代器放入一个容器,在迭代器所指位置之后的元素就会被覆盖。也意味着溢出或随之而来的存储器破坏。
void f(vector<int>& vi)
{
fill_n(vi.begin(), 200, 7);
}
在<iterator>中提供了三个迭代器模板类,专门处理上述问题。加了三个函数使得迭代器的使用更加方便。
template<class Cont> back_insert_iterator<Cont> back_inserter(Cont& c);
template<class Cont> front_insert_iterator<Cont> front_inserter(Cont& c);
template<class Cont, class Out> insert_iterator<Cont> inserter(Cont& c, Out p);
在每次写入时,插入器将使用push_back(),push_front(),或者insert()把元素插入序列,而不是覆盖已有元素。
void f(vector<int>& vi)
{
fill_n(back_inserter(vi.begin()), 200, 7);
}
反向迭代器:
注: 由于它与 迭代器的顺序正好相反,因此迭代器的++操作,要通过对iterator的 -- 操作来实现。
分配器:
allocator(分配器)被用于将算法和容器的实现隔离于物理存储的细节之外。一个分配器提供了一套分配与释放存储的标准方式。
以及一套用作指针类型和引用类型的标准名字。分配器也是一种纯粹的抽象,任何在行为上像分配器的类型都是分配器。