C/C++回調(diào)函數(shù)
首先看一下回調(diào)函數(shù)的官方解釋:回調(diào)函數(shù)就是一個通過函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),當(dāng)這個指針被用來調(diào)用其所指向的函數(shù)時,我們就說這是回調(diào)函數(shù)?;卣{(diào)函數(shù)不是由該函數(shù)的實(shí)現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時由另外的一方調(diào)用的,用于對該事件或條件進(jìn)行響應(yīng)。這段解釋比較官方。個人可以簡單的理解為:**一個通過函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),回調(diào)函數(shù)不是由該函數(shù)的實(shí)現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時由另外的一方調(diào)用的,用于對該事件或條件進(jìn)行響應(yīng)。**如果代碼立即被執(zhí)行就稱為同步回調(diào),如果過后再執(zhí)行,則稱之為異步回調(diào)。
入門案例
int Callback_1(int a) ///< 回調(diào)函數(shù)1
{
printf("Hello, this is Callback_1: a = %d ", a);
return 0;
}
int Callback_2(int b) ///< 回調(diào)函數(shù)2
{
printf("Hello, this is Callback_2: b = %d ", b);
return 0;
}
int Callback_3(int c) ///< 回調(diào)函數(shù)3
{
printf("Hello, this is Callback_3: c = %d ", c);
return 0;
}
int Handle(int x, int (*Callback)(int)) ///< 注意這里用到的函數(shù)指針定義
{
Callback(x);
}
int main()
{
Handle(4, Callback_1);
Handle(5, Callback_2);
Handle(6, Callback_3);
return 0;
}
在這個入門案例中,Callback_1、2、3就是回調(diào)函數(shù),handle函數(shù)的第二個參數(shù)就是函數(shù)指針,也就是通過函數(shù)指針來調(diào)用。純C語言通過函數(shù)指針來進(jìn)行回調(diào)函數(shù)的調(diào)用,C++則可以通過引用、Lambda等多種方式來進(jìn)行,下面進(jìn)行具體的介紹。
函數(shù)指針
首先函數(shù)指針也是一種指針,只不過指向的是函數(shù)(C語言中沒有對象)。然后通過這個指針就可以調(diào)用。
int Func(int x); /*聲明一個函數(shù)*/
int (*p) (int x); /*定義一個函數(shù)指針*/
p = Func; /*將Func函數(shù)的首地址賦給指針變量p*/
p = &Func; /*將Func函數(shù)的首地址賦給指針變量p*/
經(jīng)過上述后,指針變量 p 就指向函數(shù) Func() 代碼的首地址了。下面看一個具體的例子。
int Max(int x, int y) //定義Max函數(shù)
{
if (x > y){
return x;
}else{
return y;
}
}
int main()
{
int(*p)(int, int); //定義一個函數(shù)指針
p = Max; //把函數(shù)Max賦給指針變量p, 使p指向Max函數(shù)
int c= (*p)(1,2);//通過函數(shù)指針調(diào)用Max函數(shù)
printf("%d",c);
return 0;
}
p指向Max函數(shù)之后,然后用p調(diào)用Max函數(shù),返回兩個數(shù)中的最大值。特別注意的是,因?yàn)楹瘮?shù)名本身就可以表示該函數(shù)地址(指針),因此在獲取函數(shù)指針時,可以直接用函數(shù)名,也可以取函數(shù)的地址。
p = Max可以改成 p = &Max;
c = (*p)(a, b) 可以改成 c = p(a, b)
所以函數(shù)指針的通常寫法是
函數(shù)返回值類型 (* 指針變量名) (函數(shù)參數(shù)列表);
在這里指針變量名也可以叫做函數(shù)名,
但是通??梢杂胻ypedef進(jìn)行描述
typedef 函數(shù)返回值類型 (* 指針變量名) (函數(shù)參數(shù)列表);
最后需要注意的是,指向函數(shù)的指針變量沒有 ++ 和 -- 運(yùn)算。
C++類的靜態(tài)函數(shù)作為回調(diào)函數(shù)
前面函數(shù)指針的方式作為回調(diào)函數(shù)的一種方式,可以同時用于C和C++,下面介紹另外的一些方式,因?yàn)镃++引入了對象的概念,可以使用類的成員和靜態(tài)函數(shù)作為回調(diào)函數(shù)。
class ProgramA {
public:
void FunA1() { printf("I'am ProgramA.FunA1() and be called..\\n"); }
static void FunA2() { printf("I'am ProgramA.FunA2() and be called..\\n"); }
};
class ProgramB {
public:
void FunB1(void (*callback)()) {
printf("I'am ProgramB.FunB1() and be called..\\n");
callback();
}
};
int main(int argc, char **argv) {
ProgramA PA;
PA.FunA1();
ProgramB PB;
PB.FunB1(ProgramA::FunA2);
}
在類B中調(diào)用類A中的靜態(tài)函數(shù)作為回調(diào)函數(shù),從而實(shí)現(xiàn)了回調(diào)。但這種實(shí)現(xiàn)有一個很明顯的缺點(diǎn):static 函數(shù)不能訪問非static 成員變量或函數(shù),會嚴(yán)重限制回調(diào)函數(shù)可以實(shí)現(xiàn)的功能。
類的非靜態(tài)函數(shù)作為回調(diào)函數(shù)
這種方式比較麻煩,可以先看一下下面的例子。
class ProgramA {
public:
void FunA1() { printf("I'am ProgramA.FunA1() and be called..\\n"); }
void FunA2() { printf("I'am ProgramA.FunA2() and be called..\\n"); }
};
class ProgramB {
public:
void FunB1(void (ProgramA::*callback)(), void *context) {
printf("I'am ProgramB.FunB1() and be called..\\n");
((ProgramA *)context->*callback)();
}
};
int main(int argc, char **argv) {
ProgramA PA;
PA.FunA1();
ProgramB PB;
PB.FunB1(&ProgramA::FunA2, &PA); // 此處都要加&
}
功能總體與上面一個相同,但是,類的靜態(tài)函數(shù)本身不屬于該類,所以和普通函數(shù)作為回調(diào)函數(shù)類似。這種方式存在一些不足,,也就我預(yù)先還要知道回調(diào)函數(shù)所屬的類定義,當(dāng)ProgramB想獨(dú)立封裝時就不好用了。(違背了一些設(shè)計(jì)模式的原則)
Lambda表達(dá)式作為回調(diào)函數(shù)
Lambda本身就是一種匿名函數(shù),是一種函數(shù)的簡寫形式(此處參考上一篇博客Lambda表達(dá)式)
#include
#include
void func1(int a,std::function<void(int)> func2){
func2(a);
}
int main(int argc, char **argv) {
auto fun3 = [](int a){
std::cout<std::endl;
};
func1(3,fun3);
}
這種方式也較為簡單,但要注意在C++11版本才開始引入Lambda表達(dá)式,在一些較為老舊的編譯器上可能無法通過。
std::funtion和std::bind的使用
這種方式也是適用于C++,要引入functional的頭文件。存儲、復(fù)制、和調(diào)用操作,這些目標(biāo)實(shí)體包括普通函數(shù)、Lambda表達(dá)式、函數(shù)指針、以及其它函數(shù)對象等。std::bind()函數(shù)的意義就像它的函數(shù)名一樣,是用來綁定函數(shù)調(diào)用的某些參數(shù)的。
#include
#include // fucntion/bind
class ProgramA {
public:
void FunA1() { printf("I'am ProgramA.FunA1() and be called..\\n"); }
void FunA2() { printf("I'am ProgramA.FunA2() and be called..\\n"); }
static void FunA3() { printf("I'am ProgramA.FunA3() and be called..\\n"); }
};
class ProgramB {
typedef std::function<void ()> CallbackFun;
public:
void FunB1(CallbackFun callback) {
printf("I'am ProgramB.FunB2() and be called..\\n");
callback();
}
};
void normFun() { printf("I'am normFun() and be called..\\n"); }
int main(int argc, char **argv) {
ProgramA PA;
PA.FunA1();
printf("\\n");
ProgramB PB;
PB.FunB1(normFun);
printf("\\n");
PB.FunB1(ProgramA::FunA3);
printf("\\n");
PB.FunB1(std::bind(&ProgramA::FunA2, &PA));
}
主要看最后一行,通過std::bind函數(shù)綁定了對象與對應(yīng)的函數(shù),這種方式比上面的通過類的成員函數(shù)進(jìn)行回調(diào)更為簡單方便。下面看一下如果有參數(shù)的話,需要引入占位符std::placeholders::_1來進(jìn)行回調(diào)。
#include
#include
using namespace std;
int TestFunc(int a, char c, float f)
{
cout << a << endl;
cout << c << endl;
cout << f << endl;
return a;
}
int main()
{
auto bindFunc1 = bind(TestFunc, std::placeholders::_1, 'A', 100.1);
bindFunc1(10);
cout << "=================================\\n";
auto bindFunc2 = bind(TestFunc, std::placeholders::_2, std::placeholders::_1, 100.1);
bindFunc2('B', 10);
cout << "=================================\\n";
auto bindFunc3 = bind(TestFunc, std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);
bindFunc3(100.1, 30, 'C');
return 0;
}
上述例子中引入了占位符std::placeholders::_1,可以有多個,通過下劃線加數(shù)字來實(shí)現(xiàn),從而實(shí)現(xiàn)有參數(shù)的回調(diào)。這個bind函數(shù)中的重載通常第一個是函數(shù)的指針,第二個是調(diào)用對象的指針,后面跟上參數(shù)占位符。
審核編輯:湯梓紅
-
函數(shù)
+關(guān)注
關(guān)注
3文章
4331瀏覽量
62630 -
指針
+關(guān)注
關(guān)注
1文章
480瀏覽量
70564 -
C++
+關(guān)注
關(guān)注
22文章
2108瀏覽量
73657 -
回調(diào)函數(shù)
+關(guān)注
關(guān)注
0文章
87瀏覽量
11563
發(fā)布評論請先 登錄
相關(guān)推薦
評論