真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

c/c++如何回調(diào)函數(shù)

這篇文章將為大家詳細(xì)講解有關(guān)c/c++如何回調(diào)函數(shù),文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

為城步等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計制作服務(wù),及城步網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為網(wǎng)站建設(shè)、網(wǎng)站制作、城步網(wǎng)站設(shè)計,以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!

1:函數(shù)名為指針

首先,在C語言中函數(shù)是一種function-to-pointer的方式,即對于一個函數(shù),會將其自動轉(zhuǎn)換成指針的類型.如:

 1 #include 2  3 void fun() 4 { 5 } 6  7 int main() 8 { 9    printf("%p %p %p\n", &fun, fun, *fun);10    return 0;11 }

這三個值的結(jié)果是一樣的. 其實對于最后的那個*fun, 即使前面加上很多個*號, 其結(jié)果也不變, 即**fun, ***fun的結(jié)果都是一樣的. 對于這個問題, 因為之前講過函數(shù)是一種function-to-pointer方式, 其會自動轉(zhuǎn)換成指針的類型, &fun是該函數(shù)的地址, 為指針類型, fun是一個函數(shù), 會轉(zhuǎn)換成其指針類型, 而對于*fun, 由于fun已經(jīng)變成了指針類型,
指向這個函數(shù), 所以*fun就是取這個地址的函數(shù), 而又根據(jù)function-to-pointer, 該函數(shù)也轉(zhuǎn)變成了一個指針, 所以以此類推, 這三個值的結(jié)果是相同的.

2:回調(diào)函數(shù)

     通過將回調(diào)函數(shù)的地址傳給調(diào)用者從而實現(xiàn)動態(tài)調(diào)用不同的函數(shù)。因此當(dāng)我們想通過一個統(tǒng)一接口實現(xiàn)不同的內(nèi)容,這時用回掉函數(shù)非常合適。

      若要實現(xiàn)回調(diào)函數(shù),最關(guān)鍵的是要把調(diào)用函數(shù)的參數(shù)定義為函數(shù)指針類型。函數(shù)指針的定義這里稍 
微提一下。比如: 
    int (*ptr)(void); 這里ptr是一個函數(shù)指針,其中(*ptr)的括號不能省略,因為括號的優(yōu)先級高于星號,那樣就成了一個返回類型為整型的函數(shù)聲明了。int為返回類型,括號內(nèi)為函數(shù)的參數(shù)。

     下面通過一個例子來解釋回調(diào)函數(shù)的用法:

 1 #include  
 2 #include  
 3 int Test1(int num) 
 4 { 
 5   printf("i am test1,the data is %d \n",num); 6   return 0; 
 7 } 
 8 int Test2(int num) 
 9 { 
10   printf("i am test2,the data is %d\n",num);11   return 0; 
12 } 
13 14 int Caller(int (*ptr)(int n),int n)//指向函數(shù)的指針作函數(shù)參數(shù),這里第二個參數(shù)是函數(shù)指針的參數(shù) 15 {                                               //不能寫成void Caller2(int (*ptr)(int n)),這樣的定義語法錯誤。 16   int a=(*ptr)(n); 
17   return a; 
18 } 
19 int main() 
20 { 
21      22    Caller(Test1,20);  
23    printf("************************\n"); 
24    Caller(Test2,10);25 26   return 0; 
27 }

下面介紹幾種比較容易混淆的指針概念:

1:函數(shù)指針

   1:函數(shù)指針的定義方式:

   返回值類型  (* 指針變量名)(形參列表);

      返回值為指針的函數(shù)定義: 返回指針類型 * 函數(shù)名(形參列表);

  2:函數(shù)指針的賦值:

    在賦值時,可以直接將函數(shù)指針指向函數(shù)名(函數(shù)名即代表該段代碼的首地址),但是前提是:函數(shù)指針和它指向的函數(shù)的參數(shù)個數(shù)以及類型必須一致。函數(shù)指針的返回值類型與函數(shù)的返回值類型必須一致。

   3:通過函數(shù)指針調(diào)用函數(shù):

加上指針f指向函數(shù)func。(*f ) 和 func代表同一函數(shù)。

使用方法如下:

聲明函數(shù)指針:int (*f)(int x);

函數(shù)指針賦值: f=func   ( int func(int x));

函數(shù)指針調(diào)用函數(shù):  (*f)(x)  (x為整型變量)

2:函數(shù)指針數(shù)組

      函數(shù)指針數(shù)組是一個其元素是函數(shù)指針的數(shù)組。即,此數(shù)據(jù)結(jié)構(gòu)是是一個數(shù)組,且其元素是一個指向函數(shù)入口地址的指針。

      定義方式:  返回值   ( *數(shù)組名[個數(shù)]) (參數(shù)列表)

