虛函數的作用及應用


 1、虛函數的作用

  ①、子類重寫父類的虛函數后,在子類、父類中調用的虛函數都是子類的虛函數。(有一個特殊情況就是在父類的構造函數中調用的虛函數是父類中的虛函數,因為此時子類還沒有構造完成。)

  ②、子類重寫父類的虛函數后,父類指針指向父類對象的話,通過這個父類指針調用的是父類中的虛函數;子類指針指向子類對象的話,通過這個子類指針調用的是子類的虛函數;父類指針指向子類對象的話,通過這個父類指針調用的是子類的虛函數。

  ③、父類指針可以指向父類對象和子類對象,當指向子類對象的時候我們一般是想實現對於一些虛函數的自定義。

  ④、子類指針可以指向子類對象,但子類指針不可以指向父類對象,除非使用dynamic_cast 或 static_cast進行強制轉換,但我們一般不會這樣做,因為是很危險的。

  應用1: 比如在MFC中,我們想要實現一個自定義的按鈕(改變按鈕背景、文本顏色),這時候我們就可以生成一個繼承自CButton類的子類CMyButton來使用,在子類中重寫父類的虛函數DrawItem,因為DrawItem是父類CButton的繪制虛函數。當我們在子類中重寫父類的DrawItem虛函數后,父類中調用的就是子類的函數,這樣就實現了我們對於CButton的訂制,即CButton的自繪。

           再比如MFC的對話框類中,右擊屬性可以看到重寫下面有很多函數,比如OnInitDialog、OnCancel、OnOK等,這些函數都是父類的虛函數,當特定事件發生時會被自動調用,我們可以對這些函數進行重寫,然后父類中到時候調用的就是我們在子類中自定義的函數,從而實現對於特定事件的自定義。

  應用2:比如有兩個類CServer和CTcpServer,CServer中包含了CTcpServer類型的成員變量,而CTcpServer中也要包含CServer對象指針以便回調CServer中的成員函數。為了避免頭文件的重復包含,我們在CTcpServer中只保存CServer的父類CListener指針,在CListener中將回調函數聲明為虛函數,在CServer中重寫這個虛函數。這樣在CTcpServer中通過CListener指針調用的虛函數就是CServer中重寫的虛函數。

//Listener.h
class CListener { public: virtual void VirtualFun1() {} virtual void VirtualFun2() {} }; //Server.h
#include "Listener.h" #include "TcpServer.h"
using namespace std; class CServer : public CListener { public: CServer():m_TcpSvr(this){} ~CServer(); virtual void VirtualFun1() { cout << "CServer::VirtualFun1() called" << endl; } virtual void VirtualFun2() { cout << "CServer::VirtualFun2() called" << endl; } private: CTcpServer m_TcpSvr; }; //TcpServer.h
#include "Listener.h"

class CTcpServer { public: CTcpServer(CListener* pSvr):m_pSvr(pSvr){} ~CTcpServer(); private: CListener* m_pSvr; };
View Code

  上面代碼中的CServer的父類CListener可以不單獨在一個文件中聲明,而是在CTcpServer類中聲明,這樣更加方便:

//Server.h
#include "TcpServer.h"
using namespace std; class CServer : public CTcpServer::CListener { public: CServer():m_TcpSvr(this){} ~CServer(); virtual void VirtualFun1() { cout << "CServer::VirtualFun1() called" << endl; } virtual void VirtualFun2() { cout << "CServer::VirtualFun2() called" << endl; } private: CTcpServer m_TcpSvr; }; //TcpServer.h
class CTcpServer { public: class CListener { public: virtual void VirtualFun1() {} virtual void VirtualFun2() {} }; public: CTcpServer(CListener* pSvr):m_pSvr(pSvr){} ~CTcpServer(); private: CListener* m_pSvr; };
View Code

   界面編程中也經常用到這種方法,比如一個主窗口包含一個或多個子窗口(子控件),在子窗口(子控件)類中需要回調主窗口的一些函數。這種情況就可以使用上面所說的方法來實現。

   當然上面所說的子控件一般都是主窗口的成員對象,如果子控件只是一個臨時展示的對象即函數棧上的一個對象的話也可以在子控件中通過dynamic_cast<>將主窗口的父類指針轉換成子類指針來使用,這樣只需要在源文件中包含主窗口的頭文件,避免了頭文件重復包含。

   不過因為C++11中有了function可以很方便的進行兩個對象間的通信,所以推薦使用function來實現應用2中使用虛函數來進行兩個對象的通信。

2、虛函數表

  虛函數是C++多態性的表現,具體為子類重寫(或者叫覆蓋override)父類的虛函數,可以實現通過父類指針調用子類的虛函數。函數的重載不是多態性的表現,因為這些函數擁有不同的參數類型或參數個數。

  虛函數是通過虛函數表來實現的,虛函數表實際上是一個函數指針數組,它保存了本類中的虛函數的地址。虛函數表屬於類中而不屬於類的某個實例,所以不會為每個實例專門生成一個虛函數表,但每個類的實例中保存指向了這個虛函數表的指針(所以包含虛函數的對象的大小會增加一個指針的大小),而且這個指針保存在對象實例空間的最前面。

  比如對於一個Base基類(b為其實例),其包含三個虛函數f、g和h,虛函數表就是這樣的:

  而Base的派生類Derive(d為其實例),也包含三個虛函數f1、g1和h1,它的虛函數表是這樣的:

  而如果派生類重寫了基類的f函數的話就造成了在派生類的虛函數表中,派生類的虛函數覆蓋了基類的虛函數,在實際調用的時候就會調用Derive::f():

 

  轉載及參考出處:log.csdn.net/sanfengshou/article/details/4574604


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM