C++抽象類和純虛擬函式

2020-07-16 10:04:41
在繼承層次結構中,有一項很方便的做法,可以在基礎類別中定義一個成員函數,該函數只能在每個派生類中實現,而不能由基礎類別本身來實現,因為合理實現所需的細節只能在派生類中找到。

如果是這種情況,那麼 C++ 語言允許程式設計師將該函數宣告為一個純虛擬函式,也就是,一個在類中沒有提供實現的成員函數。C++ 宣告一個純虛擬函式的方法是將表示式 =0 放在類宣告中,而函數的主體則不存在。

例如,如果要將一個成員函數 void draw() 宣告為純虛擬函式,那麼它的類中的宣告語句範例如下:

void draw()= 0;

純虛擬函式有時稱為抽象函數,而如果某個類至少有一個純虛擬函式,那麼它將被稱為抽象類C++ 編譯器不允許範例化一個抽象類。抽象類只能被子類化,也就是說,只能使用它們作為派生其他類的基礎類別。

派生自抽象類的類將繼承基礎類別中的所有函數,除非它覆蓋繼承的所有抽象函數,否則它本身也是抽象類。抽象類的用處在於它定義了一個介面,介面必須由從它派生的所有類的物件來支援。

可以把抽象類看作一個除子類外沒有範例的類。現實生活中有許多抽象類的例子。例如,在動物王國中,"動物" 類就是所有動物的抽象類。所有動物的範例(例如狗、雞、狐狸等)都屬於 "動物" 抽象類中的某個子類,但 "動物" 本身不能範例化。

現在來看一看由一個形狀的集合組成的圖形系統,這些形狀必須繪製在螢幕的某些位置上。每個形狀物件都有一些成員變數來記錄它的位置,並且還有一個成員函數,用於在正確位置繪製形狀。該系統支援的不同形狀可能包括矩形、六邊形和其他形狀。

因為矩形是一個形狀,而六邊形也是一個形狀,所以單獨建立一個 Shape 類是有意義的,然後就可以從 Shape 類中派生出 Rectangle 類和 Hexagon 類。Shape 類有一個成員函數 setPosition,將用於設定形狀的位置;還有一個成員函數 Draw,用於繪製形狀。但是,由於 Shape 是一個抽象類(沒有任何形狀可以是一個籠統概念上的“形狀”,它必須是矩形、六邊形、三角形或其他具體的形狀),繪製一個特定形狀的邏輯必須委託給一個合適的子類,因此,draw() 函數不能在 Shape 類中實現,必須將它宣告為一個純虛擬函式。

下面的程式演示了上述 Shape 類,它具有兩個派生類:Rectangle 和 Hexagon。該類宣告了一個純虛擬函式 draw(),該函數將由其兩個子類實現。main 函數使用了一個指標向量來維護一個 Shape 物件的集合。
// This program demonstrates abstract base
// classes and pure virtual functions.
#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class Shape
{
    protected:
        int posX, posY;
    public:
        virtual void draw() const =0;
        void setPosition(int pX, int pY)
        {
            posX = pX;
            posY = pY;
        }
};

class Rectangle : public Shape
{
    public:
        virtual void draw() const
        {
            cout << "Drawing rectangle at " << posX << " " << posY << endl;
        }
};

class Hexagon : public Shape
{
    public:
        virtual void draw() const
        {
            cout << "Drawing hexagon at " << posX << " " << posY << endl;
        }
};

int main()
{

    // Create vector of pointers to Shapes of various types
    vector<shared_ptr<Shape>> shapes
    {
        make_shared<Hexagon>(),
        make_shared<Rectangle>(),
        make_shared<Hexagon>()
    };
    // Set positions of all the shapes
    int posX = 5, posY = 15;
    for (int k = 0; k < shapes.size (); k++)
    {
        shapes[k]->setPosition(posX, posY);
        posX += 10;
        posY += 10;
    };
    // Draw all the shapes at their positions
    for (int j =0; j < shapes.size (); j++)
    {
        shapes[j]->draw();
    }
    return 0;
}
程式輸出結果:

Drawing hexagon at 5 15
Drawing rectangle at 15 25
Drawing hexagon at 25 35

此程式提供了動態系結和多型的另一個範例。來看以下宣告:

shapeArray[j]->draw();

它將在迴圈中執行不同的次數。
for (int j = 0; j <shapes.size (); j ++)
{
    shapeArray[j]->draw();
}
第一次執行語句時,它將呼叫六邊形物件的 draw 函數;而第二次執行時,它將呼叫矩形物件的 draw 函數。因為這兩個 draw 函數是在不同的類中,所以它們會產生不同的行為。

關於抽象基礎類別和純虛擬函式,請記住以下知識要點:
  1. 當一個類包含一個純虛擬函式時,它是一個抽象基礎類別。
  2. 抽象基礎類別不能被範例化。
  3. 純虛擬函式用 =0 表示式宣告,並且沒有函數體或定義。
  4. 在需要範例化的派生類中必須覆蓋純虛擬函式。