C++STL学习(9)仿函数(function objects, functor)

注:博客内容均来自于对《C++标准库》侯捷,华中科技大学出版社一书的笔记。转载请注明出处。
所有例程在Red Hat Linux 3.2.2-5版本上编译运行,g++的版本是 g++ (GCC) 3.2.2 20030222。


1、仿函数

      仿函数(functor),就是使一个类的使用看上去象一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。
例如:

class FunctorType
{
public:
    void operator() ()
    {
        //...
    }
};

在调用的时候可以:
FunctorType fo;
fo();

或者是直接使用FunctorType()的方式来进行调用。
相比一般的函数,仿函数的形式显然很复杂。但是仿函数相比普通函数也是有优势的:
1> 仿函数比普通函数更灵活。它可以有很多“状态”(后面举例说明),这是因为仿函数是在类的内部实现的,类中的成员变量可以表示对象的不同状态,这就使得仿函数有了不同的状态。


2> 每个仿函数都有自己的型别。因此可以将仿函数的型别当做template参数来传递,从而指定某种行为模式。
     容器型别也会因为仿函数的不同而不同。

   
3> 在执行的速度上,仿函数通常比函数指针要快。


2、仿函数使用


下面举例说明:


A>  仿函数当做排序准则


这个其实是前面已经举过的例子,利用自定义排序规则对容器类元素进行排序。

#include <iostream>
#include <string>
#include <set>
#include <algorithm>
using namespace std;

class Person
{
public:
	Person(string a,string b) : 
	       strFirstname(a),strLastname(b)
	{}
public:
	string firstname() const
	{
		return strFirstname;
	}
	string lastname() const
	{
		return strLastname;
	}
private:
	const string strFirstname;
	const string strLastname;
};

//仿函数实现自定义排序
class PersonSortCriterion
{
public :
    //仿函数
	//排序规则为:按照lastname升序排列,lastname相同时按firstname升序排列
	bool operator()(const Person &p1, const Person &p2)
	{
		return (p1.lastname() < p2.lastname() ||
			(!(p2.lastname() < p1.lastname()) && 
			p1.firstname() < p2.firstname()));
	}
};


int main(int argc, char *argv[])
{
	//类型重定义,并指定排序规则
	typedef set<Person, PersonSortCriterion> PersonSet;
	PersonSet col1;
	//创建元素,并添加到容器
	Person p1("Jay", "Chou");
	Person p2("Robin", "Chou");
	Person p3("Robin", "Lee");
	Person p4("Bob", "Smith");
	//向容器中插入元素
	col1.insert(p1);
	col1.insert(p2);
	col1.insert(p3);
	col1.insert(p4);
	PersonSet::iterator pos;
	//输出PersonSet中的所有元素
	for(pos = col1.begin(); pos != col1.end(); ++pos)
	{
		cout<<pos->firstname()<< " " << pos->lastname() << endl;
	}
	cout<<endl;
	return 0;
}


B> 有多种状态的仿函数

#include <iostream>
#icnlude <list>
#icnlude <algorithm>
#icnlude "print.h"

using namespace std;

class IntSequence
{
private:
	int value;     //记录内部状态的成员变量
public:
	IntSequence (int initialValue) : value(initialValue)
	{
	}
	//仿函数
	int operator()()
	{
		return value++;
	}
};

int main()
{
	list<int> col1;
	//产生长度为9的序列,依次插值到col1容器的尾部
	generate_n(back_inserter(col1),
			   9,
			   IntSequence(1));
	//1 2 3 4 5 6 7 8 9 
	PRINT_ELEMENTS(col1);
	//替换col1容器中第2个到倒数第2个,从42开始
	generate(++col1.begin(),
			 --col1.end(),
			 IntSequence(42));
	//1 42 43 44 45 46 47 48 9
	PRINT_ELEMENTS(col1);
	return 0;
}


特别注意:

    仿函数都是传值,而不是传址的因此算法并不会改变随参数而来的仿函数的状态

比如:
IntSequence seq(1);  //从1开始的序列
//从1开始向容器col1中插入9个元素
generate_n(back_inserter(col1), 9, seq);
//仍然从1开始向容器col1中插入9个元素
generate_n(back_inserter(col1), 9, seq);


当然,也有方法来解决上述使仿函数内部状态改变的问题。方法有两种:
1、以引用的方式传递仿函数;
2、运用for_each()算法的返回值。

       因为for_each()算法它返回其仿函数。也就是说,我们可以通过返回值可以取得仿函数的状态。


