您好,登錄后才能下訂單哦!
簡介:
回調(diào)函數(shù)是基于C編程的Windows SDK的技術(shù),不是針對C++的,程序員可以將一個C函數(shù)直接作為回調(diào)函數(shù),但是如果試圖直接使用C++的成員函數(shù)作為回調(diào)函數(shù)將發(fā)生錯誤,甚至編譯就不能通過。普通的C++成員函數(shù)都隱含了一個傳遞函數(shù)作為參數(shù),亦即“this”指針,C++通過傳遞一個指向自身的指針給其成員函數(shù)從而實(shí)現(xiàn)程序函數(shù)可以訪問C++的數(shù)據(jù)成員。這也可以理解為什么C++類的多個實(shí)例可以共享成員函數(shù)但是確有不同的數(shù)據(jù)成員。由于this指針的作用,使得將一個CALLBACK型的成員函數(shù)作為回調(diào)函數(shù)安裝時就會因?yàn)殡[含的this指針使得函數(shù)參數(shù)個數(shù)不匹配,從而導(dǎo)致回調(diào)函數(shù)安裝失敗。這樣從理論上講,C++類的成員函數(shù)是不能當(dāng)作回調(diào)函數(shù)的。但我們在用C++編程時總希望在類內(nèi)實(shí)現(xiàn)其功能,即要保持封裝性,如果把回調(diào)函數(shù)寫作普通函數(shù)有諸多不便。經(jīng)過網(wǎng)上搜索和自己研究,發(fā)現(xiàn)了幾種巧妙的方法,可以使得類成員函數(shù)當(dāng)作回調(diào)函數(shù)使用。
使用場景:
回調(diào)函數(shù)是不能顯式調(diào)用的函數(shù);通過將回調(diào)函數(shù)的地址傳給調(diào)用者從而實(shí)現(xiàn)調(diào)用?;卣{(diào)函數(shù)使用是必要的,在我們想通過一個統(tǒng)一接口實(shí)現(xiàn)不同的內(nèi)容,這時用回掉函數(shù)非常合適。比如,我們?yōu)閹讉€不同的設(shè)備分別寫了不同的顯示函數(shù):void TVshow(); void ComputerShow(); void NoteBookShow()...等等。這是我們想用一個統(tǒng)一的顯示函數(shù),我們這時就可以用回掉函數(shù)了。
void show(void (*ptr)());
使用時根據(jù)所傳入的參數(shù)不同而調(diào)用不同的回調(diào)函數(shù)。不同的編程語言可能有不同的語法,下面舉一個c語言中回調(diào)函數(shù)的例子,其中一個回調(diào)函數(shù)不帶參數(shù),另一個回調(diào)函數(shù)帶參數(shù)。如例:
//Test.h
#include <stdlib.h>
#include <stdio.h>
int Test1()
{
for (int i=0; i<30; i++) {
printf("The %d th charactor is: %c/n", i, (char)('a' + i%26));
}
return 0;
}
int Test2(int num)
{
for (int i=0; i<num; i++) {
printf("The %d th charactor is: %c/n", i, (char)('a' + i%26));
}
return 0;
}
void Caller1(int (*ptr)())//指向函數(shù)的指針作函數(shù)參數(shù)
{
(*ptr)();
}
void Caller2(int n, int (*ptr)(int n))//指向函數(shù)的指針作函數(shù)參數(shù),
//這里第一個參數(shù)是為指向函數(shù)的指針服務(wù)的,
{
(*ptr)(n);
}
int main( int argc, char *argv[], char *envp[] )
{
printf("************************/n");
Caller1(Test1); //相當(dāng)于調(diào)用Test1();
printf("&&&&&&************************/n");
Caller2(30, Test2); //相當(dāng)于調(diào)用Test2(30);
return 0;
}
以上通過將回調(diào)函數(shù)的地址傳給調(diào)用者從而實(shí)現(xiàn)調(diào)用,但是需要注意的是帶參回調(diào)函數(shù)的用法。要實(shí)現(xiàn)回調(diào),必須首先定義函數(shù)指針。函數(shù)指針的定義這里稍微提一下。比如: int (*ptr)(int n); 這里ptr是一個函數(shù)指針,其中(*ptr)的括號不能省略,因?yàn)槔ㄌ柕膬?yōu)先級高于星號,那樣就成了一個返回類型為整型的函數(shù)聲明了。
本段參考:http://zq2007.blog.hexun.com/9068988_d.html
這里采用Linux C++中線程創(chuàng)建函數(shù)pthread_create舉例,其原型如下:
int pthread_create( pthread_t *restrict tidp , const pthread_attr_t *restrict attr , void* (*start_rtn)(void*) , void *restrict arg );
第一個參數(shù)為指向線程標(biāo)識符的指針。
第二個參數(shù)用來設(shè)置線程屬性。
第三個參數(shù)是線程運(yùn)行函數(shù)的起始地址,即回調(diào)函數(shù)。
最后一個參數(shù)是運(yùn)行函數(shù)的參數(shù)。
這里我們只關(guān)注第三個參數(shù)start_run,它是一個函數(shù)指針,指向一個以void*為參數(shù),返回值為void*的函數(shù),這個函數(shù)被當(dāng)作線程的回調(diào)函數(shù)使用,線程啟動后便會執(zhí)行該函數(shù)的代碼。
方法一:回調(diào)函數(shù)為普通函數(shù),但在函數(shù)體內(nèi)執(zhí)行成員函數(shù)
class MyClass
{
pthread_t TID;
public:
void func()
{
//子線程執(zhí)行代碼
}
bool startThread()
{//啟動子線程
int ret = pthread_create( &TID, NULL, callback, this );
if( ret != 0 ) return false;
else return true;
}
};
static void* callback( void* arg )
{//回調(diào)函數(shù)
((MyClass*)arg)->func();調(diào)用成員函數(shù)
return NULL;
}
int main()
{
MyClass a;
a.startThread();
}
類MyClass需要在自己內(nèi)部開辟一個子線程來執(zhí)行成員函數(shù)func()中的代碼,子線程通過調(diào)用startThread()成員函數(shù)來啟動。這里將回調(diào)函數(shù)callback寫在了類外面,傳遞的參數(shù)是一個指向MyClass對象的指針(在pthrad_create()中由第4個參數(shù)this指定),回調(diào)函數(shù)經(jīng)過強(qiáng)制轉(zhuǎn)換把void*變?yōu)镸yClass*,然后再調(diào)用arg->func()執(zhí)行子線程的代碼。這樣做的原理是把當(dāng)前對象的指針當(dāng)作參數(shù)先交給一個外部函數(shù),再由外部函數(shù)調(diào)用類成員函數(shù),以外部函數(shù)作為回調(diào)函數(shù),但執(zhí)行的是成員函數(shù)的功能,這樣相當(dāng)于在中間作了一層轉(zhuǎn)換。缺點(diǎn)是回調(diào)函數(shù)在類外,影響了封裝性,這里把callback()限定為static,防止在其它文件中調(diào)用此函數(shù)。
方法二:回調(diào)函數(shù)為類內(nèi)靜態(tài)成員函數(shù),在其內(nèi)部調(diào)用成員函數(shù)
在方法一上稍作更改,把回調(diào)函數(shù)搬到類MyClass里,這樣就保持了封裝性。代碼如下:
class MyClass
{
static MyClass* CurMy;//存儲回調(diào)函數(shù)調(diào)用的對象
static void* callback(void*);//回調(diào)函數(shù)
pthread_t TID;
void func()
{
//子線程執(zhí)行代碼
}
void setCurMy()
{//設(shè)置當(dāng)前對象為回調(diào)函數(shù)調(diào)用的對象
CurMy = this;
}
public:
bool startThread()
{//啟動子線程
setCurMy();
int ret = pthread_create(&TID, NULL, MyClass::callback, NULL);
if( ret != 0 ) return false;
else return true;
}
};
MyClass* MyClass::CurMy = NULL;
void* MyClass::callback(void*)
{
CurMy->func();
return NULL;
}
int main()
{
MyClass a;
a.startThread();
}
類MyClass有了1個靜態(tài)數(shù)據(jù)成員CurMy和1個靜態(tài)成員函數(shù)callback。CurMy用來存儲一個對象的指針,充當(dāng)方法一中回調(diào)函數(shù)的參數(shù)arg。callback當(dāng)作回調(diào)函數(shù),執(zhí)行CurMy->func()的代碼。每次建立線程前先要調(diào)用setCurMy()來讓CurMy指向當(dāng)前自己。這個方法的好處時封裝性得到了很好的保護(hù),MyClass對外只公開一個接口startThread(),子線程代碼和回調(diào)函數(shù)都被設(shè)為私有,外界不可見。另外沒有占用callback的參數(shù),可以從外界傳遞參數(shù)進(jìn)來。但每個對象啟動子線程前一定要注意先調(diào)用setCurMy()讓CurMy正確的指向自身,否則將為其它對象開啟線程,這樣很引發(fā)很嚴(yán)重的后果。
方法三:對成員函數(shù)進(jìn)行強(qiáng)制轉(zhuǎn)換,當(dāng)作回調(diào)函數(shù)
class MyClass
{
pthread_t TID;
void func()
{
//子線程執(zhí)行代碼
}
public:
bool startThread()
{//啟動子線程
typedef void* (*FUNC)(void*);//定義FUNC類型是一個指向函數(shù)的指針,
//該函數(shù)參數(shù)為void*,返回值為void*
FUNC callback = (FUNC)&MyClass::func;//強(qiáng)制轉(zhuǎn)換func()的類型
int ret = pthread_create( &TID , NULL , callback , this );
if( ret != 0 ) return false;
else return true;
}
};
int main()
{
MyClass a;
a.startThread();
}
這個方法是原理是,MyClass::func最終會轉(zhuǎn)化成 void func(MyClass *this); 也就是說在原第一個參數(shù)前插入指向?qū)ο蟊旧淼膖his指針。可以利用這個特性寫一個非靜態(tài)類成員方法來直接作為線程回調(diào)函數(shù)。對編譯器而言,void (MyClass::*FUNC1)()和void* (*FUNC)(void*)這兩種函數(shù)指針雖然看上去很不一樣,但他們的最終形式是相同的,因此就可以把成員函數(shù)指針強(qiáng)制轉(zhuǎn)換成普通函數(shù)的指針來當(dāng)作回調(diào)函數(shù)。在建立線程時要把當(dāng)前對象的指針this當(dāng)作參數(shù)傳給回調(diào)函數(shù)(成員函數(shù)func),這樣才能知道線程是針對哪個對象建立的。方法三的封裝性比方法二更好,因?yàn)椴簧婕岸鄠€對象共用一個靜態(tài)成員的問題,每個對象可以獨(dú)立地啟動自己的線程而不影響其它對象。
注:與tr1::function對象結(jié)合使用,能獲得更好的效果,詳情見http://blog.csdn.net/this_capslock/article/details/38564719
本段參考:http://blog.csdn.net/this_capslock/article/details/1700100
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。