虛基類
在說明其做用前先看一段代碼
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, 純虛函數不能實化化,但能夠聲明指針。函數