C++抽象類的純虛函數


 1,定義:

   純虛函數是在基類中聲明的虛函數,它在基類中沒有定義,但要求任何派生類都要定義自己的實現方法。在基類中實現純虛函數的方法是在函數原型后加"=0" ,同 java中抽象方法類似

virtual void funtion1()=0 

二、引入原因:


1、為了方便使用多態特性,我們常常需要在基類中定義虛擬函數。
2、在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。

    為了解決上述問題,引入了純虛函數的概念,將函數定義為純虛函數(方法:virtual ReturnType Function()= 0;),則編譯器要求在派生類中必須予以重載以實現多態性。同時含有純虛擬函數的類稱為抽象類它不能生成對象。這樣就很好地解決了上述兩個問題。

 

三、相似概念:

1、多態性

    指相同對象收到不同消息或不同對象收到相同消息時產生不同的實現動作。C++支持兩種多態性:編譯時多態性,運行時多態性。

a.編譯時多態性:通過重載函數實現

b 運行時多態性:通過虛函數實現。

2、虛函數

    虛函數是在基類中被聲明為virtual,並在派生類中重新定義的成員函數,可實現成員函數的動態重載

3、抽象類

   包含純虛函數的類稱為抽象類。由於抽象類包含了沒有定義的純虛函數,所以不能定義抽象類的對象。

程序舉例:

基類:
class A 
{
    public:
    A();
   void f1();
   virtual void f2();
   virtual void f3()=0;
   virtual ~A();

};

子類:
class B : public A 
{
   public:
   B();
   void f1();
   void f2();
   void f3();
   virtual ~B();

};
主函數:
int main(int argc, char* argv[])
{
   A *m_j=new B();
   m_j->f1();
   m_j->f2();
   m_j->f3();
   delete m_j;
   return 0;
}

f1()是一個普通的重載.
調用m_j->f1();會去調用A類中的f1(),它是在我們寫好代碼的時候就會定好的.
也就是根據它是由A類定義的,這樣就調用這個類的函數.
f2()是虛函數.
調用m_j->f2();會調用m_j中到底保存的對象中,對應的這個函數.這是由於new的B
對象.
f3()與f2()一樣,只是在基類中不需要寫函數實現.

轉:

多態上java和C++的不同之處

發表於1年前
java和C++在多態上最主要的區別是,前者只支持單繼承,后者則支持多繼承。為了達到多繼承的目的,java中有了內部類和接口。

1.java中接口相當於C++中的“純虛類”(所有的成員方法都是純虛函數的類),即其中所有的方法都不包括函數體;而java中的抽象類和C++中的抽象類在意義上則大差不離。

2.java中的抽象類由關鍵字abstract clas 聲明,若一個類被聲明為抽象類那么則該類中必須要有一個抽象方法abstract function;C++中的一個類中若有一個方法聲明成 純虛函數virtual function=0,那么該類就是抽象類。若一個抽象類被其派生類繼承,基類中的所有抽象(純虛)方法若被子類實現那么該類就不在是抽象類,否則這個子類還是抽象類,那么它同樣不能被實例化,這一點在java和C++中是相同的。

3.在面向對象的編程中,請牢記所有基類最好是抽象類(或者接口),這樣才符合OO編程思想。因為基類(接口)存在的目的是因為,它們包含同一類對象中的某些共性的行為和屬性,將這些共性的行為和屬性抽象成抽象(純虛)函數放在基類中,當不同子類繼承基類時,可以按照自身特有稟賦對這些抽象的共性的行為進行具體地實現(動物都是進食的行為,但不同的動物會有不同的進食方式),這些特定的具體實現的函數就變為虛函數,可以用於多態了;但是若是該類別的共性行為可以確定,不隨子類的差別而又區分,那么與這個共性行為相關的函數不需要聲明為純虛函數。按照這個意義,基類中的抽象(純虛)方法就如同一個個“接口”(畫布),不同的子類對這些抽象(純虛)方法進行實現,就如同在這些“畫布”上畫出不同的風格的作品。但有一點要注意:由於java封裝了C++中的對象的指針、句柄和引用等相關概念,讓人不能那么明顯的體會到C++中的這些概念,這就產生了這么一個現象:java類中的普通方法相當於C++中的虛方法,java中的抽象方法相當於C++中的純虛方法。例子:JAVA中 B extends A, A和B 中都有函數fun,即B重載了fun,那么當A a=new B;a.fun調用的是B類中的fun,也就是調用對象引用a所指的對象的fun;C++中 B :public A,A和B 中都有虛函數fun,即B重載了fun,那么當A* a=new()B,a->fun調用的也是B類中的fun,就是指針a所指對象的fun。在C++中如若不把fun聲明為虛函數,那么a->fun調用的將是A類中的fun,,因為C++OO中的函數調用是根據原始指針類型所擁有的函數來進行的,這樣就無法實現在運行時的多態功能,只有將它們聲明為虛函數才能實現動態綁定。

