C++虛函數,純虛函數,抽象類以及虛基類的區別
Part1.C++中的虛函數
- 什么是虛函數:
- 直觀表達就是,如果一個函數的聲明中有 virtual 關鍵字,那么這個函數就是虛函數。
- 直觀表達就是,如果一個函數的聲明中有 virtual 關鍵字,那么這個函數就是虛函數。
- 虛函數的作用:
- 虛函數的最大作用就是實現面向對象程序設計的一大特點,多態性,多態性表達的是一種動態的概念,是在函數調用期間,進行動態綁定,以達到什么樣的對象就實現什么樣的功能的效果。
- 虛函數的最大作用就是實現面向對象程序設計的一大特點,多態性,多態性表達的是一種動態的概念,是在函數調用期間,進行動態綁定,以達到什么樣的對象就實現什么樣的功能的效果。
- 虛函數的一般聲明語法:
- virtual 函數類型 函數名 (形參表)
- virtual 函數類型 函數名 (形參表)
- 注意:
- 虛函數的聲明只能出現在類的定義中,不能出現在成員函數實現的時候
- 虛函數一般不聲明為內聯函數,但是聲明為內聯函數也不會引起錯誤
- 在運行過程中要實現多態的三個條件:
- 類之間滿足賦值兼容關系(也就是類之間有繼承關系)
- 要聲明為虛函數
- 調用虛函數時,要由成員函數或者是指針和引用來訪問
- 代碼舉例
#include <iostream>
using namespace std;
class Base1 {
public:
public:
virtual void play();
};
void Base1::play()
{
cout << "Base1::play()" << endl;
}
class Base2:
public Base1
{
virtual void play();
};
void Base2::play() {
cout << "Base2::play()" << endl;
}
class Derived :
public Base2
{
virtual void play();
};
void Derived::play() {
cout << "Derived:: play()" << endl;
}
void fun(Base1* ba) { //聲明一個基類的指針
ba->play();
}
int main()
{
Base1 ba1;
Base2 ba2;
Derived de;
//分別用不同的對象指針來調用 fun 函數
fun(&ba1);
fun(&ba2);
fun(&de);
return 0;
}
這代碼含義就充分體現了虛函數作為實現多態條件的原因,由於 Base1 是 Base2 和 Derived 的父類,所以,Base1 是可以兼容 Base2 和 Derived 的,所以在 fun 函數這里是用的 Base1 的指針來作為形參,不同的是當我傳入參數不同時,fun 函數執行的是不同的結果,這就體現了多態的效果,我需要那個類型的實例,他就執行那個實例對應的方法。
需要注意的是:
基類的指針可以指向派生類的對象,基類的引用可以作為派生類的別名,但是基類的對象不能表示派生類的對象,所以在 fun 函數中我使用的是 Base1 的指針(換成引用也可以),而不是 Base1 的對象。
Part2.純虛函數和抽象類
純虛函數一般是和抽象類一起使用的,因為抽象類的定義就是:擁有純虛函數的類是抽象類,所以需要先了解一下純虛函數。
- 什么是純虛函數
- 在虛函數的聲明基礎上,在函數聲明后面加上 =0,的函數為純虛函數
- 在虛函數的聲明基礎上,在函數聲明后面加上 =0,的函數為純虛函數
- 一般定義語法:
- virtual 函數類型 函數名(參數表)=0;
- virtual 函數類型 函數名(參數表)=0;
- 什么時候使用純虛函數:
- 當一個方法不用實例化
- 方法的實現必須在派生類中
- 注意:
- 純虛函數不用被實現
- 區分純虛函數和函數體為空的函數,這是兩中不同的函數
抽象類:
- 抽象類的主要作用:
- 為一個類族建立起一個公共的接口,使他們能夠完整發揮多態的特性
- 為一個類族建立起一個公共的接口,使他們能夠完整發揮多態的特性
- 注意:
- 抽象類不能實例化
- 抽象類不能實例化
代碼舉例
#include <iostream>
using namespace std;
class Base1 {
public:
public:
virtual void play()=0;
};
void Base1::play()
{
cout << "Base1::play()" << endl;
}
class Base2:
public Base1
{
virtual void play();
};
void Base2::play() {
cout << "Base2::play()" << endl;
}
class Derived :
public Base2
{
virtual void play();
};
void Derived::play() {
cout << "Derived:: play()" << endl;
}
void fun(Base1* ba) { //聲明一個基類的指針
ba->play();
}
int main()
{
Base2 ba2;
Derived de;
//分別用不同的對象指針來調用 fun 函數
fun(&ba2);
fun(&de);
return 0;
}
這段代碼和上面的代碼區別不大,唯一的區別在於,將 Base1 的 play方法聲明為了純虛函數,所以 base1 成了一個抽象類,就不能在直接聲明一個 Base1 類型的對象,如果在聲明一個純虛函數,編譯器就會報錯,因為抽象類不能被實例化。
Part3.虛基類
之所以把虛基類放到這里為了做一個比較,因為學習了虛函數,抽象類后容易把這幾個概念弄混,所以在這里對比記憶。
- 為什么要使用虛基類
#include <iostream>
using namespace std;
class A {
public :
int varA;
};
class B:
public A
{
public:
int varB;
};
class C:
public A
{
public:
int varC;
};
class D :
public C, public B
{
public:
int varC;
};
int main()
{
D d;
d.varA;
return 0;
}
解析上面的代碼,由於對象 D 是繼承與 B 和 C的,然而 B 和 C 又共同繼承與 A,所以 B 和 C都具有從 A 中繼承下來的屬性 varA,所以我可以用 d 對象來調用 varA ,但是正是因為 B 和 C 中都具有 varA,所以我用
d 對象調用 varA 時,編譯器會產生疑問,到底是調用的 B 的 varA 還是 C 的 varA,這樣就會產生錯誤,當然我們可以使用作用域分辨符來區分到底是誰的 varA,但是這樣就不能很好的體現繼承的概念了,所以這里使用虛基類來解決這個問題。
- 虛基類的作用
- 當一個派生類中有多個直接基類,而這些直接基類又有一個共同的基類,則在最終的派生類中會保留該間接基類的數據成員的多份同名成員,就會造成二義性,將這個共同基類設置為虛基類時,同名的數據成員就會只存在一個副本,同一個函數名只有一個映射,就解決了二義性。
也就是說,當聲明為虛基類時,在程序中只保留了一個共同基類的數據副本,對象 B 和對象 C 的 varA 都指向同一個 varA(本來也應該是這樣),這樣再調用 varA 時,在程序中只有一個 varA,編譯器就不會產生疑問了。
- 基本語法
class A {
public :
int varA;
};
class B:
virtual public A
{
public:
int varB;
};
class C:
virtual public A
{
public:
int varC;
};
class D :
public A, public B
{
public:
int varC;
};
Part4.總結
虛函數,是在類中中的概念,他的重要作用就是實現面向對象的多態的特性,
抽象類,是類的概念,他必須和純虛函數函數搭配使用
虛基類,是繼承中的概念,是繼承過程中用來解決多個間接基類同一個副本產生的二義性的方法。