获取成员函数地址及获取函数地址
?首先我們定義一個類Ctest,類里面包含三個不同形式的成員函數,靜態成員函數statFunc()、動態成員函數dynFunc()和虛擬函數virtFunc()。在main函數中我們利用cout標準輸出流分別輸出這三個函數的地址,程序如下所示:
#include< iostream>
#include< stdio.h>
using namespace std;
class Ctest
{
public:
?static void statFunc()
?{??cout<< "statFunc"<< endl;?}
?void dynFunc()
?{??cout<< "dynFunc"<< endl;?}
?virtual void virtFunc()
?{??cout<< "virtFunc"<< endl;?}
};
void main()
{
?cout<< "address of Ctest::statFunc:"<<& Ctest::statFunc<< endl;
?cout<< "address of Ctest::dynFunc :"<<& Ctest::dynFunc<< endl;
?cout<< "address of Ctest::virtFunc:"<<& Ctest::virtFunc<< endl;
?while(1);
}
屏幕輸出結果如下圖所示:
從圖中可以看出靜態函數的地址顯示正常,是一個32位地址值,但是動態函數和虛擬函數的地址都輸出1,明顯不是地址值。
?????為了知道為什么,我們必須分析一下這幾種成員函數在運行機制的不同。我們都知道,靜態函數是獨立于對象的,是類擁有的,所以我們調用靜態函數,既可以通過類調用(如Ctest::statFunc())也可以通過對象調用(如Ctest Object; Object.statFunc())。無論是通過類調用還是對象調用,對應的都是同一個函數。
?????但是,對于動態函數,只能通過對象來調用。因為我們在動態成員函數中,往往都需要訪問對象的成員變,我們知道同一類型的不同對象,它們擁有類中成員變量的不同副本,所以假如動態成員函數由類來調用,我們怎么知道在函數中訪問的是哪一個對象的成員變量。另外要說一下,我們說動態函數的調用必須通過對象來調用,是不是說動態成員函數是跟對象綁定的,是不是不同的對象所調用的成員函數就是不同的呢(注意,我們說成員函數不同,是指函數代碼存儲的地址不同,不是說函數的行為不同),所以我們要輸出動態函數的地址,必須通過對象來獲取呢,如Ctest Object;& Object.statFunc,我們在編譯器上編譯一下,可以發現編程出錯,原因是對象只能用來調用函數的,并不能用來獲取函數的地址。因為我們要獲取函數地址,還是得通過類來獲取的,動態函數同樣是跟類綁定而不是跟對象綁定的。C++調用非靜態的成員函數時,采用的是一種__thiscall的函數調用方式。采用這種調用方式,編譯器在編譯的時候,會在調用的函數形參表中增加一個指向調用該成員函數的指針,也就是我們經常說的this指針。調用的形式類似于Ctest::dynFunc(Ctest* this, otherparam...),在函數體中,涉及到對象的成員變量或者其他成員函數,都會通過這個this指針來調用,從而達到在成員函數中處理調用對象所對應的數據,而不會錯誤處理其他對象的數據。可見,雖然我們必須通過對象來調用動態函數,但是其實我們訪問的都是同一個成員函數。所以我們上面采用&Ctest::dynFunc類名來獲取成員函數地址是沒錯的,動態函數同樣是跟類綁定而不是跟對象綁定的。
?????出錯的原因是,我們輸出操作符<<沒有對void(__thiscall A::*)()類型重載,編譯器將這種類型轉換為bool類型,所以輸出了1;對于靜態函數,其調用方式并非__thiscall,<<有對它的重載,因此類的靜態函數可以直接用cout輸出函數地址。我們可以用printf輸出,因為他可以接收任意類型的參數,包括__thiscall類型,所以我們將cout<< "address of Ctest::dynFunc :"<<& Ctest::dynFunc<< endl;改為printf( "address of Ctest::dynFunc :X\n",& Ctest::dynFunc);輸出如下圖所示:
從圖中可以看書,通過使用printf輸出,我們得到了動態函數的地址。那么對于虛擬函數,我們同樣采用printf來輸出,是不是就可以了呢,我們將cout<< "address of Ctest::virtFunc:"<<& Ctest::virtFunc<< endl;
改為printf( "address of Ctest::virtFunc :X\n",& Ctest::virtFunc);運行的輸出如下:
由上圖可以看出同樣可以得出一個準地址值。
為了驗證取到的地址是否正確,我們可以分別定義三個成員函數指針來保存獲得的函數地址,然后通過調用函數指針來看輸出是否正確,就可以判斷取到的地址是否正確了。以下是驗證的代碼:
#include< iostream>
#include< stdio.h>
using namespace std;
class Ctest
{
public:
?static void statFunc()
?{
??cout<< "statFunc"<< endl;
?}
?void dynFunc()
?{
??cout<< "dynFunc"<< endl;
?}
?virtual void virtFunc()
?{
??cout<< "virtFunc"<< endl;
?}
};
void main()
{
?Ctest Object;
?Ctest* pObject =& Object;
?cout<< "address of Ctest::statFunc:"<<& Ctest::statFunc<< endl;
?printf( "address of Ctest::dynFunc :X\n",& Ctest::dynFunc);
?printf( "address of Ctest::virtFunc:X\n",& Ctest::virtFunc);
?static void (*p_statFunc)();
?void (Ctest::*p_dynFunc)();//注意非靜態成員函數指針的定義需指明在那個類的域內
?void (Ctest::*p_virtFunc)();
?p_statFunc =& Ctest::statFunc;
?p_dynFunc =& Ctest::dynFunc;
?p_virtFunc =& Ctest::virtFunc;
?p_statFunc();
?//非靜態成員函數指針的調用也與普通函數不同,另外因為.*的優先級比()低,所以需要用括號把左邊的操作
?//數括起來,如果寫成Object.*p_dynFunc();將無法通過編譯
?(Object.*p_dynFunc)();
?(Object.*p_virtFunc)();
?while(1);
}
代碼運行之后顯示如下,從輸出內容可見我們成功調用了對應的成員函數:
總結
以上是生活随笔為你收集整理的获取成员函数地址及获取函数地址的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习中的十七条建议
- 下一篇: 产品与市场,究竟哪一个重要