3:指向數(shù)組的指針

      類型 (*變量名)[元素個數(shù)]

4:  指針數(shù)組

類型 *變量名[元素個數(shù)]

因為[] 比*具有更好的優(yōu)先級。所以如果是變量a先和*結(jié)合則表示其為一個指針,如果a先和[]結(jié)合,則表示是一個數(shù)組。

帶參數(shù)的回調(diào)函數(shù):

//定義帶參回調(diào)函數(shù)void PrintfText(char* s) {    printf(s);}//定義實現(xiàn)帶參回調(diào)函數(shù)的"調(diào)用函數(shù)"void CallPrintfText(void (*callfuct)(char*),char* s){    callfuct(s);}//在main函數(shù)中實現(xiàn)帶參的函數(shù)回調(diào)int main(int argc,char* argv[]){    CallPrintfText(PrintfText,"Hello World!\n");    return 0;}

c++回調(diào)機(jī)制:

非靜態(tài)成員函數(shù)作回調(diào)函數(shù)

      當(dāng)然如果是靜態(tài)成員函數(shù)就好辦跟全局函數(shù)是類似,到此為止世界還沒有變亂,如在VC編程中用AfxBeginThread開啟一個線程,就經(jīng)常將參數(shù)AFX_THREADPROC pfnThreadProc定義為一個全局函數(shù)或靜態(tài)成員函數(shù),可是這兩個都不方便訪問類的非靜態(tài)成員,之所以鄭重其事地寫這篇文章,就是以前靜態(tài)回調(diào)用起來非常不爽。

      回調(diào)函數(shù)是非靜態(tài)成員函數(shù)呢?我們可不能簡單地設(shè)為這樣:

class CCallback
{
public:
    void Func(int a)
    {
        cout<<"member function callback called with para="<    }
};
typedef void (CCallback::*pMemberFunc)(int);
void Caller(pMemberFunc p)
{
    (*p)(1);
}

     這樣編譯就不會通過的,因為非靜態(tài)的成員函數(shù)必須通過對象來訪問,好,我們稍稍改進(jìn)一下:

class CCallback
{
public:
    void Func(int a)
    {
        cout<<"member function callback called with para="<    }
};
typedef void (CCallback::*pMemberFunc)(int);
void Caller(CCallback* pObj,pMemberFunc p)
{
    (pObj->*p)(1);
}

int main(int argc, char* argv[])

    CCallback obj;
    Caller(&obj,&CCallback::Func);
}

      即給Caller多傳個對象進(jìn)去,好吧,貌似問題解決了,可是,調(diào)用者(如庫的提供商)只知道回調(diào)函數(shù)接口長這樣而已,事先全然不知客戶的類是如何定義,終于模板登上場了:

template
void Caller(T* pObj,void (T::*p)(int))
{
    (pObj->*p)(1);
}

     其他不變的,把調(diào)用者這里換成模板就OK了,當(dāng)然這個Caller也可以是成員函數(shù),現(xiàn)在用這個方法寫個小應(yīng)用是沒什么問題了,但是限制多多,如調(diào)用者一次只調(diào)用了一個實現(xiàn),但現(xiàn)實情況往往是產(chǎn)生某個事件時,應(yīng)該依次調(diào)用多個行為,即把掛在這個事件上的所有回調(diào)函數(shù)通通臨幸一遍,還有回調(diào)是如此的重要,以至于C#不用庫在語言本身層面就實現(xiàn)了它,我們也不可以到此草草了事,而是按照組件化的思維提供一套完善的回調(diào)機(jī)制,所謂完善,如上個例子中Caller只能接收一個參數(shù)為int,返回值為void的成員函數(shù)指針,等等,必須是這樣的接口嗎,想想?yún)?shù)為double行不行,如void (T::*p)(double)這樣的函數(shù)傳給它可以嗎,int不是可自動轉(zhuǎn)換為double嗎,那這個函數(shù)指針也能自動轉(zhuǎn)換嗎,就像C#中的協(xié)變與逆變一樣,不行,C++不允許,當(dāng)然我們可以強(qiáng)制轉(zhuǎn)換,不過要在十分清楚類型的情況下才能這么做,否則因為不是類型安全的很容易引起程序錯誤甚至崩潰。所以要支持各種參數(shù),多個參數(shù),還得模板,嗯嗯,努力尚未成功,同志還需革命!

