《C++程序设计语言》笔记之五

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(分配器)被用于将算法和容器的实现隔离于物理存储的细节之外。一个分配器提供了一套分配与释放存储的标准方式。
以及一套用作指针类型和引用类型的标准名字。分配器也是一种纯粹的抽象,任何在行为上像分配器的类型都是分配器。

你可能感兴趣的:(《C++程序设计语言》笔记之五)