使用STL的过程中,有时适当地使用绑定器,能够使得代码更简洁、更高效。谈到绑定器,就不得不说说函数对象(function object),或称为仿函数(functor)。
C++中的函数对象是这样一种特殊的类型,它定义了操作符“()”,所以能够像函数调用一样在对象名后加上“()”,并传入对应的参数,从而执行相应的功能。这样的类型就是函数对象,STL中通常使用函数对象作为容器的排序规则并且在算法中广泛应用。如下面定义的类就是一个简单的函数对象:
- class Functor
- {
- public:
- int operator()(int a, int b)
- {
- return a < b;
- }
- };
- int main()
- {
- Functor f;
- int a = 5;
- int b = 7;
- int ans = f(a, b);
- }
STL中的绑定器有类绑定器和函数绑定器两种,类绑定器有binder1st和binder2nd,而函数绑定器是bind1st和bind2nd。下面是STL在vs2008中的实现,可以看到类绑定器binder2nd是从模板类unary_function继承而来的,所以从根本上说它是一个一元函数对象,同理binder1st亦然。而bind2nd是一个全局的模板函数其返回值为一个binder2nd模板类的实例。所以本质上无论类绑定器还是函数绑定器都是用于构造一个一元的函数对象。
- template<class _Fn2>
- class binder2nd : public unary_function<typename _Fn2::first_argument_type,
- typename _Fn2::result_type>
- { // functor adapter _Func(left, stored)
- public:
- typedef unary_function<typename _Fn2::first_argument_type,
- typename _Fn2::result_type> _Base;
- typedef typename _Base::argument_type argument_type;
- typedef typename _Base::result_type result_type;
- binder2nd(const _Fn2& _Func,
- const typename _Fn2::second_argument_type& _Right)
- : op(_Func), value(_Right)
- { // construct from functor and right operand
- }
- result_type operator()(const argument_type& _Left) const
- { // apply functor to operands
- return (op(_Left, value));
- }
- result_type operator()(argument_type& _Left) const
- { // apply functor to operands
- return (op(_Left, value));
- }
- protected:
- _Fn2 op; // the functor to apply
- typename _Fn2::second_argument_type value; // the right operand
- };
- // TEMPLATE FUNCTION bind2nd
- template<class _Fn2,
- class _Ty> inline
- binder2nd<_Fn2> bind2nd(const _Fn2& _Func, const _Ty& _Right)
- { // return a binder2nd functor adapter
- typename _Fn2::second_argument_type _Val(_Right);
- return (std::binder2nd<_Fn2>(_Func, _Val));
- }
那么到底什么是绑定器呢?它有什么具体的用途?MSDN中对绑定器的解释是:“an adaptor to convert a binary function object into a unary function object by binding the first/second argument of the binary function to a specified value”,意思是绑定器是一种适配器,用于将二元函数对象的第一或者第二个参数绑定为一个特定的值的方式来实现二元函数对象向一元函数对象的转换。所以,绑定器的实质是一个函数适配器,它能够实现将二元函数对象转换为一元函数对象。在很多STL的算法函数中,需要给某个参数传入一个一元的函数对象,但有些时候由于实际的需要,直接定义一个一元函数对象并不是很直观有效,此时应用绑定器或许会有意外的收获。
下面的例子给出了一个可能应用到绑定器的实际情境。在一个序列容器,如vector或者list中,存储着结构体或者类的实例对象,或者是它的指针形式,现在的需求是,需要在这样的序列容器中快速找出结构体或者类这样的复合类型对象中的某个成员的值满足一定条件的某个对象或其指针的值。
让我们先来看看函数find_if的原型,可以看到前两个参数是输入迭代器,表明了查找的区间,第三个参数则是一个一元谓词。所谓谓词即是一种特殊的函数对象,它的返回值始终为bool类型,对应于函数对象便有一元谓词和二元谓词之分。对于find_if来说,一元谓词的唯一参数其类型必须是序列容器中元素的类型。
- template<class InputIterator, class T, class Predicate> inline
- InputIterator find_if(
- InputIterator First,
- InputIterator Last,
- Predicate Predicate
- )
类似的其他函数如count_if、remove_if等都需要这样的谓词,通常利用这些函数的自定义谓词版本来快速查找序列容器中符合条件的元素时,代码可以写得很elegant。
- bool IsObjEquals(int n)
- {
- if (n%2) return true;
- }
- remove_if(MyList.begin(), MyList.end(), IsObjEquals());
然而,当需要在复合类型对象中找出某个满足条件的元素,其某个成员值满足相应的条件时,单纯的定义一个一元谓词是无能为力的。此时一元的函数对象难以表达复合对象和目标类型之间的关系,想像中可以定义类似于如下这样的二元函数对象(二元谓词)来实现。
- struct IsObjIDEquals : binary_function<COMPOSEOBJ, int, bool>
- {
- bool operator()(const COMPOSEOBJ &_left, int _right) const
- {
- if (_left.nID == _right)
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- };
但是,事情并不是这么简单,find_if等函数只接受一元谓词,而且需要查找的复合类型中某个成员的值在一次运算(查找、计数或者删除等)过程中是恒定的。如何将这样的二元谓词转换为一元谓词呢?绑定器此时可以发挥它的威力了。当在一次查找(find_if)、计数(count_if)或删除(remove_if)操作中,复合类型中的单个成员值总是相对恒定的,所以可以利用bind2nd通过绑定二元函数对象中的第二个参数的方式来实现二元谓词向一元谓词的转换。于是运算的代码可以如下例这样写,同样很elegant。
- remove_if(MyList.begin(), MyList.end(),bind2nd(IsObjIDEquals(), 3));
- //STL示例 绑定器函数对象bind1st
#include <iostream>
#include <algorithm>
#include <functional>
#include <list>
using namespace std;
int iarray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
list<int> aList(iarray, iarray + 10);
int main()
{
int k = 0;
//count_if统计满足条件的元素个数
/*其中greater()实现如下
template<class T>
struct greater : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const
{return (x>y);};
};
bind1st函数作用是将8绑定到greater的第一个参数上,看上面greater类型定义及实现
可以知道这里是求小于8的元素*/
k=count_if(aList.begin(), aList.end(),bind1st(greater<int>(),8));
cout << "小于8的数的个数 :" << k << endl;
return 0;
}
-
前面在“STL中绑定器的使用” 一文中已经初步阐述了使用STL中的绑定器和函数对象实现对序列容器中的数据进行排序或者查找的方法,使用此方法能够写出优雅高效的代码。本文在原先的基础上作进一步的延伸,通过一个实际问题的使用情景,介绍使用“绑定器和函数对象嵌套调用”的方法在最少代码的基础上实现高效的功能。
问题:有一组电池,需要对其根据各自的参数和指标信息进行分组。参数和指标信息即是指电池的电压和放电时间,分组的基本思想是将彼此之间的的电压误差和放电时间误差分别在某个容许值范围内的电池归为一组 。
例如,有这样一组电池,每个电池均有唯一的编号,每个电池的电压值和放电时间均已测定,现在要讲这些电池按每三个一组来分类,每组中两两之间的电压误差不大于0.02,放电时间误差不大于1。
分析:这个问题的算法并不复杂,一般的处理思路就是先开一个数组或者链表存放组的信息,然后遍历每个电池,确定这个电池可以分配到已经存在的各个组中的哪一个,如果找到可以分配的组,则将电池分配到这个组中,同时开始下一个,否则,则新增一个组,并插入其中,并继续处理下一个电池。
如果我们不使用STL,而直接用C代码或者C++代码来书写,那么代码难免会显得冗长,抛开其他不谈,单单for循环都得有好几个,这其中还不考虑由于多方面的原因而带来的效率降低问题。而借助于STL中的绑定器和函数对象,我们可以用更简洁更优雅的代码来实现这个处理逻辑。
“ 源代码就是设计”,直接看代码。
先定义电池信息的基本结构,以及其他一些数据结构。
- // 电池信息结构
- struct BATTERY_INFO
- {
- unsigned int Code; // 序号
- double Voltage;// 电压值
- double DischargeTime; // 放电时间
- };
- typedef vector<BATTERY_INFO> BATTERYINFOARRAY;
- BATTERYINFOARRAY vecBatteryInfo; // 电池信息数组
- vector<BATTERYINFOARRAY> vecGroups; // 分组信息数组
- double dblVoltageErrLimit = 0.02; // 电压误差限值
- double dblTimeErrLimit = 1; // 放电时间误差限值
- unsigned int nMaxLimit = 3; // 每组的最大电池数
判断组是否可用的二元函数对象IsGroupAvailable 的实现:在重载其“()”操作符时,用find_if在组中查找是否超限的情况存在,如果找到超限的,则find_if的返回值即不等于end,注意在这里嵌套调用了另一个二元函数对象IsLimitExceed 来判断是否超限。
- struct IsGroupAvailable : public binary_function<BATTERYINFOARRAY, pair<double, double>, bool>
- {
- bool operator ()(BATTERYINFOARRAY &_left, pair<double, double> _right) const
- {
- BATTERYINFOARRAY::iterator itr;
- itr = find_if(_left.begin(), _left.end(), bind2nd(IsLimitExceed(), _right));
- return itr==_left.end() && _left.size()<=nMaxLimit-1;
- }
- };
二元函数对象IsLimitExceed的实现如下:
- struct IsLimitExceed : public binary_function<BATTERY_INFO, pair<double, double>, bool>
- {
- bool operator ()(BATTERY_INFO &_left, pair<double, double> _right) const
- {
- if (fabs(_left.Voltage-_right.first)>dblVoltageErrLimit ||
- fabs(_left.DischargeTime-_right.second)>dblTimeErrLimit)
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- };
有了判断当前电池信息是否可以分配到某个组中的关键代码,以及判断分组是否可用的代码,那么剩下的问题就很显而易见了,下面是自动分组的函数代码,其调用find_if,并传入应用了绑定器之后的函数对象IsGroupAvailable。
- // 自动分组
- void AutoGrouping()
- {
- BATTERYINFOARRAY::iterator itrInfo;
- vector<BATTERYINFOARRAY>::iterator itrGroup;
- for (itrInfo=vecBatteryInfo.begin(); itrInfo!=vecBatteryInfo.end(); ++itrInfo)
- {
- // 对每个电池,到组信息中查找,找出能够插入的组的迭代器
- itrGroup = find_if(vecGroups.begin(), vecGroups.end(), bind2nd(IsGroupAvailable(),
- make_pair((*itrInfo).Voltage, (*itrInfo).DischargeTime)));
- // 如果没找到,则新增一个组
- if (itrGroup == vecGroups.end())
- {
- BATTERYINFOARRAY _array;
- _array.push_back(*itrInfo);
- vecGroups.push_back(_array);
- }
- else
- {
- // 增加到找到的组中
- (*itrGroup).push_back(*itrInfo);
- }
- }
- }
这样,整个逻辑的处理流程就已经完毕。可以看到,采用绑定器结合嵌套的函数对象调用的方式,写出来的代码更优雅。同时由于使用了标准的STL算法函数,还可以借助于编译器的优化来提高程序的性能