實例解析C++中類的成員函數指針。本站提示廣大學習愛好者:(實例解析C++中類的成員函數指針)文章只能為提供參考,不一定能成為您想要的結果。以下是實例解析C++中類的成員函數指針正文
C說話的指針相當的靈巧便利,但也相當輕易失足。很多C說話初學者,乃至C說話老鳥都很輕易栽倒在C說話的指針下。但弗成否定的是,指針在C說話中的地位極端主要,或許可以過火一點的來講:沒有指針的C法式不是真實的C法式。
但是C++的指針卻經常給我一種束手束腳的感到。C++比C說話有更嚴厲的靜態類型,加倍強調類型平安,強調編譯時檢討。是以,關於C說話中最輕易錯用的指針,更是不克不及放過:C++的指針被分紅數據指針,數據成員指針,函數指針,成員函數指針,並且不克不及隨意互相轉換。並且這些指針的聲明格局都紛歧樣:
數據指針
T *
成員數據指針
T::*
函數指針
R (*)(...)
成員函數指針
R (T::*)(...)
還有一個更主要的差別是,指針所占的空間也紛歧樣了。即便在32位體系中,所占的空間也有能夠是4字節、8字節、12字節乃至16字節,這個根據平台及編譯器,有很年夜的變更。
雖然C++中依然有全能指針void*,但它卻屬於被批斗的對象,並且不再能“全能”了。它不克不及轉換成成員指針。
如許一來,C++的指針就變得很為難:我們須要一種指針可以或許指向統一類型的數據,不論這個數據是通俗數據,照樣成員數據;我們更須要一種指針可以或許指向統一類型的函數,不論這個函數是靜態函數,照樣成員函數。然則沒有,至多從如今的C++尺度中,還沒有看到。
在編程任務中常會碰到在一個“類”中經由過程函數指針挪用成員函數的請求,如,當在一個類中應用了C++尺度庫中的排序函數qsort時,因qsort參數須要一個“比擬函數”指針,假如這個“類”應用某個成員函數作“比擬函數”,就須要將這個成員函數的指針傳給qsort供其挪用。本文所評論辯論的用指針挪用 “類”的成員函數包含以下三種情形:
(1).將 “類”的成員函數指針付與同類型非成員函數指針,如:
例子1
#include <stdlib.h> typedef void (*Function1)( ); //界說一個函數指針類型。 Function1 f1; class Test1 { public: //…被挪用的成員函數。 void Memberfun1( ){ printf("%s \n","Calling Test3::Memberfun2 OK");}; // void Memberfun2() { f1=reinterpret_cast<Function1>(Memberfun1);//將成員函數指針付與f1。編譯失足。 f1(); } //… }; int main() { Test1 t1; t1.Memberfun2(); return 0; }
(2) 在一個“類”內,有尺度庫函數,如qsort, 或其他全局函數,用函數指針挪用類的成員函數。如:
例子2:
#include <stdlib.h> class Test2 { private: int data[2]; //… public: //… int __cdecl Compare(const void* elem1, const void* elem2) //成員函數。 { printf("%s \n","Calling Test2::Memberfun OK"); return *((int*)elem1)- *((int*)elem2) ; } void Memberfun() { data[0]=2; data[1]=5; qsort( data, 2, sizeof(int), Compare); //尺度庫函數挪用成 //員函數。編譯失足。 } //… }; int main( ) { Test2 t2; t2.Memberfun(); //挪用成員函數。 return 0; }
(3)統一個“類”內,一個成員函數挪用另外一個成員函數, 如:
例子3:
#include "stdlib.h" class Test3 { public: //… void Memberfun1( void (* f2)( ) ) { f2( ) ;} //成員函數1挪用成員函數//2。 void Memberfun2( ) { printf("%s \n","Calling Test3::Memberfun2 OK");} //成員函數2。 void Memberfun3( ) { Memberfun1( Memberfun2);} // 編譯失足 //… }; int main( ) { Test3 t3; t3.Memberfun3(); //挪用成員函數。 return 0; }
以上三種情形的代碼語法上沒有明顯的毛病,在一些較早的編譯情況中,如,VC++ 4.0, 平日可以編譯經由過程,或至少給出成績提示(Warning)。後來的編譯對象,如,VC++6.0和其他一些經常使用的C++編譯軟件,不克不及經由過程以上代碼的編譯, 並指失足誤以下(以第三種情形用VC++ 6.0編譯為例):
error C2664: 'Memberfun1' : cannot convert parameter 1 from 'void (void)' to 'void (__cdecl *)(void)' None of the functions with this name in scope match the target type
即:Memberfun1參數中所挪用的函數類型纰謬。
依照以上提醒,僅經由過程轉變函數的類型沒法清除毛病,然則,假如單將這幾個函數從類的界說中拿出來,不作任何轉變便可以清除毛病經由過程編譯, 仍以第三種情形為例,以下代碼可經由過程編譯:
#include <stdlib.h> void Memberfun1( void (* f2)( ) ) { f2( ) ;} //原成員函數1挪用成員函數//2。 void Memberfun2( ) { printf("%s \n","Calling Test3::Memberfun2 OK");} //原成員函數2。 void Memberfun3( ) { Memberfun1( Memberfun2);} int main( ) { Memberfun3 (); return 0; }
第1、 2種情形和第3種情形完整雷同。
由此可以的得出結論,以上三種情形編譯不克不及經由過程的緣由外面上其實不在於函數類型挪用纰謬,而是與 “類”有關。沒經由過程編譯的情形是用函數指針挪用了 “類”的成員函數,經由過程編譯的是用函數指針挪用了非成員函數,而函數的類型完整雷同。那末, “類”的成員函數指針和非成員函數指針有甚麼分歧嗎?
鄙人面的法式中,用sizeof()函數可以檢查各類“類”的成員函數指針和非成員函數指針的長度(size)並輸入到屏幕上。
#include "stdafx.h" #include <iostream> #include <typeinfo.h> class Test; //一個不決義的類。 class Test2 //一個空類。 { }; class Test3 //一個有界說的類。 { public: //... void (* memberfun)(); void Memberfun1( void (* f2)( ) ) { f2( ) ;} //成員函數1挪用成員函數//2。 void Memberfun2( );//成員函數2。 //… }; class Test4: virtual Test3 ,Test2 //一個有virtual繼續的類(derivative class)。 { public: void Memberfun1( void (* f2)( ) ) { f2( ) ;} }; class Test5: Test3,Test2 //一個繼續類(derivative class)。 { public: void Memberfun1( void (* f2)( ) ) { f2( ) ;} }; int main() { std::cout <<"普通函數指針長度= "<< sizeof(void(*)()) << '\n'; std::cout <<"-類的成員函數指針長度-"<<'\n'<<'\n'; std::cout <<"Test3類成員函數指針長度="<< sizeof(void(Test3::*)())<<'\n'<<'\n'; std::cout <<"Test5類成員函數指針長度="<<sizeof(void (Test5:: *)())<<'\n'; std::cout <<"Test4類成員函數指針長度="<<sizeof(void (Test4:: *)())<<'\n'; std::cout <<"Test類成員函數指針長度="<<sizeof(void(Test::*)()) <<'\n'; return 0; }
輸入成果為(VC++6.0編譯,運轉於Win98操作體系,其他操作體系能夠有所分歧):
以上成果注解,在32位Win98操作體系中,普通函數指針的長度為4個字節(32位),而類的成員函數指針的長度隨類的界說與否、類的繼續品種和關系而變,從無繼續關系類(Test3)的4字節(32位)到有虛繼續關系類(Virtual Inheritance)(Test4)的12字節(96位),唯一解釋(declaration)沒有界說的類(Test)由於與其有關的一些信息不明白成員函數指針最長為16字節(128位)。明顯, 與普通函數指針分歧,指向“類”的成員函數的指針不只包括成員函數地址的信息,並且包括與類的屬性有關的信息,是以,普通函數指針和類的成員函數指針是基本分歧的兩品種型,固然,也就不克不及用普通函數指針直接挪用類的成員函數,這就是為何本文開端提到的三種情形編譯失足的緣由。雖然應用較早版本的編譯軟件編譯依然可以經由過程,但這會給法式留下嚴重的隱患。
至於為何異樣是指向類的成員函數的指針,其長度居然分歧,從32位到128位,差異很年夜,因為沒有看到微軟官方的材料只能推想VC++6.0在編譯時對類的成員函數指針停止了優化,以盡可能延長指針長度,究竟應用128位或96位指針在32位操作體系上對法式機能會有影響。然則,不管若何優化,類的成員函數指針包括必定量的對象(Objects)信息是肯定的。其他的操作體系和編譯軟件能否停止了相似的處置,讀者可以用以上法式本身驗證。
那末,當須要時,若何用指針挪用類的成員函數?可以斟酌以下辦法:
(1) 將須要挪用的成員函數設為static 類型,如:在前述例子2中,將class Test2 成員函數Compare 界說前加上static 以下:
class Test2 { //…. int static __cdecl Compare(const void* elem1, const void* elem2) //成員函數。 //其他不變 }
轉變後的代碼編譯順遂經由過程。緣由是,static 類型的成員函數與類是離開的,其函數指針也不包括對象信息,與普通函數指針分歧。這類辦法固然輕便,但有兩個缺陷:1、被挪用的函數成員界說內不克不及湧現任何類的成員(包含變量和函數);2、因為應用了static 成員,類在被繼續時遭到了限制。
(2) 應用一個函數參數含有對象信息的static 類型的成員函數為直達直接地挪用其他成員函數,以例3為例,將類Test3作以下修正,main()函數不變,則可順遂經由過程編譯:
class Test3 { public: //… void static __cdecl Helper(Test3* test3) { test3->Memberfun2(); } void Memberfun1( void (* f2)(Test3*)) { f2(this) ;} //將對象信息傳給Helper函數。 void Memberfun2( ) {printf("%s \n","Calling Test3::Memberfun2 OK"); } //成員函數2。 void Memberfun3( ) { Memberfun1( Helper);} //… };
這類直接方法對成員函數沒有任何限制,戰勝了第一種辦法成員函數不克不及應用任何類的成員的缺陷,但因為有static 成員,類的繼續仍遭到制約。
(3)應用一個全程函數(global function)為直達直接挪用類的成員函數,仍以例3為例,將代碼作以下修正(VC++6.0編譯經由過程):
class Test3; void __cdecl Helper(Test3* test3); class Test3 { public: //… void Memberfun1( void (* f2)(Test3*)) { f2(this) ;} //成員函數1挪用成員函數//2。 void Memberfun2( ) {printf("%s \n","Calling Test3::Memberfun2 OK"); } //成員函數2。 void Memberfun3( ) { Memberfun1( Helper);} //… }; void __cdecl Helper(Test3* test3) { test3->Memberfun2(); };
這個辦法對成員函數沒有任何請求,然則須要較多的代碼。
除上述三種辦法外還有其他辦法,如, 可以在匯編層面上修正代碼處理上述成績等,不屬於本文規模。
結論:函數指針不克不及直接挪用類的成員函數,需采用直接的辦法,緣由是成員函數指針與普通函數指針有基本的分歧,成員函數指針除包括地址信息外,同時攜帶其所屬對象信息。本文供給三種方法用於直接挪用成員函數。這三種方法各有優缺陷,實用於分歧的場所。