溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C++領(lǐng)域回調(diào)函數(shù)總結(jié)<一> ---- 常見使用

發(fā)布時間:2020-07-23 06:27:42 來源:網(wǎng)絡(luò) 閱讀:1513 作者:844133395 欄目:編程語言

簡介:

    回調(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 );

  1. 第一個參數(shù)為指向線程標(biāo)識符的指針。

  2. 第二個參數(shù)用來設(shè)置線程屬性。

  3. 第三個參數(shù)是線程運(yùn)行函數(shù)的起始地址,即回調(diào)函數(shù)。

  4. 最后一個參數(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

向AI問一下細(xì)節(jié)

免責(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)容。

AI