理解虛基類、虛函數與純虛函數的概念

引言

     一直以來都沒有寫過一篇關於概念性的文章,因爲我覺得這些概念性的東西書本上都有並且說的也很詳細寫來也無用,今天突發奇想想寫一寫,下面就和大家討論一下虛基類、虛函數與純虛函數,一看名字就讓人很容易覺得混亂。不過不要緊待看完本文後你就會理解了。

正文

       虛基類
       在說明其作用前先看一段代碼

class A
{
public:
    
int iValue;
}
;

class B:public A
{
public:
    
void bPrintf(){cout<<"This is class B"<<endl;};
}
;

class C:public A
{
public:
    
void cPrintf(){cout<<"This is class C"<<endl;};
}
;

class D:public B,public C
{
public:
    
void dPrintf(){cout<<"This is class D"<<endl;};
}
;

void main()
{
    D d;
    cout
<<d.iValue<<endl; //錯誤,不明確的訪問
    cout<<d.A::iValue<<endl; //正確
    cout<<d.B::iValue<<endl; //正確
    cout<<d.C::iValue<<endl; //正確
}


從代碼中可以看出類B C都繼承了類A的iValue成員,因此類B C都有一個成員變量iValue ,而類D又繼承了B C,這樣類D就有一個重名的成員 iValue(一個是從類B中繼承過來的,一個是從類C中繼承過來的).在主函數中調用d.iValue 因爲類D有一個重名的成員iValue編譯器不知道調用 從誰繼承過來的iValue所以就產生的二義性的問題.正確的做法應該是加上作用域限定符 d.B::iValue 表示調用從B類繼承過來的iValue。不過 類D的實例中就有多個iValue的實例,就會佔用內存空間。所以C++中就引用了虛基類的概念,來解決這個問題。

class A
{
public:
    
int iValue;
}
;

class B:virtual public A
{
public:
    
void bPrintf(){cout<<"This is class B"<<endl;};
}
;

class C:virtual public A
{
public:
    
void cPrintf(){cout<<"This is class C"<<endl;};
}
;

class D:public B,public C
{
public:
    
void dPrintf(){cout<<"This is class D"<<endl;};
}
;

void main()
{
    D d;
    cout
<<d.iValue<<endl; //正確
}


在繼承的類的前面加上virtual關鍵字表示被繼承的類是一個虛基類,它的被繼承成員在派生類中只保留一個實例。例如iValue這個成員,從類 D這個角度上來看,它是從類B與類C繼承過來的,而類B C又是從類A繼承過來的,但它們只保留一個副本。因此在主函數中調用d.iValue時就不 會產生錯誤。

       虛函數
       還是先看代碼

class A
{
public:
    
void funPrint(){cout<<"funPrint of class A"<<endl;};
}
;

class B:public A
{
public:
    
void funPrint(){cout<<"funPrint of class B"<<endl;};
}
;

void main()
{
    A 
*p; //定義基類的指針
    A a;
    B b;
    p
=&a;
    p
->funPrint();
    p
=&b;
    p
->funPrint();
}


大家以爲這段代碼的輸出結果是什麼?有的人可能會馬上回答funPrint of class A 與 funPrint of class B 因爲第一次輸出是引用類A的實例啊,第二次輸出是引用類B的實例啊。那麼我告訴你這樣想就錯啦,答案是funPrint of class A 與 funPrint of class A 至於爲什麼輸出這樣的結果不在本文討論的範圍之內;你就記住,不管引用的實例是哪個類的當你調用的時候系統會調用左值那個對象所屬類的方法。比如說 上面的代碼類A B都有一個funPrint 函數,因爲p是一個A類的指針,所以不管你將p指針指向類A或是類B,最終調用的函數都是類A的funPrint 函數。這就是靜態聯篇,編譯器在編譯的時候就已經確定好了。可是如果我想實現跟據實例的不同來動態決定調用哪個函數呢?這就須要用到虛函數(也就是動態聯篇)

class A
{
public:
    
virtual void funPrint(){cout<<"funPrint of class A"<<endl;};
}
;

class B:public A
{
public:
    
virtual void funPrint(){cout<<"funPrint of class B"<<endl;};
}
;

void main()
{
    A 
*p; //定義基類的指針
    A a;
    B b;
    p
=&a;
    p
->funPrint();
    p
=&b;
    p
->funPrint();
}