多態(tài)回調(diào)

     甭管什么名詞,總之我們的目的是:產(chǎn)生某個事件時,調(diào)用某個待客戶實現(xiàn)的行為,調(diào)用者什么時候調(diào)用確定了,關(guān)鍵是客戶按照規(guī)定接口實現(xiàn)這個行為,這聽起來有點(diǎn)像多態(tài)了,是的,有時候被調(diào)用者與調(diào)用者是繼承關(guān)系,這就不需要其它理論了,就多態(tài)唄,不過多態(tài)不一定非得用虛函數(shù)來實現(xiàn),就像MFC一樣,考慮到每個類背負(fù)一個龐大的虛函數(shù)表會帶來很大的性能損失,換做用幾個結(jié)構(gòu)體和強(qiáng)大的宏而實現(xiàn)消息映射。在wincore.cpp中,CWnd::OnWndMsg源碼里,當(dāng)來了消息,在事先建立的鏈表中從派生類依次向上查找第一個實現(xiàn)了這個消息的類的AFX_MSGMAP結(jié)構(gòu)體,再取得它的AFX_MSGMAP_ENTRY成員,即真正的消息入口地址,

struct AFX_MSGMAP_ENTRY
{
    UINT nMessage;   // windows message
    UINT nCode;      // control code or WM_NOTIFY code
    UINT nID;        // control ID (or 0 for windows messages)
    UINT nLastID;    // used for entries specifying a range of control id's
    UINT nSig;       // signature type (action) or pointer to message #
    AFX_PMSG pfn;    // routine to call (or special value)
};

     就類似于寫一個普通的鏈表結(jié)構(gòu):struct list_node{list_node* next; int data},只不過這里的鏈表的next不能再隨便指,要指向基類的節(jié)點(diǎn),根據(jù)next指針找到對應(yīng)的節(jié)點(diǎn)后取出數(shù)據(jù)data成員即可,在這里,data就是AFX_MSGMAP_ENTRY,如上圖,AFX_MSGMAP_ENTRY里定義了消息標(biāo)號即各種附加參數(shù),還有最關(guān)鍵的成員pfn,代表了事先派生類通過宏填充好的回調(diào)成員函數(shù)地址。但是pfn的類型即AFX_PMSG定義為typedef void (AFX_MSG_CALL
CCmdTarget::*AFX_PMSG)(void); 只能代表一種類型,而客戶的派生類的為響應(yīng)消息的回調(diào)函數(shù)的類型有很多種,在框架中如何保證以正確的形式調(diào)用呢?原來客戶在填充消息標(biāo)號和函數(shù)地址時,也順便填充好了函數(shù)類型交給nSig成員保存,根據(jù)nSig,如前文所說,將pfn強(qiáng)制轉(zhuǎn)換到相應(yīng)的類型就OK了,不過這成員函數(shù)指針轉(zhuǎn)換來轉(zhuǎn)換去,代碼非常難看啊可讀性不強(qiáng),于是使用union進(jìn)行類型轉(zhuǎn)換:

//afximpl.h
union MessageMapFunctions
{
    AFX_PMSG pfn;   // generic member function pointer

    // specific type safe variants for WM_COMMAND and WM_NOTIFY messages
    void (AFX_MSG_CALL CCmdTarget::*pfn_COMMAND)();
    BOOL (AFX_MSG_CALL CCmdTarget::*pfn_bCOMMAND)();
    void (AFX_MSG_CALL CCmdTarget::*pfn_COMMAND_RANGE)(UINT);
    BOOL (AFX_MSG_CALL CCmdTarget::*pfn_COMMAND_EX)(UINT);
...
}

//wincore.cpp  CWnd::OnWndMsg
union MessageMapFunctions mmf;
mmf.pfn = lpEntry->pfn;
nSig = lpEntry->nSig;
switch (nSig)
    {
    default:
        ASSERT(FALSE);
        break;

    case AfxSig_bD:
        lResult = (this->*mmf.pfn_bD)(CDC::FromHandle((HDC)wParam));
        break;

    case AfxSig_bb:     // AfxSig_bb, AfxSig_bw, AfxSig_bh
        lResult = (this->*mmf.pfn_bb)((BOOL)wParam);
        break;

    case AfxSig_bWww:   // really AfxSig_bWiw
        lResult = (this->*mmf.pfn_bWww)(CWnd::FromHandle((HWND)wParam),
            (short)LOWORD(lParam), HIWORD(lParam));
        break;
...
}

     當(dāng)然這里只是一個小插曲而已,它只是MFC為滿足于自己應(yīng)用設(shè)計這么一套機(jī)制,派生類的回調(diào)函數(shù)類型是有限的,再則要求與框架類是繼承關(guān)系,如果沒有繼承關(guān)系怎么辦,例如當(dāng)產(chǎn)生串口或者網(wǎng)口收到數(shù)據(jù)的事件時,需要更新UI界面,UI界面與串口類可是沒有絲毫繼承關(guān)系的,呃...鐵人王進(jìn)喜說:有條件要上,沒條件創(chuàng)造條件也要上,我們大不了專門定義一個回調(diào)抽象類,讓UI界面繼承自它,實現(xiàn)類里的回調(diào)函數(shù),然后串口類通過抽象類型對象指針就可以多態(tài)地調(diào)用到UI的真正回調(diào)實現(xiàn)。COM/ATL的回調(diào),Java的回調(diào)就是這么干。不過在C++中,情形有些不一樣,這樣實現(xiàn)很勉強(qiáng),它需要多重繼承,仍然不能直接實現(xiàn)同時調(diào)用多個行為,耦合性高,每個回調(diào)都需要單獨(dú)定義一個類(只要接口不一樣),效率也不夠高,我們想直接調(diào)用到綁定好的回調(diào),基于這些缺點(diǎn),還得尋找更好的方法。

