前言
關於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