從C的回調(diào)函數(shù)說起
在C語言中,回調(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í)說白了就是把一個函數(shù)當(dāng)做參數(shù)傳下去。
C中實(shí)現(xiàn)回調(diào)函數(shù)比較簡單,請看下面這個例子:
#include
typedef void (*p_CB)(int);
void CB(int a) { printf("CB a = %d\\n", a); }
void runCB(p_CB CB, int a) { printf("run CB\\n"); CB(a); }
int main()
{
p_CB cb = &CB;
runCB(cb, 20);
return 0;
}
C++中的回調(diào)函數(shù)的問題
在C++中的一個重要概念就是類,所以我們一般想讓類的成員函數(shù)作為回調(diào)函數(shù)(如果直接用非類的成員函數(shù)作為回調(diào)函數(shù),其實(shí)就和C語言中的方法一樣),但是想實(shí)現(xiàn)這樣的功能,還是存在一些限制的。
主要原因是,類的成員函數(shù)的參數(shù)都隱藏了this指針這個參數(shù),既然多加了一個參數(shù),那么成員函數(shù)的參數(shù)個數(shù)就和定義的回調(diào)函數(shù)個數(shù)不匹配,導(dǎo)致調(diào)用失敗。
舉個例子:
#include
typedef void (*p_CB)(int);
void runCB(p_CB CB, int a) { printf("run CB\\n"); CB(a); }
class B
{
public:
void CB(int a) { printf("CB a = %d\\n", a); }
};
int main()
{
B b;
runCB(B::CB, 20); //錯誤
return 0;
}
我們這樣寫,在15行會報(bào)下面的錯誤。
test_class_cb.cpp: In function ‘int main()’:
test_class_cb.cpp:15:14: error: invalid use of non-static member function ‘void B::CB(int)’
runCB(B::CB, 20); //錯誤
報(bào)的錯說使用了非靜態(tài)的函數(shù),分析下原因,其實(shí)是B::CB的函數(shù)是非靜態(tài)成員函數(shù),它隱藏了this參數(shù),導(dǎo)致p_CB和B::CB這兩個函數(shù)的參數(shù)不匹配。
而編譯器報(bào)的這個錯誤,其實(shí)也是同樣的意思,編譯器說我們調(diào)用的不是非靜態(tài)函數(shù),當(dāng)我們把B::CB定義成靜態(tài)函數(shù)也就不會報(bào)錯了(也就是去掉了this指針)。
C++中的回調(diào)函數(shù)的實(shí)現(xiàn)
靜態(tài)函數(shù)
既然成員函數(shù)都會存在this指針,那么我們可以去掉this指針呢?其實(shí)是可以的,我們把成員函數(shù)定義為靜態(tài)函數(shù),靜態(tài)函數(shù)是屬于整個類的,不屬于某個對象,也就沒有this指針了。
這個例子的話,就是我們在上一個章節(jié)提到的,大家可以直接加上static。
這種方法其實(shí)有一定的缺點(diǎn),靜態(tài)會帶來諸多的不變,因?yàn)殪o態(tài)函數(shù)沒有this指針,那么它就訪問不了普通的成員函數(shù)或者數(shù)據(jù)成員,會給我們在開發(fā)中帶來不小的影響,特別是工程比較大的時候。
成員函數(shù)的函數(shù)指針
這個方法從另外一個角度出發(fā),我們可以定義一個成員函數(shù)的函數(shù)指針,其實(shí)相當(dāng)于這個函數(shù)指針有了this參數(shù)了。
例子如下:
#include
class B
{
public:
void CB(int a) { printf("CB a = %d\\n", a); }
};
typedef void (B::*p_CB)(int);
// using p_CB = void (B::*)(int);
void runCB(p_CB CB, B b, int a) { printf("run CB\\n"); (b.*CB)(a); }
int main()
{
B b;
p_CB p_f1 = &B::CB;
runCB(p_f1, b, 20);
return 0;
}
這種寫法晦澀難懂,特別是(b.*CB)(a)這種寫法一看上去就覺得很奇怪,代碼寫的越生僻,越復(fù)雜,帶來的風(fēng)險(xiǎn)就越大。
另外,如果想要采用這種方法實(shí)現(xiàn)回調(diào),那么在定義回調(diào)函數(shù)的時候就得知道誰會調(diào)用它,顯然,這個要求是不合理的,所以,這種方法很不常用,只是介紹這種方法。
單例模式
這個方法是上面靜態(tài)函數(shù)方法的進(jìn)一步優(yōu)化,因?yàn)殪o態(tài)函數(shù)沒有傳進(jìn)去this指針,那么我們可不可以想辦法得到this指針呢?單例模式就可以做到,一般上單例模式為了讓類只存在一個對象,一般都會在類的內(nèi)部定義一個指向自己的指針,我們就可以通過這個指針,在任何地方都可以訪問到這個對象。
簡單例子:
#include
class B
{
public:
static B *get_instance() {
static B instance;
return &instance;
}
void CB(int a) { printf("CB a = %d\\n", a); }
};
void runCB(int a)
{
printf("run CB\\n");
B *b_ptr = B::get_instance();
b_ptr->CB(a);
}
int main(int argc, char *argv[])
{
B *b_ptr = B::get_instance();
runCB(20);
return 0;
}
使用這種方法,在任何地方都可以訪問該對象,可以很方便得調(diào)用對象的方法,是比較好的一種方法了,需要注意的就是單例模式的一些限制而已。
另外說一下,單例模式只能說是一種可以解決回調(diào)函數(shù)的辦法,不一定是因?yàn)橐卣{(diào),才設(shè)計(jì)成單例。
C++11的function和bind
如果你的編譯器支持C++11標(biāo)準(zhǔn)的話(現(xiàn)在編譯器基本上都會支持),還有一種更加方便快捷的方法。
function模板
首先介紹一下什么叫 可調(diào)用對象 ,簡單來說就是可以使用()來調(diào)用的對象,在C++11中,包括函數(shù)、函數(shù)指針、lambda表達(dá)式、bind創(chuàng)建的對象和重載了()的類。
function是一個模板函數(shù),它的功能和函數(shù)指針很像,但是函數(shù)指針只能針對非成員函數(shù),而function則可以調(diào)用C++中所有的可調(diào)用實(shí)體。
對于成員函數(shù)來說,function不能直接調(diào)用,這就需要通過下面的bind來做“媒介”。
bind函數(shù)
bind函數(shù)簡單介紹,就是可以綁定到任意一個可調(diào)用對象,下面是它的定義。
可以講bind看作一個通用的函數(shù)適配器,他接受一個可調(diào)用對象,生成一個新的可調(diào)用對象來“適應(yīng)”原對象的參數(shù)列表。 ----《c++Primer》
也就是說,通過bind綁定類的非靜態(tài)成員函數(shù),生成一個可調(diào)用對象,然后通過function模板去調(diào)用這個可調(diào)用對象,也就實(shí)現(xiàn)了調(diào)用類的非靜態(tài)成員函數(shù)。
下面通過一個簡單的例子來說明如何使用。
例子
#include
#include
// typedef std::function
using funCB = std::function<void(int)>; //可以這樣寫,更直觀
class B
{
public:
void setCB(funCB CB) { m_CB = CB; }
void runCB(int a) { std::cout << "runCB" << std::endl; m_CB(a); }
void printB(int a) { std::cout << "a = " << a << std::endl; }
funCB m_CB;
};
class C
{
public:
void cbfun(int a) { std::cout << "CB a = "
<< a << std::endl; }
};
int main()
{
B b; C c;
funCB fun = std::bind(&C::cbfun, &c, std::placeholders::_1);
b.setCB(fun);
b.runCB(20);
return 0;
}
在這個例子中,b.runCB(),然后會調(diào)用m_CB函數(shù),而m_CB通過setCB綁定到了C::cbfun,所以C::cbfun也會被調(diào)用。也就是就是通過b的成函數(shù)調(diào)用到了c的成員函數(shù),實(shí)現(xiàn)了回調(diào)。
這種方式是不是看起很簡單呢,
后記
這邊文章介紹了在C++中實(shí)現(xiàn)回調(diào)的幾種方法,總的來說C++11的function&bind是比較好的方法,另外說一點(diǎn),function的bind的功能不止這些,這里介紹的只是他們的一種用法。
審核編輯:劉清
-
C語言
+關(guān)注
關(guān)注
180文章
7614瀏覽量
137801 -
編譯器
+關(guān)注
關(guān)注
1文章
1642瀏覽量
49311 -
回調(diào)函數(shù)
+關(guān)注
關(guān)注
0文章
87瀏覽量
11626
發(fā)布評論請先 登錄
相關(guān)推薦
Android NDK編程--- C/C++調(diào)用Java不同類中的靜態(tài)方法
回調(diào)函數(shù)在程序開發(fā)中有何作用呢
如何在screenViewBase中寫這兩個回調(diào)?
C/C++回調(diào)函數(shù)
VISUAL C++教程之VISUAL C++的安裝和使用方法
![VISUAL <b class='flag-5'>C++</b>教程之VISUAL <b class='flag-5'>C++</b>的安裝和使用<b class='flag-5'>方法</b>](https://file.elecfans.com/web1/M00/7F/3A/o4YBAFwkkXWAI3XbAAMQcR8pLac666.png)
詳解回調(diào)函數(shù)的概念及使用步驟
一文詳解C/C++回調(diào)函數(shù)
javajvm調(diào)優(yōu)有幾種方法
??嵌入式中回調(diào)函數(shù)的實(shí)現(xiàn)方法
回調(diào)函數(shù)(callback)是什么?回調(diào)函數(shù)的實(shí)現(xiàn)方法
C++中實(shí)現(xiàn)類似instanceof的方法
![<b class='flag-5'>C++</b><b class='flag-5'>中</b><b class='flag-5'>實(shí)現(xiàn)</b>類似instanceof的<b class='flag-5'>方法</b>](https://file1.elecfans.com/web2/M00/FE/0C/wKgaomaYe1CAQ31QAAAnf0IkoSU605.png)
評論