信號與槽(Signal/Slots)

      說了這么多,終于來到正題了,在C++中,信號與槽才是回調(diào)的完美解決方案,其實本質(zhì)上是一個觀察者模式,包括其它的叫法:delegate,notifier/receiver,observer,C#中的delegate也是一個觀察者的實現(xiàn)。Qt中提供了信號與槽的整套機(jī)制,任何對象的槽可以綁定到另一個對象的信號上,一個信號可以擁有多個槽,經(jīng)典的圖例如下:

         c/c++如何回調(diào)函數(shù)

     可是qt中的實現(xiàn)用了signal slot關(guān)鍵字,不是C++標(biāo)準(zhǔn)的啊,其它編譯器不能隨便編譯(好像先經(jīng)過qmake生成標(biāo)準(zhǔn)的代碼就可以了),直接上源碼不妥得搞清楚為什么,一切從最簡單的入手,我們先來用標(biāo)準(zhǔn)C++實現(xiàn)一個簡易的signal/slots,如何實現(xiàn)呢,說白了,就是想方設(shè)法把回調(diào)函數(shù)信息保存起來,必要時利用它就OK了,回調(diào)函數(shù)信息就兩個,類對象指針與成員函數(shù)地址,我們將這對信息存儲到名叫slot的類中,而在signal類中,維護(hù)多個slot即可,仍然用帶一個int參數(shù),返回值為void的函數(shù)接口:

#include 
#include 
using namespace std;

template
class slot
{
public:
    slot(T* pObj,void (T::*pMemberFunc)(T1))
    {
        m_pObj=pObj;
        m_pMemberFunc=pMemberFunc;
    }
    void Execute(T1 para)
    {
        (m_pObj->*m_pMemberFunc)(para);
    }
private:
    T* m_pObj;
    void (T::*m_pMemberFunc)(T1);
};

template
class signal
{
public:
    void bind(T* pObj,void (T::*pMemberFunc)(T1 para))
    {
        m_slots.push_back(new slot(pObj,pMemberFunc));
    }
    ~signal()
    {
        vector* >::iterator ite=m_slots.begin();
        for (;ite!=m_slots.end();ite++)
        {
            delete *ite;
        }
    }
    void operator()(T1 para)
    {
        vector* >::iterator ite=m_slots.begin();
        for (;ite!=m_slots.end();ite++)
        {
            (*ite)->Execute(para);
        }
    }
    
private:
    vector* > m_slots;
};

class receiver
{
public:
    void callback1(int a)
    {
        cout<<"receiver1: "<    }
    void callback2(int a)
    {
        cout<<"receiver2: "<    }
};

class sender
{
public:
    sender(): m_value(0)  {}
    int get_value()
    {
        return m_value;
    }
    void set_value(int new_value)
    {
        if (new_value!=m_value)
        {
            m_value=new_value;
            m_sig(new_value);
        }
    }
    signal m_sig;
private:
    int m_value;
};

