前言
就几个例子简单介绍一下【普通函数、函数指针】和【函数对象】的区别,后面还会继续更新
1. 区别
C++函数对象不是函数指针、普通函数,它是一个类重载()
,它的调用方式与普通函数一样,后面加个括号,不加括号就是指地址。既然C++函数对象与普通函数在使用方式上没什么区别,那为什么要用函数对象呢?很简单,函数对象可以携带附加数据,而普通函数的地址或指针 就不行了。
函数对象作参数:
下面给出例子进行说明,主要强调函数对象的优势,
class myless
{
public:
myless(int num):n(num){}
bool operator()(int value)
{return value < n;}
private:
int n;
};
class Greater10
{
public:
bool operator()(int value)
{return value > 10;}
};
int main(){
const int SIZE = 5;
int array[SIZE] = { 50, 30, 9, 7, 20};
// 找到小于数组array中小于10的第一个数的位置
int * pa = std::find_if(array, array + SIZE, myless(10));// pa 指向 9 的位置
int * pa2 = std::find_if(array, array + SIZE, Greater10());// pa2 指向 9 的位置同上效果一样
// 找到小于数组array中小于40的第一个数的位置
int * pb = std::find_if(array, array + SIZE, myless(40)); // pb 指向 30 的位置
return 0;
}
理解: 上面myless
和Greater10
这两个类都重载了(),都是函数对象,区别在于myless
有有参构造,它能突显函数对象的优势,即附加初始化数据10、40,如myless(10)
,意思是传参给find_if
的是myless(10)
这个实例化后匿名类。而Greater10()
传的是调用无参构造后的实例化匿名类。你也许会混淆以为传参的是Greater10.operator()
这个类成员函数地址,但这种方法是错误的,因为没有实例化的类,其成员是没有地址的,不同于函数,函数在编译的时候就生成了地址。这里应该理解为实例化的匿名类,传入一个类地址,然后find_if
内会调用这个类的operator()
函数,如Greater10( array[i] )
,相当于用这个函数不断遍历array数组,直到这个函数的返回值为true。也就找到了大于10的第一个数。
普通函数、函数指针作参数:
- 所以既然
find_if
第三个参数是地址,那么就可以传函数指针或者函数地址。如下代码,注意:第三个参数没有+()哦。这样一对比普通函数、函数指针作参和函数对象作参的区别就有了 - 什么是函数指针?
定义: 函数指针,其本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针。
声明格式: 返回类型 (*函数名) (参数)
如下:
int (*fun)(int x,int y);
下面给出函数指针和 函数地址(函数名) 作参的使用方式:
bool Greater10 (int value)
{return value > 10;};
bool (* funPtr)(int val); //声明函数指针funPtr
int main(){
int array[5] = { 50, 30, 9, 7, 20};
funPtr=Greater10;//让函数指针指向Greater10函数
// 找到小于数组array中小于10的第一个数的位置
int * pa1 = std::find_if(array, array + 5, Greater10);// 使用函数地址传参,pa1 指向 9 的位置
int * pa2 = std::find_if(array, array + 5, funPtr);//使用函数指针传参, pa2 指向 9 的位置
int * pa3 = std::find_if(array, array + SIZE, Greater10());// 使用函数对象传参,pa3 指向 9 的位置
cout << pa << " " << pa2 << " " << pa3 << endl;
system("pause");
return 0;
}
结果如下:
006FFB0C 006FFB0C 006FFB0C
请按任意键继续. . .
总结
这里可以看出C++函数对象可以更改myless( num)的num值附加数据,而函数指针、函数只是一个地址,不具备这种功能。但是函数对象并不能完全替代函数指针、函数,比如函数对象不能直接做回调函数,因为类的成员函数的地址只有在实例化类的对象后,类的成员函数的地址才确定了,所以不能直接用类的成员函数(函数对象也是一种类的成员函数)做函数指针。关于回调函数的内容可参考回调函数。
那么问题来了,为什么如前面find_if
怎么既可以接受函数指针又可以接受函数对象呢?怎么做到的呢?那要想让一个函数既能接受函数指针,也能接受函数对象,最方便的方法就是用模板。如:
template<typename FUNC>
int count_n(int* array, int size, FUNC func)
{
int count = 0;
for(int i = 0; i < size; ++i)
if(func(array[i]))
count ++;
return count;
}
int main(){
const int SIZE = 5;
int array[SIZE] = { 50, 30, 9, 7, 20};
cout << count_n(array, SIZE, myless(10));
// 2用函数指针也没有问题:
bool less10(int v)
{
return v < 10;
}
cout << count_n(array, SIZE, less10); // 2
}
因此有结论:函数对象、函数指针/函数 可以被模板函数使用,如sort()、find_if。而只有函数指针可以被回调函数使用。
为了更好的理解,
下面是函数对象(函数对象也是一种类成员函数)不能被作为回调函数的例子
class AddClass {
private:
int a, b;
public:
int operator ()(int aa, int bb) {
a = aa;
b = bb;
return a + b;
}
};
typedef int(*callbackfun)(int, int);//定义函数指针类型:typedef 返回类型(*函数指针类型名)(函参列表);
void Add(callbackfun pf, int a, int b) {
cout << pf(a, b) << endl;
cout << pf << endl;
}
int myAdd(int aa, int bb) {
AddClass a;
return a(aa, bb);
}
void main()
{
AddClass a;
cout << a.operator() << endl;//调用失败报错
//Add(AddClass(), 1, 2);//调用失败,函数对象AddClass()不可以作回调函数
//Add(myAdd, 1, 2);//用中间函数myAdd作媒介,调用成功。
cout << myAdd << endl; //myAdd和Add里面的pf地址一样。
callbackfun cf1 = myAdd;//&myAdd也支持,定义函数指针变量cf1
cout << cf1 << endl;
cout << cf1(3, 2) << endl;//用函数指针调用函数
system("pause");
}
2.拓展
可以发现当一个函数(count_n)有模板的时候,其模板类型FUNC是根据传入的参数myless(10)/less10
决定的,所以为什么count_n的调用不需要指定模板参数类型。而对于类vector,vector<int> pq
需要指定<int>
模板参数。再去个例子sort库函数,调用的时候也不需要指定模板参数下面为sort的源码。这里我主要想说的是模板函数不需要指定模板参数。
template<class _RanIt,
class _Pr> inline
void sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred)