1.爲何須要virtual
按照Java的思惟方式,在有了繼承和向上類型轉換(upcasting)以後,就能夠實現多態性了。可是在C++中彷佛並不能orz。考慮這種狀況:ios
#include<iostream> using std::cout; using std::endl; class A{ public: void f() const{ cout<<"class A's function"<<endl; } }; class B : public A{ public: void f() const{ cout<<"class B's function"<<endl; } }; int main(){ B b; A *ptr_a = &b; A &ref_a = b; ptr_a->f();//print: class A's function ref_a.f();//print: Class A's function }
在使用基類指針或引用調用一個派生類對象的函數時,咱們發現程序仍然在調用基類的函數,要想解決這種狀況,就要引入virtual關鍵字,將上面代碼裏的class A修改以下,main中的輸出就變成類B中f()的輸出了。程序員
class A{ public: virtual void f() const{ cout<<"class A's function"<<endl; } };
那麼爲何Java不須要呢?由於virtual關鍵字實現功能的同時,會增長該類一些操做的時間和空間佔用,C++將這部分佔用的優化決定權交給了程序員,以實現可能的效率提升;而Java內置了virtual的機制,沒有提升效率的選擇,可是簡化了編程。(關於virtual的具體機制,建議參考Thinking in C++)編程
有兩點須要注意的:
第1、當使用基類指針指向派生類時,沒法經過基類指針直接調用派生類中增長的函數(基類中沒有同名虛函數),除非將基類指針強制類型轉換爲派生類指針。
第2、只能經過基類指針或者引用來調用派生類對象,若是咱們將一個派生類對象經過值傳遞的方式傳遞給基類對象,這個對象被真的切成一個基類對象,而不具備任何派生類的內容。函數
2.純虛函數和抽象類
在類設計時,經常但願基類僅僅做爲派生類的一個接口,被繼承實現,而不會去建立基類對象,這時,能夠在基類中定義純虛函數,使其成爲一個抽象類。定義純虛函數語法是在一個虛函數聲名的基礎上,加上=0。例如:virtual void f() = 0;
注意:當繼承一個抽象類時,必須實現其全部的純虛函數,不然繼承出的類也是一個抽象類。優化
通常狀況下,在基類中咱們不會對純虛函數進行實現,可是C++提供了實現純虛函數的機制,這種方法可讓咱們定義一段公共代碼,使派生類能夠公用。spa
class A{ public: virtual void do() = 0; }; /* *純虛函數不能做爲inline函數實現,要放在類外! */ void A::do(){ //一些公共代碼 } class B : public A{ public: void do() { A::do(); //其餘代碼 } };
3.構造函數與虛函數
如上文所說,定義一個虛函數時,須要作一些額外的工做,完成這些工做的代碼其實被祕密插入到類構造函數的開頭部分。那麼就有一個問題,若是咱們在構造函數中調用虛函數會發生什麼現象?答案是,會調用這個虛函數的本地版本,即虛函數機制在構造函數中不工做。
另外,構造函數也不能被定義爲虛函數。
4.虛析構函數與純虛析構函數
構造函數不能被定義爲虛函數,而析構函數能夠,而且常常被定義爲虛函數。設計
#include<iostream> using namespace std; class Base1{ public: ~Base1(){cout<<"~Base1()"<<endl;} }; class Base2{ public: virtual ~Base2(){cout<<"~Base2()"<<endl;} }; class Derived1 : public Base1{ public: ~Derived1(){cout<<"~Derived1()"<<endl;} }; class Derived2 : public Base2{ public: ~Derived2(){cout<<"~Derived2()"<<endl;} }; int main(){ Base1* pd1 = new Derived1(); Base2* pd2 = new Derived2(); delete pd1; delete pd2; }
上面代碼的控制檯輸出:
~Base1()
~Derived2()
~Base2()
上面的代碼暴露出在使用多態性時,不把析構函數定義成虛函數所帶來的影響。這種錯誤不會馬上使程序崩潰,可是它不知不覺中使內存泄漏。指針
純虛析構函數的應用
在一些時候,咱們須要定義一個抽象類,可是恰好沒有其餘純虛函數,這時候咱們不妨將析構函數定義爲純虛的,由於做爲基類的析構函數原本就要求爲虛函數,將其進一步定義爲純虛函數並沒有太大不一樣。惟一須要注意的是,定義純虛析構函數時必須爲其提供函數體,以下。code
class A{ public: virtual ~A() = 0; }; A::~A(){ } class B:public A{ //不必定須要重定義析構函數,根據須要 }
還要注意一點,在析構函數中,虛機制也是不存在的,可經過下面的代碼體會。對象
#include<iostream> using namespace std; class Base{ public: virtual ~Base(){cout<<"~Base()"<<endl;f();} virtual void f(){cout<<"Base::f()"<<endl;} }; class Derived : public Base{ public: ~Derived(){cout<<"~Derived()"<<endl;} void f(){cout<<"Derived::f()"<<endl;} }; int main(){ Base * pb = new Derived(); delete pb; }
控制檯輸出爲:~Derived()~Base()Base::f()