4.由於C++中存在析構函數,為了在多態的過程中,不造成內存“泄漏”,通常將虛基類的析構函數定義為虛析構函數()。考慮下面代碼:

class Animal

{

public:

Animal()

{

}

virtual ~Animal()

{

cout<<"Animal descontruct"<<endl;

}

};

class Fish:public Animal

{

public:

Fish()

{

}

~Fish()

{

cout<<"Fish descontruct"<<endl;

}

};

 

int main()

{

Animal* a=new Fish();

delete a;

}

結果運行為:Fish deconstruct

Animal deconstruct。若基類Animal的析構函數不為虛,那么程序將只打印Animal deconstruct,因為a在編譯期間就被定義為指向Animal類對象的指針,在Animal類 的一個對象(由於父類Animal的構造函數沒有參數,那么在new fish的時候父類構造函數會被自動調用,這一點和java相同;當父類的構造函數有參數時,C++中在子類的構造函數那里用:父類名(參數表)來實現的,而java中是在子類的構造函數函數中用super關鍵字來調用父類的構造函數)生命結束后,編譯器認為a是指向父類anima類型的指針,此時會跳過子類的析構函數,直接根據a的指針類型(指向父類)來調用父類的析構函數。而當父類的析構函數是虛時,在delete a時,會有動態綁定,即先調用a所指的對象的析構函數,再調用父類的析構函數。

JAVA中則不存在C++中相應的問題:只要將父類的引用指向一個子類對象,那么只要子類覆蓋了父類中的方法,當通過父類引用來調用方法時,會直接調用子類中的方法。

5.對比:C++中的虛方法相當於java中的一般方法,C++中的純虛方法相當於java中的虛方法。C++中的一般方法無法實現多態,而java的可以。C++中,父類中最好有純虛函數(父類不具有而子類具有的共性而又具體的行為),虛函數根據設計思路可有可無,當你需要在子類中重新定義(重載)父類中的一個函數時,那么為了實現多態,切記要在父類中把該函數聲明為虛函數(父類和子類具有的個性或者共性行為),在子類最好實現父類中的純虛方法,如若不然(雖然程序不會出錯,但是這個子類還是抽象類,無法被實實例化),你繼承這個父類還有什么意思?虛函數可以在子類中重載,也可以不重載。重載了的話,那么該虛函數對應的行為就是子類特有的;否則就是父類和子類共有的行為。最后無論子類重不重載子類中的虛函數,該函數始終都是一個虛函數。

當創建子類對象時,java和C++都會調用父類的構造函數,但調用父類的初始化方法(不是構造函數),並不會產生父類的對象,:java中的路由機制是:當調用父類構造函數時,會逐層遞歸調用每個父類的初始化方法直到OBJECT類為止(OBJECT無父類),在編譯階段將該子類所有父類的屬性,方法和構造函數作為初始化方法寫進方法區,每個父類有幾個構造函數,就會有相應數目的初始化方法,編譯器會根據子類構造函數的參數來調用相應的初始化方法,來進行父類的初始化。當子類對象生命完結后,JVM會先釋放子類對象的內存,在釋放父類的初始化方法所占的內存。而C++中由於存在析構函數,當父類有虛析構時,會先析構子類的對象的內存,然后在析構父類的初始化方法所占的內存。不過C++的析構函數所具有的作用不僅僅在於釋放資源,也可以用來用作其他功能(通過static類成員NUM來計數細菌的數量,每新建一個細菌對象,在構造函數中進行++NUM;一個細菌死亡在析構函數中進行--NUM)。


免責聲明!

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



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