我们学习C或C++一定会碰到函数指针(function pointer) 这个概念,而C++中又有了仿函数(Functor)特性,在上课学习的时候了解过但一直没怎么使用它,这段时间在项目中碰到了回调(callback)这样的问题,正好可以拿出来谈谈。

假设我们有这样的一个需求:

我们想实现一个叫床功能,当然不是那个“叫床”哈哈,是叫人起床的意思。

在远古时代,我们只能靠别人叫我们起床:

1
2
def wakeup(name,time):
print("Get up you sleepyhead! It's %s %s!"(time,name))

但现在,我们可以设闹钟让我们起床了:

1
2
def wakeup(name,time):
print("Ding Ding Ding~It's %s %s!"(time,name))

假如我们的生活是可以程序化的,那么一天将会是这样的:

1
2
3
4
wakeup(name,time)
eatBreakfast()
goToschool()
...

我们发现wakeup可以有不同的动作方式来唤醒,我们可以选择人工叫醒也可以选择闹钟,但是都是

wakeup(name,time)这样会产生命名冲突,那么我们变成wakeup1(name,time)wakeup2(name,time)怎么样?可以是可以的,但是如果有上百个方式的话,我们得改100遍函数名和里面的方法。

这样既增加了重复代码,又很不好看。

所以我们改进了这个程序:

1
2
3
4
5
6
7
8
9
10
#我们可以定义这样两个方式:
def alarm():
print("Ding Ding Ding~")

def shout():
print("Get up you sleepyhead!")

def wakeup(wakeup_method,name,time):
wakeup_method()
print("It's %s %s"%(name,time))
1
2
3
4
wakeup(shout,name,time)
eatBreakfast()
goToschool()
...

这样是不是简单多了?

我们定义一个wakeup_method参数,它就是函数指针,我们把函数当做参数传入wakeup(),在函数里调用wakeup_method()

这样带来的好处是,我们可以针对不同的人用不同的方法(在特定的事件或条件发生时由另外的一方调用,用于对该事件或条件进行响应),我们对懒人可以用人工叫醒,对自觉一些的人用闹钟就可以了。

简单来说,回调函数的使用增加了程序的通用性和简洁性。

那么C++里的Functor又跟函数指针有什么关系?

Functor是一种更抽象化的函数指针。

看下面函数指针的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int fnadd1( int i ) { return i + 1; }
int fntake10( int i ) { return i - 10; }
void ApplyToAllElements2( int* aiArray,int iNumElems, int(*funcptr)(int) )
{
for ( int i = 0; i < iNumElems; i++ )
aiArray[i] = funcptr( aiArray[i] );
}

int aiMyArray[] = { 0,1,2,3,4,5,6 };

ApplyToAllElements2( aiMyArray, 7, fnadd1 );//对数组每个元素加1

for ( const int& myelem : aiMyArray )
cout << myelem << " ";cout << endl;

我们定义了一个数组,和一个函数ApplyToAllElements2(),它接收一个函数指针funcptr(),对数组每个元素运用此函数。比如可以用fnadd1()对每个元素加1,也可以用fntake10()。但是不好的地方就是我每次有新需求就要定义一个新的函数。那么能不能对这一类加法操作做一个更加通用的办法呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MyFunctor

{

public:

MyFunctor( int var )

: var( var )

{

}

int operator() ( int x )

{

return x + var;

}

protected:

int var;

这就是Functor,functor对operator()进行重载,我们可以在里面随意编写我们想进行的操作。实际上是把函数变成了一个对象了。

在这个例子中,初始化这个functor时我们要给var一个值,这个var就相当于fnadd1()里的1,也可以是2或者任何数。当这个functor被调用时,会执行opeartor()方法,需要一个参数int x,也就是数组里的一个元素,对元素进行加法操作。

再看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int aiMyArray[] = { 0,1,2,3,4,5,6 };

iAdd = 1;
addVariable = MyFunctor(iAdd)//实例化MyFunctor
ApplyToAllElements( aiMyArray, 7, addVariable );
for ( const int& myelem : aiMyArray ) // range-for
cout << myelem << " ";cout << endl;

template < typename T >
void ApplyToAllElements( int* aiArray, int iNum, T addfunc )//相当于一个map函数
{
for ( int i = 0; i < iNum; i++ )
{
aiArray[i] = addfunc( aiArray[i] );
}
}

相比较于第一个方式而言,我们不需要定义那么多的加法函数,而只需要每次变动不同的iAdd值就可以了。

参考:回调函数(callback)是什么? https://www.zhihu.com/question/19801131)

浅谈:函数指针、仿函数和函数适配

实现functor - 增强型的函数指针