将类的成员函数作为回调函数(外一篇:友元函数)
轉自:http://blog.csdn.net/xylary/article/details/1548596
提出問題:?
回調函數是基于C編程的Windows SDK的技術,不是針對C++的,程序員可以將一個C函數直接作為回調函數,但是如果試圖直接使用C++的成員函數作為回調函數將發生錯誤,甚至編譯就不能通過。
分析原因:
普通的C++成員函數都隱含了一個傳遞函數作為參數,亦即“this”指針,C++通過傳遞一個指向自身的指針給其成員函數從而實現程序函數可以訪問C++的數據成員。這也可以理解為什么C++類的多個實例可以共享成員函數但是確有不同的數據成員。由于this指針的作用,使得將一個CALLBACK型的成員函數作為回調函數安裝時就會因為隱含的this指針使得函數參數個數不匹配,從而導致回調函數安裝失敗
解決方案:
一,不使用成員函數,直接使用普通C函數,為了實現在C函數中可以訪問類的成員變量,可以使用友元操作符(friend),在C++中將該C函數說明為類的友元即可。這種處理機制與普通的C編程中使用回調函數一樣。
二,使用靜態成員函數,靜態成員函數不使用this指針作為隱含參數,這樣就可以作為回調函數了。靜態成員函數具有兩大特點:其一,可以在沒有類實例的情況下使用;其二,只能訪問靜態成員變量和靜態成員函數,不能訪問非靜態成員變量和非靜態成員函數。由于在C++中使用類成員函數作為回調函數的目的就是為了訪問所有的成員變量和成員函數,如果作不到這一點將不具有實際意義。我們通過使用靜態成員函數對非靜態成員函數包裝的辦法來解決問題。類實例可以通過附加參數或全局變量的方式的方式傳遞到靜態成員函數中。分別舉例如下:
1,參數傳遞的方式
?? #include <iostream.h>???
?? class TClassA
?? {
?? public:
????? void Display(const char* text) { cout << text << endl; };
????? static void Wrapper_To_Call_Display(void* pt2Object, char* text);
??????// more....
?? };
???// 靜態包裝函數,能夠調用成員函數Display(),本身做為回調函數來使用
?? void TClassA::Wrapper_To_Call_Display(void* pt2Object, char* string)
?? {
?????? // 顯式類型轉換
?????? TClassA* mySelf = (TClassA*) pt2Object;
?????? // 調用普通成員函數
?????? mySelf->Display(string);
?? }
???// 回調函數的宿主,在這里回調用函數被使用
?? void DoItA(void* pt2Object, void (*pt2Function)(void* pt2Object, char* text))
?? {
??????// 使用回調函數
????? pt2Function(pt2Object, "hi, i'm calling back using a argument ;-)");??
?? }
?? // 執行示例
?? void Callback_Using_Argument()
?? {
????? TClassA objA;
????? DoItA((void*) &objA, TClassA::Wrapper_To_Call_Display);
?? }
2,全局變量的方式
?? #include <iostream.h>???
?? void* pt2Object;??????? // 全局變量,可以指向任意對象
?? class TClassB
?? {
?? public:
????? void Display(const char* text) { cout << text << endl; };
????? static void Wrapper_To_Call_Display(char* text);
?? };
?? // 靜態的包裝函數
?? void TClassB::Wrapper_To_Call_Display(char* string)
?? {
???????//需要保證全局變量值的正確性
?????? TClassB* mySelf = (TClassB*) pt2Object;
?????? mySelf->Display(string);
?? }
?? // 回調用函數的宿主,在這里回調用函數被使用
?? void DoItB(void (*pt2Function)(char* text))
?? {
???
????? pt2Function("hi, i'm calling back using a global ;-)");?? // make callback
?? }
?? // 執行示例
?? void Callback_Using_Global()
?? {
????? TClassB objB;??
????? pt2Object = (void*) &objB;
????? DoItB(TClassB::Wrapper_To_Call_Display);
?? }
注意:通過上面兩種方法的比較可以看出,第2種方法中靜態包裝函數可以和普通成員函數保持簽名一致,當回調函數的宿主接口不能改變時,這種方法特別有用。但因為使用了全局變量,也不是一個好的設計。
=======================================================
=======================================================
===================== ?友 ? 元 ? 函 ? 數 ? =====================
=======================================================
=======================================================
問題的提出
我們已知道類具有封裝和信息隱藏的特性。只有類的成員函數才能訪問類的私有成員,程序中的其他函數是無法訪問私有成員的。非成員函數可以訪問類中的公有成員,但是如果將數據成員都定義為公有的,這又破壞了隱藏的特性。另外,應該看到在某些情況下,特別是在對某些成員函數多次調用時,由于參數傳遞,類型檢查和安全性檢查等都需要時間開銷,而影響程序的運行效率。
為了解決上述問題,提出一種使用友元的方案。友元是一種定義在類外部的普通函數,但它需要在類體內進行說明,為了與該類的成員函數加以區別,在說明時前面加以關鍵字friend。友元不是成員函數,但是它可以訪問類中的私有成員。友元的作用在于提高程序的運行效率,但是,它破壞了類的封裝性和隱藏性,使得非成員函數可以訪問類的私有成員。
友元可以是一個函數,該函數被稱為友元函數;友元也可以是一個類,該類被稱為友元類。
友元函數
友元函數的特點是能夠訪問類中的私有成員的非成員函數。友元函數從語法上看,它與普通函數一樣,即在定義上和調用上與普通函數一樣。下面舉一例子說明友元函數的應用。
#include?
#include?
class?Point
{
public:
Point(double?xx,?double?yy)?{?x=xx;?y=yy;?}
void?Getxy();
friend?double?Distance(Point?&a,?Point?&b);
private:
double?x,?y;
};
void?Point::Getxy()
{
cout<<"("<<x<<","<<y<<")"<<endl;< font="">
}
double?Distance(Point?&a,?Point?&b)
{
double?dx?=?a.x?-?b.x;
double?dy?=?a.y?-?b.y;
return?sqrt(dx*dx+dy*dy);
}
void?main()
{
Point?p1(3.0,?4.0),?p2(6.0,?8.0);
p1.Getxy();
p2.Getxy();
double?d?=?Distance(p1,?p2);
cout<<"Distance?is"<<d<<endl;< font="">
}
說明:在該程序中的Point類中說明了一個友元函數Distance(),它在說明時前邊加friend關鍵字,標識它不是成員函數,而是友元函數。它的定義方法與普通函數定義一樣,而不同于成員函數的定義,因為它不需要指出所屬的類。但是,它可以引用類中的私有成員,函數體中a.x,b.x,a.y,b.y都是類的私有成員,它們是通過對象引用的。在調用友元函數時,也是同普通函數的調用一樣,不要像成員函數那樣調用。本例中,p1.Getxy()和p2.Getxy()這是成員函數的調用,要用對象來表示。而Distance(p1,?p2)是友元函數的調用,它直接調用,不需要對象表示,它的參數是對象。(該程序的功能是已知兩點坐標,求出兩點的距離。)
總結
以上是生活随笔為你收集整理的将类的成员函数作为回调函数(外一篇:友元函数)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大白菜装系统怎么做 制作大白菜装系统的方
- 下一篇: 提取稳定前景的两种方法