想要理解回调机制,先要理解函数指针
函数指针
函数指针指向的是函数而非对象,和其他指针一样,函数指针指向某种特定的类型
函数的类型由他的返回类型和参数类型共同决定,与函数名无关,如:
bool lengthCompare(const string &, const string &);
该函数的类型是bool (const string &, const string &)
,要声明一个可以指向该函数的指针,只需要用指针替换函数名:
//pf指向一个函数,该函数的参数是两个const string的引用,返回值是bool类型
bool (*pf)(const string &, const string &);
pf是指针,右侧是形参列表,表示pf指向的是函数;左侧返回类型是布尔值,因此pf就是一个指向函数的指针,其中函数的参数是两个const string的引用,返回值是bool类型
(*pf) 少了括号,pf就是一个 返回值为bool指针的函数
使用函数指针
当我们把函数名作为一个值使用时,该函数自动转换成指针:
bool lengthCompare(const string &, const string &);
bool (*pf)(const string &, const string &);
pf = lengthCompare; //pf指向名为lengthCompare的函数
pf = &lengthCompare; //等价的赋值语句,&是可选的
直接使用指向函数的指针调用该函数:
bool b1 = pr("hello","goodbye"); //调用lengthCompare函数
bool b2 = (*pf)("hello","goodbye"); //等价调用
bool b3 = lengthCompare("hello","goodbye"); //等价调用
函数的类型由他的返回类型和参数类型共同决定,指向不同函数类型的指针间不存在转换规则,但可以为指针赋一个nullptr或者值为0的整形常量表达式,表示指针没有指向任何一个函数:
bool (*pf)(const string &, const string &);
string::size_type sumLength(const string&, const string&);
bool cstringCompare(const char*, const char*);
bool lengthCompare(const string &, const string &);
pf = 0; //正确:pf不指向任何函数
pf = sumLength; //错误:返回类型不匹配
pf = cstringCompare; //错误:形参类型不匹配
pf = lengthCompare; //正确:函数和指针的类型精确匹配
重载函数指针
当我们使用重载函数时,上下文必须清晰地界定到底该选用哪个函数,如果定义了指向重载函数的指针:
void ff(int*);
void ff(unsigned int);
void (*pf1)(unsigned int) = ff; //pf1指向ff(unsigned int);
编译器通过指针类型决定选用哪个函数,指针类型必须与重载函数其中之一精确匹配
void (*pf2)(int) = ff; //错误:没有任何一个ff与该形参列表匹配
double (*pf3)(int*) = ff; //错误:ff和pf3的返回类型不匹配
函数指针形参
和数组类似,不能定一函数类型的形参,但形参可以是指向函数的指针
此时形参看起来像函数类型,实际上被当成指针使用:
//第三个形参是函数类型,会自动转换成指向函数的指针
void useBigger(const string &s1, const string &s2,
bool pf(const string &, const string &));//看起来像函数类型,实际上被当成指针使用
//等价的声明:显示地将形参定义成指向函数的指针
void useBigger(const string &s1, const string &s2,
bool (*pf)(const string &, const string &));
我们也可以直接把函数作为实参使用,此时它会自动转换成指针:
//自动将函数lengthCompare转换成指向该函数的指针
useBigger(s1,s2,lengthCompare);
typedef和decltype
简化使用了函数指针的代码
//Func和Func2是函数类型
typedef bool Func(const string&, const string&);
typedef decltype(lengthCompare) Func2; //等价的类型
//FuncP和FuncP2是指向函数的指针
typedef bool (*FuncP)(const string&, const string&);
typedef decltype(lengthCompare) *FuncP2; //等价的类型
我们使用typedef定义自己的类型,Func和Func2是函数类型,而FuncP和FuncP2是指针类型
decltype返回函数类型,此时不会将函数类型自动转换成指针类型,因为decltype的结果是函数类型,所以只有在结果前面加上 * 才能得到指针,可以使用如下形式重新声明useBigger:
// useBigger的等价声明,其中使用了类型别名
void useBigger(const string&, const string&, Func);//自动将Func表示的函数类型转换成指针
void useBigger(const string&, const string&, FuncP2);
返回指向函数的指针
和数组类似,虽然不能返回一个函数,但是能返回指向函数类型的指针,我们也必须把返回类型写成指针形式,因为编译器不会自动地将函数返回类型当成对应的指针类型处理:
using F = int(int*, int); //F是函数类型,不是指针
using PF = int(*)(int*, int); //PF是指针类型
我们使用类型别名将F定义成函数类型,将PF定义成指向函数类型的指针
和函数类型的形参不一样,但会类型不会自动地转换成指针,必须显示地将返回类型定为指针
PF f1(int); //正确:PF是指向函数的指针,f1返回指向函数的指针
F f1(int); //错误:F是函数类型,f1不能返回一个函数
F *f1(int); //正确:显示地制定返回类型是指向函数的指针
我们也能用如下形式直接声明f1:
int (*f1(int)(int, int));
按照由内向外地顺序阅读:f1有形参列表,所以f1是个函数;f1前有*,所以f1返回一个指针;进一步观察发现,指针的类型本身也包含形参列表,因此指针指向函数,该函数返回的类型是int
回调函数
Callback方式(C的回调函数)
Callback的本质是设置一个函数指针进去,然后在需要需要触发某个事件时调用该方法,比如Windows的窗口消息处理函数就是这种类型
//callbackTest.c
//1.定义函数onHeight(回调函数)
//@onHeight 函数名
//@height 参数
//@contex 上下文
void onHeight(double height, void* contex)
{
sprint("current height is %lf",height);
}
//2.定义onHeight函数的原型
//@CallbackFun 指向函数的指针类型
//@height 回调参数,当有多个参数时,可以定义一个结构体
//@contex 回调上下文,在C中一般传入nullptr,在C++中可传入对象指针
typedef void (*CallbackFun)(double height, void* contex);
//3.定义注册回调函数
//@registHeightCallback 注册函数名
//@callback 回调函数原型
//@contex 回调上下文
void registHeightCallback(CallbackFun callback, void* contex)
{
double h=100;
callback(h,nullptr);
}
//4.main函数
void main()
{
//注册onHeight函数,即通过registHeightCallback的参数将onHeight函数指针
//传入给registHeightCallback函数,在registHeightCallback函数中调用
//callback就相当于调用onHeight函数。
registHeightCallback(onHeight,nullptr);
}
我们就可以用来实现一个小需求:Download完成时需要触发一个通知外面的事件:
//Callback方式
typedef void (__stdcall *DownloadCallback)(const char *pURL,bool OK);
void DownLoadFile(const char *pURL,DownloadCallback callback)
{
std::cout<<"downloading..."<<pURL<<""<<std::endl;
callback(pURL,true);
}
void __stdcall onDownloadFinished(const char* pURL,bool bOK)
{
std::cout<<"onDownloadFinished..."<<pURL<<" status:"<<bOK<<std::endl;
}
void main()
{
DownLoadFile("http://wwww.baidu.com",onDownloadFinished);
system("pause");
}
Sink方式
Sink的本质是按对方的需求实现一个C++接口,然后把实现的接口设置给对方,对方需要触发事件时调用该接口, COM中连接点就是基于这种方式,上面下载文件的需求,如果用Sink实现,代码如下:
//sink方式
class IDownloadSink
{
public:
virtual void OnDownloadFinished(const char *pURL,bool bOK) = 0;
};
class CMyDownloader
{
public:
CMyDownloader (IDownloadSink *pSink)
:m_pSink(pSink)
{
}
void DownloadFile(const char* pURL)
{
std::cout<<"downloading..."<<pURL<<""<<std::endl;
if(m_pSink!=NULL)
{
m_pSink->OnDownloadFinished(pURL,true);
}
}
private:
IDownloadSink *m_pSink;
};
class CMyFile:public IDownloadSink
{
public:
void download()
{
CMyDownloader downloader(this);
downloader.DownloadFile("www.baidu.com");
}
virtual void OnDownloadFinished(const char *pURL,bool bOK)
{
std::cout<<"onDownloadFinished..."<<pURL<<" status:"<<bOK<<std::endl;
}
};
void main()
{
CMyFile *file = new CMyFile();
file->download();
system("pause");
}
Delegate方式
Delegate的本质是设置成员函数指针给对方,然后让对方在需要触发事件时调用,C#中用Delegate的方式实现Event,C++中因为语言本身的关系,要实现Delegate还是很麻烦的,我不会写,鸽了