C++ 虛函數與純虛函數

一、C++虛函數
1、什麼是虛函數?(虛函數只能藉助於指針或者引用來達到多態的效果)
class A
{
public:
    virtual void foo()    // 虛函數關鍵字 virtual
    {
        cout<<"A::foo() is called"<<endl;
    }
};
class B:public A
{
public:
    void foo()
    {
        cout<<"B::foo() is called"<<endl;
    }
};
int main(void)
{
    A *a = new B();
    a->foo();   // 在這裏,a雖然是指向A的指針,但是被調用的函數(foo)卻是B的!
    return 0;
}
這個例子是虛函數的一個典型應用,通過這個例子,也許你就對虛函數有了一些概念。它虛就虛在所謂“推遲聯編”或者“動態聯編”上,一個類函數的調用並不是在編譯時刻被確定的,而是在運行時刻被確定的。由於編寫代碼的時候並不能確定被調用的是基類的函數還是哪個派生類的函數,所以被成爲“虛”函數。

2、虛函數用來做什麼?(虛函數只能藉助於指針或者引用來達到多態的效果)
虛函數的作用:基於向上類型轉換,基類通過虛函數可以對多個子類相似的功能實現統一管理。
下面我們通過一個例子來理解一下:
#include <iostream>
using namespace std;
class Animal{
public:
    virtual void speak(){ cout << "Animal" << endl; }
};
class Cat : public Animal
{
public:
    void speak(){ cout << "Cat" << endl; }
};
class Dog : public Animal
{
public:
    void speak(){ cout << "Dog" << endl; }
};
// 注意:此處必須是引用,不然類cat、dog傳參時會被自動轉換爲基類animal,輸出均爲Animal
void Speak(Animal &a){
    a.speak();
}
int main(){
    Animal animal;
    Cat cat;
    Dog dog;
    Speak(animal);  // 輸出 "Animal"
    Speak(cat);     // 輸出 "Cat"
    Speak(dog);     // 輸出 "Dog"
}
通過顯示我們注意到以下兩點:
(1) 儘管在頂層函數的定義中是以基類A作爲其參數,但卻能接受基類A的任一子類作爲其參數。事實上,這是基於自動向上類型轉換,即子類轉換爲它的父類型。
(2) 雖然子類轉換成了它的父類型,但卻可正確調用屬於子類而不屬於父類的成員函數。這是虛函數的功勞。
這樣,我們通過設計一個以基類型作爲參數的頂層函數,就可實現基類及其所有子類相似功能的統一管理,而不用理會不同對象自身的類型。

二、C++純虛函數
1、定義
 純虛函數是在基類中聲明的虛函數,它在基類中沒有定義,但要求任何派生類都要定義自己的實現方法。在基類中實現純虛函數的方法是在函數原型後加“=0”
 virtual void funtion1()=0
2、例子
#include <iostream>
using namespace std;
class Animal{
public:
    virtual void speak() = 0;   // 聲明虛函數
};
class Cat : public Animal
{
public:
    void speak(){ cout << "Cat" << endl; }
};
class Dog : public Animal
{
public:
    void speak(){ cout << "Dog" << endl; }
};
// 注意:此處必須是引用,不然類cat、dog傳參時會被自動轉換爲基類animal,輸出均爲Animal
void Speak(Animal &a){
    a.speak();
}
int main(){
    // Animal animal; 出錯,純虛函數不可創建對象
    Cat cat;
    Dog dog;
    Speak(cat);     // 輸出 "Cat"
    Speak(dog);     // 輸出 "Dog"
}
(1)爲了方便使用多態特性,我們常常需要在基類中定義虛擬函數。
(2)在很多情況下,基類本身生成對象是不合情理的。例如,動物作爲一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。
爲了解決上述問題,引入了純虛函數的概念,將函數定義爲純虛函數(方法:virtual ReturnType Function()= 0;),則編譯器要求在派生類中必須予以重寫以實現多態性。同時含有純虛擬函數的類稱爲抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。
聲明瞭純虛函數的類是一個抽象類。所以,用戶不能創建類的實例,只能創建它的派生類的實例。
純虛函數最顯著的特徵是:它們必須在繼承類中重新聲明函數(不要後面的=0,否則該派生類也不能實例化),而且它們在抽象類中往往沒有定義。
定義純虛函數的目的在於,使派生類僅僅只是繼承函數的接口。
純虛函數的意義,讓所有的類對象(主要是派生類對象)都可以執行純虛函數的動作,但類無法爲純虛函數提供一個合理的缺省實現。所以類純虛函數的聲明就是在告訴子類的設計者,“你必須提供一個純虛函數的實現,但我不知道你會怎樣實現它”。

