C/C++回調函數
首先看一下回調函數的官方解釋:回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應。這段解釋比較官方。個人可以簡單的理解為:**一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應。**如果代碼立即被執行就稱為同步回調,如果過后再執行,則稱之為異步回調。
入門案例
int Callback_1(int a) ///< 回調函數1
{
printf("Hello, this is Callback_1: a = %d ", a);
return 0;
}
int Callback_2(int b) ///< 回調函數2
{
printf("Hello, this is Callback_2: b = %d ", b);
return 0;
}
int Callback_3(int c) ///< 回調函數3
{
printf("Hello, this is Callback_3: c = %d ", c);
return 0;
}
int Handle(int x, int (*Callback)(int)) ///< 注意這里用到的函數指針定義
{
Callback(x);
}
int main()
{
Handle(4, Callback_1);
Handle(5, Callback_2);
Handle(6, Callback_3);
return 0;
}
在這個入門案例中,Callback_1、2、3就是回調函數,handle函數的第二個參數就是函數指針,也就是通過函數指針來調用。純C語言通過函數指針來進行回調函數的調用,C++則可以通過引用、Lambda等多種方式來進行,下面進行具體的介紹。
函數指針
首先函數指針也是一種指針,只不過指向的是函數(C語言中沒有對象)。然后通過這個指針就可以調用。
int Func(int x); /*聲明一個函數*/
int (*p) (int x); /*定義一個函數指針*/
p = Func; /*將Func函數的首地址賦給指針變量p*/
p = &Func; /*將Func函數的首地址賦給指針變量p*/
經過上述后,指針變量 p 就指向函數 Func() 代碼的首地址了。下面看一個具體的例子。
int Max(int x, int y) //定義Max函數
{
if (x > y){
return x;
}else{
return y;
}
}
int main()
{
int(*p)(int, int); //定義一個函數指針
p = Max; //把函數Max賦給指針變量p, 使p指向Max函數
int c= (*p)(1,2);//通過函數指針調用Max函數
printf("%d",c);
return 0;
}
p指向Max函數之后,然后用p調用Max函數,返回兩個數中的最大值。特別注意的是,因為函數名本身就可以表示該函數地址(指針),因此在獲取函數指針時,可以直接用函數名,也可以取函數的地址。
p = Max可以改成 p = &Max;
c = (*p)(a, b) 可以改成 c = p(a, b)
所以函數指針的通常寫法是
函數返回值類型 (* 指針變量名) (函數參數列表);
在這里指針變量名也可以叫做函數名,
但是通常可以用typedef進行描述
typedef 函數返回值類型 (* 指針變量名) (函數參數列表);
最后需要注意的是,指向函數的指針變量沒有 ++ 和 -- 運算。
C++類的靜態函數作為回調函數
前面函數指針的方式作為回調函數的一種方式,可以同時用于C和C++,下面介紹另外的一些方式,因為C++引入了對象的概念,可以使用類的成員和靜態函數作為回調函數。
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中調用類A中的靜態函數作為回調函數,從而實現了回調。但這種實現有一個很明顯的缺點:static 函數不能訪問非static 成員變量或函數,會嚴重限制回調函數可以實現的功能。
類的非靜態函數作為回調函數
這種方式比較麻煩,可以先看一下下面的例子。
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); // 此處都要加&
}
功能總體與上面一個相同,但是,類的靜態函數本身不屬于該類,所以和普通函數作為回調函數類似。這種方式存在一些不足,,也就我預先還要知道回調函數所屬的類定義,當ProgramB想獨立封裝時就不好用了。(違背了一些設計模式的原則)
Lambda表達式作為回調函數
Lambda本身就是一種匿名函數,是一種函數的簡寫形式(此處參考上一篇博客Lambda表達式)
#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表達式,在一些較為老舊的編譯器上可能無法通過。
std::funtion和std::bind的使用
這種方式也是適用于C++,要引入functional的頭文件。存儲、復制、和調用操作,這些目標實體包括普通函數、Lambda表達式、函數指針、以及其它函數對象等。std::bind()函數的意義就像它的函數名一樣,是用來綁定函數調用的某些參數的。
#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函數綁定了對象與對應的函數,這種方式比上面的通過類的成員函數進行回調更為簡單方便。下面看一下如果有參數的話,需要引入占位符std::placeholders::_1來進行回調。
#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,可以有多個,通過下劃線加數字來實現,從而實現有參數的回調。這個bind函數中的重載通常第一個是函數的指針,第二個是調用對象的指針,后面跟上參數占位符。
審核編輯:湯梓紅
-
函數
+關注
關注
3文章
4327瀏覽量
62571 -
指針
+關注
關注
1文章
480瀏覽量
70551 -
C++
+關注
關注
22文章
2108瀏覽量
73619 -
回調函數
+關注
關注
0文章
87瀏覽量
11554
發布評論請先 登錄
相關推薦
評論