二、算法和函数(Algorithm and Function Object)
STL中提供能在各种容器中通用的算法,比如插入,删除,查找,排序等。大约有70种标准算法。
算法就是一个个函数模板。
算法通过迭代器来操纵容器中的元素。许多算法需要两个参数,一个是起始元素的迭代器,一个是终止元素的后面一个元素的迭代器。比如,排序和查找
有的算法返回一个迭代器。比如 find() 算法,在容器中查找一个元素,并返回一个指向该元素的迭代器。
算法可以处理容器,也可以处理C语言的数组
容器只有被诸如获取容器大小、循环访问、复制、排序和搜寻等基本操作支持才是真正有效的。标准模板库提供的算法用于能满足用户对使用容器的通用和基本的需要。函数对象提供了一种机制,通过这种机制用户可以定制标准算法的行为。
函数对象为使用一个算法完成对用户数据的操作提供所需要的关键信息。因此,重要的是如何定义和使用这些函数对象。标准模板库中的算法覆盖了大多数在容器上的操作,例如,往返移动、排序、搜寻以及插入和删除元素等。
标准算法都是定义在 namespace std 中的模板,所有的算法模板的原型声明在头文件 <algorithm> 中。此外还有其他算法,比如<numeric>中的算法。
1、下面的 7 个表中列出了各种算法模板的功能:表1 中所列的操作用于从序列中提取元素信息或在序列中寻找元素位置,而不修改元素值。
(1)不修改序列的操作(表1)
示例:算法示例:find()
template<class InIt, class T>
InIt find(InIt first, InIt last, const T& val);
first 和 last 这两个参数都是容器的迭代器,它们给出了容器中的查找区间起点和终点,这个区间是个左闭右开的区间,即区间的起点是位于查找范围之中的,而终点不是。val参数是要查找的元素的值函数返回值是一个迭代器。如果找到,则该迭代器指向被找到的元素;如果找不到,则该迭代器指向查找区间终点。
#include "iostream"
#include "vector"
#include "algorithm"
using namespace std;
int main()
{
int array[10]={10,20,30,40};
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator p;
p=find(v.begin(),v.end(),3);
if(p!=v.end())
cout<<*p<<endl;
p=find(v.begin(),v.end(),9);
if(p==v.end())
cout<<"not found"<<endl;
p=find(v.begin()+1,v.end()-2,1);
if(p!=v.end())
cout<<*p<<endl;
int *pp=find(array,array+4,20);
cout<<*pp<<endl;
return 1;
}
表2 中所列出的操作均可以用于修改序列中的元素值。
(2)修改序列的操作(表2)
表3 中所列出的操作用于序列中元素的排序、搜索和基于有序序列的管理。
序列排序操作(表3)
集合算法(表4)
表5 中所列出了将序列按照堆结构的操作。堆操作在状态上仍然保持为一个序列,使得在需要的时候能够使用序列的操作对堆的元素进行相应的操作。
堆操作算法(表5)
表6 中的操作用于建立在比较操作上的选取序列元素。
最小和最大算法(表6)
表7 中的两种置换操作均返回布尔标志,表示序列是否已经按字典顺序或逆序排列。
置换算法(表7)(<numeric>)
表8 通用数字算法
2、标准函数对象也是定义在 namespace std 中的模板,所有函数对象的原型声明在头文件 <fuctional> 中。下面的 3 个表中列出了各种函数对象的功能:
(1)断言函数对象(表1)
算术操作函数对象(表2)
标准模板库提供了一些函数对象用于构造定制的函数对象:
⑴ Binder 类函数对象允许通过将一个参数绑定为值,使一个双目函数对象作为单目函数对象使用。
⑵ 成员函数的Adapter类函数对象允许将一个成员函数作为一个算法的参数。
⑶ 成员函数指针的Adapter类函数对象允许将一个指向成员函数的指针作为一个算法的参数。
⑷ Negate 类函数对象允许用户表达一个断言函数的相反含义。
Binders、Adapters 和 Negaters 类函数对象
例如对一个整数序列容器中小于指定值 n 的元素进行排序就可以使用算法partition 或 stable_partition ,而该算法所需要的断言操作函数对象可使用 less ,并使用 Binders 类函数对象 bind2nd 将 less 的第 2 操作数绑定为指定值 n 。例如算法 partition的调用表达式如下:
partition(start, end, bind2nd(less<int>(), n));
其中 start 和 end 是分别指示序列容器中的第一个元素和容器边界的循环子。
3、循环子(Iterators)
循环子是将容器和算法连接在一起的黏结剂。它提供了一个抽象的数据概念,以便算法的编写者不需要关注所操作的数据结构的具体细节。由于循环子提供了访问数据的标准模块,从而避免了不同的容器必须提供自己特定的访问操作集合。与循环子的实现机制相似,内存管理器(Allocator)的使用将容器的实现和内存分配的具体细节分离开。
循环子是一个纯抽象。循环子是指向序列中一个元素的指针概念的抽象,它的关键操作概念是:
- "the element currently pointed to"
(referencing, represented by operator *and ->),
- "point to next element"
(increment, represented by operator ++), and
- "equality" (represented by operator ==).
例如:
int* p 可以作为数组 int[] 的“循环子”,
list<int>::iterator 是链表容器类 list<int> 的循环子。
使用循环子操作的序列的概念被抽象描述为:
"something where we can get from the beginning to the end by
using a next-element operation:"
例如:数组 array、向量 vector、单链表 singly-link lists、双链表 doubly-link lists、树 trees、输入 cin 和输出 cout 流等序列。每种序列都有相应种类的循环子。
循环子类和相应函数都是定义在 namespace std 中的模板,这些循环子类模板和函数模板的原型声明在头文件 <iterator> 中。
注意,循环子没有 “null iterator” 的概念。测试循环子是否指向一个元素是通过比较循环子是否到达序列的 end 而不应比较循环子的当前值是否为 null。下列情况之一都会引起使用循环子访问序列失效:
① 循环子未初始化;
② 循环子所指向的容器被显式或隐式地调整了大小;
③ 循环子所指向的容器已经撤消;
④ 循环子指向了序列的 end。
序列的 end 可以被认为循环子指向了一个不存在的假设元素位置:one-past-the-last element of a sequence。下表列出了循环子的操作和种类:
循环子的不同类型,即经常提及的循环子分类符合下面的分等级顺序:
不同的算法需要不同类型的循环子作为参数。相同的算法有时可以使用不同类型的循环子达到不同效率的实现。标准模板库提供了五种类型的循环子:
struct input_iterator_tag;
struct output_iterator_tag ;
struct forward_iterator_tag ;
struct bidirectional_iterator_tag ;
struct random_access_iterator_tag;