在基類的成員函數前加virtual關鍵字表示這個函數是一個虛函數,所謂虛函數就是在編譯的時候不確定要調用哪個函數,而是動態決定將要調 用哪個函數,要實現虛函數必須派生類的函數名與基類相同,參數名參數類型等也要與基類相同。但派生類中的virtual關鍵字可以省略,也表示這是一個虛函數。下面來解決一下代碼,聲明一個基類的指針(必須是基類,反之則不行)p,把p指向類A的實例a,調用funPrint函數,這 時系統會判斷p所指向的實例的類型,如果是A類的實例就調用A類的funPrint函數,如果是B類的實例就調用B類的funPrint函數。

    純虛函數
    與其叫純虛函數還不如叫抽象類,它只是聲明一個函數但不實現它,讓派生類去實現它,其實這也很好理解。

class Vehicle
{
public:
    
virtual void PrintTyre()=0//純虛函數是這樣定義的
}
;

class Camion:public Vehicle
{
public:
    
virtual void PrintTyre(){cout<<"Camion tyre four"<<endl;};
}
;

class Bike:public Vehicle
{
public:
    
virtual void PrintTyre(){cout<<"Bike tyre two"<<endl;};
}
;

void main()
{
    Camion c;
    Bike b;
    b.PrintTyre();
    c.PrintTyre();
}


如上代碼,定義了一個交通工具類(Vehicle),類中有一函數可打印出交通工具的輪胎個數,但交通工具很多輪胎個數自然也就不確定,所以 就把它定義爲純虛函數,也就是光定義函數名不去實現它,類Camion繼承了Vehicle並實現了裏面的代碼,打印出有4個輪胎。Bike類也是一樣。有一點須要注意一下,純虛函數不能實化化,但可以聲明指針。


總結

    虛基類
    1, 一個類可以在一個類族中既被用作虛基類,也被用作非虛基類。
    2, 在派生類的對象中,同名的虛基類只產生一個虛基類子對象,而某個非虛基類產生各自的子對象。
    3, 虛基類子對象是由最派生類的構造函數通過調用虛基類的構造函數進行初始化的。
    4, 最派生類是指在繼承結構中建立對象時所指定的類。
    5, 派生類的構造函數的成員初始化列表中必須列出對虛基類構造函數的調用;如果未列出,則表示使用該虛基類的缺省構造函數。
    6, 從虛基類直接或間接派生的派生類中的構造函數的成員初始化列表中都要列出對虛基類構造函數的調用。但只有用於建立對象的最派生 類的構造函數調用虛基類的構造函數,而該派生類的所有基類中列出的對虛基類的構造函數的調用在執行中被忽略,從而保證對虛基類子對象只初始化一次。
    7, 在一個成員初始化列表中同時出現對虛基類和非虛基類構造函數的調用時,虛基類的構造函數先於非虛基類的構造函數執行。

    虛函數
    1, 虛函數是非靜態的、非內聯的成員函數,而不能是友元函數,但虛函數可以在另一個類中被聲明爲友元函數。
    2, 虛函數聲明只能出現在類定義的函數原型聲明中,而不能在成員函數的函數體實現的時候聲明。
    3, 一個虛函數無論被公有繼承多少次,它仍然保持其虛函數的特性。
    4, 若類中一個成員函數被說明爲虛函數,則該成員函數在派生類中可能有不同的實現。當使用該成員函數操作指針或引用所標識的對象時 ,對該成員函數調用可採用動態聯編。
    5, 定義了虛函數後,程序中聲明的指向基類的指針就可以指向其派生類。在執行過程中,該函數可以不斷改變它所指向的對象,調用不同 版本的成員函數,而且這些動作都是在運行時動態實現的。虛函數充分體現了面向對象程序設計的動態多態性。純虛函數 版本的成員函數,而且這些動作都是在運行時動態實現的。虛函數充分體現了面向對象程序設計的動態多態性。

    純虛函數
    1, 當在基類中不能爲虛函數給出一個有意義的實現時,可以將其聲明爲純虛函數,其實現留待派生類完成。
    2, 純虛函數的作用是爲派生類提供一個一致的接口。
    3, 純虛函數不能實化化,但可以聲明指針。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章