下面来分别举例说明:


A> 以引用的方式传递仿函数

#include <iostream>
#include <list>
#include <algorithm>
#include "print.h"

using namespace std;

class IntSequence
{
	private:
	int value;
	public:
	IntSequence(int initValue) : value(initValue)
	{}
	
	int operator()()
	{
		return value++;
	}
};

int main()
{
	list<int> col1;
	IntSequence seq(1);
	//采用引用类型
	generate_n<back_insert_iterator<list<int> >, 
	           int, IntSequence&>(back_inserter(col1),
			   4,
			   seq);
    //1 2 3 4;
	PRINT_ELEMENTS(col1);
	
	//相当于重新构建一个对象从42开始插入4个元素
	generate_n(back_inserter(col1),
	           4,
			   IntSequence(42));
	//1 2 3 4; 42 43 44 45 
	PRINT_ELEMENTS(col1);
	
	//前面使用的是引用类型,所以seq的内部状态已经被改变了
	//插值从上次完成后的5开始
	//注意:这次调用仍然使用的是传值类型
	generate_n(back_inserter(col1),
			   4,
			   seq);
	//1 2 3 4; 42 43 44 45; 5 6 7 8 
	PRINT_ELEMENTS(col1);
    
    //上一次调用使用的是传值类型,所以这次还是从5开始插值	
	generate_n(back_inserter(col1),
			   4,
			   seq);
	//1 2 3 4; 42 43 44 45; 5 6 7 8; 5 6 7 8 		   
	PRINT_ELEMENTS(col1);
	return 0;	
}

B> 运用for_each()算法的返回值

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class MeanValue
{
private:
	long num;
	long sum;
public:
	MeanValue(): num(0), sum(0)
	{}
	void operator() (int elem)
	{
		num++;
		sum += elem;
	}
	
	double value()
	{
		return static_cast<double>(sum) / static_cast<double>(num);
	}
};

int main()
{
	vector<int> col1;
	for(int i=1; i<=8; ++i)
	{
		col1.push_back(i);
	}
	MeanValue mv = for_each(col1.begin(), col1.end(), MeanValue());
	cout<< "Mean Value: " << mv.value() << endl
	return 0;
}

C> 判断式与仿函数

    判断式就是返回布尔型的函数或者仿函数。对于STL而言,并非所有返回布尔值的函数都是合法的判断式。这可能会导致很多出人意料的行为,比如下例:
#include <iostream>
#include <list>
#include <algorithm>
#include "print.h"
using  namespace std;

class Nth
{
private:
	int nth;
	int count;
public:
	Nth(int n): nth(n), count(0)
	{
	}
	bool operator() (int)
	{
		return ++count == nth;
	}
};


int main()
{
	list<int> col1;
	for(int i=1; i<=9; ++i)
	{
		col1.push_back(i);
	}
	//1 2 3 4 5 6 7 8 9
	PRINT_ELEMENTS(col1);
	
	list<int>::iterator pos;
	pos = remove_if(col1.begin(), col1.end(), Nth(3));
	col1.erase(pos,col1.end());
	PRINT_ELEMENTS(col1);
}


看到上述结果会不会很吃惊?为什么6会被删除呢?
这个就和remove_if的实现有关系,remove_if一般的实现方法如下:

template <class ForwIter, class Predicate>
ForwIter std::remove_if(ForwIter beg, ForwIter end,
Predicate op)
{
beg = find_if(beg, end, op);
if(beg == end) return beg;
else
{
ForwIter next = beg;
return remove_copy_if(++next, end, beg, op);
}
}

这个算法使用find_if()来搜寻应被删除的第一个元素。然而,接下来它使用传进来的判断式op的副本去处理剩余的元素。这时原始状态下的Nth再一次被使用,因此会删除剩余元素中的第3个元素,也就是第6个元素。
使用的时候要尽量避免这种问题的出现。为了获得C++ STL 的保证行为,不应该传递一个“行为取决于拷贝次数或被调用次数”的仿函数。


3、预定义仿函数

STL中已经预定义好了很多仿函数。这些仿函数都包含在头文件<functional>中。
如下表所示:

C++STL学习(9)仿函数(function objects, functor)_第1张图片


3.1 函数配接器

函数配接器:能够将仿函数和另一个仿函数(或某个值,或某个一般函数)结合起来的仿函数
函数配接器包含在头文件<functional>中。预定义的函数配接器如下表所示:

C++STL学习(9)仿函数(function objects, functor)_第2张图片


函数配接器本身也是仿函数,所以函数配接器介意结合仿函数以形成更强大(复杂)的表达式。
下面来看着几种情况:

A> 针对成员函数设计的函数配接器


举例如下:

#include <iostream>
#include <string>
#include <vector>
#include <functional>
using namespace std;

//针对成员函数设计的函数配接器
class Person
{
private:
	string name;
public:
	Person(){}
	
	Person(string str): name(str)
	{}
	//输出name
	void print() const
	{
		cout << name << endl;
	}
	//输出name 和 prefix
	void PrintWithPrefix(string prefix) const
	{
		cout << prefix << name << endl;
	}
};

void foo(const vector<Person>& col1)
{
	//对容器中的每一个元素执行仿函数mem_fun_ref(&Person::print)
	for_each(col1.begin(), col1.end(), 
	         mem_fun_ref(&Person::print));
	
	//对容器中的每一个元素执行仿函数bind2nd(mem_fun_ref(&Person::PrintWithPrefix)		 
	for_each(colq.begin(), col1.end(),
			 bind2nd(mem_fun_ref(&Person::PrintWithPrefix),"Person: "));
}


int main()
{
	vector<Person> col1;
	Person p1("Robin");
	Person p2("Romio");
	col1.push_back(p1);
	col2.push_back(p2);
	foo(col1);
	return 0;
}


B> 针对一般函数设计的函数配接器

C++STL学习(9)仿函数(function objects, functor)_第3张图片

举例如下:

#include <iostream>
#include <vector>
#include <functional>
using namespace std;

//针对一般函数设计的函数配接器
bool isThree(int em)
{
	if(em == 3)
	{
		cout<< em << "is 3" <<endl;
		return true;
	}
	else
	{
		cout<< em << "is not 3"<<endl;
		return false;
	}
}

bool isElement(int em, int rem)
{
	if(em == rem)
	{
		cout<< em << "is "<< rem << endl;
	}
	else
	{
		cout<< em << "is not "<< rem << endl;
	}
}

int main()
{
	vector<int> col1;
	for(int i=0;i<9;++i)
	{
		col1.push_back(i);
	}
	for_each(col1.begin(), col1.end(), not1(ptr_fun(isThree))); //将一般函数isThree转换成仿函数
	cout<<endl;
	for_each(col1.begin(), col1.end(), bind2nd(ptr_fun(isElement),5));
	return 0;
}


C> 自定义仿函数使用函数配接器

#ifndef __FOPOW_H
#define __FOPOW_H

#include <functional>
#include <cmath>

template <class T1, class T2>
struct fopow : public std::binary_function<T1, T2, T1>
{
	T1 operator() (T1 base, T2 exp) const
	{
		return std::pow(base, exp);
	}
};

#endif //__FOPOW_H

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

#include "fopow.h"

using namespace std;

int main()
{
	vector<int> col1;
	for(int i=1; i<=9; ++i)
	{
		col1.push_back(i);
	}
	
	transform(col1.begin(), col1.end(), 
			  ostream_iterator<int>(cout, " "),
			  bind1st(fopow<float,int>(), 3));
	cout<<endl;
	
	transform(col1.begin(), col1.end(), 
			  ostream_iterator<int>(cout, " "),
			  bind2nd(fopow<float,int>(), 3));
	cout<<endl;
	return 0;
}

 

4、组合型仿函数

    仿函数的组合能力非常重要,我们可以使用简单的仿函数来构建出复杂的仿函数。

C++STL学习(9)仿函数(function objects, functor)_第4张图片




4.1一元组合函数配接器

举一例子:
#ifndef __COMPOSE_H
#define __COMPOSE_H

#include <functional>

template<class OP1, class OP2>
 class compose_f_gx_t 
	: public std::unary_function<typename OP2::argument_type,
								 typename OP1::result_type>
{
private:
	OP1 op1;
	OP2 op2;
public:
	compose_f_gx_t(const OP1& o1, const OP2& o2)
	: op1(o1), op2(o2)
	{}
	
	typename OP1::result_type
	operator() (const typename OP2::argument_type& x) const
	{
		return op1(op2(x));
	}
};

