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