函数对象

为什么要写函数对象这篇博客呢?主要原因就是函数对象在STL中用得出神入化,给人一种神来之笔的感觉。不知道你是不是也有同感呢?

学习某个新东西,肯定是这个东西有用,我们有需求。所以在学习的时候,我喜欢把一个东西的来龙去脉全搞清楚。知其然,也要知其所以然。

废话不说,切入正题。

许多算法只使用一些迭代器和一些值在序列上的操作。我们来看下面的代码:在序列中找到第一个值为7的元素

void f(list<int> s){
      list<int>::iterator p = find(s.begin(),s.end(),7);
      //..........
}


假如我们想要代码去执行找到第一个小于7的元素,该怎么办呢?我们会希望算法去执行我们提供的一些代码。


bool less_than_7(int v){
      return v<7;
}
void f(list<int> &c){
      list<int>::iterator p = find_if(c.begin(),c.end(),less_than_7);
}

存在许多需要函数作为参数传递的使用方法,如:逻辑谓词,算术运算等等。为这类使用中的每一种写一个独立的函数,既不方便,也不是很有效。在很多情况下,对每个元素作用的函数还需要再调用之间保存一些数据,并将其作为许多这类应用的最后结果。与独立函数相比,类成员函数能更好地服务于这种需要。

下面一个拟函数的类,去完成一些求和工作:

template<class T>class Sum{
      T res;
public:
      Sum(T i=0):res(i){}
      void operator()(T x){res = x;}
      T result()const{return res;}
};

显然,Sum是为了那些用0初始化,并定义有+=的算法类型设计的。

void f(list<double> &ld){
     Sum<double> s;
     s = for_each(ld.begin(),ld.end(),s);
     cout<<"the Sum is "<<s.result()<<'\n';
}
//我们可以猜测下,for_each()中大概是这样子运行
for_each(.....){
       while(ld.begin()!=ld.end()){
              s(*ld.begin());
              ++ld.begin();
       }
       //..........
}

具有适当定义的对象也能很多的(通常是更好地)作为一个函数使用。例如,将一个类的应用运算符在

线化,比将一个指针传递的函数在线化容易的多。因此函数对象比常规函数执行速度更快。

标准库中提供了许多很有用的函数对象的基类:

template<class Arg,class Res>struct unary_function{
       typedef Arg argument_type;
       typedef Res result_type;
};
template<class Arg,class Arg2,class Res>struct binary_function{
       typedef Arg first_argument_type;
       typedef Arg2 second_argument_type;
       typedef Res result_type; 
};

我们可以利用这些基类派生出自己的类。例如:

template<class T>struct logical_not:public unary_function<T,bool>{
    bool operator()(const T&x)const{return !x;}
};

template<class T>struct less:public binary_function<T,T,bool>{
      bool operator()(const T&x,const T&y)const{return x<y;}
};

所谓谓词就是想上面这种返回类型为bool的函数对象。具体用法如下:

void f(vector<int>&vi,list<int>&li){
     typedef list<int>::iterator LI;
     typedef vector<int>::iterator VI;
     pair<VI,LI> p1 = mismatch(vi.begin(),vi.end(),li.begin(),less<int>());
     //..........
}

在函数mismatch中,less就可以像普通带两个参数的函数一样使用。

接下来,讨论下如何定义自己的能接受函数对象的函数:

在开始之前,我们先来看看STL中for_earch()与find_if()的定义:

template<class _InIt,
    class _Fn1> inline
    _Fn1 for_each(_InIt _First, _InIt _Last, _Fn1 _Func)
    {    // perform function for each element

    _DEBUG_RANGE(_First, _Last);
    _DEBUG_POINTER(_Func);
    _CHECKED_BASE_TYPE(_InIt) _ChkFirst(_CHECKED_BASE(_First));
    _CHECKED_BASE_TYPE(_InIt) _ChkLast(_CHECKED_BASE(_Last));
    for (; _ChkFirst != _ChkLast; ++_ChkFirst)
        _Func(*_ChkFirst);
    return (_Func);
  }


