如下图,STL中预定于的这些仿函数
使用请先头文件#include
由上图可以看出,总的来说可以分成一元仿函数(只有一个参数)和二元仿函数(两个参数);又可以分为:算术类,相对关系类,逻辑运算类。
目的在于将无法匹配的仿函数适配成可以匹配的型别,类似于生活中的插座。先看一个例子1:给定一个vector,其元素为【0,0,0,10,0,2】,如何通过not_equal_to匹配到第一个非零元素呢?也许你会这么解
vector<int>::iterator pos = find_if(col.begin(),col.end(),not_equal_to<int>(0));
但是却行不通,原因有两个
1. not_equal_to构造函数不接受参数“0”
2. not_equal_to的operator()不接受一个参数,需要两个参数!
其C++98声明如下所示
template <class T>
struct not_equal_to : binary_function <T,T,bool>
{
bool operator() (const T& x, const T& y) const
{return x!=y;}
};
其C++11声明如下:
template <class T>
struct not_equal_to
{
bool operator() (const T& x, const T& y) const {return x!=y;}
typedef T first_argument_type;
typedef T second_argument_type;
typedef bool result_type;
};
解决办法是通过binder1st套接not_equal_to,可以看出仿函数的作用就是:如果想要用的仿函数,由于参数方面不符合要求,那么可以通过适配器来进行适配。
先来看下binder1st型别的部分定义
template <class Operation>
class binder1st : public unary_function <typename Operation::second_argument_type, typename Operation::result_type>
{
protected:
Operation op;
typename Operation::first_argument_type value;
public:
binder1st ( const Operation& x,
const typename Operation::first_argument_type& y) : op (x), value(y) {}
typename Operation::result_type operator() (const typename Operation::second_argument_type& x) const
{ return op(value,x); }
};
/* 可以看出 1. binder1st是个template class型别,共有两个成员变量:op和value,其中根据命名含义可以得知op是操作(函数),而value是个op操作中所需的第一个参数; 2.binder1st共有两个成员函数,一个构造函数,一个重载了operator()运算符; 3.其中构造函数需要两个参数来初始化其成员,显然一个是op,一个是value; 4.其operator()操作返回op(value,x),显然value就是op操作的第一个参数,而还需要传入第二个参数x。 5.由于其重载了operator()操作,可以说binder1st也是个仿函数 */
再看下not_equal_to的源代码(C++ 11)
template <class T>
struct not_equal_to
{
bool operator() (const T& x, const T& y) const {return x!=y;}
typedef T first_argument_type;
typedef T second_argument_type;
typedef bool result_type;
};
接下来看使用过程
typename not_equal_to<int>::first_argument_type nonZero(0);
not_equal_to<int> f;
vector<int> pos;
pos = find_if(col.begin(),col.end(),
binder1st<not_equal_to<int> >(f,nonZero));
/* 1.显然是把binder1st当作仿函数使用; 2.由上面的定义可知,binder1st是个template型别,需要op(操作)来初始化; 3.调用其operator()操作,由上面其binder1st源代码看出f即是op操作,nonZero即是Operation::first_argument_type value; 4.因此就是调用op(value,x)也就是f(value,x),也就是not_equal_to<int>(value,x); 5.显然value是左值,也就是nonZero(就是0啦),那么x(右值是谁),可以猜到,正式find_if()中迭代器范围中的每个值。 6.因此find_if(...)调用就可以看成如下行为了: for (pos=beg; pos!=end; ++pos) if (not_equal_to(value,*pos)) break; */
然后看下find_if的源代码
template<class InputIterator, class UnaryPredicate>
InputIterator find_if (InputIterator first, InputIterator last, UnaryPredicate pred)
{
while (first!=last) {
if (pred(*first)) return first;
++first;
}
return last;
}
/* 可以知道其实调用pred的operator()操作 */
实际调用的时候,其实更简单,用bind1st(template function)替代binder1st(template class)
vector<int>::iterator pos;
pos = find_if(vol.begin(),col.end(),bind1st(not_equal_to<int>(),0);//因此这个0就是not_equal_to()操作中的左值,而容器中的元素就是其右值
因此这个0就是not_equal_to()操作中的左值,而容器中的元素就是其右值
bind1st封装了binder1st的复杂性
template <class Operation, class T>
binder1st<Operation> bind1st (const Operation& op, const T& x)
{
return binder1st<Operation>(op, typename Operation::first_argument_type(x));
}
/* 可知需要带入两个参数:第一个参数是仿函数,第二个是个值(左值) */
其实bind2nd和bind1st也一样,区别在于bind2nd中的参数value是其参数op的右值,而bind1st是左值。下面看代码就清楚了
template <class Operation, class T>
binder1st<Operation> bind1st (const Operation& op, const T& x)
{
return binder1st<Operation>(op, typename Operation::first_argument_type(x));
}
/* 可以看出x是op操作的第一个参数(左值) */
template <class Operation, class T>
binder1st<Operation> bind1st (const Operation& op, const T& x)
{
return binder1st<Operation>(op, typename Operation::first_argument_type(x));
}
/* 可以看出x是op操作的第二个参数(左值) */
下面看一个使用两个适配器对比的例子
vector<int>::iterator pos;
pos = find_if(col.begin(),col.end(),bind1st(less<int>(),0)); //0<容器的元素
pos = find_if(col.begin(),col.end(),bind2nd(greater<int>(),0)); //容器的元素>0
/* 这两个表达式是等效的 */
所谓的函数适配器就是将仿函数和另一个仿函数(或某个值,或一般函数)结合起来组成一个仿函数,其声明也在#include 中
如以下
find_if(col.begin(),col.end(),bind2nd(greater<int>(),42)) /* 解释:greater<int>() 其中greater是个template型别,因此需要指定类型,如int,则写成greater<int>,然后再产生一个临时对象则可以写成greater<int>(),且会调用其operator()成员函数(而这个是由于算法决定的) */
其中bind2nd(greater(),42)是一个组合仿函数,检查某个值是否大于42,实际上是bind2nd是将一个二元仿函数(greater<>)转换为一元仿函数,其中42作为二元仿函数greater<>的第二个参数。因此上诉程序以42来作为参数来调用greater<>。
如下图是一些预定义的函数适配器及其效果
函数适配器也是仿函数,因此可以结合仿函数以形成更为强大(更复杂)的表达式,如下
pos = find_if(col.begin(),col.end(),not1(bind2nd(modulus<int>(),2)));
/*
bind2nd(modulus<int>(),2)表示对2取模,其中奇数会返回1(相当于true),偶数会返回0(false),因此是一个寻找奇数的,而not1又使之反过来了
*/
为什么需要成员函数的适配器?
对于函数及对象obj,在obj上调用f的形式有以下三种
(1)f(obj);//f是全局函数(非obj的成员函数)
(2)obj.f();//f是成员函数,obj非指针
(3)obj->f();//f是成员函数,obj指针
但是看下STL中算法的实现过程就可知道了,比如for_each
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function fn) { while (first!=last) {
fn (*first);
++first;
}
return fn; // or, since C++11: return move(fn); }
如果这样情况下就只能使用(1)的形式了。
而我们是通过 mem_fun(容器中的元素是指针就使用这个)/mem_fun_ref(否则使用这个)来实现调用对象的成员函数的
就是透过这些适配器你可以对区间的每个元素都调用其成员函数,如下
例子如下
#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <functional>
using namespace std;
class Person
{
private:
string _name;
public:
Person(string name)
: _name(name) {}
void print() const
{
cout << _name << endl;
}
void print_with_prefix(const string& prefix) const
{
cout << prefix << _name << endl;
}
};
void foo(const vector<Person>& col)
{
for_each(col.begin(),col.end(),mem_fun_ref(&Person::print)); //刚开始错写为&Person::print(),不需要加()
//for_each(col.begin(),col.end(),bind2nd(mem_fun_ref(&Person::print_with_prefix("person: ")))); 开始的错误写法
string str = "person : ";
for_each(col.begin(),col.end(),bind2nd(mem_fun_ref(&Person::print_with_prefix),str));
//但是这样就会出现乱码?为什么
}
int main(void)
{
vector<Person> col;
Person p1("david");
Person p2("summer");
col.push_back(p1);
col.push_back(p2);
foo(col);
return 0;
}
若将类型换为vector
你可以编写自己的仿函数(如前面的例子),但如果要希望它们能够和函数适配器搭配使用的话,就必须满足以下:提供一些型别成员来反映其参数和返回值的型别。C++标准程序库提供了以下的一些结构
template<typename Arg, typename Result>
struct unary_function
{
typedef Arg argument_type;
typedef Result result_type;
}
template<typename Arg1, typename Arg2, typename Result>
struct binary_function
{
typedef struct first_argument_type;
typedef struct second_argument_type;
typedef struct result_type;
}
见下例子
#include <iostream>
#include <functional>
#include <cmath>
#include <algorithm>
#include <iterator>
#include <vector>
using namespace std;
template<typename T1, typename T2>
class fopow : public binary_function<T1, T2, T1>
{
public:
T1 operator() (T1 base, T2 exp) const
{
return pow(base,exp);
}
};
int main(void)
{
vector<int> col;
for (int i=1; i<=9; ++i)
col.push_back(i);
transform(col.begin(),col.end(),ostream_iterator<int>(cout," "),bind1st(fopow<float,int>(),3));
cout << endl;
transform(col.begin(),col.end(),ostream_iterator<int>(cout," "),bind2nd(fopow<float,int>(),3));
return 0;
}
/* 输出:3 9 27 81 243 729 2187 6561 19683 1 8 27 64 125 216 343 512 729 */
可以使用函数适配器ptr_fun,规则如下
ptr_fun(op);
/* 可翻译如下: (1)op(param); (2)op(param1,param2); */
看一个例子,一个参数的全局函数
bool check(int elem);
pos = find_if(col.begin(),col.end(),not1(ptr_fun(check)));
两个参数的全局函数
pos = find_if(col.begin(),col.end(),bind2nd(ptr_fun(strcmp)," "));