泛型算法的设计(2)
前言:
前面我们讨论了一个泛型算法的实现思想,即设计一个算法,使其独立于所操作的容器,并且独立于所操作的元素类型,并分别用不同的机制实现了这两个目的,下面我们在算法设计中引入函数对象,实现算法独立于对元素的底层操作方式,这样我们实现了终极意义上的泛化,下面我们一步步的实现一个真正意义上的泛型算法。
1.首先我们写一个函数,它可以找到vector内小于10的所有元素;
vector<int> less_than_10(const vector<int> &vec)
{
vector<int> nvec;
for(int ix=0; ix!=vec.size(); ++ix)
if(vec[ix]<=10)
nvec.push_back(vec[ix]);
return nvec;
}
结:显而易见,这个函数过于死板,没有弹性;
1).当我们想要找到小于11的元素就无能为力了,我们希望由用户指定上限值;
vector<int> less_than(const vector<int> &vec, int less_than_val);
2).我们希望用户指定不同的比较操作,如大于,小于等等,我们设法把比较操作参数化;
2.通过函数指针将比较操作参数化
由于我们把比较操作通用化了,函数的功能不局限于less_than,因此我们重新将函数命名为filter;
vector<int> filter(const vector<int> &vec,int filter_value,
bool (*pred)(int, int))
{
vector<int> nvec;
for(int ix=0; ix!=vec.begin; ++ix)
if(pred(vec[ix], filter_value))
nvec.push_back(vec[ix]);
return nvec;
}
编程小技巧:
在泛型算法中,我们常用find_if()来取代for循环,将find_if()反复施行于数列上,找到符合条件的每一个元素,其中条件由用户指定的函数指针定义;
while(iter=find(iter,vec.end(),val)!=vec.end())
{
//执行相关操作
++iter;
}
补充:
函数对象的函数适配器:
标准库提供了一组函数适配器,用于特化和扩展一元和二元函数对象,函数适配器分为以下两种:
1)绑定器(binder):一种函数适配器,通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象,每个绑定器接受一个函数对象和一个值;
bind1st: 将给定值绑定到二元函数对象的第一个实参;
bind2st: 将给定值绑定到二元函数对象的第二个实参;
如:
bind2st(less<int>(),10);
该适配器返回一个函数对象,这个对象用10做右操作数应用<操作符;
2)求反器(negator)
not1: 将一元函数对象真值求反;
not2: 将二元函数对象真值求反;
注:
二元函数对象通过绑定器转换成一元函数对象后,用not1求反器;
not1(bind2st(less<int>(),10))
3.用函数对象实现操作参数化
vector<int> filter(const vector<int> &vec, int val,
less<int> <)
{
vector<int> nvec;
vector<int>::const_iterator iter=nvec.begin();
while((iter=find_if(iter,vec.end(),
bind2nd(ld,val)))!=vec.end())
{
nvec.push_back(*iter);
++iter;
}
return nvec;
}
4.终极泛化
这一步我们设法消除filter()与vector元素型别的相依关联,及filter()与容器类型的相依关联;
为了消除它和元素型别间的相依性,我们引入模板机制,将filter()改为function template,并将元素型别加入template的声明;
为了消除它和容器类型间的依赖性,我们传入一对迭代器 iterators[first,last],并在参数中增加另一个iterator,用以指定从何处复制元素;
template <typename InputIterator, typename OutIterator,
typename ElemType, typeName Comp>
OutputIterator
filter(InputIterator first, InputIterator last,
OutIterator at, const ElemType &val, Comp pred)
{
while((first=find_if(first,last,bind2st(pred,val)))!=last)
{
*at++=*first++;
}
return at;
}
结:
这里我们将容器类型,元素类型,操作类型都进行了泛化;
注意我们使用template的方法;
当一个函数用到function template时,我们将函数中所有需要泛化的类型都在template语句中声明,并声明为意义相关的类型,以便于理解。
结:
我们一步步的实现了一个真正意义上的泛型算法,每一步都有确定的技术机制支持,下面我们简单的回顾下每一步的实现过程及机制;
一开始我们实现一个函数,可以找到vector中小于10的所有元素,然而函数过于死板,没有弹性;
接下来为函数加上了数值参数,让用户指定某个数值,以此和vector中的元素进行比较;
后来,加上了新参数:函数指针,让用户可以比较方式;
然后,引入函数对象的概念,其功能和函数指针类似,但其用内联函数实现,效率更高;
最后,我们将函数用function template的方式重新实现,通过用一对iterator标识出一组元素范围,独立与容器类型,并在参数中定义一个单独的迭代器,使其独立于目的端容器,将元素型别参数化,实现了与元素型别的独立;最后将施加于元素身上的比较操作参数化,同时支持函数指针和函数对象两种方式。
编程技巧:
将函数用function template的方式实现,把需要泛化的元素在template中声明,通常需要泛化的元素不止一个,如上面迭代器类型(何种容器的迭代器),元素类型,比较操作的传递方式(函数指针or函数对象),我们一一在template中声明,并声明为一个有意义的类型名。