貌似我们关注的重点在红色部分(额....不给显示红色,应该能看出来我说的红色部分在哪),其他部分是

些安全检查之类操作。

我们再来看看find_if()的源码:

template<typename _InputIterator, typename _Predicate>
    inline _InputIterator
    find_if(_InputIterator __first, _InputIterator __last,
        _Predicate __pred)
    {
      // concept requirements
      __glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
      __glibcxx_function_requires(_UnaryPredicateConcept<_Predicate,
          typename iterator_traits<_InputIterator>::value_type>)
      __glibcxx_requires_valid_range(__first, __last);
      return std::__find_if(__first, __last, __pred,
                std::__iterator_category(__first));
    }
 
  /// This is an overload used by find_if() for the Input Iterator case.
  template<typename _InputIterator, typename _Predicate>
    inline _InputIterator
    __find_if(_InputIterator __first, _InputIterator __last,
          _Predicate __pred, input_iterator_tag)
    {
      while (__first != __last && !bool(__pred(*__first)))
    ++__first;
      return __first;
    }


认真分析源码,向大师学习。

那么我们先来定义自己的函数。

template<class _Iter1,class _Iter2,class _Fcn>inline 
_Iter1 get_first_less(_Iter1 first,_Iter1 last,_Iter2 first2,_Fcn f){
      while(first!=last){
            if(f(*first,*first2))return first;
      }
      return last;
}

在使用的时候,这样:

vector<int>::iterator p1 = get_first_less(vin1.begin(),vin1.end(),vin2.begin(),less_than<int>);

下面我们来看看成员函数适配器

当我们写的函数模板的参数为成员函数的时候,我们该怎么办呢?例如:

void draw_all(list<Shap *>&c){
     for_each(c.begin(),c.end(),&shap::draw);  // 肯定出错
}

成员函数总是需要一个对象去调用它:p->draw(),静态成员函数除外。这个时候我们总需要有一种方便而有效的方式

去建立某种东西,使一个算法能够调用成员函数。

我们先来定义个自己的函数对象。

template<class R,class T>class Mem_fun_t:public unary_function<T*,R>{
      R(T::*pmf)();
public:
      explicit Mem_fun_t(R(T::*p)()):pmf(p){}
      R operator()(T *p)const{
             return (p->*pmf)();
      }
}

template<class R,class T>Mem_fun_t<R,T>Mem_fun(R(T::*f)()){
       return Mem_fun_t<R,T>(f);
}

这样我们就可以处理Shape::draw()了。


void draw_all(list<Shape*>&lsp){ for_each(lsp.begin(),lsp.end(),Mem_fun(&Shape::draw));}


如果没看懂,我再啰嗦下。看懂的跳过此段。当我们遇到这种情况时,一般会想到什么样的办法呢?通过成员函数指针
来调用成员函数。返回类型必须是类的成员函数。这就是Mem_fun_t<R,T> Mem_fun(R(T::*f)())。其参数为R(T::*f)()
成员函数指针,返回值为Mem_fun_t<R,T>成员函数。要想做到Mem_fun_t<R,T>可以像函数一样调用,需要在其中加入一
个operator()(T *p)。Mem_fun_t的构造函数的参数必须是类的成员函数。
标准库不需要处理多于一个参数的成员函数,因为没有一个标准库算法以多于两个参数的函数为操作数。
至于函数参数为函数指针的情况,它的定义与实现很像成员适配器,我们只须将成员函数指针改为普通函数指针即可。
下面我们以一个例子结束。(若还有什么疑问,答案尽在例子中)
//为了大家的阅读方便,我将测试部分与函数实现部分写在一块儿
#include<iostream>
#include<vector>
#include<algorithm>
using std::for_each;
using std::vector;
using std::cout;
using std::endl;
using std::unary_function;
using std::mem_fun;
template<class T,class R>
class mem_fun_t:public unary_function<T*,R>{
	R(T::*pmf)();
public:
	explicit mem_fun_t(R(T::*p)()):pmf(p){}
	R operator()(T *p){return (p->*pmf)();}
};

