算法泛化意义
有时存在一些比较底层且常用的算法,这些算法需要被复用,并且应该独立于特定的容器(其实这这也是STL中算法的思想)
算法泛化的关键
1. 将操作对象的类别进行泛化(可以使用模板)
2. 把操作对象的标示法和区间目标的移动行为抽象化
这样就可以使得算法在一个抽象的层面进行工作,从而独立于特定的使用场景
算法泛化过程
以find功能为例,在array中寻找特定值,首先我们实现一个仅仅支持数组的查找算法
int* find(int* array, int size, int value)
{
for (int i = 0; i < size; ++i)
{
if (array[i] == value)
break;
}
return &(array[i]);
}
该算法的大致意思为在array中查找value,若存在value,则返回value所在地址(也就是指针),否则返回最后一个元素的下一个元素的地址(最后一个元素也成为end)。若不常用stl可能觉得返回end非常奇怪,这边明明是可以返回一个nullptr来表示查找无结果。但思考一下一个指向array元素的指针,不但可能合法的指向array内部的元素,其实也可以指向array尾端以外的任何位置(只是这些位置对我们来说没有意义,只是不能对其进行取值,所以在使用时需要判断一下是否为我们想要的值)
但是find使用这种做法使用了容器太多的细节,以至于不能比较好的实现泛化(例如使用size来获取容器中元素的个数)。为了适用于所有类型的容器,其操作应该回更抽象化,可以让find函数接受两个指针作为参数,标示出一个操作区间
int* find(int* begin, int* end, int value)
{
while(begin != end && *begin != value)
++begin;
return begin;
}
该函数在前闭后开区间[begin, end)(stl中所有区间都是这种表示形式)内查找value并返回指针,指向所找到的第一个符合条件的元素,若没找到则返回end。
到现在为止,find函数只适合int类型容器,所以可以将其增强为一个函数模板(使用模板是实现库函数一个非常好的思想)
template<typename T>
T* find(T* begin, T* end, const T& value)
{
while(begin != end && *begin != value)
++begin;
return begin;
}
到目前为止,修改后的find()几乎适用于任何允许指针指入的容器。为了打破find只能使用指针的限制,我们可以设计一个类来替代原生指针的行为,并为其实现必要的功能(判断不等,取值,前置递增,复制),修改完的find()如下所示
template<class Iterator, class T>
Iterator find(Iterator begin, Iterator end, const T& value)
{
while (begin != end, && *begin != value)
++begin;
return begin; // 此处会发生拷贝构造
}
这便是一个完全泛化的find()函数,与任何c++标准库中的长相几乎一模一样。我们在自己实现一些库函数时,可以参考这种写法,来提高自己代码的复用性。以及stl这种数据与算法分离的想法,也值得我们学习。