【普通函数/函数指针】和【函数对象】作参数的区别及使用

前言

就几个例子简单介绍一下【普通函数、函数指针】和【函数对象】的区别,后面还会继续更新

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;
}

理解: 上面mylessGreater10这两个类都重载了(),都是函数对象,区别在于myless有有参构造,它能突显函数对象的优势,即附加初始化数据10、40,如myless(10),意思是传参给find_if的是myless(10)这个实例化后匿名类。而Greater10()传的是调用无参构造后的实例化匿名类。你也许会混淆以为传参的是Greater10.operator() 这个类成员函数地址,但这种方法是错误的,因为没有实例化的类,其成员是没有地址的,不同于函数,函数在编译的时候就生成了地址。这里应该理解为实例化的匿名类,传入一个类地址,然后find_if内会调用这个类的operator()函数,如Greater10( array[i] ),相当于用这个函数不断遍历array数组,直到这个函数的返回值为true。也就找到了大于10的第一个数。

普通函数、函数指针作参数:

  1. 所以既然find_if第三个参数是地址,那么就可以传函数指针或者函数地址。如下代码,注意:第三个参数没有+()哦。这样一对比普通函数、函数指针作参函数对象作参的区别就有了
  2. 什么是函数指针?
    定义: 函数指针,其本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针。
    声明格式: 返回类型 (*函数名) (参数)
    如下:
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)
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值