您好,登錄后才能下訂單哦!
這篇文章主要講解了“C++ 多態與虛函數、與構造函數和析構函數有什么聯系”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“C++ 多態與虛函數、與構造函數和析構函數有什么聯系”吧!
面向對象編程中,多態的含義是“一個接口,多種實現”。
多態分為靜態多態和動態多態。靜態多態是通過模板化和重載技術來實現,在編譯的時候確定。動態多態通過虛函數和繼承關系來實現,執行動態綁定,在運行的時候確定。
C++中運行時的多態是指根據對象的實際類型來調用相應的函數。如果對象類型是派生類,就調用派生類的函數;如果對象類型是基類,就調用基類的函數。
C++多態性是通過虛函數來實現的,虛函數允許子類重新定義成員函數,而子類重新定義父類方法稱為覆蓋或重寫(override)。
虛函數:
用virtual關鍵字申明的函數叫做虛函數,虛函數肯定是類的成員函數。
虛函數的作用就是實現動態綁定,也就是在程序的運行階段動態地選擇合適的成員函數。
具體的實現方式,在基類中定義了虛函數后,可以在基類的派生類中對虛函數重新定義,在派生類中重新定義的函數應與虛函數具有相同的形參個數和形參類型,以實現統一的接口,不同定義過程。
如果在派生類中沒有對虛函數重新定義,則它繼承其基類的虛函數,此時派生類也為抽象類,不能實例化對象。
虛函數表:
當一個類含有一個乃至多個虛函數的時候,將在全局數據區(靜態區)中存儲該類相應的虛函數表vtbl,編譯器給類的每個對象添加一個相同隱藏的成員——虛函數表指針vptr,指向自身類的虛函數表。
虛函數表的大小在編譯時確定,不必動態分配內存空間進行存儲,因此不虛函數表不存在堆中,根據以上特征,虛函數表類似于類中靜態成員變量。靜態成員變量也是全局共享,大小確定,所以虛函數表和靜態成員變量一樣,存放在全局數據區。
虛函數表中保存了類對象進行聲明的虛函數的地址,即虛函數表的元素是指向類成員函數的指針。也就是說我們可以通過vptr訪問虛函數表,進而訪問被聲明的虛函數的的地址,從而調用相應的虛函數。
同一個類的不同對象的vptr實際上指向同一張虛函數表。vptr的設定和重置都由每一個類的構造函數,析構函數和拷貝賦值運算符自動完成。一般來說,將在構造函數中進行虛表的創建和虛表指針的初始化。
基類的虛函數表和派生類的虛函數表分別為保存在不同位置的兩個獨立數組,也就是說基類的隱藏成員和派生類的隱藏成員指向不同的地址。
如果派生類沒有重新定義基類的某個虛函數A,則派生類的虛函數表vtbl將保存基類的虛函數A的原始地址(此時派生類和基類的虛函數表中保存的虛函數A的地址是一樣的)。
如果派生類重寫了基類的某個虛函數B,則派生類的虛函數表vtbl將保存新的虛函數B的地址(此時的虛函數B其實有兩個版本,分別被基類和派生類的虛函數表分開保存)。
純虛函數:
純虛函數是在基類中聲明的虛函數,它在基類中沒有定義,要求任何派生類都要定義自己的實現方法。在基類中實現純虛函數的方法是在函數原型后加“=0”,即virtual void funtion1() = 0;
含有純虛函數的類為抽象類,不能聲明對象,只是作為基類為派生類服務。除非在派生類中完全實現基類的所有虛函數,否則派生類也是抽象類,不能實例化對象。
抽象類不能定義對象,但是可以作為指針或引用類型使用。
不能聲明虛函數:
常見的不能聲明為虛函數的有:普通函數(非成員函數);靜態成員函數;內聯成員函數;構造函數;友元函數。
普通函數(非成員函數)只能被overload,不能被override,聲明為虛函數也沒有意義。
靜態成員函數對于每個類來說只有一份代碼,所有的對象都共享這一份代碼,也沒有要動態綁定的必要性。
內聯函數在編譯時被展開,從而可減少函數調用花費的代價,虛函數是在運行時才進行動態綁定,從而使得繼承對象能夠準確的執行自己的動作。
構造函數目的是為了生成對象時進行對象初始化,虛函數目的是在不同類型的對象中調用不同的方法以產生不同的動作,當對象還沒有生成時,虛函數是沒有意義的,而構造函數是為了在對象還沒有生成時實例化對象。
友元函數不支持繼承,對于沒有繼承特性的函數就沒有虛函數的說法。
早綁定和晚綁定:
c++編譯器在編譯的時候,要確定每個對象調用的函數(非虛函數)的地址,這稱為早期綁定,當我們將Son類對象的地址賦給指針pFather時,C++編譯器進行了類型轉換,此時C++編譯器認為指針變量pFather保存的就是Father對象的地址,當在main函數中執行pFather->Say(),調用的是Father對象的Say函數。
從內存角度看:
前面輸出的結果是因為編譯器在編譯的時候,就已經確定了對象調用的函數地址,要解決這個問題就要使用晚綁定,當編譯器使用晚綁定時候,就會在運行時再去確定對象的類型以及正確的調用函數,而要讓編譯器采用晚綁定,就要在基類中聲明函數時使用virtual關鍵字.一旦某個函數在基類中聲明為virtual,那么在所有的派生類中該函數都是virtual,而不需要再顯式地聲明為virtual。
編譯器為每個對象提供了一個虛表指針(即vptr),這個指針指向了對象所屬類的虛表,在程序運行時,根據對象的類型去初始化vptr,從而讓vptr正確的指向了所屬類的虛表,從而在調用虛函數的時候,能夠找到正確的函數。由于pFather實際指向的對象類型是Son,因此vptr指向的Son類的vtable,當調用pFather->Son()時,根據虛表中的函數地址找到的就是Son類的Say()函數。
從內存角度看:
構造函數不能為虛函數。
從C++之父Bjarne的回答我們應該知道C++為什么不支持構造函數是虛函數了,簡單講就是沒有意義。
虛函數的作用在于通過子類的指針或引用來調用父類的那個成員函數。而構造函數是在創建對象時自己主動調用的,不可能通過子類的指針或引用去調用。
構造函數目的是為了生成對象時進行對象初始化,虛函數目的是在不同類型的對象中調用不同的方法以產生不同的動作,當對象還沒有生成時,內存中不存在虛函數指針和虛函數表,此時虛函數是沒有意義的,而構造函數是為了在對象還沒有生成時實例化對象,如果構造函數被聲明為虛函數,內存空間還沒有虛函數表,該構造函數將變得沒有意義,所以構造函數不能是虛函數。
當派生類指針指向用new運算符生成的派生類對象時,delete派生類指針,將執行派生類的析構函數,再執行基類的析構函數。因為在實例化派生類對象時,先實例了基類對象。
當基類指針指向用new運算符生成的派生類對象時,delete基類指針,因為編譯器又進行了類型轉換,默認為基類指針指向基類對象的地址,根據早綁定中內存的關系,如果基類析構函數沒有聲明為虛函數,將只執行基類的構造函數,如果基類析構函數聲明為虛函數,盡管進行類型轉換,根據晚綁定中內存的關系,不管基類對象還是派生類對象都有相應的虛函數表指針,因此析構時會先調用派生類的析構函數(vptr指向自身的析構函數),再調用基類的析構函數(聲明派生類對象先實例了基類對象)。
#include<iostream> using namespace std; class Base { public: Base() { cout<<"Base::Base()"<<endl; fun(); } virtual ~Base() { cout<<"Base::~Base()"<<endl; fun(); } virtual void fun() { cout<<"Base::fun() virtual"<<endl; } }; class Derived:public Base { public: Derived() { cout<<"Derived::Derived()"<<endl; fun();; } ~Derived() { cout<<"Derived::~Derived()"<<endl; fun(); } virtual void fun() { cout<<"Derived::fun() virtual"<<endl; } }; int main() { //basa Base *b = new Base(); delete b; cout<<endl; /* Base::Base() Base::fun() virtual Base::~Base() Base::fun() virtual */ //Derived Derived *d = new Derived(); delete d; cout<<endl; /* Base::Base() Base::fun() virtual //派生類還不存在 Derived::Derived() Derived::fun() virtual //派生類已存在 Derived::~Derived() Derived::fun() virtual //派生類還存在 Base::~Base() Base::fun() virtual //派生類不存在 */ //Base* Derived,父類指針指向子類對象的實現原理 Base *bd = new Derived(); delete bd; cout<<endl; /* Base::Base() Base::fun() virtual //派生類不存在 Derived::Derived() Derived::fun() virtual //派生類已存在 //當基類析構函數沒用聲明為虛函數時,將不調用派生類的析構函數 Derived::~Derived() Derived::fun() virtual //派生類還存在 Base::~Base() Base::fun() virtual //派生類不存在 */ system("pause"); return 0; }
感謝各位的閱讀,以上就是“C++ 多態與虛函數、與構造函數和析構函數有什么聯系”的內容了,經過本文的學習后,相信大家對C++ 多態與虛函數、與構造函數和析構函數有什么聯系這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。