1、什么叫回调函数
程序在运行过程中,调用某些函数接口时,某些函数的参数要求传入另一个函数的指针和该的函数的参数,以备在合适的时候在接口函数中就直接调用另一个函数,来完成程序的执行任务。这种通过参数形式把函数的指针传递给其它函数,在那个函数里面调用这个函数指针就相当于调用这个函数,这个过程就叫回调,而通过指针被调用的函数就叫回调函数(callback function)。回调函数并不是由该函数的实现方来直接调用,而是在特定的事件或条件发生时在其他模块或者库中进行调用,用于对该事件或条件进行响应和处理。
例如:应用程序(application program)会时常通过API调用库里所预先备好的函数。但是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。运行过程如下图所示:
2、C回调函数
C++的回调函数是扩展自C语言的回调函数,要想理解C++回调函数,先要理解C回调函数。我们通过一个实例来讲解C回调函数的使用方法,如下callbackFunc_1.cpp例程所示。
//callbackFunc_1.cpp
typedef struct _DataInfo_{
int a;
int b;
}stDataInfo;
/*
//定义回调函数的原型
//@CallbackFun 指向函数的指针类型
//@data 回调参数
//@contex 回调上下文,在C中一般传入NULL,在C++中可传入对象指针
*/
typedef void (*CallbackFun)(stDataInfo *data, void* contex);
/*
//callbackTest.c
*/
void getMaxData(stDataInfo *data, void* contex)
{
int max = 0;
data->a > data->b ? max = data->a : max = data->b;
printf("current data max is %d\n",max);
}
void getMinData(stDataInfo *data, void* contex)
{
int min = 0;
data->a < data->b ? min = data->a : min = data->b;
printf("current data min is %d\n",min);
}
/*
//定义注册回调函数
*/
void registHeightCallback(CallbackFun callback, void* contex)
{
stDataInfo data = {5, 12};
callback(&data, NULL);
}
int main(int argc, char* argv[])
{
registHeightCallback(getMaxData, NULL);
registHeightCallback(getMinData, NULL);
return 0;
}
程序callbackFunc_1.cpp运行的结果就是:
current data max is 12
current data min is 5
这是一种使用回调函数的方法,当几个函数的大部分代码及其相似,只有少部分功能代码有所区别的时候,可以使用这种回调函数的方法。将相同部分的功能合并成一个函数,将其不同的部分通过回调函数的方式,调用外部不同的函数来处理,以达到程序的简洁。
很多时候,注册的时候并不调用回调函数,而是在其他函数中调用,那我们可以定义一个CallbackFun全局指针变量,在注册的时候将函数指针赋给它,在要调用的调用它。如例子callbackFunc_2.cpp所示:
程序callbackFunc_2.cpp运行的结果:data.a = 12, data.b = 18
3、C++回调函数
但是如果在C++程序中使用上述的C语言方式,试图直接使用类成员函数作为回调函数时将发生错误,甚至编译就不能通过。深究其错误的原因是:如果一个运算符函数是成员函数,则它的第一个(左侧)运算对象绑定到隐式的this指针上。因此成员运算符函数的显示参数数量比运算符的运算对象总数少一个。成员运算符函数正是通过传递this指针从而实现成员函数可以访问类的其它数据成员。这也就不难理解,为什么C++类的多个实例可以共享成员函数,却有不同的数据成员。
由于普通C++函数会传递this指针的缘故,使得将一个Callback型的成员函数作为回调函数安装时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败。要解决这一问题的关键就是不让this指针起作用,通过采用以下两种典型技术可以解决在C++中使用回调函数所遇到的问题。这种方法具有通用性,适合于任何C++。
方法一:使用全局函数
可以使用全局函数作为回调函数,实现在类中对类外部的函数的调用。
C++中,全局函数的显示的函数参数就是它实际的函数的形参个数,所以全局变量其本身就不带有this指针,所以当然可以作为C++类成员函数的回调函数使用。如下文例程callbackFunc_3.cpp所示:
//callbackFunc_3.cpp
typedef struct _DataInfo_{
double height;
double length;
double width;
}stDataInfo;
//CallbackFun类型
//@CallbackFun 指向函数的指针类型
//@data 回调参数,当有多个参数时,可以定义一个结构体
//@contex 回调上下文,在C中一般传入nullptr,在C++中可传入对象指针
typedef void (*CallbackFun)(stDataInfo *data, void* contex);
//注册库回调函数接口
void registHeightCallback(CallbackFun callback, stDataInfo *data, void* contex)
{
if (NULL == callback || NULL == data )
{
return;
}
callback(data, contex);
}
//定义回调函数onHeight
void onHeight(stDataInfo *data, void* contex)
{
cout << "rectangle length= " << data->length << ", width=" <<
data->width << ", height = " << data->height <<endl;
}
//接收数据类class Sensor
class Sensor{
public:
Sensor(){}
~Sensor(){}
//定义注册回调函数
void PrintfAllParam(stDataInfo *data)
{
registHeightCallback(onHeight, data, this);
}
};
int main(int argc, char* argv[])
{
stDataInfo data = {1.2, 5.6, 3.4};
Sensor sens;
sens.PrintfAllParam(&data);
data.length = 7.8;
data.height = 7.8;
data.width = 7.8;
sens.PrintfAllParam(&data);
return 0;
}
程序callbackFunc_3.cpp运行的结果:
rectangle length= 5.6, width=3.4, height = 1.2
rectangle length= 7.8, width=7.8, height = 7.8
方法二:使用静态成员函数
可以使用静态成员函数作为回调函数,来实现函数回调功能。
//callbackFunc_4.cpp
typedef struct _DataInfo_{
double height;
double length;
double width;
}stDataInfo;
//CallbackFun类型
//@CallbackFun 指向函数的指针类型
//@data 回调参数,当有多个参数时,可以定义一个结构体
//@contex 回调上下文,在C中一般传入nullptr,在C++中可传入对象指针
typedef void (*CallbackFun)(stDataInfo *data, void* contex);
//注册库回调函数接口
void registHeightCallback(CallbackFun callback, stDataInfo *data, void* contex)
{
if (NULL == callback || NULL == data )
{
return;
}
callback(data, contex);
}
//接收数据类class Sensor
class Sensor{
public:
Sensor(){}
~Sensor(){}
//定义回调函数onHeight
static void onHeight(stDataInfo *data, void* contex)
{
cout << "rectangle length= " << data->length << ", width=" <<
data->width << ", height = " << data->height <<endl;
}
//定义注册回调函数
void PrintfAllParam(stDataInfo *data)
{
registHeightCallback(onHeight, data, this);
}
};
int main(int argc, char* argv[])
{
stDataInfo data = {1.2, 5.6, 3.4};
Sensor sens;
sens.PrintfAllParam(&data);
data.length = 7.8;
data.height = 7.8;
data.width = 7.8;
sens.PrintfAllParam(&data);
return 0;
}
程序callbackFunc_4.cpp运行的结果:
rectangle length= 5.6, width=3.4, height = 1.2
rectangle length= 7.8, width=7.8, height = 7.8
静态成员函数不使用this指针作为隐含参数,这样就可以作为回调函数了。静态成员函数具有两大特点:其一,可以在没有类实例的情况下使用;其二,只能访问静态成员变量和静态成员函数,不能访问非静态成员变量和非静态成员函数。由于在C++中使用类成员函数作为回调函数的目的就是为了访问所有的成员变量和成员函数,如果做不到这一点将不具有实际意义。解决的办法也很简单,就是通过void *contex传一个类指针参数到静态成员函数中,通过这个类指针,如Sensor *sen=(Sensor *)contex,然后在回调函数中通过该指针就可以访问所有成员变量和成员函数了
请见callbackFunc_5.cpp例程所示:
//callbackFunc_5.cpp
typedef struct _DataInfo_{
double height;
double length;
double width;
}stDataInfo;
//CallbackFun类型
//@CallbackFun 指向函数的指针类型
//@data 回调参数,当有多个参数时,可以定义一个结构体
//@contex 回调上下文,在C中一般传入nullptr,在C++中可传入对象指针
typedef void (*CallbackFun)(stDataInfo *data, void* contex);
//注册库回调函数接口
void registHeightCallback(CallbackFun callback, stDataInfo *data, void* contex)
{
if (NULL == callback || NULL == data )
{
return;
}
callback(data, contex);
}
//接收数据类class Sensor
class Sensor{
public:
Sensor(){}
~Sensor(){}
//定义回调函数onHeight
static void onHeight(stDataInfo *data, void* contex)
{
Sensor* sen = (Sensor*)contex;
if(sen)
{
sen->getHeight(data);
}
}
//定义注册回调函数
void PrintfAllParam(stDataInfo *data)
{
registHeightCallback(onHeight, data, this);
}
//新增的成员函数
void getHeight(stDataInfo *data)
{
cout << "rectangle height= " << data->height << endl;
}
};
int main(int argc, char* argv[])
{
stDataInfo data = {1.2, 5.6, 3.4};
Sensor sens;
sens.PrintfAllParam(&data);
data.length = 7.8;
data.height = 7.8;
data.width = 7.8;
sens.PrintfAllParam(&data);
return 0;
}
程序callbackFunc_5.cpp运行的结果是:
rectangle height= 1.2
rectangle height= 7.8
如此修改之后,得到与修改前一样的效果(实时打印height),关键点在于注册回调函数的时候将Sensor对象的指针传给了contex,在回调函数中又将contex转换为Sensor对象指针,所以能调用普通函数。
同理,如果注册时传入某一个对象的指针,就可以在回调函数中对该对象进行操作,这就是我们可以在一个对象中回调另一个对象的思想。使用方法也跟上述的方法想类似。
方法三:使用友元操作符(friend)
在C++中,类的友元函数是定义在类的外部,但有权访问类的所有私有(private)成员和保护(protected)成员的函数。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是类的成员函数。友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字friend。
友元函数和类的成员函数的区别:
1.成员函数有this指针,而友元函数没有this指针。
2.友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友
//callbackFunc_6.cpp
#include <iostream>
#include <string>
#include <stdio.h>
typedef struct _DataInfo_{
double height;
double length;
double width;
}stDataInfo;
//CallbackFun类型
//@CallbackFun 指向函数的指针类型
//@height 回调参数,当有多个参数时,可以定义一个结构体
//@contex 回调上下文,在C中一般传入nullptr,在C++中可传入对象指针
typedef void (*getHeightFunc)(stDataInfo &data);
typedef void (*CallbackFun)(stDataInfo &data, getHeightFunc func, void* contex);
void onSideLength(stDataInfo &data, getHeightFunc func, void* contex);
void getHeight(stDataInfo &data);
//接收数据类class Sensor
class Sensor{
public:
Sensor()
{
memset(&data, 0x00, sizeof(stDataInfo));
}
~Sensor(){}
friend void onSideLength(stDataInfo &data, getHeightFunc func, void* contex)
{
cout << "rectangle length1= " << data.length << ", width1=" <<
data.width << ", height1 = " << data.height <<endl;
data.height = 2*data.height;
func(data);
}
//新增的成员函数
friend void getHeight(stDataInfo &data)
{
cout << "rectangle height2= " << data.height << endl;
}
//注册库回调函数接口
void registHeightCallback(CallbackFun callback, stDataInfo &data, getHeightFunc func,void* contex)
{
if (NULL == callback || NULL == func)
{
return;
}
callback(data, func, contex);
}
//定义注册回调函数
void PrintfAllParam(void)
{
registHeightCallback(onSideLength, data, getHeight, (void *) this);
}
//定义注册回调函数
void SetAllSideLengthParam(double len, double width, double height)
{
data.length = len;
data.height = width;
data.width = height;
}
private:
stDataInfo data;
};
//定义回调函数onSideLength
void onSideLength(stDataInfo *data, void* contex)
{
cout << "rectangle side length= " << data->length << ", width=" <<
data->width << ", height = " << data->height <<endl;
}
//main 函数
int main(int argc, char* argv[])
{
Sensor sens;
sens.SetAllSideLengthParam(1.2, 2.3, 3.4);
sens.PrintfAllParam();
sens.SetAllSideLengthParam(7.7, 7.8, 7.9);
sens.PrintfAllParam();
return 0;
}
程序callbackFunc_6.cpp运行的结果:
rectangle length1= 1.2, width1=3.4, height1 = 2.3
rectangle height2= 4.6
rectangle length1= 7.7, width1=7.9, height1 = 7.8
rectangle height2= 15.6
上述程序中定义了连个函数指针类型如下:
typedef void (*getHeightFunc)(stDataInfo &data);
typedef void (*CallbackFun)(stDataInfo &data, getHeightFunc func, void* contex);
同时又定义了两个友元函数如下:
void onSideLength(stDataInfo &data, getHeightFunc func, void* contex);
void getHeight(stDataInfo &data);
通过观察可以发现回调函数CallBackFun的形参中又会回调一个回调函数。实现了多层的外部函数的调用。
同时还通过友元函数来修改了一个私有类成员变量的数据内容。具体实现如下所示:
friend void onSideLength(stDataInfo &data, getHeightFunc func, void* contex)
{
cout << "rectangle length1= " << data.length << ", width1=" <<
data.width << ", height1 = " << data.height <<endl;
data.height = 2*data.height;
func(data);
}
最后在通过一个回调函数的指针func来调用外部函数 getHeight 来实现修改后的私有类成员变量的数据内容展示。