溫馨提示×

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

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

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

發(fā)布時(shí)間:2021-10-14 15:38:58 來源:億速云 閱讀:215 作者:柒染 欄目:編程語(yǔ)言

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

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

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

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

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

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

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

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

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

 1 #include <stdlib.h> 
 2 #include <stdio.h> 
 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ù),這里第二個(gè)參數(shù)是函數(shù)指針的參數(shù) 15 {                                               //不能寫成void Caller2(int (*ptr)(int n)),這樣的定義語(yǔ)法錯(cuò)誤。 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ù)個(gè)數(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ù)組是一個(gè)其元素是函數(shù)指針的數(shù)組。即,此數(shù)據(jù)結(jié)構(gòu)是是一個(gè)數(shù)組,且其元素是一個(gè)指向函數(shù)入口地址的指針。

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

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

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

4:  指針數(shù)組

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

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

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

//定義帶參回調(diào)函數(shù)void PrintfText(char* s) {    printf(s);}//定義實(shí)現(xiàn)帶參回調(diào)函數(shù)的"調(diào)用函數(shù)"void CallPrintfText(void (*callfuct)(char*),char* s){    callfuct(s);}//在main函數(shù)中實(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開啟一個(gè)線程,就經(jīng)常將參數(shù)AFX_THREADPROC pfnThreadProc定義為一個(gè)全局函數(shù)或靜態(tài)成員函數(shù),可是這兩個(gè)都不方便訪問類的非靜態(tài)成員,之所以鄭重其事地寫這篇文章,就是以前靜態(tài)回調(diào)用起來非常不爽。

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

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

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

class CCallback
{
public:
    void Func(int a)
    {
        cout<<"member function callback called with para="<<a<<endl;
    }
};
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多傳個(gè)對(duì)象進(jìn)去,好吧,貌似問題解決了,可是,調(diào)用者(如庫(kù)的提供商)只知道回調(diào)函數(shù)接口長(zhǎng)這樣而已,事先全然不知客戶的類是如何定義,終于模板登上場(chǎng)了:

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

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

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

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

     就類似于寫一個(gè)普通的鏈表結(jié)構(gòu):struct list_node{list_node* next; int data},只不過這里的鏈表的next不能再隨便指,要指向基類的節(jié)點(diǎn),根據(jù)next指針找到對(duì)應(yīng)的節(jié)點(diǎn)后取出數(shù)據(jù)data成員即可,在這里,data就是AFX_MSGMAP_ENTRY,如上圖,AFX_MSGMAP_ENTRY里定義了消息標(biāo)號(hà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)號(hào)和函數(shù)地址時(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)然這里只是一個(gè)小插曲而已,它只是MFC為滿足于自己應(yīng)用設(shè)計(jì)這么一套機(jī)制,派生類的回調(diào)函數(shù)類型是有限的,再則要求與框架類是繼承關(guān)系,如果沒有繼承關(guān)系怎么辦,例如當(dāng)產(chǎn)生串口或者網(wǎng)口收到數(shù)據(jù)的事件時(shí),需要更新UI界面,UI界面與串口類可是沒有絲毫繼承關(guān)系的,呃...鐵人王進(jìn)喜說:有條件要上,沒條件創(chuàng)造條件也要上,我們大不了專門定義一個(gè)回調(diào)抽象類,讓UI界面繼承自它,實(shí)現(xiàn)類里的回調(diào)函數(shù),然后串口類通過抽象類型對(duì)象指針就可以多態(tài)地調(diào)用到UI的真正回調(diào)實(shí)現(xiàn)。COM/ATL的回調(diào),Java的回調(diào)就是這么干。不過在C++中,情形有些不一樣,這樣實(shí)現(xiàn)很勉強(qiáng),它需要多重繼承,仍然不能直接實(shí)現(xiàn)同時(shí)調(diào)用多個(gè)行為,耦合性高,每個(gè)回調(diào)都需要單獨(dú)定義一個(gè)類(只要接口不一樣),效率也不夠高,我們想直接調(diào)用到綁定好的回調(diào),基于這些缺點(diǎn),還得尋找更好的方法。

信號(hào)與槽(Signal/Slots)

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

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

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

#include <vector>
#include <iostream>
using namespace std;

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

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

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

#include <vector>
#include <iostream>
using namespace std;

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

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

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

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

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

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<int> 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;
}

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


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

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

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI