C++回調函數使用心得


前言
關於C++回調函數的介紹網上有很多,要知道它的概念很容易,難的是靈活應用,這里就筆者遇到的一個使用場景對回調函數進行一個簡單的介紹,如果能對您有所幫助是我的榮幸。本文不會對C++回調函數的基礎知識做過多的介紹,若對其概念不夠理解的,筆者在此推介兩篇個人認為相當優秀的博客。
鏈接: https://blog.csdn.net/hellozex/article/details/81742348


回調函數也是普通函數

回調函數也是普通函數
首先明確一個概念,回調函數也是普通函數,而不是什么神秘的東西。至於為什么叫回調函數,是因為程序通過參數把該函數的函數指針傳遞給了其它函數,在那個函數里面調用這個函數指針就相當於調用這個函數,這樣的過程就叫回調,而被調用的函數就叫回調函數。看得出來,回調的本質是函數指針傳遞,所以想要理解回調機制,先要理解函數指針。

C回調函數
C++回調函數擴展自C回調函數,要想理解C++回調函數,先要理解C回調函數。我們通過一個實例來講解C回調函數的使用方法。
————————————————

//callbackTest.c
//1.定義函數onHeight(回調函數)
//@onHeight 函數名
//@height   參數
//@contex   上下文
void onHeight(double height, void* contex)
{
    sprint("current height is %lf",height);
}

//2.定義onHeight函數的原型
//@CallbackFun 指向函數的指針類型
//@height      回調參數,當有多個參數時,可以定義一個結構體
//@contex      回調上下文,在C中一般傳入nullptr,在C++中可傳入對象指針
typedef void (*CallbackFun)(double height, void* contex);

//3.定義注冊回調函數
//@registHeightCallback 注冊函數名
//@callback             回調函數原型
//@contex               回調上下文
void registHeightCallback(CallbackFun callback, void* contex)
{
    double h=100;
    callback(h,nullptr);
}

//4.main函數
void main()
{
    //注冊onHeight函數,即通過registHeightCallback的參數將onHeight函數指針
    //傳入給registHeightCallback函數,在registHeightCallback函數中調用
    //callback就相當於調用onHeight函數。
    registHeightCallback(onHeight,nullptr);
}

程序的運行結果是:
current height is 100
很多時候,注冊的時候並不調用回調函數,而是在其他函數中調用,那我們可以定義一個CallbackFun全局指針變量,在注冊的時候將函數指針賦給它,在要調用的調用它。如

//定義全局指針變量
CallbackFun* m_pCallback;
//定義注冊回調函數
void registHeightCallback(CallbackFun callback, void* contex)
{
    m_pCallback = callback;
}
//定義調用函數
void printHeightFun(double height)
{
    m_pCallback(height,nullptr);
}
//main函數
void main()
{
    //注冊回調函數onHeight
    registHeightCallback(onHeight,nullptr);
    //打印height
    double h=99;
    printHeightFun(99);
}

程序的運行結果是:
current height is 99

 

C++回調函數
C++回調函數擴展自C,與C略有不同的是,C++可以使用全局函數和靜態函數作為回調函數。考慮到全局函數會破壞封裝性,所以一般都用靜態成員函數。故除了理解函數指針,還要理解靜態成員函數,具體一點是在靜態成員函數中訪問非靜態成員函數的方法,因為我們很可能需要獲取靜態成員函數中的數據。

使用場景描述
比如說你使用了別人提供的sdk,這個sdk可能來自供應商,也有可能來自你的同事,他就提供給你一個注冊回調函接口,比如就下面這個,你可以通過回調函數獲取到height(某種傳感器的實時返回的數據),你要怎么做?

C++回調函數定義
————————————————

//CallbackFun類型
//@CallbackFun 指向函數的指針類型
//@height      回調參數,當有多個參數時,可以定義一個結構體
//@contex      回調上下文,在C中一般傳入nullptr,在C++中可傳入對象指針
typedef void (*CallbackFun)(double height, void* contex);

//注冊回調函數接口
//@registHeightCallback 注冊函數名
//@callback             回調函數原型
//@contex               回調上下文
void registHeightCallback(CallbackFun callback, void* contex)

首先,你要定義一個靜態成員函數並注冊。

//sensorTest.cpp
//接收數據類class Sensor
class Sensor{
public:
    Sensor(){}
    ~Sensor(){}
    //定義回調函數onHeight
    static void onHeight(double height, void* contex)
    {
        cout << "current height is  " << height << endl;
    }
    //定義注冊回調函數
    void registCallback()
    {
        registHeightCallback(onHeight, this);
    }
};

//main 函數
void main()
{
    Sensor sens;
    sens.registCallback();
}

運行程序,我們發現控制台一直在打印
current height is **
說明我們的回調函數正確實現了。到這一步不難,只要掌握基本的回調函數概念都能實現。
現在我們有這樣一種情況,我們有另外一個類,要在這個類里面實時打印獲取的數據,要怎么做呢?

靜態成員函數訪問非靜態成員函數的方法
我們知道靜態成員函數中是只能出現靜態變量和靜態函數的,但是有些時候真的需要訪問非靜態成員函數或變量,比如我上面說的那種情況。讓我們先來實現對同一個類中的非靜態成員函數的訪問。
修改class Sensor如下
————————————————

