我們知道,在c++的非靜態成員函式中,有乙個隱含的引數,即this指標,利用它,我們可以訪問相應物件的資料成員,那麼究竟this指標是如何作用的呢?下面先來看乙個例子。有下面的乙個簡單的類:
[cpp]view plain
copy
print?
class cnullpointcall
; int cnullpointcall::m_istatic = 0;
void cnullpointcall::test1()
void cnullpointcall::test2()
void cnullpointcall::test3(int itest)
void cnullpointcall::test4()
class cnullpointcall
;int cnullpointcall::m_istatic = 0;
void cnullpointcall::test1()
void cnullpointcall::test2()
void cnullpointcall::test3(int itest)
void cnullpointcall::test4()
那麼下面的**都正確嗎?都會輸出什麼?
[cpp]view plain
copy
print?
cnullpointcall *pnull = null; // 沒錯,就是給指標賦值為空
pnull->test1(); // call 1
pnull->test2(); // call 2
pnull->test3(13); // call 3
pnull->test4(); // call 4
cnullpointcall *pnull = null; // 沒錯,就是給指標賦值為空
pnull->test1(); // call 1
pnull->test2(); // call 2
pnull->test3(13); // call 3
pnull->test4(); // call 4
你肯定會很奇怪我為什麼這麼問。乙個值為null的指標怎麼可以用來呼叫類的成員函式呢?!可是實事卻很讓人吃驚:除了call 4那行**以外,其餘3個類成員函式的呼叫都是成功的,都能正確的輸出結果,而且包含這3行**的程式能非常好的執行。經過細心的比較就可以發現,call 4那行**跟其他3行**的本質區別:類cnullpointcall的成員函式中用到了this指標。
在編繹階段,當遇到通過指標呼叫成員函式時,編繹器會首先檢查該成員函式是否為虛函式,如果不是(是虛函式的情況待會兒再講),編繹器會再此插入一些實現**,它會在函式呼叫之前,首先將物件的首位址放入到ecx暫存器中,然後才是函式呼叫的語句(注意,不同的編繹器在this指標的實現上可能會有所區別,但是c++編譯器必須遵守c++標準,因此對於this指標的實現應該都是差不多的)。在我們這個程式中物件的首位址就是null,由於前三個函式並沒有使用到時this指標,所以自然不會出問題,而對於test4,由於要訪問m_itest資料成員,程式將從ecx暫存器中取出物件的首位址,也就是null,然後再通過null訪問m_itest資料成員,這才是出問題的原因。
了解了這一點後,我們再來看呼叫函式為虛函式的情況。要了解這一情況發生時的機理,我們必需首先了解虛表的概念。c++中的虛函式的作用主要是實現了多型的機制,而這一機制的實現靠的就是虛表,簡稱為v-table。在這個表中,主是要乙個類的虛函式的位址表,這張表解決了繼承、覆蓋的問題,保證其容真實反應實際的函式。對於含有虛函式的類,它的每個物件分配的記憶體空間的最前端就儲存有指向該類的虛表的位址。如下圖所示:
為了更好的理解它,我們來看乙個比較奇怪的例子。
假設我們有這樣的乙個類:
[cpp]view plain
copy
print?
class a
virtual
void fun1()
virtual
void fun2()
virtual
void fun3()
private:
int d;
};
class a
virtual void fun1()
virtual void fun2()
virtual void fun3()
private:
int d;
};
按照上面的說法,我們可以通過a的例項來得到虛函式表。 下面是實際例程:
[cpp]view plain
copy
print?
typedef
void (*func)();
int main(int argc, char** argv)
typedef void (*func)();
int main(int argc, char** argv)
在這個例項裡面,fs實際上就指向了虛表的首位址,我們可以把它想象成乙個陣列,這樣就可以對虛函式進行呼叫了,而且這一方法還可以繞過它對訪問修飾的限制,即使是私有虛函式,我們也可以照呼叫不誤。
關於虛表就說到這裡,下面還是回到對通過指標呼叫的函式是虛函式的討論中來。這在種情況下,編繹器插入的**完成的功能大概如下:還是首先將物件的首位址放到ecx暫存器中,然後插入函式呼叫的**,只不過這裡不再是乙個簡單的函式首位址完事,它會插入一些**通過前面提到的虛表機制找尋真正要呼叫的函式的入口位址。
在上乙個例子中,如果我們呼叫fs[2]()函式會怎麼樣呢?因為我們是直接通過虛表呼叫的成員函式,所以它沒有事先將物件的首位址放到ecx暫存器中,而該成員函式是會通過exc暫存器中儲存的原先的值去訪問資料成員d的,那麼它輸出的就將是乙個沒有意義的隨機值罷了。
通過以上對this指標以及虛表的討論,回想c++中關於成員函式指標的使用,我們就可以理解它到底和普通的函式指標有何不同了,一切都是this指標的緣故,通過成員指標呼叫函式會事先將物件的首位址放到ecx暫存器中,這也就是它們的根本區別所在。
**c++中的this指標
c++ 虛函式表解析
虛表與虛指標
網上各種關於這個的文章,但是我卻找了很久才找到我想要的東西,小小的總結一下。因為我心中一直有個疑問 虛表存在什麼地方,是類還是物件裡面?虛指標又存在什麼地方,是類還是物件?鍥而不捨終於在第一篇文章找到我想要的東西。1.c 類中的過載 看看下面的 html include using namespac...
虛表 虛指標
虛函式在c 中的實現機制就是用虛表和虛指標,但是具體是怎樣的呢?從more effecive c 其中一篇文章裡面可以知道 是每個類用了乙個虛表,每個類的物件用了乙個虛指標。具體的用法如下 cpp view plain copy class a class b public a a,b的實現省略 因...
虛函式,虛表,虛表指標
分享一篇文章,詳細解釋了為什麼通過基類指標指向基類物件或派生類物件,就可以呼叫相應類的虛函式。自 一 概述 為了實現c 的多型,c 使用了一種動態繫結的技術。這個技術的核心是虛函式表 下文簡稱虛表 本文介紹虛函式表是如何實現動態繫結的。二 類的虛表 每個包含了虛函式的類都包含乙個虛表。我們知道,當乙...