int main(int argc,char** arg)
{
    receiver r;
    sender s;
    s.m_sig.bind(&r,&receiver::callback1);
    s.m_sig.bind(&r,&receiver::callback2);
    s.set_value(1);
    return 0;
}

     程序在VC6下順利通過,這個版本相比前面所說的繼承手法耦合性低了,被調(diào)用者receiver與規(guī)定函數(shù)接口的slot類沒有任何關(guān)系,但仔細(xì)以觀察這個程序在概念上是有問題的,signal類有兩個模板參數(shù),一個是類的類型,一個是函數(shù)參數(shù)類型,如果把這個signal/slots組件提供出去,使用者如上面的sender類不免會有個疑慮:在實例化signal類型時,必須提供這兩個模板參數(shù),可是調(diào)用方事先哪就一定知道接收方(receiver)的類型呢,而且從概念上講事件發(fā)送方與接收方只需遵循一個共同的函數(shù)接口就可以了,與類沒什么關(guān)系,上個程序要求在實例化時就得填充receiver的類型,也就決定了它與receiver只能一對一,而不能一對多,于是作此改進(jìn):將signal的參數(shù)T去掉,將T類型的推導(dǎo)延遲到綁定(bind)時,signal沒有參數(shù)T,signal的成員slot也就不能有,那slot的成員也就不能有,可是,參數(shù)T總得找個地方落腳啊,怎么辦?有個竅門:讓slot包含slotbase成員,slotbase沒有參數(shù)T的,但slotbase只定義接口,真正的實現(xiàn)放到slotimpl中,slotimpl就可以掛上參數(shù)T了,boost中any、shared_ptr就是用此手法,改進(jìn)后全部代碼如下:

#include 
#include 
using namespace std;

template
class slotbase
{
public:
    virtual void Execute(T1 para)=0;
};

template
class slotimpl : public slotbase
{
public:
    slotimpl(T* pObj,void (T::*pMemberFunc)(T1))
    {
        m_pObj=pObj;
        m_pMemberFunc=pMemberFunc;
    }
    virtual void Execute(T1 para)
    {
        (m_pObj->*m_pMemberFunc)(para);
    }
private:
    T* m_pObj;
    void (T::*m_pMemberFunc)(T1);
};

template
class slot 
{
public:
    template
        slot(T* pObj,void (T::*pMemberFunc)(T1)) 
    {
        m_pSlotbase=new slotimpl(pObj,pMemberFunc);
    }
    ~slot()
    {
        delete m_pSlotbase;
    }
    void Execute(T1 para)
    {
        m_pSlotbase->Execute(para);
    }
private:
    slotbase* m_pSlotbase;
};

template
class signal
{
public:
    template
    void bind(T* pObj,void (T::*pMemberFunc)(T1 para))
    {
        m_slots.push_back(new slot(pObj,pMemberFunc));
    }
    ~signal()
    {
        vector* >::iterator ite=m_slots.begin();
        for (;ite!=m_slots.end();ite++)
        {
            delete *ite;
        }
    }
    void operator()(T1 para)
    {
        vector* >::iterator ite=m_slots.begin();
        for (;ite!=m_slots.end();ite++)
        {
            (*ite)->Execute(para);
        }
    }
    
private:
    vector* > m_slots;
};

#define CONNECT(sender,signal,receiver,slot)  sender.signal.bind(receiver,slot)

class receiver
{
public:
    void callback1(int a)
    {
        cout<<"receiver1: "<    }
};
class receiver2
{
public:
    void callback2(int a)
    {
        cout<<"receiver2: "<    }
};

class sender
{
public:
    sender(): m_value(0)  {}
    int get_value()
    {
        return m_value;
    }
    void set_value(int new_value)
    {
        if (new_value!=m_value)
        {
            m_value=new_value;
            m_valueChanged(new_value);
        }
    }
    signal m_valueChanged;
private:
    int m_value;
    
};

int main(int argc,char** arg)
{
    receiver r;
    receiver2 r2;
    sender s;
    CONNECT(s,m_valueChanged,&r,&receiver::callback1);
    CONNECT(s,m_valueChanged,&r2,&receiver2::callback2);
    s.set_value(1);
    return 0;
}

     這個版本就比較像樣了,一個signal可與多個slots連接,增加了類似QT的connect,用宏實現(xiàn)#define CONNECT(sender,signal,receiver,slot) sender.signal.bind(receiver,slot),這樣使用者就非常方便,而且現(xiàn)在已完全解耦,sender只管定義自己的signal,在恰當(dāng)時機(jī)用仿函數(shù)形式調(diào)用即可,而receiver只管實現(xiàn)callback,互不影響,可獨(dú)立工作,如果需要再通過CONNECT將它們連接起來即可,已經(jīng)很組件化了,可是離真正的工程應(yīng)用尚有一段距離,如它不能接收全局函數(shù)或靜態(tài)成員函數(shù)或仿函數(shù)為回調(diào)函數(shù),不能帶兩個或更多的函數(shù)參數(shù),最后一步了。

關(guān)于c/c++如何回調(diào)函數(shù)就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。


新聞標(biāo)題:c/c++如何回調(diào)函數(shù)
網(wǎng)址分享:http://weahome.cn/article/ijeoes.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部