三、抽象類與純虛函數
抽象類是一種特殊的類,它是爲了抽象和設計的目的爲建立的,它處於繼承層次結構的較上層。
(1)抽象類的定義:  稱帶有純虛函數的類爲抽象類。
(2)抽象類的作用:
抽象類的主要作用是將有關的操作作爲結果接口組織在一個繼承層次結構中,由它來爲派生類提供一個公共的根,派生類將具體實現在其基類中作爲接口的操作。所以派生類實際上刻畫了一組子類的操作接口的通用語義,這些語義也傳給子類,子類可以具體實現這些語義,也可以再將這些語義傳給自己的子類。
(3)使用抽象類時注意:
•   抽象類只能作爲基類來使用,其純虛函數的實現由派生類給出。如果派生類中沒有重新定義純虛函數,而只是繼承基類的純虛函數,則這個派生類仍然還是一個抽象類。如果派生類中給出了基類純虛函數的實現,則該派生類就不再是抽象類了,它是一個可以建立對象的具體的類。
•   抽象類是不能定義對象的。

總結:
1、純虛函數聲明如下: virtual void funtion1()=0; 純虛函數一定沒有定義,純虛函數用來規範派生類的行爲,即接口。包含純虛函數的類是抽象類,抽象類不能定義實例,但可以聲明指向實現該抽象類的具體類的指針或引用。
2、虛函數聲明如下:virtual ReturnType FunctionName(Parameter);虛函數必須實現,如果不實現,編譯器將報錯,錯誤提示爲:error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"
3、對於虛函數來說,父類和子類都有各自的版本。由多態方式調用的時候動態綁定。
4、實現了純虛函數的子類,該純虛函數在子類中就編程了虛函數,子類的子類即孫子類可以覆蓋該虛函數,由多態方式調用的時候動態綁定。
5、虛函數是C++中用於實現多態(polymorphism)的機制。核心理念就是通過基類訪問派生類定義的函數。
6、在有動態分配堆上內存的時候,析構函數必須是虛函數,但沒有必要是純虛的。
7、友元不是成員函數,只有成員函數纔可以是虛擬的,因此友元不能是虛擬函數。但可以通過讓友元函數調用虛擬成員函數來解決友元的虛擬問題。
8、析構函數應當是虛函數,將調用相應對象類型的析構函數,因此,如果指針指向的是子類對象,將調用子類的析構函數,然後自動調用基類的析構函數。

有純虛函數的類是抽象類,不能生成對象,只能派生。他派生的類的純虛函數沒有被改寫,那麼,它的派生類還是個抽象類。
定義純虛函數就是爲了讓基類不可實例化化
因爲實例化這樣的抽象數據結構本身並沒有意義。
或者給出實現也沒有意義
實際上我個人認爲純虛函數的引入,是出於兩個目的
1、爲了安全,因爲避免任何需要明確但是因爲不小心而導致的未知的結果,提醒子類去做應做的實現。
2、爲了效率,不是程序執行的效率,而是爲了編碼的效率。

此文對原文稍作修改,並添加了例子,查看原文:
虛函數的作用:https://zhidao.baidu.com/question/192496025.html
虛函數和純虛函數的區別:http://blog.csdn.net/hackbuteer1/article/details/755886
發佈了30 篇原創文章 · 獲贊 3 · 訪問量 6808
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章