//接收數據類class Sensor
class Sensor{
public:
    Sensor(){}
    ~Sensor(){}
    //定義回調函數onHeight
    static void onHeight(double height, void* contex)
    {
        //cout << "current height is  " << height << endl;
        Sensor* sen = (Sensor*)contex;
        if(sen)  //注意判斷sen是否有效
            sen->getHeight(height);
    }
    //定義注冊回調函數
    void registCallback()
    {
        registHeightCallback(onHeight, this);
    }
    //新增的成員函數
    void getHeight(double height)
    {
        cout << "current height is  " << height << endl;
    }
};

如此修改之后,得到與修改前一樣的效果(實時打印height),關鍵點在於注冊回調函數的時候將Sensor對象的指針傳給了contex,在回調函數中又將contex轉換為Sensor對象指針,所以能調用普通函數。
同理,如果注冊時傳入某一個對象的指針,就可以在回調函數中對該對象進行操作,這就是我們可以在一個對象中回調另一個對象的思想。

回調對象
現在開始解決之前提出的問題,本質是不變的,回調是指針傳遞,可以是函數指針,也可以是對象指針。
————————————————

//先定義一個類class DataPrint
//打印數據類class DataPrint
class DataPrint{
public:
    DataPrint(){}
    ~DataPrint(){}
    void printHeight(double height)
    {
        cout << "print height is " << height << endl;
    }
};

//要在類Sensor中增加DataPrint的指針和一個DataPrint指針賦值函數,class Sensor修改為
//接收數據類class Sensor
class Sensor{
public:
    Sensor(){}
    ~Sensor(){}
    //定義回調函數onHeight
    static void onHeight(double height, void* contex)
    {
        DataPrint* dp = (DataPrint*)contex;
        if(dp)  //注意判斷dp是否有效
            dp->printHeight(height);
    }
    //定義注冊回調函數
    void registCallback()
    {
        registHeightCallback(onHeight, m_pDataPrint );
    }
    //新增的成員函數
    void getHeight(double height)
    {
        //cout << "current height is  " << height << endl;
    }
    void setDataPrint(DataPrint* dp)
    {
        m_pDataPrint = dp;
    }
private:
    DataPrint* m_pDataPrint;
};

//main主函數
void main()
{
    DataPrint* dp=new DataPrint();
    Sensor* sens=new Sensor();
    //注意這兩句的順序不能顛倒
    sens->setDataPrint(dp);
    sens->registCallback();
}

這樣就能實現在另一個類中取得回調函數的數據,如果無法保證DataPrint的實例化一定在Sensor之前,我們可以這樣做

//先定義一個類class DataPrint
//打印數據類class DataPrint
class DataPrint{
public:
    DataPrint(){}
    ~DataPrint(){}
    void printHeight(double height)
    {
        cout << "print height is " << height << endl;
    }
};

//要在類Sensor中增加DataPrint的指針和一個DataPrint指針賦值函數,class Sensor修改為
//接收數據類class Sensor
class Sensor{
public:
    Sensor(){}
    ~Sensor(){}
    //定義回調函數onHeight
    static void onHeight(double height, void* contex)
    {
        Sensor* sen= (Sensor*)contex;
        if(sen)  //注意判斷sen是否有效
            sen->getHeight(height);
    }
    //定義注冊回調函數
    void registCallback()
    {
        registHeightCallback(onHeight, m_pDataPrint );
    }
    //新增的成員函數
    void getHeight(double height)
    {
        if(m_pDataPrint )
            m_pDataPrint ->printHeight(height);
    }
    void setDataPrint(DataPrint* dp)
    {
        m_pDataPrint = dp;
    }
private:
    DataPrint* m_pDataPrint;
};

//main主函數
void main()
{
    DataPrint* dp=new DataPrint();
    Sensor* sens=new Sensor();
    //注意這兩句的順序可以顛倒
    sens->setDataPrint(dp);
    sens->registCallback();
}

兩個的區別是一個直接注冊指定類的對象指針,另一個注冊當前類的對象指針,間接調用另一個類的對象指針。

更復雜的討論
剛才討論的問題稍微復雜一點了,不過應該也容易理解,但是我們在實際項目中遇到的情況可能比這個復雜。比如在有層次的軟件工程中,回調函數在底層,顯示數據的類在上層,我們要如何把底層的數據顯示到上層去?容易想到的是上層調用底層,如開個timer刷新,但這是不對的,你無法做到實時調用,你需要的是一個異步的機制,即底層一發生上層就能接收到。
怎么做呢?還是一樣的道理,把上層的類的對象指針傳給底層,這時底層需要包含上層的頭文件,但這是不對的,上層已經包含了底層的頭文件,它們不能互相包含,如何解決這個問題?那就要用到C++繼承的特性,首先在底層定義一個基類,然后在上層繼承它,在上層實例化這個繼承類后,將其指針設置給底層,底層對該指針的操作就是對繼承類對象的操作,以此實現數據的傳遞。
這里就不貼代碼了,思想是這樣的,很多情況下需要實際問題實際分析,歡迎討論。
————————————————
版權聲明:本文為CSDN博主「Simon.Y」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/sinat_38183777/article/details/83958887


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM