学习了标准库算法(参见C++学习之标准库泛型算法_STL算法)后,就需要掌握函数对象来配合使用标准库算法。比如一些 _if 版本算法,需要一个断言(函数指针)参数,那么我们就可以使用函数对象作为断言参数。
参考http://www.cplusplus.com/reference/functional/和C++primer第4版14.8节。cplusplus.com是在看书有疑惑时,可以参考的网站。
函数对象就是重载了调用操作符的类实例化的对象。这样我们就可以通过 对象名() 来像函数调用一样来执行重载的调用操作符函数,如下面:
#include<iostream> #include<cstdlib> class funObj{ public: void operator()(int a,int b){ std::cout<<"a="<<a<<",b="<<b<<std::endl; } }; int main(int argc,char** argv){ funObj fo; fo(1,2); return EXIT_SUCCESS; }输出:a=1,b=2
标准库定义了一组算术、关系、逻辑函数对象为标准库算法使用。
Arithmetic operations:
plus<Type> | + |
minus<Type> | - |
multiplies<Type> | × |
divides<Type> | / |
modulus<Type> | % |
negate<Type> | - |
equal_to<Type> | == |
not_equal_to<Type> | != |
greater<Type> | > |
less<Type> | < |
greater_equal<Type> | >= |
less_equal<Type> | <= |
Logical operations:
logical_and<Type> | && |
logical_or<Type> | || |
logical_not<Type> | ! |
下面是使用greater结合sort算法实现递减排序的例子:
#include<iostream> #include<vector> #include<string> #include<functional> #include<algorithm> void printElement(const std::string& e){ std::cout<<e<<std::endl; } int main(int argc,char**argv){ std::vector<std::string>v; v.push_back("enock"); v.push_back("xschen"); v.push_back("functional"); std::for_each(v.begin(),v.end(),printElement); std::sort(v.begin(),v.end(),std::greater<std::string>());// std::cout<<"------------"<<std::endl; std::for_each(v.begin(),v.end(),printElement); return EXIT_SUCCESS; }
函数适配器是C++3种适配器之一,其他两种为容器适配器,迭代器适配器,它用于特化或者扩展一元和二元函数对象。函数适配器都是函数模板,它包括下面两种:
1.绑定器,它通过将一个操作数绑定到二元函数对象的一个参数,来将其转换为一元函数对象,其中bind1st是将操作数绑定到第一个参数,bind2nd是将操作数绑定到第二个参数。下面是bind2nd的例子:
std::vector<std::string>v; v.push_back("enock"); v.push_back("xschen"); v.push_back("functional"); //统计v中比"functional"大的字符串数量 std::size_t num=std::count_if(v.begin(),v.end(),std::bind2nd(std::greater<std::string>(),"functional")); std::cout<<num<<std::endl;
2.求反器,它的参数是一个函数对象,返回的函数对象的语义与参数函数对象的语义相反。其中not1、not2分别作用于一元、二元函数对象,如下例:
//统计v中小于等于"functional"的字符串数量,下面两条语句是相同的 std::size_t num1=std::count_if(v.begin(),v.end(),std::not1(std::bind2nd(std::greater<std::string>(),"functional"))); std::size_t num2=std::count_if(v.begin(),v.end(),std::bind2nd(std::less_equal<std::string>(),"functional"));
函数转换器:将函数指针转换为函数对象,这样我们自己定义的函数就能转换为函数对象,然后就可以使用上述的函数适配器。
标准库中有3个函数转换器,ptr_fun,mem_fun,mem_fun_ref,他们也都是函数模板,其中ptr_fun是 将普通函数转换为函数对象。
mem_fun和mem_fun_ref都是将类成员函数转换为函数对象,区别在于两者返回的函数对象的调用操作符的参数不一样,前者是对象的指针,后者是对象的引用,mem_fun返回mem_fun_t或mem_fun1_t类型的函数对象,mem_fun_ref返回mem_fun_ref_t或mem_fun1_ref_t类型的函数对象,定义如下,可以看出mem_fun_t和mem_fun_ref_t是一元函数对象类模板,另外两个是二元的:
下面是3个函数转换器的例子:
#include<iostream> #include<vector> #include<string> #include<functional> #include<algorithm> #include<cmath> void printElement(const std::string& e){ std::cout<<e<<std::endl; } bool myGreater(std::string str1,std::string str2){ return str1>str2; } class goods{ public: int i; goods(int ii):i(ii){} bool isOk(){ return i<=10; } bool isTrue(int t){ return i<=t; } }; int main(int argc,char**argv){ std::vector<std::string>v; v.push_back("enock"); v.push_back("xschen"); v.push_back("functional"); std::sort(v.begin(),v.end(),std::ptr_fun(myGreater));//转换为函数对象 std::for_each(v.begin(),v.end(),printElement); //转换为函数对象后就可以使用函数适配器了 std::size_t num=std::count_if(v.begin(),v.end(),std::bind2nd(std::ptr_fun(myGreater),"funObj")); std::cout<<num<<std::endl; std::vector<goods>vg; for(int i=1;i<=15;++i) vg.push_back(goods(i)); num=std::count_if(vg.begin(),vg.end(),std::mem_fun_ref(&goods::isOk)); std::cout<<num<<std::endl; std::vector<goods*>vgp; for(int i=1;i<=15;++i) vgp.push_back(new goods(i)); num=std::count_if(vgp.begin(),vgp.end(),std::not1(std::mem_fun(&goods::isOk))); std::cout<<num<<std::endl; // num=std::count_if(vg.begin(),vg.end(),std::bind2nd(std::mem_fun1_ref(&goods::isTrue),8)); std::cout<<num<<std::endl; return EXIT_SUCCESS; }
1.将函数(普通函数或者是成员函数)通过转换器转换为函数对象,如果此函数对象不作为函数适配器(绑定器、求反器)的参数来构建新的函数对象的话,那么这个函数的参数可以是引用(当然亦可以是const引用),但是如果转换来的这个函数对象还要进行函数适配器适配,那么函数的参数就不能是引用。
比如上面代代码中如果将myGreater、goods::isTrue参数改为引用(如std::string &),会出现编译错误,这是stl函数适配器实现决定的,解释见六。
2.自己定义的函数对象,如果要使用函数适配器,还需要继承 unary_function(针对一元函数)或binary_function(针对二元函数) ,而且 继承时unary_function和binary_function 的模板参数不能是引用类型(重载的调用操作符参数可以是引用类型)。而且重载的调用操作符函数需要是const类型,如下
class funObj:public std::binary_function<goods,int,bool>{ public: bool operator ()(const goods &g,const int& t)const{ return g.i<=t; } };
num=std::count_if(vg.begin(),vg.end(),std::bind2nd(funObj(),4)); std::cout<<num<<std::endl;
之所以要继承unary_function或binary_function类模板,因为函数适配器中需要用到这些类模板中定义的类型,当然可以在自己定义的函数对象类中定义这些类型,不过为了与STL更好的兼容,推荐采用继承方式。
这部分的定义在functional头文件,这里只分析 五中的函数参数(或者是模板参数)不能为引用,以及需要继承unary_function或binary_function类模板。
下面的代码中myGreater函数参数为引用,在sort算法中,只是使用函数转换器将其转换成了函数对象,所以编译没问题,但是在count_if算法中继续将转换器返回的函数对象作为了函数适配器的参数,这是就出现了编译错误:”引用的引用“
#include<iostream> #include<vector> #include<string> #include<functional> #include<algorithm> void printElement(const std::string& e){ std::cout<<e<<std::endl; } bool myGreater(const std::string& str1,std::string& str2){ return str1>str2; } int main(int argc,char**argv){ std::vector<std::string>v; v.push_back("enock"); v.push_back("xschen"); v.push_back("functional"); //转换为函数对象,但没继续使用函数适配器,所以函数参数可以是引用,所以可以编译通过 std::sort(v.begin(),v.end(),std::ptr_fun(myGreater)); std::for_each(v.begin(),v.end(),printElement); //转换为函数对象,需要继续使用函数适配器,所以函数参数不能是引用,所以出现编译错误 std::size_t num=std::count_if(v.begin(),v.end(),std::bind2nd(std::ptr_fun(myGreater),"funObj")); std::cout<<num<<std::endl; return EXIT_SUCCESS; }
通过上面的代码也就不能理解为什么要继承unary_function或binary_function类模板,
这些注意暴露出了stl中函数适配器的不足,论坛里面有人推荐使用boost的bind,见http://bbs.csdn.net/topics/390747443?page=1#post-397086212
其他定义请参见http://www.cplusplus.com/reference/functional/