目录
1. 什么是函数对象
尽管函数指针被广泛用于实现函数回调,但是回调函数是来自C语言的,在C++中应只在与C代码建立接口,或与已有的回调接口打交道时,才使用回调函数。更重要的是,C++提供了另外一个可以实现回调函数的方法,那就是函数对象(Function Object),也叫函数符(Functor)。
讲个题外话,在c#中也有类似函数对象的方法,那就是委托。
在《C++ Primer Plus》里面,函数对象是可以以函数方式与()结合使用的任意对象。这包括函数名、指向函数的指针和重载了“operator()”操作符的类对象。
//定义方法
class FuncObject {
public:
void operator() (const string s) const{
cout << s << endl;
}
};
// 实例化对象
FuncOdject val;
val("hello"); //其中val()所实现的效果跟函数一致
从语法上讲,函数对象与普通的函数行为类似,但它有几个优点:
-
因为是对象,所以可以在内部修改而不用改动外部接口,设计更灵活,富有弹性。
-
函数对象可以有自己的状态。我们可以在类中定义状态变量,这样一个函数对象在多次的调用中可以共享这个状态。但是函数调用没这种优势,除非它使用全局变量来保存状态。
-
函数对象有自己特有的类型,而普通函数无类型可言。这种特性对于使用C++标准库来说是至关重要的。这样我们在使用STL中的函数时,可以传递相应的类型作为参数来实例化相应的模板,从而实现我们自己定义的规则。比如自定义容器的排序规则。
2. 函数对象的按值传递
在调用函数对象的标准库算法时,除非显式地指定模板类型为“传引用”,否则默认情况下函数对象是“按值传递”的。因此,如果传递的是一个具有内部状态的函数对象,则被改变状态的是函数内部被复制的临时对象,函数结束后随之消失,真正的函数对象状态并不会改变,如下所示。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class FuncObject{
public:
FuncObject(int n = 0) : num(n), count(1){}
bool operator()(int) {
return count++ == num;
}
int GetCount(){
return count;
}
private:
int count;
int num;
};
int main(){
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
vector<int> dest(a, a + 10);
FuncObject test(3);
vector<int>::iterator itr = find_if(dest.begin(),dest.end(), test);
cout << "3rd:" << *itr << endl; //9rd:3
cout << "State:" << test.GetCount() << endl; //State:1
}
上例中,我们把函数对象test作为参数传入find_if,由于这里是按值传递,所以test的值并不会发生变化。因此,在编程过程中,被用作谓词函数的函数对象的判断结果,最好不要依赖内部变动状态。
3. 函数适配器
在理解函数适配器之前,我们先了解一下一元、二元函数以及一元、二元谓词(Predicate)的意思。
- 一元函数(一个参数)
- 二元函数(两个参数)
- 一元谓词(一个参数,返回类型为bool型)
- 二元谓词(两个参数,返回类型为bool型)
C++中有三类适配器,分别是容器适配器,迭代器适配器和函数适配器。
其中,函数适配器是用来让一个函数对象表现出另外一种类型的函数对象的特征。因为许多情况下,我们所持有的函数对象或普通函数的参数个数或是返回值类型并不是我们想要的,这时候就需要函数适配器进行适配。
函数适配器用于特化和扩展一元二元函数对象,它主要有以下两类:
3.1 绑定器
该类适配器用于将二元函数适配成一元函数,tongg将二元函数的一个参数绑定到一个特定的值上,将二元函数对象转换成一元函数对象。
绑定器适配器有两种:bind1st和bind2nd。每个绑定器接受一个函数对象和一个值。
- bind1st将给定值绑定到二元函数对象的第一个实参
- bind2nd将给定值绑定到二元函数对象的第二个实参
【例子】
count_if是利用输入的函数,对标志范围内的元素进行比较操作,返回结果为true的个数。例如:vec是用vector声明的容器,已包含1,3,15,7,9元素,现要求求小于等于10的元素个数。
#include <iostream>
#include <vector>
using namespace std;
bool GreaterThanThree(int num){
if(num <= 10) return true;
else return false;
};
int main(){
int a[5] = {1, 3, 15, 7, 9};
vector<int> vec(a, a + 5);
int iCount = count_if(vec.begin(),vec.end(), GreaterThanThree);
cout << iCount << endl;
}
STL里有一个函数对象,叫less_equal。它有两个参数,作用是比较第一个参数值是否小于等于第二个参数值。为了达到上面的目的,我们也可以利用这个函数。
但是count_if要求我们第三个参数必须是一个一元谓词,所以我们用bind2nd对该函数对象进行适配,将10绑定到该函数对象的第二个参数上。
#include <iostream>
#include <vector>
using namespace std;
bool GreaterThanThree(int num){
if(num <= 10) return true;
else return false;
};
int main(){
int a[5] = {1, 3, 15, 7, 9};
vector<int> vec(a, a + 5);
//函数适配器第一种:绑定器(bind1st, bind2nd)
int iCount1 = count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 10));
cout << iCount1 << endl;
}
3.2 取反器
该类适配器将函数对象的结果真值求反,主要有两种:not1和not2。
- not1是对一元函数对象求反的取反器,传递给函数对象的只有一个参数,则要使用这个not1
- not2是对二元函数对象求反的取反器
下面为求1,3,15,7,9中小于等于10的元素个数。
#include <iostream>
#include <vector>
using namespace std;
bool GreaterThanThree(int num){
if(num <= 10)
return true;
else
return false;
};
int main(){
int a[5] = {1, 3, 15, 7, 9};
vector<int> vec(a, a + 5);
//函数适配器第二种:取反器(not1, not2)
int iCount2 = count_if(vec.begin(), vec.end(), not1(bind2nd(less_equal<int>(),10)));
cout << iCount2 << endl;
}
4. 自定义容器的排序
未完待续。