template<class T,class R>
mem_fun_t<T,R> mem_fun_my(R(T::*p)()){
	return mem_fun_t<T,R>(p);
}

template<class T>
class Check{
	T num;
public:
	Check(T tmp):num(tmp){}
	bool is_positive(){
		return num? 1:0;
	}
};
template<class _Iter,class _fun>
_Iter check_each(_Iter first,_Iter last,_fun f){
	while(first != last)
	{
		if(!f(*first))
		{
			cout<<"find ...."<<endl;
			return first;
		}
		++first;
		
	}
	return last;
}

void test1(){  //类的成员函数做参数
	vector<Check<int>*> vecI;
	vecI.push_back(new Check<int>(1));
	vecI.push_back(new Check<int>(2));
	vecI.push_back(new Check<int>(3));
	vecI.push_back(new Check<int>(0));
	vector<Check<int>*>::iterator p1 = 
	check_each(vecI.begin(),vecI.end(),mem_fun_my(&Check<int>::is_positive));
}

template<class T>
class D{
	T value;
public:
	D(T a=0):value(a){}
	T get_value(){
		return value;
	}
	T operator *(){
		return value;
	}

};
template<class T>
class D_fun{
public:
	bool operator()(T *a){
		if(**a)return 1;
		return 0;
	}
	bool is_positive(T *a){
		if(**a)return 1;
		return 0;
	}
};
void test2(){    //仿函数做参数
	vector<D<int> *> vecD;
	vecD.push_back(new D<int>(1));
	vecD.push_back(new D<int>(2));
	vecD.push_back(new D<int>(3));
	vecD.push_back(new D<int>());
	vector<D<int> *>::iterator p1 = check_each(vecD.begin(),vecD.end(),D_fun<D<int>>());
	//vector<D<int> *>::iterator p1 =
	//	check_erach(vecD.begin(),vecD.end(),mem_fun_my(&D_fun<D<int>>::is_positive));//错误,必须有对象,才能调用成员函数
}


 //template<class T>
typedef bool (*fun_ptr)(D<int> *);//这里不能用指向模板函数的模板指针,因为这样做指针的类型不能确定  故编译时候会出错
//上述想法行不通,下面是改进方案。如果实在想做一个函数指针模板,我们可以这样...
template<class T>
struct Fun_ptr_struct{  //这样就不会说指针类型不确定了,因为类的参数类型定下来了,指针的类型也跟着定下来了。
	typedef bool(*fun_ptr)(T *);
};
template<class T>
bool is_positive(T *a){
	if(**a)return 1;
	return 0;
}
void test3(){  //函数指针做参数
	vector<D<int> *> vecD;
	vecD.push_back(new D<int>(1));
	vecD.push_back(new D<int>(2));
	vecD.push_back(new D<int>(3));
	vecD.push_back(new D<int>());
	fun_ptr fp;
	fp = is_positive<D<int>>;
	vector<D<int> *>::iterator p1=
		check_each(vecD.begin(),vecD.end(),fp);

	Fun_ptr_struct<D<int>>::fun_ptr fp2;
	fp2 = is_positive<D<int>> ;
	vector<D<int> *>::iterator p2=
		check_each(vecD.begin(),vecD.end(),fp2);
}

//一个有意思的东西,让我们的函数对象用起来更精彩
class Base{
public:
	virtual void do_something( )=0;
};
class Derive1:public Base{
public:
	void do_something(){
			cout<<"I am Derive1."<<endl;
	}
};
class Derive2:public Base{
public:
	void do_something(){
		cout<<"I am Derive2."<<endl;
	}
};
void test4(){
	vector<Base *> vecB;
	vecB.push_back(new Derive1);
	vecB.push_back(new Derive2);
	for_each(vecB.begin(),vecB.end(),mem_fun(&Base::do_something));
}
int main(){
	test1();
	test2();
	test3();
	test4();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值