C 學習之旅 為什麼要用虛函式

2021-06-18 17:36:32 字數 2245 閱讀 6870

收集於:值得好好研究
一,  什麼是虛函式(如果不知道虛函式為何物,但有急切的想知道,那你就應該從這裡開始)

簡單地說,那些被virtual關鍵字修飾的成員函式,就是虛函式。虛函式的作用,用專業術語來解釋就是實現多型性(polymorphism),多型性是將介面與實現進行分離;用形象的語言來解釋就是實現以共同的方法,但因個體差異而採用不同的策略。下面來看一段簡單的**

class a

執行一下看看結果,喲呵,驀然回首,結果卻是兩個this is a。問題來了,p2明明指向的是class b的物件但卻是呼叫的class a的print()函式,這不是我們所期望的結果,那麼解決這個問題就需要用到虛函式

class a

用vc或dev-c++編譯執行一下,看看結果是不是輸出3,如果不是,那麼太陽明天肯定是從西邊出來。現在一步一步開始分析

void (*fun)(a*); 這段定義了乙個函式指標名字叫做fun,而且有乙個a*型別的引數,這個函式指標待會兒用來儲存從vtbl裡取出的函式位址

a* p=new b; 這個我不太了解,算了,不解釋這個了

long lvptraddr; 這個long型別的變數待會兒用來儲存vptr的值

memcpy(&lvptraddr,p,4); 前面說了,他們的例項物件裡只有vptr指標,所以我們就放心大膽地把p所指的4bytes記憶體裡的東西複製到lvptraddr中,所以複製出來的4bytes內容就是vptr的值,即vtbl的位址

現在有了vtbl的位址了,那麼我們現在就取出vtbl第乙個slot裡的內容

memcpy(&fun,reinterpret_cast

(lvptraddr),4); 取出vtbl第乙個slot裡的內容,並存放在函式指標fun裡。需要注意的是lvptraddr裡面是vtbl的位址,但lvptraddr不是指標,所以我們要把它先轉變成指標型別

fun(p); 這裡就呼叫了剛才取出的函式位址裡的函式,也就是呼叫了b::fun()這個函式,也許你發現了為什麼會有引數p,其實類成員函式呼叫時,會有個this指標,這個p就是那個this指標,只是在一般的呼叫中編譯器自動幫你處理了而已,而在這裡則需要自己處理。

delete p;和system("pause"); 這個我不太了解,算了,不解釋這個了

如果呼叫b::fun2()怎麼辦?那就取出vtbl的第二個slot裡的值就行了

memcpy(&fun,reinterpret_cast

(lvptraddr+4),4); 為什麼是加4呢?因為乙個指標的長度是4bytes,所以加4。或者memcpy(&fun,reinterpret_cast(lvptraddr)+1,4); 這更符合陣列的用法,因為lvptraddr被轉成了long*型別,所以+1就是往後移sizeof(long)的長度

三, 以一段**開始

#includeusing namespace std;

class a

你能估算出輸出結果嗎?如果你估算出的結果是a::fun和a::fun2,呵呵,恭喜恭喜,你中圈套了。其實真正的結果是b::fun和b::fun2,如果你想不通就接著往下看。給個提示,&a::fun和&a::fun2是真正獲得了虛函式的位址嗎?

首先我們回到第二部分,通過段實作**,得到乙個「通用」的獲得虛函式位址的方法

#includeusing namespace std;

//將上面「虛函式示例**2」新增在這裡

void callvirtualfun(void* pthis,int index=0)

int main()

現在我們擁有乙個「通用」的callvirtualfun方法。

這個通用方法和第三部分開始處的**有何聯絡呢?聯絡很大。由於a::fun()和a::fun2()是虛函式,所以&a::fun和&a::fun2獲得的不是函式的位址,而是一段間接獲得虛函式位址的一段**的位址,我們形象地把這段**看作那段callvirtualfun。編譯器在編譯時,會提供類似於callvirtualfun這樣的**,當你呼叫虛函式時,其實就是先呼叫的那段類似callvirtualfun的**,通過這段**,獲得虛函式位址後,最後呼叫虛函式,這樣就真正保證了多型性。同時大家都說虛函式的效率低,其原因就是,在呼叫虛函式之前,還呼叫了獲得虛函式位址的**。

最後的說明:本文的**可以用vc6和dev-c++4.9.8.0通過編譯,且執行無問題。其他的編譯器小弟不敢保證。其中,裡面的模擬方法只能看成模型,因為不同的編譯器的低層實現是不同的。例如this指標,dev-c++的gcc就是通過壓棧,當作引數傳遞,而vc的編譯器則通過取出位址儲存在ecx中。所以這些模擬方法不能當作具體實現

為什麼要用虛析構函式

1 問題引出 為什麼要使用虛析構函式?class a 父親 int main 我們知道在delete p 中 delete 操作符,會呼叫物件的析構函式,但是這裡傳入的是父類物件指標,所以delete 此時並不知道應該呼叫哪個析構函式,保險起見則只呼叫父類的析構函式,也就是說如果沒有使用虛析構函式,...

C 中為什麼要用虛函式 指標或引用才能實現多型?

學過c 的都知道,要實現c 的多型性必須要用到虛函式,並且還要使用引用或者指標,以前學習的時候書本上也是這麼說,但是書本上沒有說為什麼?其實只要你認真思考過這個問題你會有三個疑問 為什麼要用虛函式?為什麼要用指標或者引用?為什麼使用派生類和基類物件之間直接賦值不能實現?乙個簡單的例子 cpp vie...

C 析構函式為什麼要為虛函式

1.為什麼基類的析構函式是虛函式?在實現多型時,當用基類操作派生類,在析構時防止只析構基類而不析構派生類的狀況發生。下面 網路 源位址 a.第一段 includeusing namespace std class clxbase clxbase void dosomething class clxd...