template <class OP1, class OP2>
inline compose_f_gx_t<OP1, OP2>
compose_f_gx(const OP1& o1, const OP2& o2)
{
	return compose_f_gx_t<OP1, OP2>(o1, o2);
}

#endif //__COMPOSE_H

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <iterator>

#include "print.h"
#include "compose11.h"

using namespace std;

int main()
{
	vector<int> col1;
	
	for(int i=1; i<=9; ++i)
	{
		col1.push_back(i);
	}
	PRINT_ELEMENTS(col1);
	
	transform(col1.begin(), col1.end(),
			ostream_iterator<int>(cout, " "),
			compose_f_gx(bind2nd(multiplies<int>(),5),
						 bind2nd(plus<int>(),10)));
	cout<<endl;
}

第二例:

#ifndef __COMPOSE_H
#define __COMPOSE_H

#include <functional>

template<class OP1, class OP2, class OP3>
 class compose_f_gx_hx_t 
	: public std::unary_function<typename OP2::argument_type,
								 typename OP1::result_type>
{
private:
	OP1 op1;
	OP2 op2;
	OP3 op3;
public:
	compose_f_gx_hx_t(const OP1& o1, const OP2& o2,const OP3& o3)
	: op1(o1), op2(o2), op3(o3)
	{}
	
	typename OP1::result_type
	operator() (const typename OP2::argument_type& x) const
	{
		return op1(op2(x), op3(x));
	}
};

template <class OP1, class OP2, class OP3>
inline compose_f_gx_hx_t<OP1, OP2, OP3>
compose_f_gx_hx(const OP1& o1, const OP2& o2, const OP3& o3)
{
	return compose_f_gx_hx_t<OP1, OP2, OP3>(o1, o2, o3);
}

#endif //__COMPOSE_H

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <iterator>

#include "print.h"
#include "compose21.h"

using namespace std;

int main()
{
	vector<int> col1;
	
	for(int i=1; i<=9; ++i)
	{
		col1.push_back(i);
	}
	PRINT_ELEMENTS(col1);
	
	vector<int>::iterator pos;
	pos = remove_if(col1.begin(), col1.end(),
					compose_f_gx_hx(logical_and<bool>(),
					                bind2nd(greater<int>(), 4),
									bind2nd(less<int>(), 7)));
	col1.erase(pos, col1.end());
	PRINT_ELEMENTS(col1);	
	cout<<endl;
	return 0;
}


4.2 二元组合函数配接器

#ifndef __COMPOSE_H
#define __COMPOSE_H
#include <functional>

template <class OP1, class OP2, class OP3>
class compose_f_gx_hy_t
	: public std::binary_function<typename OP2::argument_type,
								  typename OP3::argument_type,
								  typename OP1::result_type>
{
private:
	OP1 op1; //op1(op2(x),op3(y))
	OP2 op2;
	OP3 op3;
public:
	compose_f_gx_hy_t(const OP1& o1, const OP2& o2, const OP3& o3)
		: op1(o1), op2(o2), op3(o3)
	{
	}
	
	typename OP1::result_type
	operator()(const typename OP2::argument_type& x,
			   const typename OP3::argument_type& y) const
	{
		return op1(op2(x), op3(y));
	}
};

template <class OP1, class OP2, class OP3>
inline compose_f_gx_hy_t<OP1, OP2, OP3>
compose_f_gx_hy(const OP1& o1, const OP2& o2, const OP3& o3)
{
	return compose_f_gx_hy_t<OP1,OP2,OP3>(o1, o2, o3);
}

#endif //__COMPOSE_H

#include <cctype>
#include <iostream>
#include <algorithm>
#include <functional>
#include <string>
#include "compose22.h"
using namespace std;

int main()
{
	string s("Internationalization");
	string sub("Nation");
	string::iterator pos;
	
	pos = search(s.begin(), s.end(),
				 sub.begin(), sub.end(),
				 compose_f_gx_hy(equal_to<int>(),
				                 ptr_fun(::toupper),
								 ptr_fun(::toupper)));
	if(pos != s.end())
	{
		cout<< "\"" << sub << "\" is a part of \"" << s << "\"" << endl;
	}
	
	//int sum = compose_f_gx_hy(plus<int>(),20,30);
	
	return 0;
}




你可能感兴趣的:(C++STL学习(9)仿函数(function objects, functor))